| 1 | /** |
| 2 | * Typed IndexedDB schema version definitions. |
| 3 | * |
| 4 | * This module represents one logical IndexedDB database version as a non-empty set of `IndexedDbTable` definitions. |
| 5 | * Versions are consumed by `IndexedDbDatabase.make` and `.add` to type query builders and migration transactions, so |
| 6 | * applications can describe the tables available after initialization or after each schema upgrade. |
| 7 | * |
| 8 | * Use an `IndexedDbVersion` when defining the initial stores for a browser database, adding or removing object stores, |
| 9 | * changing indexes, or moving data between differently shaped table schemas. The version value is a typed description of |
| 10 | * the target schema; creating and deleting object stores or indexes still happens explicitly inside the corresponding |
| 11 | * `IndexedDbDatabase` migration callback. |
| 12 | * |
| 13 | * IndexedDB versioning is ordered by the migration chain rather than by a number stored here. Each `.add` step becomes |
| 14 | * the next browser database version, and only migrations after the browser's current version are run. Include every table |
| 15 | * that should be queryable in each target version, avoid duplicate table names, and remember that key-path or |
| 16 | * auto-increment changes usually require creating a new object store and copying data during the upgrade transaction. |
| 17 | * |
| 18 | * @since 4.0.0 |
| 19 | */ |
| 20 | import type { NonEmptyReadonlyArray } from "effect/Array" |
| 21 | import type { Pipeable } from "effect/Pipeable" |
| 22 | import { pipeArguments } from "effect/Pipeable" |
| 23 | import type * as IndexedDbTable from "./IndexedDbTable.ts" |
| 24 | |
| 25 | const TypeId = "~@effect/platform-browser/IndexedDbVersion" |
| 26 | |
| 27 | /** |
| 28 | * Typed IndexedDB version definition containing the tables available in that schema version. |
| 29 | * |
| 30 | * @category models |
| 31 | * @since 4.0.0 |
| 32 | */ |
| 33 | export interface IndexedDbVersion< |
| 34 | out Tables extends IndexedDbTable.AnyWithProps |
| 35 | > extends Pipeable { |
| 36 | new(_: never): {} |
| 37 | readonly [TypeId]: typeof TypeId |
| 38 | readonly tables: ReadonlyMap<string, Tables> |
| 39 | } |
| 40 | |
| 41 | /** |
| 42 | * Type-erased shape of an `IndexedDbVersion`. |
| 43 | * |
| 44 | * @category models |
| 45 | * @since 4.0.0 |
| 46 | */ |
| 47 | export interface Any { |
| 48 | readonly [TypeId]: typeof TypeId |
| 49 | } |
| 50 | |
| 51 | /** |
| 52 | * Type-erased `IndexedDbVersion` retaining version properties with broad table types. |
| 53 | * |
| 54 | * @category models |
| 55 | * @since 4.0.0 |
| 56 | */ |
| 57 | export type AnyWithProps = IndexedDbVersion<IndexedDbTable.AnyWithProps> |
| 58 | |
| 59 | /** |
| 60 | * Extracts the table union from an `IndexedDbVersion`. |
| 61 | * |
| 62 | * @category utility types |
| 63 | * @since 4.0.0 |
| 64 | */ |
| 65 | export type Tables<Db extends Any> = Db extends IndexedDbVersion<infer _Tables> ? _Tables : never |
| 66 | |
| 67 | /** |
| 68 | * Selects a table by name from an `IndexedDbVersion`. |
| 69 | * |
| 70 | * @category utility types |
| 71 | * @since 4.0.0 |
| 72 | */ |
| 73 | export type TableWithName< |
| 74 | Db extends Any, |
| 75 | TableName extends string |
| 76 | > = IndexedDbTable.WithName<Tables<Db>, TableName> |
| 77 | |
| 78 | /** |
| 79 | * Extracts the schema for a named table within an `IndexedDbVersion`. |
| 80 | * |
| 81 | * @category utility types |
| 82 | * @since 4.0.0 |
| 83 | */ |
| 84 | export type SchemaWithName< |
| 85 | Db extends Any, |
| 86 | TableName extends string |
| 87 | > = IndexedDbTable.TableSchema<IndexedDbTable.WithName<Tables<Db>, TableName>> |
| 88 | |
| 89 | const Proto = { |
| 90 | [TypeId]: TypeId, |
| 91 | pipe() { |
| 92 | return pipeArguments(this, arguments) |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | const makeProto = <Tables extends IndexedDbTable.AnyWithProps>(options: { |
| 97 | readonly tables: ReadonlyMap<string, Tables> |
| 98 | }): IndexedDbVersion<Tables> => { |
| 99 | // oxlint-disable-next-line typescript/no-extraneous-class |
| 100 | class Version {} |
| 101 | Object.assign(Version, Proto) |
| 102 | ;(Version as any).tables = options.tables |
| 103 | return Version as any |
| 104 | } |
| 105 | |
| 106 | /** |
| 107 | * Creates an `IndexedDbVersion` from one or more table definitions. |
| 108 | * |
| 109 | * **When to use** |
| 110 | * |
| 111 | * Use when you need a typed description of the target IndexedDB schema that a |
| 112 | * database migration will materialize. |
| 113 | * |
| 114 | * **Details** |
| 115 | * |
| 116 | * The returned version exposes a `tables` map keyed by each table's |
| 117 | * `tableName`, and its type is the union of the supplied table definitions. |
| 118 | * |
| 119 | * **Gotchas** |
| 120 | * |
| 121 | * This constructor only describes the target schema; object stores and indexes |
| 122 | * still need to be created in the corresponding `IndexedDbDatabase` migration. |
| 123 | * Duplicate table names are not rejected, and the runtime map keeps the later |
| 124 | * table for a repeated key. |
| 125 | * |
| 126 | * @see {@link IndexedDbTable.make} for creating table definitions consumed by this constructor |
| 127 | * |
| 128 | * @category constructors |
| 129 | * @since 4.0.0 |
| 130 | */ |
| 131 | export const make = < |
| 132 | const Tables extends NonEmptyReadonlyArray<IndexedDbTable.AnyWithProps> |
| 133 | >( |
| 134 | ...tables: Tables |
| 135 | ): IndexedDbVersion<Tables[number]> => |
| 136 | makeProto({ |
| 137 | tables: new Map(tables.map((table) => [table.tableName, table])) |
| 138 | }) |