| 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 | */ |
| 12 | import * as Context from "effect/Context" |
| 13 | import * as Effect from "effect/Effect" |
| 14 | import * as Layer from "effect/Layer" |
| 15 | import * as Schema from "effect/Schema" |
| 16 | |
| 17 | const 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 | */ |
| 25 | export 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 | */ |
| 37 | export const IndexedDb: Context.Service<IndexedDb, IndexedDb> = Context.Service<IndexedDb, IndexedDb>(TypeId) |
| 38 | |
| 39 | /** @internal */ |
| 40 | const 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 | */ |
| 57 | export 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 | */ |
| 77 | export 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 | */ |
| 91 | export 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 | */ |
| 99 | export 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 | ) |