edinburgh 0.6.2 → 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 +92 -82
- 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 +39 -27
- package/build/src/models.js.map +1 -1
- package/build/src/types.d.ts +8 -13
- 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 +4 -4
- package/skill/link.md +1 -1
- package/skill/setOnSaveCallback.md +3 -3
- package/src/edinburgh.ts +25 -13
- package/src/models.ts +69 -38
- package/src/types.ts +9 -11
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
|
@@ -65,6 +65,20 @@ 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>;
|
|
69
|
+
|
|
70
|
+
export interface ModelLookup<PKA extends readonly any[] = readonly any[]> {
|
|
71
|
+
get(...args: PKA): any;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
type ModelInstance<FIELDS, PKA extends readonly any[] = readonly any[]> = Model<FIELDS, ModelLookup<PKA>>;
|
|
75
|
+
|
|
76
|
+
type PublicModelOf<T extends new () => any> = Model<ModelFields<T>>;
|
|
77
|
+
|
|
78
|
+
type IndexSpec<T extends new () => any> =
|
|
79
|
+
| (keyof ModelFields<T> & string)
|
|
80
|
+
| readonly (keyof ModelFields<T> & string)[]
|
|
81
|
+
| ((instance: PublicModelOf<T>) => any);
|
|
68
82
|
|
|
69
83
|
type PKArgs<FIELDS, PK> =
|
|
70
84
|
PK extends readonly (keyof FIELDS & string)[]
|
|
@@ -82,14 +96,25 @@ type IndexArgs<FIELDS, SPEC> =
|
|
|
82
96
|
? R extends (infer V)[] ? [V] : [R]
|
|
83
97
|
: never;
|
|
84
98
|
|
|
99
|
+
type PublicIndexSpec<SPEC> =
|
|
100
|
+
SPEC extends (instance: infer INSTANCE) => infer R
|
|
101
|
+
? (instance: INSTANCE) => R
|
|
102
|
+
: SPEC;
|
|
103
|
+
|
|
104
|
+
type PublicIndexSpecs<SPECS> = {
|
|
105
|
+
[K in keyof SPECS]: PublicIndexSpec<SPECS[K]>;
|
|
106
|
+
};
|
|
107
|
+
|
|
85
108
|
/**
|
|
86
109
|
* A model constructor with its generic information erased.
|
|
87
110
|
*
|
|
88
111
|
* Useful when accepting or storing arbitrary registered model classes.
|
|
89
112
|
*/
|
|
90
|
-
export type AnyModelClass = ModelClass<
|
|
113
|
+
export type AnyModelClass = ModelClass<object, any, readonly any[], any, any>;
|
|
114
|
+
|
|
115
|
+
type StaticMembers<T extends new () => any> = Pick<T, Exclude<keyof T, 'prototype'>>;
|
|
91
116
|
|
|
92
|
-
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[]>>;
|
|
93
118
|
|
|
94
119
|
function copyStaticMembersFromClassChain(target: object, source: Function) {
|
|
95
120
|
for (let current: any = source; current && current !== Function.prototype; current = Object.getPrototypeOf(current)) {
|
|
@@ -107,13 +132,13 @@ export const pendingModelInits = new Set<AnyModelClass>();
|
|
|
107
132
|
|
|
108
133
|
// These static members are attached dynamically in defineModel(), so 'declare' tells TypeScript
|
|
109
134
|
// they exist at runtime without emitting duplicate class fields that would shadow those assignments.
|
|
110
|
-
class ModelClassRuntime<FIELDS, PKA extends readonly any[], UNIQUE = {}, INDEX = {}> extends PrimaryKey<
|
|
135
|
+
class ModelClassRuntime<FIELDS, PKA extends readonly any[], UNIQUE = {}, INDEX = {}> extends PrimaryKey<ModelInstance<FIELDS, PKA>, readonly (keyof FIELDS & string)[], PKA> {
|
|
111
136
|
// Runtime table identifier used for index naming and diagnostics.
|
|
112
137
|
declare tableName: string;
|
|
113
138
|
// Field schema map used for validation and serialization.
|
|
114
139
|
declare fields: Record<string | symbol | number, FieldConfig<unknown>>;
|
|
115
140
|
// Registered unique/secondary indexes for this model.
|
|
116
|
-
declare _secondaries?: SecondaryRegistry<FIELDS>;
|
|
141
|
+
declare _secondaries?: SecondaryRegistry<FIELDS, PKA>;
|
|
117
142
|
// Cached list of non-primary fields used for value serialization.
|
|
118
143
|
_nonKeyFields!: (keyof FIELDS & string)[];
|
|
119
144
|
// Lazy getter/setter descriptors installed on unloaded non-key fields.
|
|
@@ -160,13 +185,13 @@ class ModelClassRuntime<FIELDS, PKA extends readonly any[], UNIQUE = {}, INDEX =
|
|
|
160
185
|
this._lazyDescriptors[fieldName] = {
|
|
161
186
|
configurable: true,
|
|
162
187
|
enumerable: true,
|
|
163
|
-
get(this:
|
|
188
|
+
get(this: ModelInstance<FIELDS, PKA>) {
|
|
164
189
|
this.constructor._lazyLoad(this);
|
|
165
|
-
return this[fieldName];
|
|
190
|
+
return (this as any)[fieldName];
|
|
166
191
|
},
|
|
167
|
-
set(this:
|
|
192
|
+
set(this: ModelInstance<FIELDS, PKA>, value: any) {
|
|
168
193
|
this.constructor._lazyLoad(this);
|
|
169
|
-
this[fieldName] = value;
|
|
194
|
+
(this as any)[fieldName] = value;
|
|
170
195
|
},
|
|
171
196
|
};
|
|
172
197
|
this._resetDescriptors[fieldName] = {
|
|
@@ -200,9 +225,9 @@ class ModelClassRuntime<FIELDS, PKA extends readonly any[], UNIQUE = {}, INDEX =
|
|
|
200
225
|
return index;
|
|
201
226
|
}
|
|
202
227
|
|
|
203
|
-
_get(txn: Transaction, args: PKA | Uint8Array, loadNow: false | Uint8Array):
|
|
204
|
-
_get(txn: Transaction, args: PKA | Uint8Array, loadNow: true):
|
|
205
|
-
_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 {
|
|
206
231
|
let key: Uint8Array;
|
|
207
232
|
let keyParts: readonly any[] | undefined;
|
|
208
233
|
if (args instanceof Uint8Array) {
|
|
@@ -213,7 +238,7 @@ class ModelClassRuntime<FIELDS, PKA extends readonly any[], UNIQUE = {}, INDEX =
|
|
|
213
238
|
}
|
|
214
239
|
|
|
215
240
|
const keyHash = hashBytes(key);
|
|
216
|
-
const cached = txn.instances.get(keyHash) as
|
|
241
|
+
const cached = txn.instances.get(keyHash) as ModelInstance<FIELDS, PKA> | undefined;
|
|
217
242
|
if (cached) {
|
|
218
243
|
if (loadNow && loadNow !== true) {
|
|
219
244
|
Object.defineProperties(cached, this._resetDescriptors);
|
|
@@ -232,7 +257,7 @@ class ModelClassRuntime<FIELDS, PKA extends readonly any[], UNIQUE = {}, INDEX =
|
|
|
232
257
|
}
|
|
233
258
|
}
|
|
234
259
|
|
|
235
|
-
const model = Object.create((this as any).prototype) as
|
|
260
|
+
const model = Object.create((this as any).prototype) as ModelInstance<FIELDS, PKA>;
|
|
236
261
|
model._txn = txn;
|
|
237
262
|
model._oldValues = {};
|
|
238
263
|
txn.instances.set(keyHash, model);
|
|
@@ -259,7 +284,7 @@ class ModelClassRuntime<FIELDS, PKA extends readonly any[], UNIQUE = {}, INDEX =
|
|
|
259
284
|
return model;
|
|
260
285
|
}
|
|
261
286
|
|
|
262
|
-
_lazyLoad(model:
|
|
287
|
+
_lazyLoad(model: ModelInstance<FIELDS, PKA>) {
|
|
263
288
|
const key = model._primaryKey!;
|
|
264
289
|
const valueBuffer = dbGet(model._txn.id, key);
|
|
265
290
|
if (!valueBuffer) throw new DatabaseError(`Lazy-loaded ${this.tableName}#${key} does not exist`, 'LAZY_FAIL');
|
|
@@ -276,7 +301,7 @@ class ModelClassRuntime<FIELDS, PKA extends readonly any[], UNIQUE = {}, INDEX =
|
|
|
276
301
|
*
|
|
277
302
|
* @returns The matching model, or `undefined` if no row exists.
|
|
278
303
|
*/
|
|
279
|
-
get(...args: PKA):
|
|
304
|
+
get(...args: PKA): ModelInstance<FIELDS, PKA> | undefined {
|
|
280
305
|
return this._get(currentTxn(), args, true);
|
|
281
306
|
}
|
|
282
307
|
|
|
@@ -287,11 +312,11 @@ class ModelClassRuntime<FIELDS, PKA extends readonly any[], UNIQUE = {}, INDEX =
|
|
|
287
312
|
*
|
|
288
313
|
* Accessing a lazy field later will load the remaining fields transparently.
|
|
289
314
|
*/
|
|
290
|
-
getLazy(...args: PKA):
|
|
315
|
+
getLazy(...args: PKA): ModelInstance<FIELDS, PKA> {
|
|
291
316
|
return this._get(currentTxn(), args, false);
|
|
292
317
|
}
|
|
293
318
|
|
|
294
|
-
_pairToInstance(txn: Transaction, keyBuffer: ArrayBuffer, valueBuffer: ArrayBuffer):
|
|
319
|
+
_pairToInstance(txn: Transaction, keyBuffer: ArrayBuffer, valueBuffer: ArrayBuffer): ModelInstance<FIELDS, PKA> {
|
|
295
320
|
return this._get(txn, new Uint8Array(keyBuffer), new Uint8Array(valueBuffer))!;
|
|
296
321
|
}
|
|
297
322
|
|
|
@@ -303,7 +328,7 @@ class ModelClassRuntime<FIELDS, PKA extends readonly any[], UNIQUE = {}, INDEX =
|
|
|
303
328
|
* @param obj Partial model data that **must** include every primary key field.
|
|
304
329
|
* @returns The loaded-and-updated or newly created instance.
|
|
305
330
|
*/
|
|
306
|
-
replaceInto(obj: Partial<FIELDS>):
|
|
331
|
+
replaceInto(obj: Partial<FIELDS>): ModelInstance<FIELDS, PKA> {
|
|
307
332
|
const keyArgs: any[] = [];
|
|
308
333
|
for (const fieldName of this._indexFields.keys()) {
|
|
309
334
|
if (!(fieldName in (obj as any))) {
|
|
@@ -311,7 +336,7 @@ class ModelClassRuntime<FIELDS, PKA extends readonly any[], UNIQUE = {}, INDEX =
|
|
|
311
336
|
}
|
|
312
337
|
keyArgs.push((obj as any)[fieldName]);
|
|
313
338
|
}
|
|
314
|
-
const existing = this.get(...keyArgs as any) as
|
|
339
|
+
const existing = this.get(...keyArgs as any) as ModelInstance<FIELDS, PKA> | undefined;
|
|
315
340
|
if (existing) {
|
|
316
341
|
for (const key in obj as any) {
|
|
317
342
|
if (!this._indexFields.has(key as any)) {
|
|
@@ -332,7 +357,7 @@ class ModelClassRuntime<FIELDS, PKA extends readonly any[], UNIQUE = {}, INDEX =
|
|
|
332
357
|
* linked model uses a composite primary key, pass the full tuple in that slot.
|
|
333
358
|
* @returns The matching model instance, if any.
|
|
334
359
|
*/
|
|
335
|
-
getBy<K extends string & keyof UNIQUE>(name: K, ...args: IndexArgs<FIELDS, UNIQUE[K]>):
|
|
360
|
+
getBy<K extends string & keyof UNIQUE>(name: K, ...args: IndexArgs<FIELDS, UNIQUE[K]>): ModelInstance<FIELDS, PKA> | undefined {
|
|
336
361
|
return (this._getSecondary(name) as any).getPK(...args);
|
|
337
362
|
}
|
|
338
363
|
|
|
@@ -343,9 +368,9 @@ class ModelClassRuntime<FIELDS, PKA extends readonly any[], UNIQUE = {}, INDEX =
|
|
|
343
368
|
* or `index` registration. Link-valued index fields accept either the linked
|
|
344
369
|
* model instance or the linked model's primary key tuple/value.
|
|
345
370
|
*/
|
|
346
|
-
findBy<K extends string & keyof (UNIQUE & INDEX)>(name: K, opts: FindOptions<IndexArgs<FIELDS, (UNIQUE & INDEX)[K]>, 'first'>):
|
|
347
|
-
findBy<K extends string & keyof (UNIQUE & INDEX)>(name: K, opts: FindOptions<IndexArgs<FIELDS, (UNIQUE & INDEX)[K]>, 'single'>):
|
|
348
|
-
findBy<K extends string & keyof (UNIQUE & INDEX)>(name: K, opts?: FindOptions<IndexArgs<FIELDS, (UNIQUE & INDEX)[K]>>): IndexRangeIterator<
|
|
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>>;
|
|
349
374
|
findBy(name: string, opts?: any): any {
|
|
350
375
|
return this._getSecondary(name).find(opts);
|
|
351
376
|
}
|
|
@@ -358,12 +383,12 @@ class ModelClassRuntime<FIELDS, PKA extends readonly any[], UNIQUE = {}, INDEX =
|
|
|
358
383
|
batchProcessBy<K extends string & keyof (UNIQUE & INDEX)>(
|
|
359
384
|
name: K,
|
|
360
385
|
opts: FindOptions<IndexArgs<FIELDS, (UNIQUE & INDEX)[K]>> & { limitSeconds?: number; limitRows?: number },
|
|
361
|
-
callback: (row:
|
|
386
|
+
callback: (row: ModelInstance<FIELDS, PKA>) => any,
|
|
362
387
|
): Promise<void> {
|
|
363
388
|
return this._getSecondary(name).batchProcess(opts, callback as any);
|
|
364
389
|
}
|
|
365
390
|
|
|
366
|
-
_loadValueFields(model:
|
|
391
|
+
_loadValueFields(model: ModelInstance<FIELDS, PKA>, valueArray: Uint8Array) {
|
|
367
392
|
const valuePack = new DataPack(valueArray);
|
|
368
393
|
const version = valuePack.readNumber();
|
|
369
394
|
|
|
@@ -399,7 +424,7 @@ class ModelClassRuntime<FIELDS, PKA extends readonly any[], UNIQUE = {}, INDEX =
|
|
|
399
424
|
return info;
|
|
400
425
|
}
|
|
401
426
|
|
|
402
|
-
_migrateValueFields(model:
|
|
427
|
+
_migrateValueFields(model: ModelInstance<FIELDS, PKA>, version: number, valuePack: DataPack) {
|
|
403
428
|
const versionInfo = this._loadVersionInfo(model._txn.id, version);
|
|
404
429
|
const record: Record<string, any> = {};
|
|
405
430
|
for (const [name] of this._indexFields.entries()) record[name] = (model as any)[name];
|
|
@@ -447,23 +472,23 @@ export const ModelClass = ModelClassRuntime;
|
|
|
447
472
|
* helpers like `get()` and `getLazy()`, range-query helpers like `find()`, and
|
|
448
473
|
* named-index helpers like `getBy()` and `findBy()`.
|
|
449
474
|
*
|
|
450
|
-
* @template
|
|
475
|
+
* @template FIELDS - The user-defined fields of the model instance.
|
|
451
476
|
* @template PKA - Tuple of primary-key argument types.
|
|
452
477
|
* @template UNIQUE - Named unique-index specifications.
|
|
453
478
|
* @template INDEX - Named secondary-index specifications.
|
|
454
479
|
*/
|
|
455
|
-
export type ModelClass<
|
|
456
|
-
|
|
457
|
-
& ModelClassRuntime<
|
|
480
|
+
export type ModelClass<STATICS, FIELDS, PKA extends readonly any[], UNIQUE = {}, INDEX = {}> =
|
|
481
|
+
STATICS
|
|
482
|
+
& ModelClassRuntime<FIELDS, PKA, UNIQUE, INDEX>
|
|
458
483
|
& {
|
|
459
|
-
new (initial?: Partial<
|
|
484
|
+
new (initial?: Partial<FIELDS>, txn?: Transaction): ModelInstance<FIELDS, PKA>;
|
|
460
485
|
};
|
|
461
486
|
|
|
462
487
|
/**
|
|
463
488
|
* Minimal instance-side model shape used for typing the constructor property.
|
|
464
489
|
*/
|
|
465
|
-
export interface ModelBase {
|
|
466
|
-
constructor: AnyModelClass;
|
|
490
|
+
export interface ModelBase<LOOKUP extends ModelLookup = ModelLookup> {
|
|
491
|
+
constructor: LOOKUP & AnyModelClass;
|
|
467
492
|
}
|
|
468
493
|
|
|
469
494
|
/**
|
|
@@ -483,14 +508,20 @@ export interface ModelBase {
|
|
|
483
508
|
*/
|
|
484
509
|
export function defineModel<
|
|
485
510
|
T extends new () => any,
|
|
486
|
-
const PK extends (keyof
|
|
487
|
-
const UNIQUE extends Record<string,
|
|
488
|
-
const INDEX extends Record<string,
|
|
511
|
+
const PK extends (keyof ModelFields<T> & string) | readonly (keyof ModelFields<T> & string)[],
|
|
512
|
+
const UNIQUE extends Record<string, IndexSpec<T>>,
|
|
513
|
+
const INDEX extends Record<string, IndexSpec<T>>,
|
|
489
514
|
>(
|
|
490
515
|
tableName: string,
|
|
491
516
|
cls: T,
|
|
492
517
|
opts?: { pk?: PK, unique?: UNIQUE, index?: INDEX, override?: boolean }
|
|
493
|
-
): ModelClass<
|
|
518
|
+
): ModelClass<
|
|
519
|
+
StaticMembers<T>,
|
|
520
|
+
ModelFields<T>,
|
|
521
|
+
PKArgs<ModelFields<T>, PK>,
|
|
522
|
+
PublicIndexSpecs<UNIQUE>,
|
|
523
|
+
PublicIndexSpecs<INDEX>
|
|
524
|
+
> {
|
|
494
525
|
Object.setPrototypeOf(cls.prototype, ModelBase.prototype);
|
|
495
526
|
const MockModel = function(this: any, initial?: Record<string, any>, txn: Transaction = currentTxn()) {
|
|
496
527
|
this._txn = txn;
|
|
@@ -977,5 +1008,5 @@ export async function deleteEverything(): Promise<void> {
|
|
|
977
1008
|
* A model instance, including its user-defined fields.
|
|
978
1009
|
* @template FIELDS - The fields defined on this model.
|
|
979
1010
|
*/
|
|
980
|
-
export type Model<FIELDS> = FIELDS & ModelBase
|
|
1011
|
+
export type Model<FIELDS, LOOKUP extends ModelLookup = ModelLookup> = FIELDS & ModelBase<LOOKUP>;
|
|
981
1012
|
export const Model = ModelBase;
|
package/src/types.ts
CHANGED
|
@@ -2,7 +2,7 @@ import DataPack from "./datapack.js";
|
|
|
2
2
|
import { DatabaseError } from "olmdb/lowlevel";
|
|
3
3
|
import { currentTxn } from "./edinburgh.js";
|
|
4
4
|
import { Model, modelRegistry } from "./models.js";
|
|
5
|
-
import type { AnyModelClass } from "./models.js";
|
|
5
|
+
import type { AnyModelClass, ModelBase as ModelInstanceBase } from "./models.js";
|
|
6
6
|
import { assert, addErrorPath, dbGet } from "./utils.js";
|
|
7
7
|
|
|
8
8
|
|
|
@@ -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,17 +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
110
|
export type FieldQueryArg<T> =
|
|
115
|
-
T extends
|
|
111
|
+
T extends ModelInstanceBase<infer LOOKUP>
|
|
112
|
+
? T | LinkPrimaryKeyInput<LinkTargetPKArgs<LOOKUP>>
|
|
113
|
+
: T extends { readonly [QUERY_ARG]?: infer QUERY }
|
|
116
114
|
? Exclude<QUERY, undefined>
|
|
117
115
|
: T;
|
|
118
116
|
|
|
@@ -657,8 +655,8 @@ export class LinkType<T extends new (...args: any[]) => Model<any>> extends Type
|
|
|
657
655
|
pack.write(model.getPrimaryKey());
|
|
658
656
|
}
|
|
659
657
|
|
|
660
|
-
deserialize(pack: DataPack) {
|
|
661
|
-
return this.getLinkedModel()._get(currentTxn(), pack.readUint8Array(), false)
|
|
658
|
+
deserialize(pack: DataPack): InstanceType<T> {
|
|
659
|
+
return this.getLinkedModel()._get(currentTxn(), pack.readUint8Array(), false) as InstanceType<T>;
|
|
662
660
|
}
|
|
663
661
|
|
|
664
662
|
getError(value: InstanceType<T>) {
|
|
@@ -826,7 +824,7 @@ export function record<const T>(inner: TypeWrapper<T>): TypeWrapper<Record<strin
|
|
|
826
824
|
*/
|
|
827
825
|
export function link<const T extends new (...args: any[]) => Model<any>>(
|
|
828
826
|
TargetModel: T,
|
|
829
|
-
): TypeWrapper<InstanceType<T
|
|
827
|
+
): TypeWrapper<InstanceType<T>>;
|
|
830
828
|
export function link<const T extends new (...args: any[]) => Model<any>>(TargetModel: () => T): TypeWrapper<InstanceType<T>>;
|
|
831
829
|
export function link(TargetModel: any): TypeWrapper<any> {
|
|
832
830
|
return new LinkType(TargetModel);
|