edinburgh 0.6.0 → 0.6.2

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/indexes.ts CHANGED
@@ -22,6 +22,17 @@ type IndexArgTypes<ITEM, F extends readonly (keyof ITEM & string)[]> = {
22
22
  [I in keyof F]: ITEM[F[I]]
23
23
  };
24
24
 
25
+ function serializeIndexArg(fieldType: TypeWrapper<any>, arg: any, pack: DataPack) {
26
+ const linkedModel = fieldType.getLinkedModel();
27
+ if (!linkedModel || arg instanceof linkedModel) {
28
+ fieldType.serialize(arg, pack);
29
+ return;
30
+ }
31
+
32
+ // Link lookups may pass the target row's primary key instead of a loaded instance.
33
+ pack.write(linkedModel._argsToKeyBytes(Array.isArray(arg) ? arg : [arg], false).toUint8Array());
34
+ }
35
+
25
36
  const MAX_INDEX_ID_PREFIX = -1;
26
37
  const INDEX_ID_PREFIX = -2;
27
38
  const VERSION_INFO_PREFIX = -3;
@@ -88,7 +99,10 @@ type ArrayOrOnlyItem<ARG_TYPES extends readonly any[]> = ARG_TYPES extends reado
88
99
  * exclusive bounds via `after` / `before`, and reverse scans.
89
100
  *
90
101
  * For single-field indexes, values can be passed directly. For composite indexes,
91
- * pass tuples or partial tuples for prefix matching.
102
+ * pass tuples or partial tuples for prefix matching. If an index field is a
103
+ * `link(...)`, you may pass either the linked model instance or the linked
104
+ * model's primary key. Composite linked primary keys are passed as tuples in
105
+ * that slot.
92
106
  *
93
107
  * @template ARG_TYPES - Tuple of index argument types.
94
108
  * @template FETCH - Optional fetch mode used by overloads that return one row.
@@ -182,7 +196,7 @@ export abstract class BaseIndex<ITEM, const F extends readonly (keyof ITEM & str
182
196
  let index = 0;
183
197
  for (const fieldType of this._indexFields.values()) {
184
198
  if (index >= args.length) break;
185
- fieldType.serialize(args[index++], bytes);
199
+ serializeIndexArg(fieldType, args[index++], bytes);
186
200
  }
187
201
  }
188
202
  return bytes;
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 } 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";
@@ -49,7 +49,7 @@ export interface FieldConfig<T> {
49
49
  * });
