1/**
2 * Browser IndexedDB primitives and key schemas for Effect applications.
3 *
4 * This module is the low-level bridge used by the platform-browser IndexedDB
5 * integration. It provides an `IndexedDb` service around the browser
6 * `indexedDB` factory and `IDBKeyRange` constructor, a `layerWindow` layer for
7 * wiring those primitives from `window`, and schemas for the key shapes accepted
8 * by IndexedDB object stores and indexes.
9 *
10 * @since 4.0.0
11 */
12import * as Context from "effect/Context"
13import * as Effect from "effect/Effect"
14import * as Layer from "effect/Layer"
15import * as Schema from "effect/Schema"
16
17const TypeId = "~@effect/platform-browser/IndexedDb"
18
19/**
20 * Service interface that provides the browser `indexedDB` factory and `IDBKeyRange` constructor.
21 *
22 * @category services
23 * @since 4.0.0
24 */
25export interface IndexedDb {
26 readonly [TypeId]: typeof TypeId
27 readonly indexedDB: globalThis.IDBFactory
28 readonly IDBKeyRange: typeof globalThis.IDBKeyRange
29}
30
31/**
32 * Service tag for browser IndexedDB primitives.
33 *
34 * @category services
35 * @since 4.0.0
36 */
37export const IndexedDb: Context.Service<IndexedDb, IndexedDb> = Context.Service<IndexedDb, IndexedDb>(TypeId)
38
39/** @internal */
40const IDBFlatKey = Schema.Union([
41 Schema.String,
42 Schema.Number.check(Schema.makeFilter((input) => !Number.isNaN(input))),
43 Schema.Date,
44 Schema.declare(
45 (input): input is BufferSource =>
46 input instanceof ArrayBuffer ||
47 (ArrayBuffer.isView(input) && input.buffer instanceof ArrayBuffer)
48 )
49])
50
51/**
52 * Schema for IndexedDB keys: strings, non-NaN numbers, valid dates, buffer sources, or arrays of those flat key values.
53 *
54 * @category schemas
55 * @since 4.0.0
56 */
57export const IDBValidKey = Schema.Union([IDBFlatKey, Schema.Array(IDBFlatKey)])
58
59/**
60 * Schema for auto-incremented IndexedDB keys, accepting integers from 1 through `2 ** 53`.
61 *
62 * **When to use**
63 *
64 * Use when you need to define numeric key-path fields for `IndexedDbTable`
65 * definitions that use IndexedDB auto-increment keys.
66 *
67 * **Details**
68 *
69 * The schema accepts integer values from `1` through `2 ** 53`, matching the
70 * range used for generated IndexedDB auto-increment keys.
71 *
72 * @see {@link IDBValidKey} for the broader IndexedDB key schema
73 *
74 * @category schemas
75 * @since 4.0.0
76 */
77export const AutoIncrement = Schema.Int.check(
78 Schema.isBetween({ minimum: 1, maximum: 2 ** 53 })
79).annotate({
80 identifier: "AutoIncrement",
81 title: "autoIncrement",
82 description: "Defines a valid autoIncrement key path for the IndexedDb table"
83})
84
85/**
86 * Creates an `IndexedDb` service from an `IDBFactory` and `IDBKeyRange` constructor.
87 *
88 * @category constructors
89 * @since 4.0.0
90 */
91export const make = (impl: Omit<IndexedDb, typeof TypeId>): IndexedDb => IndexedDb.of({ [TypeId]: TypeId, ...impl })
92
93/**
94 * Layer that provides `IndexedDb` from `window.indexedDB` and `window.IDBKeyRange`, failing with a config error when they are unavailable.
95 *
96 * @category layers
97 * @since 4.0.0
98 */
99export const layerWindow: Layer.Layer<IndexedDb> = Layer.effect(
100 IndexedDb,
101 Effect.suspend(() => {
102 if (window.indexedDB && window.IDBKeyRange) {
103 return Effect.succeed(
104 make({
105 indexedDB: window.indexedDB,
106 IDBKeyRange: window.IDBKeyRange
107 })
108 )
109 } else {
110 return Effect.die(new Error("window.indexedDB is not available"))
111 }
112 })
113)