Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Advanced typings for a strongly typed stores spec #2086

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions src/classes/version/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion src/public/types/dexie.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { IndexableType } from './indexable-type';
import { DBCore } from './dbcore';
import { Middleware, DexieStacks } from './middleware';

export type TableProp<DX extends Dexie> = {
export type TableProp<DX> = {
[K in keyof DX]: DX[K] extends {schema: any, get: any, put: any, add: any, where: any} ? K : never;
}[keyof DX] & string;

Expand Down
148 changes: 148 additions & 0 deletions src/public/types/strictly-typed-schema.d.ts
Original file line number Diff line number Diff line change
@@ -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<T> = {
[K in keyof T]: K extends string
? T[K] extends IndexableTypeArray
? K
: never
: never;
}[keyof T];

type NumberProps<T> = {
[K in keyof T]: K extends string ? (T[K] extends number ? K : never) : never;
}[keyof T];

type StringProps<T> = {
[K in keyof T]: K extends string ? (T[K] extends string ? K : never) : never;
}[keyof T];

type IgnoreObjects =
| RegExp
| DataView
| Map<any, any>
| Set<any>
| CryptoKey
| Promise<any>
| ReadableStream<any>
| ReadableStreamDefaultReader<any>
| ReadableStreamDefaultController<any>
| { whenLoaded: Promise<any> }; // Y.Doc

// Nested indexable arrays (configurable max depth)
type ArrayKeyPaths<
T,
MAXDEPTH = 'III',
CURRDEPTH extends string = ''
> = CURRDEPTH extends MAXDEPTH
? ArrayProps<T>
:
| ArrayKeyPaths<T, MAXDEPTH, `${CURRDEPTH}I`>
| {
[K in keyof T]: K extends string
? T[K] extends object
? `${K}.${keyof T[K] extends string
? ArrayKeyPaths<T[K], MAXDEPTH, `${CURRDEPTH}I`>
: never}`
: never
: never;
}[keyof T];

// Indexable root properties
type IndexableProps<T> = {
[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<T>
: {
[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<T[K], MAXDEPTH, `${CURRDEPTH}I`>
: 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<T, P> extends never
? P
: P | `${P}+${Combo<Omit<T, P>, MAXDEPTH, `${CURRDEPTH}I`>}`
: never;
}[keyof T];

type IndexablePathObj<T> = {
[P in IndexableKeyPaths<T>]: true;
};

type SingleCombound<T> = {
[P in IndexableKeyPaths<T>]: `[${P}]`;
}[IndexableKeyPaths<T>];

// -------

// Main generic type for Dexie index syntax
type DexieIndexSyntax<T> =
| `&${IndexableKeyPaths<T>}`
| IndexableKeyPaths<T>
| `*${ArrayKeyPaths<T>}` // Wildcard for array keys
| Exclude<`[${Combo<IndexablePathObj<T>>}]`, SingleCombound<T>> // Combined non-array keys
| `&${Exclude<`[${Combo<Pick<T, IndexableProps<T>>>}]`, SingleCombound<T>>}`; // Combined non-array keys

type DexiePrimaryKeySyntax<T> =
| ''
| `++${NumberProps<T>}`
| `@${StringProps<T>}`
| `&${IndexableKeyPaths<T>}`
| `${IndexableKeyPaths<T>}`
| `${IndexableKeyPaths<T>}:Y.Doc`
| `${Exclude<`[${Combo<IndexablePathObj<T>>}]`, SingleCombound<T>>}` // Combined non-array keys
| `&${Exclude<`[${Combo<IndexablePathObj<T>>}]`, SingleCombound<T>>}`;

type TypedProperties<T> = {
[K in keyof T]: K extends string
? T[K] extends { whenLoaded: Promise<any> }
? `${K}:Y.Doc`
: never
: never;
}[keyof T];

type LooseStoresSpec = { [tableName: string]: string | null | string[] };

type StoresSpec<DX extends Dexie> = TableProp<Omit<DX, keyof Dexie>> extends never ? LooseStoresSpec :
Dexie extends DX ? LooseStoresSpec :
{
[TN in TableProp<DX>]?: DX[TN] extends Table<infer T, any, any>
?
| string
| null
| [
DexiePrimaryKeySyntax<T>,
...(DexieIndexSyntax<T> | TypedProperties<T>)[]
]
: string | string[] | null;
};
3 changes: 2 additions & 1 deletion src/public/types/version.d.ts
Original file line number Diff line number Diff line change
@@ -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<any> | void): Version;
}