/** * Typed object-store descriptors for the browser IndexedDB integration. * * An {@link IndexedDbTable} is the schema-backed description of one IndexedDB * object store. It carries the store name, row schema, key path, index key * paths, auto-increment mode, and transaction durability used by database * versions, migrations, and typed queries. The `make` constructor also derives * the read, array, and auto-increment write schemas used by the query builder. * * @see {@link make} for constructing table descriptors. * * @since 4.0.0 */ import { type Pipeable, pipeArguments } from "effect/Pipeable" import * as Schema from "effect/Schema" import * as Struct from "effect/Struct" import type { NoInfer } from "effect/Types" import * as IndexedDb from "./IndexedDb.ts" import type * as IndexedDbQueryBuilder from "./IndexedDbQueryBuilder.ts" const TypeId = "~@effect/platform-browser/IndexedDbTable" /** * Typed IndexedDB table definition containing its name, schema, key path, indexes, auto-increment setting, and transaction durability. * * @category interface * @since 4.0.0 */ export interface IndexedDbTable< out Name extends string, out TableSchema extends AnySchemaStruct, out Indexes extends Record< string, IndexedDbQueryBuilder.KeyPath >, out KeyPath extends Readonly, out AutoIncrement extends boolean > extends Pipeable { new(_: never): {} readonly [TypeId]: typeof TypeId readonly tableName: Name readonly tableSchema: TableSchema readonly readSchema: Schema.Top readonly autoincrementSchema: Schema.Top readonly arraySchema: Schema.Top readonly keyPath: KeyPath readonly indexes: Indexes readonly autoIncrement: AutoIncrement readonly durability: IDBTransactionDurability } /** * Schema constraint for table schemas that expose struct fields. * * @category models * @since 4.0.0 */ export type AnySchemaStruct = Schema.Top & { readonly fields: Schema.Struct.Fields } /** * Type-erased shape of an `IndexedDbTable` used when table type parameters are not needed. * * @category models * @since 4.0.0 */ export interface Any { readonly [TypeId]: typeof TypeId readonly keyPath: any readonly tableName: string readonly tableSchema: Schema.Top readonly readSchema: Schema.Top readonly autoincrementSchema: Schema.Top readonly arraySchema: Schema.Top readonly autoIncrement: boolean readonly indexes: any } /** * Type-erased `IndexedDbTable` retaining the table interface properties with broad type parameters. * * @category models * @since 4.0.0 */ export type AnyWithProps = IndexedDbTable< string, AnySchemaStruct, any, any, boolean > /** * Extracts the table name type from an `IndexedDbTable`. * * @category models * @since 4.0.0 */ export type TableName = Table["tableName"] /** * Extracts the key-path type from an `IndexedDbTable`. * * @category models * @since 4.0.0 */ export type KeyPath
= Table["keyPath"] /** * Extracts the auto-increment flag type from an `IndexedDbTable`. * * @category models * @since 4.0.0 */ export type AutoIncrement
= Table["autoIncrement"] /** * Extracts the schema type from an `IndexedDbTable`. * * @category models * @since 4.0.0 */ export type TableSchema
= Table["tableSchema"] /** * Extracts the decoding or encoding service requirements needed by an `IndexedDbTable` schema. * * @category models * @since 4.0.0 */ export type Context
= | Table["tableSchema"]["DecodingServices"] | Table["tableSchema"]["EncodingServices"] /** * Extracts the encoded row type from an `IndexedDbTable` schema. * * @category models * @since 4.0.0 */ export type Encoded
= Table["tableSchema"]["Encoded"] /** * Extracts the index definition map from an `IndexedDbTable`. * * @category models * @since 4.0.0 */ export type Indexes
= Table["indexes"] /** * Selects the table with the given name from a union of `IndexedDbTable` types. * * @category models * @since 4.0.0 */ export type WithName
= Extract< Table, { readonly tableName: TableName } > const Proto = { [TypeId]: TypeId, pipe() { return pipeArguments(this, arguments) } } /** * Creates a typed IndexedDB table definition from its name, schema, optional key path, indexes, auto-increment flag, and durability. * * **When to use** * * Use to define a typed object-store descriptor for inclusion in an * `IndexedDbVersion` and for migration or query APIs. * * **Details** * * `autoIncrement` defaults to `false` and `durability` defaults to `"relaxed"`. * Tables without a key path get a read schema that includes an out-of-line * `key`, while auto-increment tables use a write schema where the generated key * may be omitted. * * **Gotchas** * * Tables without a key path cannot define a `key` field in their row schema. * Key paths and index paths must point to encoded fields whose values are valid * IndexedDB keys, and declared indexes still need to be created during * database migrations. * * @see `IndexedDbVersion.make` for grouping table definitions into a schema version * * @category constructors * @since 4.0.0 */ export const make = < const Name extends string, TableSchema extends AnySchemaStruct, const Indexes extends Record< string, IndexedDbQueryBuilder.KeyPath >, const KeyPath extends | (AutoIncrement extends true ? IndexedDbQueryBuilder.KeyPathNumber> : IndexedDbQueryBuilder.KeyPath>) | undefined = undefined, const AutoIncrement extends boolean = false >(options: { readonly name: Name readonly schema: [KeyPath] extends [undefined] ? "key" extends keyof TableSchema["fields"] ? "Cannot have a 'key' field when keyPath is undefined" : TableSchema : TableSchema readonly keyPath?: KeyPath readonly indexes?: Indexes | undefined readonly autoIncrement?: IsValidAutoIncrementKeyPath< TableSchema, KeyPath > extends true ? AutoIncrement | undefined : never readonly durability?: IDBTransactionDurability | undefined }): IndexedDbTable< Name, TableSchema, Indexes, Extract>, AutoIncrement > => { // oxlint-disable-next-line typescript/no-extraneous-class class Table {} Object.assign(Table, Proto) const readSchema = options.keyPath === undefined ? Schema.Struct({ ...(options.schema as Schema.Struct<{}>).fields, key: IndexedDb.IDBValidKey }) : options.schema ;(Table as any).tableName = options.name ;(Table as any).tableSchema = options.schema ;(Table as any).readSchema = readSchema ;(Table as any).arraySchema = Schema.Array(readSchema as any) ;(Table as any).autoincrementSchema = options.autoIncrement ? Schema.Struct(Struct.omit((options.schema as Schema.Struct<{}>).fields, [options.keyPath!] as any)) : options.schema ;(Table as any).keyPath = options.keyPath ;(Table as any).indexes = options.indexes ;(Table as any).autoIncrement = options.autoIncrement === true ;(Table as any).durability = options.durability ?? "relaxed" return Table as any } // ----------------------------------------------------------------------------- // internal // ----------------------------------------------------------------------------- type IsValidAutoIncrementKeyPath< TableSchema extends AnySchemaStruct, KeyPath > = KeyPath extends keyof TableSchema["Encoded"] ? TableSchema["Encoded"][KeyPath] extends number ? true : false : false