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/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 (onSaveItems?.size) {
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, allowing lazy-loads to happen. However, any
247
- * changes made to Edinburgh models will not be saved.
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, type StripQueryArg } from "./types.js";
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
- type PublicFields<FIELDS> = {
70
- [K in keyof FIELDS]: StripQueryArg<FIELDS[K]>;
71
- };
70
+ export interface ModelLookup<PKA extends readonly any[] = readonly any[]> {
71
+ get(...args: PKA): any;
72
+ }
72
73
 
73
- type QueryFields<FIELDS> = {
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<PublicFields<FieldsOf<T>>>;
76
+ type PublicModelOf<T extends new () => any> = Model<ModelFields<T>>;
78
77
 
79
78
  type IndexSpec<T extends new () => any> =
80
- | (keyof FieldsOf<T> & string)
81
- | readonly (keyof FieldsOf<T> & string)[]
79
+ | (keyof ModelFields<T> & string)
80
+ | readonly (keyof ModelFields<T> & string)[]
82
81
  | ((instance: PublicModelOf<T>) => any);
83
82
 
84
- type PKArgs<QUERY_FIELDS, PK> =
85
- PK extends readonly (keyof QUERY_FIELDS & string)[]
86
- ? { [I in keyof PK]: PK[I] extends keyof QUERY_FIELDS ? QUERY_FIELDS[PK[I]] : never }
87
- : PK extends keyof QUERY_FIELDS & string
88
- ? [QUERY_FIELDS[PK]]
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<QUERY_FIELDS, SPEC> =
92
- SPEC extends readonly (keyof QUERY_FIELDS & string)[]
93
- ? { [I in keyof SPEC]: SPEC[I] extends keyof QUERY_FIELDS ? QUERY_FIELDS[SPEC[I]] : never }
94
- : SPEC extends keyof QUERY_FIELDS & string
95
- ? [QUERY_FIELDS[SPEC]]
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, any, readonly any[], any, 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<Model<FIELDS>, readonly (keyof FIELDS & string)[], readonly any[]>>;
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<PUBLIC_FIELDS, QUERY_FIELDS, PKA extends readonly any[], UNIQUE = {}, INDEX = {}> extends PrimaryKey<Model<PUBLIC_FIELDS>, readonly (keyof PUBLIC_FIELDS & string)[], PKA> {
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<PUBLIC_FIELDS>;
141
+ declare _secondaries?: SecondaryRegistry<FIELDS, PKA>;
143
142
  // Cached list of non-primary fields used for value serialization.
144
- _nonKeyFields!: (keyof PUBLIC_FIELDS & string)[];
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: Model<PUBLIC_FIELDS>) {
188
+ get(this: ModelInstance<FIELDS, PKA>) {
190
189
  this.constructor._lazyLoad(this);
191
190
  return (this as any)[fieldName];
192
191
  },
193
- set(this: Model<PUBLIC_FIELDS>, value: any) {
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): Model<PUBLIC_FIELDS>;
230
- _get(txn: Transaction, args: PKA | Uint8Array, loadNow: true): Model<PUBLIC_FIELDS> | undefined;
231
- _get(txn: Transaction, args: PKA | Uint8Array, loadNow: boolean | Uint8Array): Model<PUBLIC_FIELDS> | undefined {
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 Model<PUBLIC_FIELDS> | undefined;
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 Model<PUBLIC_FIELDS>;
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: Model<PUBLIC_FIELDS>) {
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): Model<PUBLIC_FIELDS> | undefined {
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): Model<PUBLIC_FIELDS> {
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): Model<PUBLIC_FIELDS> {
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<PUBLIC_FIELDS>): Model<PUBLIC_FIELDS> {
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 Model<PUBLIC_FIELDS> | undefined;
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<QUERY_FIELDS, UNIQUE[K]>): Model<PUBLIC_FIELDS> | undefined {
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<QUERY_FIELDS, (UNIQUE & INDEX)[K]>, 'first'>): Model<PUBLIC_FIELDS> | undefined;
373
- findBy<K extends string & keyof (UNIQUE & INDEX)>(name: K, opts: FindOptions<IndexArgs<QUERY_FIELDS, (UNIQUE & INDEX)[K]>, 'single'>): Model<PUBLIC_FIELDS>;
374
- findBy<K extends string & keyof (UNIQUE & INDEX)>(name: K, opts?: FindOptions<IndexArgs<QUERY_FIELDS, (UNIQUE & INDEX)[K]>>): IndexRangeIterator<Model<PUBLIC_FIELDS>>;
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<QUERY_FIELDS, (UNIQUE & INDEX)[K]>> & { limitSeconds?: number; limitRows?: number },
387
- callback: (row: Model<PUBLIC_FIELDS>) => any,
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: Model<PUBLIC_FIELDS>, valueArray: Uint8Array) {
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: Model<PUBLIC_FIELDS>, version: number, valuePack: DataPack) {
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 T - The original class passed to `defineModel()`.
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, PUBLIC_FIELDS, QUERY_FIELDS, PKA extends readonly any[], UNIQUE = {}, INDEX = {}> =
480
+ export type ModelClass<STATICS, FIELDS, PKA extends readonly any[], UNIQUE = {}, INDEX = {}> =
482
481
  STATICS
483
- & ModelClassRuntime<PUBLIC_FIELDS, QUERY_FIELDS, PKA, UNIQUE, INDEX>
482
+ & ModelClassRuntime<FIELDS, PKA, UNIQUE, INDEX>
484
483
  & {
485
- new (initial?: Partial<PUBLIC_FIELDS>, txn?: Transaction): Model<PUBLIC_FIELDS>;
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 FieldsOf<T> & string) | readonly (keyof FieldsOf<T> & string)[],
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
- PublicFields<FieldsOf<T>>,
522
- QueryFields<FieldsOf<T>>,
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 new (...args: any) => any> =
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
- ? TYPE extends { readonly [QUERY_ARG]?: infer QUERY }
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 { readonly [QUERY_ARG]?: infer QUERY }
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>> & QueryArgCarrier<InstanceType<T> | LinkPrimaryKeyInput<LinkTargetPKArgs<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);