@mvd/memrel 0.0.0-stage → 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,267 @@
1
+ /**
2
+ * Public types: the schema config, the compile-time checks run against it, and the typed
3
+ * database API derived from it.
4
+ *
5
+ * Relation types are computed per collection rather than over the whole database at once,
6
+ * which keeps editor performance acceptable with dozens of collections.
7
+ */
8
+ declare const snapshotBrand: unique symbol;
9
+ /** What happens to referencing rows when the referenced row is deleted or its key changes. */
10
+ type Action = "cascade" | "restrict" | "setNull";
11
+ /** What `insert` does when a primary key is already taken. */
12
+ type OnConflict = "throw" | "ignore" | "replace";
13
+ /** Options for `insert` and `load`. */
14
+ interface InsertOptions {
15
+ /**
16
+ * What to do when a row's primary key already exists:
17
+ * `'throw'` (default), `'ignore'` (keep the existing row) or `'replace'` (PUT semantics).
18
+ * Unique key conflicts always throw.
19
+ */
20
+ readonly onConflict?: OnConflict;
21
+ }
22
+ type Field<Row> = Extract<keyof Row, string>;
23
+ /** A non-empty, ordered list of fields of `Row`. */
24
+ type FieldList<Row> = readonly [Field<Row>, ...Field<Row>[]];
25
+ /** A named outgoing reference (foreign key). */
26
+ interface RefConfig<S, Row> {
27
+ /** The child's foreign key fields, in order. */
28
+ readonly fields: FieldList<Row>;
29
+ /** Target collection name. */
30
+ readonly to: Extract<keyof S, string>;
31
+ /**
32
+ * Target fields; `fields[i]` pairs with `references[i]`. Must be the target's primary key or
33
+ * one of its `unique` keys. Required for composite foreign keys, else defaults to the target's
34
+ * `key`.
35
+ */
36
+ readonly references?: readonly [string, ...string[]];
37
+ /** What happens to referencing rows when the target is deleted. */
38
+ readonly onDelete: Action;
39
+ /** What happens to referencing rows when the referenced fields change. Defaults to `'restrict'`. */
40
+ readonly onUpdate?: Action;
41
+ /**
42
+ * The fields `setNull` clears, a subset of `fields` (like Postgres' `SET NULL (col)`).
43
+ * Defaults to all of `fields`. When these are all null, the row references nothing.
44
+ */
45
+ readonly setNullFields?: FieldList<Row>;
46
+ /** Name of the reverse relation on the target, used by `rel`. */
47
+ readonly inverse?: string;
48
+ }
49
+ /** Keys, unique keys, indexes and references of one collection. */
50
+ interface CollectionConfig<S, Row> {
51
+ /** Primary key fields, in order. */
52
+ readonly key: FieldList<Row>;
53
+ /** Unique keys besides the primary key. Rows with a null in a unique key aren't indexed. */
54
+ readonly unique?: readonly FieldList<Row>[];
55
+ /** Extra equality indexes used by `where`. Foreign keys are always indexed. */
56
+ readonly indexes?: readonly FieldList<Row>[];
57
+ /** Named outgoing references (foreign keys). */
58
+ readonly refs?: {
59
+ readonly [name: string]: RefConfig<S, Row>;
60
+ };
61
+ }
62
+ /** The runtime config for a database with row types `S`. */
63
+ type DbConfig<S> = { readonly [N in keyof S]: CollectionConfig<S, S[N]>; };
64
+ type Err<Message extends string> = `memrel: ${Message}`;
65
+ /**
66
+ * Marks an invalid config property. It is an object type rather than a string so that it
67
+ * doesn't reduce the surrounding literal types to `never`, and the message shows up in the error.
68
+ */
69
+ interface Invalid<Message extends string> {
70
+ readonly "memrel error": Message;
71
+ }
72
+ type RefOption = keyof RefConfig<unknown, unknown>;
73
+ type CollectionOption = keyof CollectionConfig<unknown, unknown>;
74
+ type Get<T, K> = K extends keyof T ? T[K] : never;
75
+ type Same<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false;
76
+ type Overlaps<A, B> = [A] extends [B] ? true : [B] extends [A] ? true : false;
77
+ type Unknowns<T, Known extends PropertyKey> = { readonly [K in Exclude<keyof T, Known>]: Invalid<Err<`unknown option '${K & string}'`>>; };
78
+ type KeyListOf<CC> = CC extends {
79
+ readonly key: infer K;
80
+ } ? K : never;
81
+ type UniqueListsOf<CC> = CC extends {
82
+ readonly unique: readonly (infer U)[];
83
+ } ? U : never;
84
+ type IsTargetKey<Refs, Lists> = true extends (Lists extends unknown ? Same<Refs, Lists> : never) ? true : false;
85
+ type PairErrors<Row, Target, F extends readonly string[], Refs extends readonly string[]> = { [I in keyof F]: I extends keyof Refs ? Overlaps<NonNullable<Get<Row, F[I]>>, NonNullable<Get<Target, Refs[I]>>> extends true ? never : Err<`'${F[I]}' and '${Refs[I] & string}' have incompatible types`> : never; }[number];
86
+ type ReferencesError<Row, Target, TargetConfig, F extends readonly string[], Refs extends readonly string[]> = IsTargetKey<Refs, KeyListOf<TargetConfig> | UniqueListsOf<TargetConfig>> extends false ? Err<"`references` must be the target's `key` or one of its `unique` keys"> : Same<F["length"], Refs["length"]> extends false ? Err<"`fields` and `references` must have the same length"> : PairErrors<Row, Target, F, Refs>;
87
+ type CheckReferences<S, C, Row, R, F extends readonly string[], T extends keyof S & keyof C> = R extends {
88
+ readonly references: infer Refs extends readonly string[];
89
+ } ? [ReferencesError<Row, S[T], C[T], F, Refs>] extends [infer E extends string] ? [E] extends [never] ? unknown : {
90
+ readonly references: Invalid<E>;
91
+ } : unknown : KeyListOf<C[T]> extends (infer K extends readonly string[]) ? [F["length"], K["length"]] extends [1, 1] ? [PairErrors<Row, S[T], F, K>] extends [never] ? unknown : {
92
+ readonly fields: Invalid<PairErrors<Row, S[T], F, K>>;
93
+ } : {
94
+ readonly references: Invalid<Err<"composite foreign keys need explicit `references`">>;
95
+ } : unknown;
96
+ type NotNullable<Row, Fields> = { [P in Fields & keyof Row]: null extends Row[P] ? never : P; }[Fields & keyof Row];
97
+ type SetNullError<Row, NullFields> = [NotNullable<Row, NullFields>] extends [never] ? never : Err<`setNull requires nullable fields, but '${NotNullable<Row, NullFields> & string}' is not nullable`>;
98
+ type CheckSetNull<Row, R, F extends readonly string[]> = (R extends {
99
+ readonly setNullFields: infer X extends readonly string[];
100
+ } ? [Exclude<X[number], F[number]>] extends [never] ? unknown : {
101
+ readonly setNullFields: Invalid<Err<"`setNullFields` must be a subset of `fields`">>;
102
+ } : unknown) & ([SetNullError<Row, NullFieldsOf<R>>] extends [never] ? unknown : (R extends {
103
+ readonly onDelete: "setNull";
104
+ } ? {
105
+ readonly onDelete: Invalid<SetNullError<Row, NullFieldsOf<R>>>;
106
+ } : unknown) & (R extends {
107
+ readonly onUpdate: "setNull";
108
+ } ? {
109
+ readonly onUpdate: Invalid<SetNullError<Row, NullFieldsOf<R>>>;
110
+ } : unknown));
111
+ type CheckRef<S, C, Row, R> = R extends {
112
+ readonly fields: infer F extends readonly string[];
113
+ readonly to: infer T extends keyof S & keyof C;
114
+ } ? Unknowns<R, RefOption> & CheckReferences<S, C, Row, R, F, T> & CheckSetNull<Row, R, F> : unknown;
115
+ type CheckCollection<S, C, Row, CC> = Unknowns<CC, CollectionOption> & (CC extends {
116
+ readonly refs: infer Refs;
117
+ } ? {
118
+ readonly refs: { readonly [R in keyof Refs]: CheckRef<S, C, Row, Refs[R]>; };
119
+ } : unknown);
120
+ /**
121
+ * Checks a config against the row types. Resolves to `unknown`-like constraints where the config
122
+ * is fine, and to an `Invalid<'memrel: ...'>` marker on the offending property where it isn't.
123
+ */
124
+ type CheckConfig<S, C> = { readonly [N in keyof C]: N extends keyof S ? CheckCollection<S, C, S[N], C[N]> : Invalid<Err<`'${N & string}' is not a collection`>>; };
125
+ type RefsOf<CC> = CC extends {
126
+ readonly refs: infer R;
127
+ } ? R : {};
128
+ type FieldsOf<R> = R extends {
129
+ readonly fields: readonly (infer F)[];
130
+ } ? F : never;
131
+ type NullFieldsOf<R> = R extends {
132
+ readonly setNullFields: readonly (infer F)[];
133
+ } ? F : FieldsOf<R>;
134
+ /** Primary key fields of a collection config, as a union. */
135
+ type KeyFieldsOf<CC> = CC extends {
136
+ readonly key: readonly (infer K)[];
137
+ } ? K : never;
138
+ type NotNullish<Row, Fields> = { [P in Fields & keyof Row]: null extends Row[P] ? never : undefined extends Row[P] ? never : P; }[Fields & keyof Row];
139
+ type AllNullish<Row, Fields> = [NotNullish<Row, Fields>] extends [never] ? true : false;
140
+ type ForwardResult<S, Row, R> = R extends {
141
+ readonly to: infer T extends keyof S;
142
+ } ? Readonly<S[T]> | (AllNullish<Row, NullFieldsOf<R>> extends true ? null : never) : never;
143
+ type MatchesUnique<U, F> = U extends readonly (infer E)[] ? Same<F, E> : never;
144
+ type IsSingle<CC, F> = true extends Same<F, KeyFieldsOf<CC>> | MatchesUnique<UniqueListsOf<CC>, F> ? true : false;
145
+ type InverseResult<S, C, M, R> = M extends keyof S & keyof C ? IsSingle<C[M], FieldsOf<R>> extends true ? Readonly<S[M]> | undefined : readonly Readonly<S[M]>[] : never;
146
+ type InverseEntries<C, N> = { [M in keyof C]: { [R in keyof RefsOf<C[M]>]: RefsOf<C[M]>[R] extends {
147
+ readonly to: N;
148
+ readonly inverse: infer I extends string;
149
+ } ? [I, M, RefsOf<C[M]>[R]] : never; }[keyof RefsOf<C[M]>]; }[keyof C];
150
+ type ForwardRels<S, Row, CC> = { readonly [R in keyof RefsOf<CC>]: ForwardResult<S, Row, RefsOf<CC>[R]>; };
151
+ type InverseRels<S, C, N> = { readonly [E in InverseEntries<C, N> as E[0]]: InverseResult<S, C, E[1], E[2]>; };
152
+ /** Relation names of collection `N` mapped to what `rel` returns for them. */
153
+ type RelsOf<S, C, N extends keyof S & keyof C> = ForwardRels<S, S[N], C[N]> & InverseRels<S, C, N>;
154
+ /** Conditions for `where`: equality per field, or `{ in: [...] }`. `undefined` is ignored. */
155
+ type Where<Row> = { readonly [F in keyof Row]?: Row[F] | {
156
+ readonly in: readonly Row[F][];
157
+ }; };
158
+ /** A patch for `update`, or a function computing one from the current row. */
159
+ type Patch<Row> = Partial<Row> | ((row: Readonly<Row>) => Partial<Row>);
160
+ /** Rows per collection, keyed by collection name. */
161
+ type RowsByCollection<S> = { readonly [N in keyof S]?: readonly Readonly<S[N]>[]; };
162
+ /** What a delete removed, and the rows whose foreign keys were set to null. */
163
+ interface DeleteResult<S> {
164
+ /** The removed rows, per collection, as they were just before removal. */
165
+ readonly deleted: RowsByCollection<S>;
166
+ /** Rows changed by a `setNull` rule, per collection, as they are after the change. */
167
+ readonly nulled: RowsByCollection<S>;
168
+ }
169
+ /** An opaque copy of the database state, made by `snapshot()` and applied by `restore()`. */
170
+ interface Snapshot<S> {
171
+ readonly [snapshotBrand]: S;
172
+ }
173
+ /** One collection of a database. */
174
+ interface Collection<S, Row, K extends keyof Row, Rels> {
175
+ /** Looks up a row by its complete primary key. */
176
+ get(key: Pick<Row, K>): Readonly<Row> | undefined;
177
+ /**
178
+ * Equality search, plus `{ in: [...] }`. Uses the primary key, unique keys, foreign keys and
179
+ * declared indexes when the conditions cover them, and scans otherwise. `where({})` returns
180
+ * every row. Rows come back in insertion order.
181
+ */
182
+ where(conditions: Where<Row>): readonly Readonly<Row>[];
183
+ /**
184
+ * Traverses a relation by name. A forward ref returns the target row (or `null` when the
185
+ * foreign key is null); an inverse returns the referencing rows, or a single row when the
186
+ * foreign key is also the child's primary or unique key. Accepts a row or just its key.
187
+ */
188
+ rel<R extends keyof Rels & string>(row: Pick<Row, K>, name: R): Rels[R];
189
+ /** Inserts a complete row and returns the stored row. */
190
+ insert(row: Row, options?: InsertOptions): Readonly<Row>;
191
+ /** Inserts rows as one batch. Rows in a batch may reference each other in any order. */
192
+ insert(rows: readonly Row[], options?: InsertOptions): readonly Readonly<Row>[];
193
+ /**
194
+ * Patches a row by key and returns the new row. Any field may change, keys included;
195
+ * referencing rows follow each ref's `onUpdate` rule.
196
+ */
197
+ update(key: Pick<Row, K>, patch: Patch<Row>): Readonly<Row>;
198
+ /** Patches several rows. Everything is checked before anything is applied. */
199
+ update(keys: readonly Pick<Row, K>[], patch: Patch<Row>): readonly Readonly<Row>[];
200
+ /**
201
+ * Deletes rows by key, following each incoming ref's `onDelete` rule. `restrict` is checked
202
+ * against the complete plan, so a row and everything referencing it can go in one call.
203
+ */
204
+ delete(keys: Pick<Row, K> | readonly Pick<Row, K>[]): DeleteResult<S>;
205
+ }
206
+ /** Keys per collection, for `remove`. */
207
+ type KeysByCollection<S, C> = { readonly [N in keyof S]?: readonly Pick<S[N], KeyFieldsOf<C[N & keyof C]> & keyof S[N]>[]; };
208
+ /** Complete rows per collection, for `load`. The shape of a JSON fixture file. */
209
+ type LoadData<S> = { readonly [N in keyof S]?: readonly S[N][]; };
210
+ /** Database-wide methods. */
211
+ interface DbMethods<S, C> {
212
+ /** Inserts rows across collections as one batch, in any order. */
213
+ load(data: LoadData<S>, options?: InsertOptions): void;
214
+ /** Deletes rows across collections as one plan. */
215
+ remove(keys: KeysByCollection<S, C>): DeleteResult<S>;
216
+ /**
217
+ * Runs `fn`; if it throws, every change made inside is rolled back. Nested transactions
218
+ * roll back to where they started. `fn` must be synchronous.
219
+ */
220
+ transaction<T>(fn: () => T): T;
221
+ /** Captures the current state. Cheap to take, O(total rows). */
222
+ snapshot(): Snapshot<S>;
223
+ /** Restores a snapshot taken from this database. A snapshot can be restored many times. */
224
+ restore(snapshot: Snapshot<S>): void;
225
+ }
226
+ /** A database: one {@link Collection} per row type, plus {@link DbMethods}. */
227
+ type Db<S, C> = { readonly [N in keyof S]: Collection<S, S[N], KeyFieldsOf<C[N & keyof C]> & keyof S[N], RelsOf<S, C, N & keyof C>>; } & DbMethods<S, C>;
228
+ /**
229
+ * Creates a database. The double call is deliberate: the first call fixes the row types, the
230
+ * second infers the exact config, which TypeScript can't do in one call.
231
+ *
232
+ * @example
233
+ * ```ts
234
+ * const db = createDb<{ user: User; post: Post }>()({
235
+ * user: { key: ['id'], unique: [['email']] },
236
+ * post: {
237
+ * key: ['id'],
238
+ * refs: {
239
+ * author: { fields: ['authorId'], to: 'user', onDelete: 'cascade', inverse: 'posts' },
240
+ * },
241
+ * },
242
+ * });
243
+ * ```
244
+ */
245
+ export declare function createDb<S extends { [N in keyof S]: object; }>(): DefineDb<S>;
246
+ /** The second step of {@link createDb}: takes the config and returns the database. */
247
+ type DefineDb<S> = <const C extends DbConfig<S>>(config: C & CheckConfig<S, C>) => Db<S, C>;
248
+ /**
249
+ * What went wrong, as a stable machine-readable code.
250
+ *
251
+ * - `INVALID_SCHEMA`: the config passed to `createDb` is inconsistent.
252
+ * - `DUPLICATE_KEY`: a primary key is already taken.
253
+ * - `UNIQUE_VIOLATION`: a `unique` key is already taken.
254
+ * - `FOREIGN_KEY_VIOLATION`: a reference points at a row that doesn't exist, or is partly null.
255
+ * - `RESTRICT_VIOLATION`: a delete or key change is blocked by a `restrict` rule.
256
+ * - `NOT_FOUND`: a key passed to `update`, `delete` or `rel` doesn't exist.
257
+ * - `INVALID_ARGUMENT`: an argument has the wrong shape, such as an unknown relation name.
258
+ */
259
+ type MemrelErrorCode = "INVALID_SCHEMA" | "DUPLICATE_KEY" | "UNIQUE_VIOLATION" | "FOREIGN_KEY_VIOLATION" | "RESTRICT_VIOLATION" | "NOT_FOUND" | "INVALID_ARGUMENT";
260
+ /** Every error memrel throws on purpose is a `MemrelError`. */
261
+ export declare class MemrelError extends Error {
262
+ override readonly name = "MemrelError";
263
+ readonly code: MemrelErrorCode;
264
+ constructor(code: MemrelErrorCode, message: string);
265
+ }
266
+ export type { Action, CheckConfig, Collection, CollectionConfig, Db, DbConfig, DbMethods, DeleteResult, FieldList, InsertOptions, KeyFieldsOf, KeysByCollection, LoadData, MemrelErrorCode, OnConflict, Patch, RefConfig, RelsOf, RowsByCollection, Snapshot, Where };
267
+ //# sourceMappingURL=index.d.ts.map