diff --git a/src/classes/version/version.ts b/src/classes/version/version.ts index 83f425458..32383b267 100644 --- a/src/classes/version/version.ts +++ b/src/classes/version/version.ts @@ -6,6 +6,7 @@ import { Transaction } from '../transaction'; import { removeTablesApi, setApiOnPlace, parseIndexSyntax } from './schema-helpers'; import { exceptions } from '../../errors'; import { createTableSchema } from '../../helpers/table-schema'; +import { LooseStoresSpec } from '../../public/types/strictly-typed-schema'; import { nop, promisableChain } from '../../functions/chaining-functions'; /** class Version @@ -38,11 +39,22 @@ export class Version implements IVersion { }); } - stores(stores: { [key: string]: string | null; }): IVersion { + stores(stores: LooseStoresSpec): IVersion { const db = this.db; - this._cfg.storesSource = this._cfg.storesSource ? - extend(this._cfg.storesSource, stores) : - stores; + const bwCompatStores = {} as { [key: string]: string | null }; + for (const table of keys(stores)) { + const spec = stores[table]; + if (spec === null) { + bwCompatStores[table] = null; + } else if (typeof spec === 'string') { + bwCompatStores[table] = spec; + } else if (Array.isArray(spec)) { + bwCompatStores[table] = spec.join(','); + } + } + this._cfg.storesSource = this._cfg.storesSource + ? extend(this._cfg.storesSource, bwCompatStores) + : bwCompatStores; const versions = db._versions; // Derive stores from earlier versions if they are not explicitely specified as null or a new syntax. diff --git a/src/public/types/dexie.d.ts b/src/public/types/dexie.d.ts index 67189f651..a3697949f 100644 --- a/src/public/types/dexie.d.ts +++ b/src/public/types/dexie.d.ts @@ -13,7 +13,7 @@ import { IndexableType } from './indexable-type'; import { DBCore } from './dbcore'; import { Middleware, DexieStacks } from './middleware'; -export type TableProp = { +export type TableProp = { [K in keyof DX]: DX[K] extends {schema: any, get: any, put: any, add: any, where: any} ? K : never; }[keyof DX] & string; diff --git a/src/public/types/strictly-typed-schema.d.ts b/src/public/types/strictly-typed-schema.d.ts new file mode 100644 index 000000000..03b666e80 --- /dev/null +++ b/src/public/types/strictly-typed-schema.d.ts @@ -0,0 +1,148 @@ +import { Dexie, TableProp } from './dexie'; +import { IndexableType, IndexableTypeArray } from './indexable-type'; +import { Table } from './table'; + +// Indexable arrays on root level only +type ArrayProps = { + [K in keyof T]: K extends string + ? T[K] extends IndexableTypeArray + ? K + : never + : never; +}[keyof T]; + +type NumberProps = { + [K in keyof T]: K extends string ? (T[K] extends number ? K : never) : never; +}[keyof T]; + +type StringProps = { + [K in keyof T]: K extends string ? (T[K] extends string ? K : never) : never; +}[keyof T]; + +type IgnoreObjects = + | RegExp + | DataView + | Map + | Set + | CryptoKey + | Promise + | ReadableStream + | ReadableStreamDefaultReader + | ReadableStreamDefaultController + | { whenLoaded: Promise }; // Y.Doc + +// Nested indexable arrays (configurable max depth) +type ArrayKeyPaths< + T, + MAXDEPTH = 'III', + CURRDEPTH extends string = '' +> = CURRDEPTH extends MAXDEPTH + ? ArrayProps + : + | ArrayKeyPaths + | { + [K in keyof T]: K extends string + ? T[K] extends object + ? `${K}.${keyof T[K] extends string + ? ArrayKeyPaths + : never}` + : never + : never; + }[keyof T]; + +// Indexable root properties +type IndexableProps = { + [K in keyof T]: K extends string + ? T[K] extends IndexableType + ? K + : never + : never; +}[keyof T]; + +// Keypaths of indexable properties and nested properteis (configurable MAXDEPTH) +type IndexableKeyPaths< + T, + MAXDEPTH = 'III', + CURRDEPTH extends string = '' +> = CURRDEPTH extends MAXDEPTH + ? IndexableProps + : { + [K in keyof T]: K extends string + ? T[K] extends IndexableType + ? K + : T[K] extends IgnoreObjects + ? never + : T[K] extends object + ? `${K}.${keyof T[K] extends string + ? IndexableKeyPaths + : never}` + : never + : never; + }[keyof T]; + +// Compound index syntax +type Combo< + T, + MAXDEPTH = 'III', + CURRDEPTH extends string = '' +> = CURRDEPTH extends MAXDEPTH + ? never + : { + [P in keyof T]: P extends string + ? keyof Omit extends never + ? P + : P | `${P}+${Combo, MAXDEPTH, `${CURRDEPTH}I`>}` + : never; + }[keyof T]; + +type IndexablePathObj = { + [P in IndexableKeyPaths]: true; +}; + +type SingleCombound = { + [P in IndexableKeyPaths]: `[${P}]`; +}[IndexableKeyPaths]; + +// ------- + +// Main generic type for Dexie index syntax +type DexieIndexSyntax = + | `&${IndexableKeyPaths}` + | IndexableKeyPaths + | `*${ArrayKeyPaths}` // Wildcard for array keys + | Exclude<`[${Combo>}]`, SingleCombound> // Combined non-array keys + | `&${Exclude<`[${Combo>>}]`, SingleCombound>}`; // Combined non-array keys + +type DexiePrimaryKeySyntax = + | '' + | `++${NumberProps}` + | `@${StringProps}` + | `&${IndexableKeyPaths}` + | `${IndexableKeyPaths}` + | `${IndexableKeyPaths}:Y.Doc` + | `${Exclude<`[${Combo>}]`, SingleCombound>}` // Combined non-array keys + | `&${Exclude<`[${Combo>}]`, SingleCombound>}`; + +type TypedProperties = { + [K in keyof T]: K extends string + ? T[K] extends { whenLoaded: Promise } + ? `${K}:Y.Doc` + : never + : never; +}[keyof T]; + +type LooseStoresSpec = { [tableName: string]: string | null | string[] }; + +type StoresSpec = TableProp> extends never ? LooseStoresSpec : + Dexie extends DX ? LooseStoresSpec : + { + [TN in TableProp]?: DX[TN] extends Table + ? + | string + | null + | [ + DexiePrimaryKeySyntax, + ...(DexieIndexSyntax | TypedProperties)[] + ] + : string | string[] | null; + }; diff --git a/src/public/types/version.d.ts b/src/public/types/version.d.ts index 8bc06b8b0..f6f9d412c 100644 --- a/src/public/types/version.d.ts +++ b/src/public/types/version.d.ts @@ -1,6 +1,7 @@ import { Transaction } from "./transaction"; +import { LooseStoresSpec, StoresSpec } from "./strictly-typed-schema"; export interface Version { - stores(schema: { [tableName: string]: string | null }): Version; + stores(schema: LooseStoresSpec): Version; upgrade(fn: (trans: Transaction) => PromiseLike | void): Version; }