50
50
  * ```
51
51
  */
52
- export function field<T>(type: TypeWrapper<T>, options: Partial<FieldConfig<T>> = {}): T {
52
+ export function field<TYPE extends TypeWrapper<any>>(type: TYPE, options: Partial<FieldConfig<FieldValue<TYPE>>> = {}): FieldValue<TYPE> {
53
53
  // Return the config object, but TypeScript sees it as type T
54
54
  options.type = type;
55
55
  return options as any;
@@ -68,16 +68,16 @@ type FieldsOf<T> = T extends new () => infer I ? I : never;
68
68
 
69
69
  type PKArgs<FIELDS, PK> =
70
70
  PK extends readonly (keyof FIELDS & string)[]
71
- ? { [I in keyof PK]: PK[I] extends keyof FIELDS ? FIELDS[PK[I]] : never }
71
+ ? { [I in keyof PK]: PK[I] extends keyof FIELDS ? FieldQueryArg<FIELDS[PK[I]]> : never }
72
72
  : PK extends keyof FIELDS & string
73
- ? [FIELDS[PK]]
73
+ ? [FieldQueryArg<FIELDS[PK]>]
74
74
  : [string];
75
75
 
76
76
  type IndexArgs<FIELDS, SPEC> =
77
77
  SPEC extends readonly (keyof FIELDS & string)[]
78
- ? { [I in keyof SPEC]: SPEC[I] extends keyof FIELDS ? FIELDS[SPEC[I]] : never }
78
+ ? { [I in keyof SPEC]: SPEC[I] extends keyof FIELDS ? FieldQueryArg<FIELDS[SPEC[I]]> : never }
79
79
  : SPEC extends keyof FIELDS & string
80
- ? [FIELDS[SPEC]]
80
+ ? [FieldQueryArg<FIELDS[SPEC]>]
81
81
  : SPEC extends (instance: any) => infer R
82
82
  ? R extends (infer V)[] ? [V] : [R]
83
83
  : never;
@@ -209,7 +209,7 @@ class ModelClassRuntime<FIELDS, PKA extends readonly any[], UNIQUE = {}, INDEX =
209
209
  key = args;
210
210
  } else {
211
211
  key = this._argsToKeyBytes(args, false).toUint8Array();
212
- keyParts = args;
212
+ keyParts = this._pkToArray(key);
213
213
  }
214
214
 
215
215
  const keyHash = hashBytes(key);
@@ -269,6 +269,10 @@ class ModelClassRuntime<FIELDS, PKA extends readonly any[], UNIQUE = {}, INDEX =
269
269
 
270
270
  /**
271
271
  * Load a model by primary key inside the current transaction.
272
+ *
273
+ * For `link(...)` primary-key fields, each argument may be the linked model
274
+ * instance or the linked model's primary key. Composite linked primary keys
275
+ * are passed as a tuple in that argument slot.
272
276
  *
273
277
  * @returns The matching model, or `undefined` if no row exists.
274
278
  */
@@ -278,6 +282,8 @@ class ModelClassRuntime<FIELDS, PKA extends readonly any[], UNIQUE = {}, INDEX =
278
282
 
279
283
  /**
280
284
  * Load a model by primary key without fetching its non-key fields immediately.
285
+ *
286
+ * Link-valued primary-key fields accept the same shorthand as `get()`.
281
287
  *
282
288
  * Accessing a lazy field later will load the remaining fields transparently.
283
289
  */
@@ -321,7 +327,9 @@ class ModelClassRuntime<FIELDS, PKA extends readonly any[], UNIQUE = {}, INDEX =
321
327
  * Look up a model through a named unique index.
322
328
  *
323
329
  * @param name The name from the model's `unique` definition.
324
- * @param args The unique-index key values.
330
+ * @param args The unique-index key values. For `link(...)` fields, pass
331
+ * either the linked model instance or the linked model's primary key. If the
332
+ * linked model uses a composite primary key, pass the full tuple in that slot.
325
333
  * @returns The matching model instance, if any.
326
334
  */
327
335
  getBy<K extends string & keyof UNIQUE>(name: K, ...args: IndexArgs<FIELDS, UNIQUE[K]>): Model<FIELDS> | undefined {
@@ -332,7 +340,8 @@ class ModelClassRuntime<FIELDS, PKA extends readonly any[], UNIQUE = {}, INDEX =
332
340
  * Query rows through a named unique or secondary index.
333
341
  *
334
342
  * This mirrors `find()`, but targets a named entry from the model's `unique`
335
- * or `index` registration.
343
+ * or `index` registration. Link-valued index fields accept either the linked
344
+ * model instance or the linked model's primary key tuple/value.
336
345
  */
337
346
  findBy<K extends string & keyof (UNIQUE & INDEX)>(name: K, opts: FindOptions<IndexArgs<FIELDS, (UNIQUE & INDEX)[K]>, 'first'>): Model<FIELDS> | undefined;
338
347
  findBy<K extends string & keyof (UNIQUE & INDEX)>(name: K, opts: FindOptions<IndexArgs<FIELDS, (UNIQUE & INDEX)[K]>, 'single'>): Model<FIELDS>;
package/src/types.ts CHANGED
@@ -87,6 +87,35 @@ export interface TypeWrapper<T> {
87
87
  default?(model: any): T;
88
88
  }
89
89
 
90
+ // Hidden type-only metadata used to widen lookup arguments without widening assignment types.
91
+ export declare const QUERY_ARG: unique symbol;
92
+
93
+ export type LinkTargetPKArgs<T extends new (...args: any) => any> =
94
+ T extends { get(...args: infer PKA): any } ? PKA : never;
95
+
96
+ export type LinkPrimaryKeyInput<PKA extends readonly any[]> =
97
+ PKA extends readonly [infer ONLY]
98
+ ? ONLY | PKA
99
+ : PKA;
100
+
101
+ type QueryArgCarrier<QUERY> = {
102
+ readonly [QUERY_ARG]?: QUERY;
103
+ };
104
+
105
+ type QueryAnnotated<T, QUERY> = T & QueryArgCarrier<QUERY>;
106
+
107
+ export type FieldValue<TYPE extends TypeWrapper<any>> =
108
+ TYPE extends TypeWrapper<infer T>
109
+ ? TYPE extends { readonly [QUERY_ARG]?: infer QUERY }
110
+ ? QueryAnnotated<T, Exclude<QUERY, undefined>>
111
+ : T
112
+ : never;
113
+
114
+ export type FieldQueryArg<T> =
115
+ T extends { readonly [QUERY_ARG]?: infer QUERY }
116
+ ? Exclude<QUERY, undefined>
117
+ : T;
118
+
90
119
 
91
120
  class StringType extends TypeWrapper<string> {
92
121
  kind = 'string';
@@ -620,7 +649,7 @@ export class LinkType<T extends new (...args: any[]) => Model<any>> extends Type
620
649
  }
621
650
 
622
651
  getLinkedModel(): AnyModelClass {
623
- if (!('getLazy' in this.TargetModel)) this.TargetModel = (this.TargetModel as unknown as () => T)();
652
+ if (!('getLazy' in (this.TargetModel as any))) this.TargetModel = (this.TargetModel as unknown as () => T)();
624
653
  return this.TargetModel as any;
625
654
  }
626
655
 
@@ -795,7 +824,9 @@ export function record<const T>(inner: TypeWrapper<T>): TypeWrapper<Record<strin
795
824
  * }, { pk: "id" });
796
825
  * ```
797
826
  */
798
- export function link<const T extends new (...args: any[]) => Model<any>>(TargetModel: T): TypeWrapper<InstanceType<T>>;
827
+ export function link<const T extends new (...args: any[]) => Model<any>>(
828
+ TargetModel: T,
829
+ ): TypeWrapper<InstanceType<T>> & QueryArgCarrier<InstanceType<T> | LinkPrimaryKeyInput<LinkTargetPKArgs<T>>>;
799
830
  export function link<const T extends new (...args: any[]) => Model<any>>(TargetModel: () => T): TypeWrapper<InstanceType<T>>;
800
831
  export function link(TargetModel: any): TypeWrapper<any> {
801
832
  return new LinkType(TargetModel);