edinburgh 0.6.3 → 0.6.4
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.
- package/README.md +84 -74
- package/build/src/edinburgh.d.ts +3 -3
- package/build/src/edinburgh.js +28 -14
- package/build/src/edinburgh.js.map +1 -1
- package/build/src/models.d.ts +43 -44
- package/build/src/models.js.map +1 -1
- package/build/src/types.d.ts +6 -20
- package/build/src/types.js.map +1 -1
- package/package.json +1 -1
- package/skill/AnyModelClass.md +1 -1
- package/skill/SKILL.md +11 -1
- package/skill/defineModel.md +2 -2
- package/skill/link.md +1 -1
- package/skill/setOnSaveCallback.md +3 -3
- package/src/edinburgh.ts +25 -13
- package/src/models.ts +55 -57
- package/src/types.ts +6 -17
package/src/edinburgh.ts
CHANGED
|
@@ -10,6 +10,7 @@ export {
|
|
|
10
10
|
type ModelClass,
|
|
11
11
|
type AnyModelClass,
|
|
12
12
|
type ModelBase,
|
|
13
|
+
type ModelLookup,
|
|
13
14
|
defineModel,
|
|
14
15
|
deleteEverything,
|
|
15
16
|
field,
|
|
@@ -158,6 +159,7 @@ export async function transact<T>(fn: () => T): Promise<T> {
|
|
|
158
159
|
const txn: Transaction = { id: txnId, instances: new Map() };
|
|
159
160
|
const onSaveItems: Map<Model<unknown>, Change> | undefined = onSaveCallback ? new Map() : undefined;
|
|
160
161
|
|
|
162
|
+
let retry = false;
|
|
161
163
|
let result: T | undefined;
|
|
162
164
|
try {
|
|
163
165
|
await txnStorage.run(txn, async function() {
|
|
@@ -179,23 +181,33 @@ export async function transact<T>(fn: () => T): Promise<T> {
|
|
|
179
181
|
onSaveItems.set(instance, change);
|
|
180
182
|
}
|
|
181
183
|
}
|
|
184
|
+
|
|
185
|
+
if (onSaveItems?.size) {
|
|
186
|
+
// Perform writes, and start a new transaction at or past the point-in-time of our commit
|
|
187
|
+
const commitResult = lowlevel.commitTransaction(txnId, true);
|
|
188
|
+
if (typeof commitResult === 'object') {
|
|
189
|
+
const commitSeq = await commitResult;
|
|
190
|
+
if (commitSeq <= 0) {
|
|
191
|
+
try { lowlevel.abortTransaction(txnId); } catch {}
|
|
192
|
+
retry = true;
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
// Run the callback within our renewed transaction context, so it can fetch linked lazy fields if needed
|
|
196
|
+
try {
|
|
197
|
+
onSaveCallback!(commitSeq, onSaveItems);
|
|
198
|
+
} catch (e) {
|
|
199
|
+
throw e;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// else: only reads
|
|
203
|
+
}
|
|
182
204
|
});
|
|
183
205
|
} catch (e: any) {
|
|
184
206
|
try { lowlevel.abortTransaction(txnId); } catch {}
|
|
185
207
|
throw e;
|
|
186
208
|
}
|
|
187
209
|
|
|
188
|
-
if (
|
|
189
|
-
// Perform writes, and start a new transaction at or past the point-in-time of our commit
|
|
190
|
-
const commitResult = lowlevel.commitTransaction(txnId, true);
|
|
191
|
-
if (typeof commitResult === 'object') {
|
|
192
|
-
const commitSeq = await commitResult;
|
|
193
|
-
if (commitSeq <= 0) continue; // Race condition - retry
|
|
194
|
-
// Run the callback within our new transaction context, so it can fetch linked lazy fields if needed
|
|
195
|
-
onSaveCallback!(commitSeq, onSaveItems);
|
|
196
|
-
}
|
|
197
|
-
// else: only reads
|
|
198
|
-
}
|
|
210
|
+
if (retry) continue;
|
|
199
211
|
|
|
200
212
|
// Make the instances read-only to make it clear that their transaction has ended.
|
|
201
213
|
for (const instance of txn.instances.values()) {
|
|
@@ -243,8 +255,8 @@ let onSaveCallback: ((commitId: number, items: Map<Model<unknown>, Change>) => v
|
|
|
243
255
|
* - A sequential number. Higher numbers have been committed after lower numbers.
|
|
244
256
|
* - A map of model instances to their changes. The change can be "created", "deleted", or an object containing the old values.
|
|
245
257
|
*
|
|
246
|
-
* The callback is called within a new transaction context
|
|
247
|
-
*
|
|
258
|
+
* The callback is called within a new transaction context at or after the committed state, so lazy-loads
|
|
259
|
+
* and additional writes are allowed.
|
|
248
260
|
*/
|
|
249
261
|
export function setOnSaveCallback(callback: ((commitId: number, items: Map<Model<unknown>, Change>) => void) | undefined) {
|
|
250
262
|
onSaveCallback = callback;
|
package/src/models.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as lowlevel from "olmdb/lowlevel";
|
|
2
2
|
import { DatabaseError } from "olmdb/lowlevel";
|
|
3
3
|
import DataPack from "./datapack.js";
|
|
4
|
-
import { deserializeType, serializeType, TypeWrapper, identifier, type FieldQueryArg, type FieldValue
|
|
4
|
+
import { deserializeType, serializeType, TypeWrapper, identifier, type FieldQueryArg, type FieldValue } from "./types.js";
|
|
5
5
|
import { transact, currentTxn, type Transaction } from "./edinburgh.js";
|
|
6
6
|
|
|
7
7
|
import { PrimaryKey, NonPrimaryIndex, IndexRangeIterator, UniqueIndex, SecondaryIndex, FindOptions, VersionInfo } from "./indexes.js";
|
|
@@ -65,34 +65,33 @@ function isObjectEmpty(obj: object) {
|
|
|
65
65
|
export type Change = Record<any, any> | "created" | "deleted";
|
|
66
66
|
|
|
67
67
|
type FieldsOf<T> = T extends new () => infer I ? I : never;
|
|
68
|
+
type ModelFields<T extends new () => any> = FieldsOf<T>;
|
|
68
69
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
70
|
+
export interface ModelLookup<PKA extends readonly any[] = readonly any[]> {
|
|
71
|
+
get(...args: PKA): any;
|
|
72
|
+
}
|
|
72
73
|
|
|
73
|
-
type
|
|
74
|
-
[K in keyof FIELDS]: FieldQueryArg<FIELDS[K]>;
|
|
75
|
-
};
|
|
74
|
+
type ModelInstance<FIELDS, PKA extends readonly any[] = readonly any[]> = Model<FIELDS, ModelLookup<PKA>>;
|
|
76
75
|
|
|
77
|
-
type PublicModelOf<T extends new () => any> = Model<
|
|
76
|
+
type PublicModelOf<T extends new () => any> = Model<ModelFields<T>>;
|
|
78
77
|
|
|
79
78
|
type IndexSpec<T extends new () => any> =
|
|
80
|
-
| (keyof
|
|
81
|
-
| readonly (keyof
|
|
79
|
+
| (keyof ModelFields<T> & string)
|
|
80
|
+
| readonly (keyof ModelFields<T> & string)[]
|
|
82
81
|
| ((instance: PublicModelOf<T>) => any);
|
|
83
82
|
|
|
84
|
-
type PKArgs<
|
|
85
|
-
PK extends readonly (keyof
|
|
86
|
-
? { [I in keyof PK]: PK[I] extends keyof
|
|
87
|
-
: PK extends keyof
|
|
88
|
-
? [
|
|
83
|
+
type PKArgs<FIELDS, PK> =
|
|
84
|
+
PK extends readonly (keyof FIELDS & string)[]
|
|
85
|
+
? { [I in keyof PK]: PK[I] extends keyof FIELDS ? FieldQueryArg<FIELDS[PK[I]]> : never }
|
|
86
|
+
: PK extends keyof FIELDS & string
|
|
87
|
+
? [FieldQueryArg<FIELDS[PK]>]
|
|
89
88
|
: [string];
|
|
90
89
|
|
|
91
|
-
type IndexArgs<
|
|
92
|
-
SPEC extends readonly (keyof
|
|
93
|
-
? { [I in keyof SPEC]: SPEC[I] extends keyof
|
|
94
|
-
: SPEC extends keyof
|
|
95
|
-
? [
|
|
90
|
+
type IndexArgs<FIELDS, SPEC> =
|
|
91
|
+
SPEC extends readonly (keyof FIELDS & string)[]
|
|
92
|
+
? { [I in keyof SPEC]: SPEC[I] extends keyof FIELDS ? FieldQueryArg<FIELDS[SPEC[I]]> : never }
|
|
93
|
+
: SPEC extends keyof FIELDS & string
|
|
94
|
+
? [FieldQueryArg<FIELDS[SPEC]>]
|
|
96
95
|
: SPEC extends (instance: any) => infer R
|
|
97
96
|
? R extends (infer V)[] ? [V] : [R]
|
|
98
97
|
: never;
|
|
@@ -111,11 +110,11 @@ type PublicIndexSpecs<SPECS> = {
|
|
|
111
110
|
*
|
|
112
111
|
* Useful when accepting or storing arbitrary registered model classes.
|
|
113
112
|
*/
|
|
114
|
-
export type AnyModelClass = ModelClass<object, any,
|
|
113
|
+
export type AnyModelClass = ModelClass<object, any, readonly any[], any, any>;
|
|
115
114
|
|
|
116
115
|
type StaticMembers<T extends new () => any> = Pick<T, Exclude<keyof T, 'prototype'>>;
|
|
117
116
|
|
|
118
|
-
type SecondaryRegistry<FIELDS> = Record<string, NonPrimaryIndex<
|
|
117
|
+
type SecondaryRegistry<FIELDS, PKA extends readonly any[]> = Record<string, NonPrimaryIndex<ModelInstance<FIELDS, PKA>, readonly (keyof FIELDS & string)[], readonly any[]>>;
|
|
119
118
|
|
|
120
119
|
function copyStaticMembersFromClassChain(target: object, source: Function) {
|
|
121
120
|
for (let current: any = source; current && current !== Function.prototype; current = Object.getPrototypeOf(current)) {
|
|
@@ -133,15 +132,15 @@ export const pendingModelInits = new Set<AnyModelClass>();
|
|
|
133
132
|
|
|
134
133
|
// These static members are attached dynamically in defineModel(), so 'declare' tells TypeScript
|
|
135
134
|
// they exist at runtime without emitting duplicate class fields that would shadow those assignments.
|
|
136
|
-
class ModelClassRuntime<
|
|
135
|
+
class ModelClassRuntime<FIELDS, PKA extends readonly any[], UNIQUE = {}, INDEX = {}> extends PrimaryKey<ModelInstance<FIELDS, PKA>, readonly (keyof FIELDS & string)[], PKA> {
|
|
137
136
|
// Runtime table identifier used for index naming and diagnostics.
|
|
138
137
|
declare tableName: string;
|
|
139
138
|
// Field schema map used for validation and serialization.
|
|
140
139
|
declare fields: Record<string | symbol | number, FieldConfig<unknown>>;
|
|
141
140
|
// Registered unique/secondary indexes for this model.
|
|
142
|
-
declare _secondaries?: SecondaryRegistry<
|
|
141
|
+
declare _secondaries?: SecondaryRegistry<FIELDS, PKA>;
|
|
143
142
|
// Cached list of non-primary fields used for value serialization.
|
|
144
|
-
_nonKeyFields!: (keyof
|
|
143
|
+
_nonKeyFields!: (keyof FIELDS & string)[];
|
|
145
144
|
// Lazy getter/setter descriptors installed on unloaded non-key fields.
|
|
146
145
|
_lazyDescriptors: Record<string | symbol | number, PropertyDescriptor> = {};
|
|
147
146
|
// Writable descriptors temporarily installed before hydrating value fields.
|
|
@@ -186,11 +185,11 @@ class ModelClassRuntime<PUBLIC_FIELDS, QUERY_FIELDS, PKA extends readonly any[],
|
|
|
186
185
|
this._lazyDescriptors[fieldName] = {
|
|
187
186
|
configurable: true,
|
|
188
187
|
enumerable: true,
|
|
189
|
-
get(this:
|
|
188
|
+
get(this: ModelInstance<FIELDS, PKA>) {
|
|
190
189
|
this.constructor._lazyLoad(this);
|
|
191
190
|
return (this as any)[fieldName];
|
|
192
191
|
},
|
|
193
|
-
set(this:
|
|
192
|
+
set(this: ModelInstance<FIELDS, PKA>, value: any) {
|
|
194
193
|
this.constructor._lazyLoad(this);
|
|
195
194
|
(this as any)[fieldName] = value;
|
|
196
195
|
},
|
|
@@ -226,9 +225,9 @@ class ModelClassRuntime<PUBLIC_FIELDS, QUERY_FIELDS, PKA extends readonly any[],
|
|
|
226
225
|
return index;
|
|
227
226
|
}
|
|
228
227
|
|
|
229
|
-
_get(txn: Transaction, args: PKA | Uint8Array, loadNow: false | Uint8Array):
|
|
230
|
-
_get(txn: Transaction, args: PKA | Uint8Array, loadNow: true):
|
|
231
|
-
_get(txn: Transaction, args: PKA | Uint8Array, loadNow: boolean | Uint8Array):
|
|
228
|
+
_get(txn: Transaction, args: PKA | Uint8Array, loadNow: false | Uint8Array): ModelInstance<FIELDS, PKA>;
|
|
229
|
+
_get(txn: Transaction, args: PKA | Uint8Array, loadNow: true): ModelInstance<FIELDS, PKA> | undefined;
|
|
230
|
+
_get(txn: Transaction, args: PKA | Uint8Array, loadNow: boolean | Uint8Array): ModelInstance<FIELDS, PKA> | undefined {
|
|
232
231
|
let key: Uint8Array;
|
|
233
232
|
let keyParts: readonly any[] | undefined;
|
|
234
233
|
if (args instanceof Uint8Array) {
|
|
@@ -239,7 +238,7 @@ class ModelClassRuntime<PUBLIC_FIELDS, QUERY_FIELDS, PKA extends readonly any[],
|
|
|
239
238
|
}
|
|
240
239
|
|
|
241
240
|
const keyHash = hashBytes(key);
|
|
242
|
-
const cached = txn.instances.get(keyHash) as
|
|
241
|
+
const cached = txn.instances.get(keyHash) as ModelInstance<FIELDS, PKA> | undefined;
|
|
243
242
|
if (cached) {
|
|
244
243
|
if (loadNow && loadNow !== true) {
|
|
245
244
|
Object.defineProperties(cached, this._resetDescriptors);
|
|
@@ -258,7 +257,7 @@ class ModelClassRuntime<PUBLIC_FIELDS, QUERY_FIELDS, PKA extends readonly any[],
|
|
|
258
257
|
}
|
|
259
258
|
}
|
|
260
259
|
|
|
261
|
-
const model = Object.create((this as any).prototype) as
|
|
260
|
+
const model = Object.create((this as any).prototype) as ModelInstance<FIELDS, PKA>;
|
|
262
261
|
model._txn = txn;
|
|
263
262
|
model._oldValues = {};
|
|
264
263
|
txn.instances.set(keyHash, model);
|
|
@@ -285,7 +284,7 @@ class ModelClassRuntime<PUBLIC_FIELDS, QUERY_FIELDS, PKA extends readonly any[],
|
|
|
285
284
|
return model;
|
|
286
285
|
}
|
|
287
286
|
|
|
288
|
-
_lazyLoad(model:
|
|
287
|
+
_lazyLoad(model: ModelInstance<FIELDS, PKA>) {
|
|
289
288
|
const key = model._primaryKey!;
|
|
290
289
|
const valueBuffer = dbGet(model._txn.id, key);
|
|
291
290
|
if (!valueBuffer) throw new DatabaseError(`Lazy-loaded ${this.tableName}#${key} does not exist`, 'LAZY_FAIL');
|
|
@@ -302,7 +301,7 @@ class ModelClassRuntime<PUBLIC_FIELDS, QUERY_FIELDS, PKA extends readonly any[],
|
|
|
302
301
|
*
|
|
303
302
|
* @returns The matching model, or `undefined` if no row exists.
|
|
304
303
|
*/
|
|
305
|
-
get(...args: PKA):
|
|
304
|
+
get(...args: PKA): ModelInstance<FIELDS, PKA> | undefined {
|
|
306
305
|
return this._get(currentTxn(), args, true);
|
|
307
306
|
}
|
|
308
307
|
|
|
@@ -313,11 +312,11 @@ class ModelClassRuntime<PUBLIC_FIELDS, QUERY_FIELDS, PKA extends readonly any[],
|
|
|
313
312
|
*
|
|
314
313
|
* Accessing a lazy field later will load the remaining fields transparently.
|
|
315
314
|
*/
|
|
316
|
-
getLazy(...args: PKA):
|
|
315
|
+
getLazy(...args: PKA): ModelInstance<FIELDS, PKA> {
|
|
317
316
|
return this._get(currentTxn(), args, false);
|
|
318
317
|
}
|
|
319
318
|
|
|
320
|
-
_pairToInstance(txn: Transaction, keyBuffer: ArrayBuffer, valueBuffer: ArrayBuffer):
|
|
319
|
+
_pairToInstance(txn: Transaction, keyBuffer: ArrayBuffer, valueBuffer: ArrayBuffer): ModelInstance<FIELDS, PKA> {
|
|
321
320
|
return this._get(txn, new Uint8Array(keyBuffer), new Uint8Array(valueBuffer))!;
|
|
322
321
|
}
|
|
323
322
|
|
|
@@ -329,7 +328,7 @@ class ModelClassRuntime<PUBLIC_FIELDS, QUERY_FIELDS, PKA extends readonly any[],
|
|
|
329
328
|
* @param obj Partial model data that **must** include every primary key field.
|
|
330
329
|
* @returns The loaded-and-updated or newly created instance.
|
|
331
330
|
*/
|
|
332
|
-
replaceInto(obj: Partial<
|
|
331
|
+
replaceInto(obj: Partial<FIELDS>): ModelInstance<FIELDS, PKA> {
|
|
333
332
|
const keyArgs: any[] = [];
|
|
334
333
|
for (const fieldName of this._indexFields.keys()) {
|
|
335
334
|
if (!(fieldName in (obj as any))) {
|
|
@@ -337,7 +336,7 @@ class ModelClassRuntime<PUBLIC_FIELDS, QUERY_FIELDS, PKA extends readonly any[],
|
|
|
337
336
|
}
|
|
338
337
|
keyArgs.push((obj as any)[fieldName]);
|
|
339
338
|
}
|
|
340
|
-
const existing = this.get(...keyArgs as any) as
|
|
339
|
+
const existing = this.get(...keyArgs as any) as ModelInstance<FIELDS, PKA> | undefined;
|
|
341
340
|
if (existing) {
|
|
342
341
|
for (const key in obj as any) {
|
|
343
342
|
if (!this._indexFields.has(key as any)) {
|
|
@@ -358,7 +357,7 @@ class ModelClassRuntime<PUBLIC_FIELDS, QUERY_FIELDS, PKA extends readonly any[],
|
|
|
358
357
|
* linked model uses a composite primary key, pass the full tuple in that slot.
|
|
359
358
|
* @returns The matching model instance, if any.
|
|
360
359
|
*/
|
|
361
|
-
getBy<K extends string & keyof UNIQUE>(name: K, ...args: IndexArgs<
|
|
360
|
+
getBy<K extends string & keyof UNIQUE>(name: K, ...args: IndexArgs<FIELDS, UNIQUE[K]>): ModelInstance<FIELDS, PKA> | undefined {
|
|
362
361
|
return (this._getSecondary(name) as any).getPK(...args);
|
|
363
362
|
}
|
|
364
363
|
|
|
@@ -369,9 +368,9 @@ class ModelClassRuntime<PUBLIC_FIELDS, QUERY_FIELDS, PKA extends readonly any[],
|
|
|
369
368
|
* or `index` registration. Link-valued index fields accept either the linked
|
|
370
369
|
* model instance or the linked model's primary key tuple/value.
|
|
371
370
|
*/
|
|
372
|
-
findBy<K extends string & keyof (UNIQUE & INDEX)>(name: K, opts: FindOptions<IndexArgs<
|
|
373
|
-
findBy<K extends string & keyof (UNIQUE & INDEX)>(name: K, opts: FindOptions<IndexArgs<
|
|
374
|
-
findBy<K extends string & keyof (UNIQUE & INDEX)>(name: K, opts?: FindOptions<IndexArgs<
|
|
371
|
+
findBy<K extends string & keyof (UNIQUE & INDEX)>(name: K, opts: FindOptions<IndexArgs<FIELDS, (UNIQUE & INDEX)[K]>, 'first'>): ModelInstance<FIELDS, PKA> | undefined;
|
|
372
|
+
findBy<K extends string & keyof (UNIQUE & INDEX)>(name: K, opts: FindOptions<IndexArgs<FIELDS, (UNIQUE & INDEX)[K]>, 'single'>): ModelInstance<FIELDS, PKA>;
|
|
373
|
+
findBy<K extends string & keyof (UNIQUE & INDEX)>(name: K, opts?: FindOptions<IndexArgs<FIELDS, (UNIQUE & INDEX)[K]>>): IndexRangeIterator<ModelInstance<FIELDS, PKA>>;
|
|
375
374
|
findBy(name: string, opts?: any): any {
|
|
376
375
|
return this._getSecondary(name).find(opts);
|
|
377
376
|
}
|
|
@@ -383,13 +382,13 @@ class ModelClassRuntime<PUBLIC_FIELDS, QUERY_FIELDS, PKA extends readonly any[],
|
|
|
383
382
|
*/
|
|
384
383
|
batchProcessBy<K extends string & keyof (UNIQUE & INDEX)>(
|
|
385
384
|
name: K,
|
|
386
|
-
opts: FindOptions<IndexArgs<
|
|
387
|
-
callback: (row:
|
|
385
|
+
opts: FindOptions<IndexArgs<FIELDS, (UNIQUE & INDEX)[K]>> & { limitSeconds?: number; limitRows?: number },
|
|
386
|
+
callback: (row: ModelInstance<FIELDS, PKA>) => any,
|
|
388
387
|
): Promise<void> {
|
|
389
388
|
return this._getSecondary(name).batchProcess(opts, callback as any);
|
|
390
389
|
}
|
|
391
390
|
|
|
392
|
-
_loadValueFields(model:
|
|
391
|
+
_loadValueFields(model: ModelInstance<FIELDS, PKA>, valueArray: Uint8Array) {
|
|
393
392
|
const valuePack = new DataPack(valueArray);
|
|
394
393
|
const version = valuePack.readNumber();
|
|
395
394
|
|
|
@@ -425,7 +424,7 @@ class ModelClassRuntime<PUBLIC_FIELDS, QUERY_FIELDS, PKA extends readonly any[],
|
|
|
425
424
|
return info;
|
|
426
425
|
}
|
|
427
426
|
|
|
428
|
-
_migrateValueFields(model:
|
|
427
|
+
_migrateValueFields(model: ModelInstance<FIELDS, PKA>, version: number, valuePack: DataPack) {
|
|
429
428
|
const versionInfo = this._loadVersionInfo(model._txn.id, version);
|
|
430
429
|
const record: Record<string, any> = {};
|
|
431
430
|
for (const [name] of this._indexFields.entries()) record[name] = (model as any)[name];
|
|
@@ -473,23 +472,23 @@ export const ModelClass = ModelClassRuntime;
|
|
|
473
472
|
* helpers like `get()` and `getLazy()`, range-query helpers like `find()`, and
|
|
474
473
|
* named-index helpers like `getBy()` and `findBy()`.
|
|
475
474
|
*
|
|
476
|
-
* @template
|
|
475
|
+
* @template FIELDS - The user-defined fields of the model instance.
|
|
477
476
|
* @template PKA - Tuple of primary-key argument types.
|
|
478
477
|
* @template UNIQUE - Named unique-index specifications.
|
|
479
478
|
* @template INDEX - Named secondary-index specifications.
|
|
480
479
|
*/
|
|
481
|
-
export type ModelClass<STATICS,
|
|
480
|
+
export type ModelClass<STATICS, FIELDS, PKA extends readonly any[], UNIQUE = {}, INDEX = {}> =
|
|
482
481
|
STATICS
|
|
483
|
-
& ModelClassRuntime<
|
|
482
|
+
& ModelClassRuntime<FIELDS, PKA, UNIQUE, INDEX>
|
|
484
483
|
& {
|
|
485
|
-
new (initial?: Partial<
|
|
484
|
+
new (initial?: Partial<FIELDS>, txn?: Transaction): ModelInstance<FIELDS, PKA>;
|
|
486
485
|
};
|
|
487
486
|
|
|
488
487
|
/**
|
|
489
488
|
* Minimal instance-side model shape used for typing the constructor property.
|
|
490
489
|
*/
|
|
491
|
-
export interface ModelBase {
|
|
492
|
-
constructor: AnyModelClass;
|
|
490
|
+
export interface ModelBase<LOOKUP extends ModelLookup = ModelLookup> {
|
|
491
|
+
constructor: LOOKUP & AnyModelClass;
|
|
493
492
|
}
|
|
494
493
|
|
|
495
494
|
/**
|
|
@@ -509,7 +508,7 @@ export interface ModelBase {
|
|
|
509
508
|
*/
|
|
510
509
|
export function defineModel<
|
|
511
510
|
T extends new () => any,
|
|
512
|
-
const PK extends (keyof
|
|
511
|
+
const PK extends (keyof ModelFields<T> & string) | readonly (keyof ModelFields<T> & string)[],
|
|
513
512
|
const UNIQUE extends Record<string, IndexSpec<T>>,
|
|
514
513
|
const INDEX extends Record<string, IndexSpec<T>>,
|
|
515
514
|
>(
|
|
@@ -518,9 +517,8 @@ export function defineModel<
|
|
|
518
517
|
opts?: { pk?: PK, unique?: UNIQUE, index?: INDEX, override?: boolean }
|
|
519
518
|
): ModelClass<
|
|
520
519
|
StaticMembers<T>,
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
PKArgs<QueryFields<FieldsOf<T>>, PK>,
|
|
520
|
+
ModelFields<T>,
|
|
521
|
+
PKArgs<ModelFields<T>, PK>,
|
|
524
522
|
PublicIndexSpecs<UNIQUE>,
|
|
525
523
|
PublicIndexSpecs<INDEX>
|
|
526
524
|
> {
|
|
@@ -1010,5 +1008,5 @@ export async function deleteEverything(): Promise<void> {
|
|
|
1010
1008
|
* A model instance, including its user-defined fields.
|
|
1011
1009
|
* @template FIELDS - The fields defined on this model.
|
|
1012
1010
|
*/
|
|
1013
|
-
export type Model<FIELDS> = FIELDS & ModelBase
|
|
1011
|
+
export type Model<FIELDS, LOOKUP extends ModelLookup = ModelLookup> = FIELDS & ModelBase<LOOKUP>;
|
|
1014
1012
|
export const Model = ModelBase;
|
package/src/types.ts
CHANGED
|
@@ -90,7 +90,7 @@ export interface TypeWrapper<T> {
|
|
|
90
90
|
// Hidden type-only metadata used to widen lookup arguments without widening assignment types.
|
|
91
91
|
export declare const QUERY_ARG: unique symbol;
|
|
92
92
|
|
|
93
|
-
export type LinkTargetPKArgs<T extends
|
|
93
|
+
export type LinkTargetPKArgs<T extends { get(...args: any): any }> =
|
|
94
94
|
T extends { get(...args: infer PKA): any } ? PKA : never;
|
|
95
95
|
|
|
96
96
|
export type LinkPrimaryKeyInput<PKA extends readonly any[]> =
|
|
@@ -102,26 +102,15 @@ type QueryArgCarrier<QUERY> = {
|
|
|
102
102
|
readonly [QUERY_ARG]?: QUERY;
|
|
103
103
|
};
|
|
104
104
|
|
|
105
|
-
type QueryAnnotated<T, QUERY> = T & QueryArgCarrier<QUERY>;
|
|
106
|
-
|
|
107
105
|
export type FieldValue<TYPE extends TypeWrapper<any>> =
|
|
108
106
|
TYPE extends TypeWrapper<infer T>
|
|
109
|
-
?
|
|
110
|
-
? QueryAnnotated<T, Exclude<QUERY, undefined>>
|
|
111
|
-
: T
|
|
107
|
+
? T
|
|
112
108
|
: never;
|
|
113
109
|
|
|
114
|
-
export type StripQueryArg<T> =
|
|
115
|
-
T extends ModelInstanceBase & { readonly [QUERY_ARG]?: any }
|
|
116
|
-
? Model<{
|
|
117
|
-
[K in Exclude<keyof T, keyof ModelInstanceBase | typeof QUERY_ARG>]: T[K]
|
|
118
|
-
}>
|
|
119
|
-
: T extends { readonly [QUERY_ARG]?: any }
|
|
120
|
-
? { [K in keyof T as K extends typeof QUERY_ARG ? never : K]: T[K] }
|
|
121
|
-
: T;
|
|
122
|
-
|
|
123
110
|
export type FieldQueryArg<T> =
|
|
124
|
-
T extends
|
|
111
|
+
T extends ModelInstanceBase<infer LOOKUP>
|
|
112
|
+
? T | LinkPrimaryKeyInput<LinkTargetPKArgs<LOOKUP>>
|
|
113
|
+
: T extends { readonly [QUERY_ARG]?: infer QUERY }
|
|
125
114
|
? Exclude<QUERY, undefined>
|
|
126
115
|
: T;
|
|
127
116
|
|
|
@@ -835,7 +824,7 @@ export function record<const T>(inner: TypeWrapper<T>): TypeWrapper<Record<strin
|
|
|
835
824
|
*/
|
|
836
825
|
export function link<const T extends new (...args: any[]) => Model<any>>(
|
|
837
826
|
TargetModel: T,
|
|
838
|
-
): TypeWrapper<InstanceType<T
|
|
827
|
+
): TypeWrapper<InstanceType<T>>;
|
|
839
828
|
export function link<const T extends new (...args: any[]) => Model<any>>(TargetModel: () => T): TypeWrapper<InstanceType<T>>;
|
|
840
829
|
export function link(TargetModel: any): TypeWrapper<any> {
|
|
841
830
|
return new LinkType(TargetModel);
|