edinburgh 0.5.0 → 0.6.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.
Files changed (72) hide show
  1. package/README.md +322 -262
  2. package/build/src/datapack.d.ts +9 -9
  3. package/build/src/datapack.js +9 -9
  4. package/build/src/edinburgh.d.ts +18 -7
  5. package/build/src/edinburgh.js +30 -51
  6. package/build/src/edinburgh.js.map +1 -1
  7. package/build/src/indexes.d.ts +85 -205
  8. package/build/src/indexes.js +150 -503
  9. package/build/src/indexes.js.map +1 -1
  10. package/build/src/migrate.js +8 -10
  11. package/build/src/migrate.js.map +1 -1
  12. package/build/src/models.d.ts +152 -107
  13. package/build/src/models.js +433 -144
  14. package/build/src/models.js.map +1 -1
  15. package/build/src/types.d.ts +30 -48
  16. package/build/src/types.js +25 -24
  17. package/build/src/types.js.map +1 -1
  18. package/build/src/utils.d.ts +4 -4
  19. package/build/src/utils.js +4 -4
  20. package/package.json +1 -1
  21. package/skill/AnyModelClass.md +7 -0
  22. package/skill/FindOptions.md +37 -0
  23. package/skill/Lifecycle Hooks.md +24 -0
  24. package/skill/{Model_delete.md → Lifecycle Hooks_delete.md } +1 -1
  25. package/skill/{Model_getPrimaryKeyHash.md → Lifecycle Hooks_getPrimaryKeyHash.md } +1 -1
  26. package/skill/{Model_isValid.md → Lifecycle Hooks_isValid.md } +1 -1
  27. package/skill/Lifecycle Hooks_migrate.md +26 -0
  28. package/skill/{Model_preCommit.md → Lifecycle Hooks_preCommit.md } +2 -2
  29. package/skill/{Model_preventPersist.md → Lifecycle Hooks_preventPersist.md } +1 -1
  30. package/skill/{Model_validate.md → Lifecycle Hooks_validate.md } +2 -2
  31. package/skill/ModelBase.md +7 -0
  32. package/skill/ModelClass.md +8 -0
  33. package/skill/SKILL.md +180 -132
  34. package/skill/Schema Evolution.md +19 -0
  35. package/skill/TypeWrapper_containsNull.md +11 -0
  36. package/skill/TypeWrapper_deserialize.md +9 -0
  37. package/skill/TypeWrapper_getError.md +11 -0
  38. package/skill/TypeWrapper_serialize.md +10 -0
  39. package/skill/TypeWrapper_serializeType.md +9 -0
  40. package/skill/array.md +2 -2
  41. package/skill/defineModel.md +3 -2
  42. package/skill/deleteEverything.md +8 -0
  43. package/skill/field.md +3 -3
  44. package/skill/link.md +3 -3
  45. package/skill/literal.md +1 -1
  46. package/skill/opt.md +1 -1
  47. package/skill/or.md +1 -1
  48. package/skill/record.md +1 -1
  49. package/skill/set.md +2 -2
  50. package/skill/setOnSaveCallback.md +2 -2
  51. package/skill/transact.md +1 -1
  52. package/src/datapack.ts +9 -9
  53. package/src/edinburgh.ts +43 -52
  54. package/src/indexes.ts +251 -599
  55. package/src/migrate.ts +9 -10
  56. package/src/models.ts +528 -231
  57. package/src/types.ts +36 -34
  58. package/src/utils.ts +4 -4
  59. package/skill/BaseIndex.md +0 -16
  60. package/skill/BaseIndex_batchProcess.md +0 -10
  61. package/skill/BaseIndex_find.md +0 -7
  62. package/skill/BaseIndex_find_2.md +0 -7
  63. package/skill/BaseIndex_find_3.md +0 -7
  64. package/skill/BaseIndex_find_4.md +0 -7
  65. package/skill/Model.md +0 -20
  66. package/skill/Model_batchProcess.md +0 -8
  67. package/skill/Model_migrate.md +0 -32
  68. package/skill/Model_replaceInto.md +0 -16
  69. package/skill/NonPrimaryIndex.md +0 -10
  70. package/skill/SecondaryIndex.md +0 -9
  71. package/skill/UniqueIndex.md +0 -9
  72. package/skill/dump.md +0 -8
package/README.md CHANGED
@@ -22,7 +22,7 @@ import * as E from "edinburgh";
22
22
  // Initialize the database (optional, defaults to ".edinburgh")
23
23
  E.init("./my-database");
24
24
 
25
- const User = E.defineModel(class {
25
+ const User = E.defineModel("User", class {
26
26
  id = E.field(E.identifier);
27
27
  name = E.field(E.string);
28
28
  age = E.field(E.number);
@@ -42,9 +42,8 @@ const User = E.defineModel(class {
42
42
  }, {
43
43
  pk: "id",
44
44
  unique: {
45
- byEmail: "email",
45
+ email: "email",
46
46
  },
47
- tableName: "User",
48
47
  });
49
48
 
50
49
  await E.transact(() => {
@@ -61,7 +60,7 @@ await E.transact(() => {
61
60
 
62
61
  await E.transact(() => {
63
62
  // Query by unique index
64
- const john = User.byEmail.get("john@example.com")!;
63
+ const john = User.getBy("email", "john@example.com")!;
65
64
 
66
65
  // The transaction will retry if there's a conflict, such as another transaction
67
66
  // modifying the same user (from another async function or another process)
@@ -77,12 +76,15 @@ await E.transact(() => {
77
76
 
78
77
  ### Defining Models
79
78
 
80
- Models are plain, usually anonymous, classes passed to `E.defineModel()`:
79
+ A model is defined using the `E.defineModel()` function by passing it..
80
+ - a consistent table name,
81
+ - an (anonymous) class containing `E.field` database properties and optionally regular properties/methods, and
82
+ - optional key/index configuration.
81
83
 
82
84
  ```typescript
83
85
  import * as E from "edinburgh";
84
86
 
85
- const User = E.defineModel(class {
87
+ const User = E.defineModel("User", class {
86
88
  id = E.field(E.identifier);
87
89
  name = E.field(E.string);
88
90
  email = E.field(E.string);
@@ -90,9 +92,12 @@ const User = E.defineModel(class {
90
92
  }, {
91
93
  pk: "id",
92
94
  unique: {
93
- byEmail: "email",
95
+ email: "email",
94
96
  },
95
97
  });
98
+ // Add this if you want to use User as a type annotation (e.g. `let u: User`).
99
+ // Not needed just to call User.get(), User.find(), new User(), etc.
100
+ type User = InstanceType<typeof User>;
96
101
  ```
97
102
 
98
103
  Instance fields are declared with `E.field(type, options?)`. Available types:
@@ -116,7 +121,7 @@ Instance fields are declared with `E.field(type, options?)`. Available types:
116
121
  #### Defaults
117
122
 
118
123
  ```typescript
119
- const Post = E.defineModel(class {
124
+ const Post = E.defineModel("Post", class {
120
125
  id = E.field(E.identifier); // auto-generated
121
126
  title = E.field(E.string);
122
127
  status = E.field(E.or("draft", "published"), {default: "draft"});
@@ -141,13 +146,13 @@ await E.transact(() => {
141
146
 
142
147
  // Read + Update
143
148
  await E.transact(() => {
144
- const user = User.byEmail.get("alice@example.com");
149
+ const user = User.getBy("email", "alice@example.com");
145
150
  if (user) user.age++;
146
151
  });
147
152
 
148
153
  // Return values from transactions
149
154
  const name = await E.transact(() => {
150
- const user = User.byEmail.get("alice@example.com");
155
+ const user = User.getBy("email", "alice@example.com");
151
156
  return user?.name;
152
157
  });
153
158
  ```
@@ -159,15 +164,15 @@ Transactions auto-retry on conflict (up to 6 times by default). Keep transaction
159
164
  Edinburgh supports three index types:
160
165
 
161
166
  ```typescript
162
- const Product = E.defineModel(class {
167
+ const Product = E.defineModel("Product", class {
163
168
  sku = E.field(E.string);
164
169
  name = E.field(E.string);
165
170
  category = E.field(E.string);
166
171
  price = E.field(E.number);
167
172
  }, {
168
173
  pk: "sku",
169
- unique: { byName: "name" },
170
- index: { byCategory: "category" },
174
+ unique: { name: "name" },
175
+ index: { category: "category" },
171
176
  });
172
177
  ```
173
178
 
@@ -181,7 +186,7 @@ await E.transact(() => {
181
186
  const p1 = Product.get("SKU-001");
182
187
 
183
188
  // Unique index lookup
184
- const p2 = Product.byName.get("Widget");
189
+ const p2 = Product.getBy("name", "Widget");
185
190
 
186
191
  // All return undefined if not found
187
192
  });
@@ -189,12 +194,12 @@ await E.transact(() => {
189
194
 
190
195
  #### Range Queries
191
196
 
192
- All index types support `.find()` for range iteration:
197
+ Primary-key queries use `.find()`. Named unique and secondary indexes use `.findBy(name, ...)`:
193
198
 
194
199
  ```typescript
195
200
  await E.transact(() => {
196
201
  // Exact match
197
- for (const p of Product.byCategory.find({is: "electronics"})) {
202
+ for (const p of Product.findBy("category", {is: "electronics"})) {
198
203
  console.log(p.name);
199
204
  }
200
205
 
@@ -213,15 +218,15 @@ await E.transact(() => {
213
218
  for (const p of Product.find({reverse: true})) { ... }
214
219
 
215
220
  // Count and fetch helpers
216
- const count = Product.byCategory.find({is: "electronics"}).count();
217
- const first = Product.byCategory.find({is: "electronics"}).fetch(); // first match or undefined
221
+ const count = Product.findBy("category", {is: "electronics"}).count();
222
+ const first = Product.findBy("category", {is: "electronics"}).fetch(); // first match or undefined
218
223
  });
219
224
  ```
220
225
 
221
226
  #### Composite Primary Keys
222
227
 
223
228
  ```typescript
224
- const Event = E.defineModel(class {
229
+ const Event = E.defineModel("Event", class {
225
230
  year = E.field(E.number);
226
231
  month = E.field(E.number);
227
232
  id = E.field(E.identifier);
@@ -244,7 +249,7 @@ await E.transact(() => {
244
249
  You can freely add regular methods, getters, and other non-persistent properties to model classes. These work normally in JavaScript but are **not stored in the database** and **not synchronized** across transactions or processes.
245
250
 
246
251
  ```typescript
247
- const User = E.defineModel(class {
252
+ const User = E.defineModel("User", class {
248
253
  firstName = E.field(E.string);
249
254
  lastName = E.field(E.string);
250
255
 
@@ -267,7 +272,7 @@ const User = E.defineModel(class {
267
272
  Instead of naming fields, you can pass a function as an index specification. The function receives a model instance and returns an **array** of index key values. Each element creates a separate index entry, enabling multi-value indexes. Return `[]` to skip indexing for that instance (partial index).
268
273
 
269
274
  ```typescript
270
- const Article = E.defineModel(class {
275
+ const Article = E.defineModel("Article", class {
271
276
  id = E.field(E.identifier);
272
277
  firstName = E.field(E.string);
273
278
  lastName = E.field(E.string);
@@ -276,11 +281,11 @@ const Article = E.defineModel(class {
276
281
  }, {
277
282
  pk: "id",
278
283
  unique: {
279
- byFullName: (a: any) => [`${a.firstName} ${a.lastName}`], // computed covering unique index
284
+ fullName: (a: any) => [`${a.firstName} ${a.lastName}`], // computed covering unique index
280
285
  },
281
286
  index: {
282
- byDomain: (a: any) => a.email ? [a.email.split("@")[1]] : [], // computed partial index
283
- byWord: (a: any) => a.title.toLowerCase().split(" "), // computed multi-index
287
+ domain: (a: any) => a.email ? [a.email.split("@")[1]] : [], // computed partial index
288
+ word: (a: any) => a.title.toLowerCase().split(" "), // computed multi-index
284
289
  },
285
290
  });
286
291
 
@@ -288,13 +293,13 @@ await E.transact(() => {
288
293
  new Article({ firstName: "Jane", lastName: "Doe", title: "Hello World", email: "jane@acme.com" });
289
294
 
290
295
  // Lookup via computed unique index
291
- const jane = Article.byFullName.get("Jane Doe");
296
+ const jane = Article.getBy("fullName", "Jane Doe");
292
297
 
293
298
  // Multi-value: each word in the title is indexed separately
294
- for (const a of Article.byWord.find({is: "hello"})) { ... }
299
+ for (const a of Article.findBy("word", {is: "hello"})) { ... }
295
300
 
296
301
  // Partial index: articles without email are skipped
297
- for (const a of Article.byDomain.find({is: "acme.com"})) { ... }
302
+ for (const a of Article.findBy("domain", {is: "acme.com"})) { ... }
298
303
  });
299
304
  ```
300
305
 
@@ -303,12 +308,12 @@ await E.transact(() => {
303
308
  Use `E.link(Model)` for foreign keys. Use a thunk (a function that just returns a value) for forward references when needed:
304
309
 
305
310
  ```typescript
306
- const Author = E.defineModel(class {
311
+ const Author = E.defineModel("Author", class {
307
312
  id = E.field(E.identifier);
308
313
  name = E.field(E.string);
309
314
  }, { pk: "id" });
310
315
 
311
- const Book = E.defineModel(class {
316
+ const Book = E.defineModel("Book", class {
312
317
  id = E.field(E.identifier);
313
318
  title = E.field(E.string);
314
319
  author = E.field(E.link(Author));
@@ -381,7 +386,7 @@ await Product.batchProcess({ limitRows: 1000 }, (product) => {
381
386
  When you change a model's schema, Edinburgh lazily migrates old records on access. You can provide a `static migrate(record)` function to transform old rows:
382
387
 
383
388
  ```typescript
384
- const UserV2 = E.defineModel(class {
389
+ const UserV2 = E.defineModel("User", class {
385
390
  id = E.field(E.identifier);
386
391
  name = E.field(E.string);
387
392
  role = E.field(E.string); // newly added field
@@ -389,7 +394,7 @@ const UserV2 = E.defineModel(class {
389
394
  static migrate(record: Record<string, any>) {
390
395
  record.role ??= record.name.indexOf("admin") >= 0 ? "admin" : "user"; // set role based on name for old records
391
396
  }
392
- }
397
+ })
393
398
  ```
394
399
 
395
400
  Edinburgh will lazily (re)run the `migrate` function on an instance whenever its implementation (the literal function code) has changed. For robustness, make sure that your `migrate` function...
@@ -424,7 +429,7 @@ console.log(result.secondaries); // { User: 1500 }
424
429
  Compute derived fields before data is written:
425
430
 
426
431
  ```typescript
427
- const Article = E.defineModel(class {
432
+ const Article = E.defineModel("Article", class {
428
433
  id = E.field(E.identifier);
429
434
  title = E.field(E.string);
430
435
  slug = E.field(E.string);
@@ -432,7 +437,7 @@ const Article = E.defineModel(class {
432
437
  preCommit() {
433
438
  this.slug = this.title.toLowerCase().replace(/\s+/g, "-");
434
439
  }
435
- }, { pk: "id" });
440
+ });
436
441
  ```
437
442
 
438
443
  ### Change Tracking
@@ -473,11 +478,14 @@ ln -s ../../node_modules/edinburgh/skill .claude/skills/edinburgh
473
478
 
474
479
  The following is auto-generated from `src/edinburgh.ts`:
475
480
 
476
- ### scheduleInit · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L6)
481
+ ### currentTxn · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L66)
477
482
 
478
- **Signature:** `() => void`
483
+ Returns the current transaction from AsyncLocalStorage.
484
+ Throws if called outside a transact() callback.
485
+
486
+ **Signature:** `() => Transaction`
479
487
 
480
- ### init · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L65)
488
+ ### init · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L85)
481
489
 
482
490
  Initialize the database with the specified directory path.
483
491
  This function may be called multiple times with the same parameters. If it is not called before the first transact(),
@@ -495,7 +503,7 @@ the database will be automatically initialized with the default directory.
495
503
  init("./my-database");
496
504
  ```
497
505
 
498
- ### transact · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L116)
506
+ ### transact · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L136)
499
507
 
500
508
  Executes a function within a database transaction context.
501
509
 
@@ -515,7 +523,7 @@ times.
515
523
 
516
524
  **Parameters:**
517
525
 
518
- - `fn: () => T` - - The function to execute within the transaction context. Receives a Transaction instance.
526
+ - `fn: () => T` - The function to execute within the transaction context. Receives a Transaction instance.
519
527
 
520
528
  **Returns:** A promise that resolves with the function's return value.
521
529
 
@@ -545,7 +553,7 @@ await E.transact(() => {
545
553
  });
546
554
  ```
547
555
 
548
- ### setMaxRetryCount · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L206)
556
+ ### setMaxRetryCount · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L226)
549
557
 
550
558
  Set the maximum number of retries for a transaction in case of conflicts.
551
559
  The default value is 6. Setting it to 0 will disable retries and cause transactions to fail immediately on conflict.
@@ -556,146 +564,121 @@ The default value is 6. Setting it to 0 will disable retries and cause transacti
556
564
 
557
565
  - `count: number` - The maximum number of retries for a transaction.
558
566
 
559
- ### setOnSaveCallback · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L220)
567
+ ### setOnSaveCallback · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L240)
560
568
 
561
569
  Set a callback function to be called after a model is saved and committed.
562
570
 
563
- **Signature:** `(callback: (commitId: number, items: Map<Model<any>, Change>) => void) => void`
571
+ **Signature:** `(callback: (commitId: number, items: Map<ModelBase, Change>) => void) => void`
564
572
 
565
573
  **Parameters:**
566
574
 
567
- - `callback: ((commitId: number, items: Map<Model<any>, Change>) => void) | undefined` - The callback function to set. It gets called after each successful
575
+ - `callback: ((commitId: number, items: Map<Model<unknown>, Change>) => void) | undefined` - The callback function to set. It gets called after each successful
568
576
  `transact()` commit that has changes, with the following arguments:
569
577
  - A sequential number. Higher numbers have been committed after lower numbers.
570
578
  - A map of model instances to their changes. The change can be "created", "deleted", or an object containing the old values.
571
579
 
572
- ### deleteEverything · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L225)
580
+ ### Model · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
573
581
 
574
- **Signature:** `() => Promise<void>`
582
+ **Type:** `typeof ModelBase`
575
583
 
576
- ### Model · [abstract class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
584
+ ### ModelClass · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
577
585
 
578
- [object Object],[object Object],[object Object],[object Object],[object Object]
586
+ Runtime base constructor for model classes returned by `defineModel()`.
579
587
 
580
- **Type Parameters:**
588
+ Prefer the `ModelClass` type alias for annotations and the result of
589
+ `defineModel()` for concrete model classes.
581
590
 
582
- - `SUB` - The concrete model subclass (for proper typing).
591
+ **Type:** `typeof ModelClassRuntime`
583
592
 
584
- **Examples:**
593
+ ### AnyModelClass · [type](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L111)
585
594
 
586
- ```typescript
587
- const User = E.defineModel(class {
588
- id = E.field(E.identifier);
589
- name = E.field(E.string);
590
- email = E.field(E.string);
591
- }, {
592
- pk: "id",
593
- unique: { byEmail: "email" },
594
- });
595
- ```
596
-
597
- #### Model.tableName · [static property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
595
+ A model constructor with its generic information erased.
598
596
 
599
- The database table name (defaults to class name).
597
+ Useful when accepting or storing arbitrary registered model classes.
600
598
 
601
- **Type:** `string`
599
+ **Type:** `ModelClass<new () => any, readonly any[], any, any>`
602
600
 
603
- #### Model.override · [static property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
601
+ ### ModelBase · [abstract class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
604
602
 
605
- When true, defineModel replaces an existing model with the same tableName.
603
+ Base class for all database models in the Edinburgh ORM.
606
604
 
607
- **Type:** `boolean`
605
+ Models represent database entities with typed fields, automatic serialization,
606
+ change tracking, and relationship management. Model classes are created using
607
+ `E.defineModel()`.
608
608
 
609
- #### Model.fields · [static property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
609
+ ### Schema Evolution
610
610
 
611
- Field configuration metadata.
611
+ Edinburgh tracks the schema version of each model automatically. When you add, remove, or
612
+ change the types of fields, or add/remove indexes, Edinburgh detects the new schema version.
612
613
 
613
- **Type:** `Record<string | number | symbol, FieldConfig<unknown>>`
614
+ **Lazy migration:** Changes to non-key field values are migrated lazily, when a row with an
615
+ old schema version is read from disk, it is deserialized using the old schema and optionally
616
+ transformed by the static `migrate()` function. This happens transparently on every read
617
+ and requires no downtime or batch processing.
614
618
 
615
- #### Model.migrate · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
619
+ **Batch migration (via `npx migrate-edinburgh` or `runMigration()`):** Certain schema changes
620
+ require an explicit migration run:
621
+ - Adding or removing secondary/unique indexes
622
+ - Changing the fields or types of an existing index
623
+ - A `migrate()` function that changes values used in secondary index fields
616
624
 
617
- Optional migration function called when deserializing rows written with an older schema version.
618
- Receives a plain record with all fields (primary key fields + value fields) and should mutate it
619
- in-place to match the current schema.
625
+ The batch migration tool populates new indexes, deletes orphaned ones, and updates index
626
+ entries whose values were changed by `migrate()`. It does *not* rewrite primary data rows
627
+ (lazy migration handles that).
620
628
 
621
- This is called both during lazy loading (when a row is read from disk) and during batch
622
- migration (via `runMigration()` / `npx migrate-edinburgh`). The function's source code is hashed
623
- to detect changes. Modifying `migrate()` triggers a new schema version.
629
+ ### Lifecycle Hooks
624
630
 
625
- If `migrate()` changes values of fields used in secondary or unique indexes, those indexes
626
- will only be updated when `runMigration()` is run (not during lazy loading).
631
+ - **`static migrate(record)`**: Called when deserializing rows written with an older schema
632
+ version. Receives a plain record object; mutate it in-place to match the current schema.
627
633
 
628
- **Signature:** `(record: Record<string, any>) => void`
629
-
630
- **Parameters:**
631
-
632
- - `record: Record<string, any>` - - A plain object with all field values from the old schema version.
634
+ - **`preCommit()`**: Called on each modified instance right before the transaction commits.
635
+ Useful for computing derived fields, enforcing cross-field invariants, or creating related
636
+ instances.
633
637
 
634
638
  **Examples:**
635
639
 
636
640
  ```typescript
637
- const User = E.defineModel(class {
641
+ const User = E.defineModel("User", class {
638
642
  id = E.field(E.identifier);
639
643
  name = E.field(E.string);
640
- role = E.field(E.string); // new field
641
-
642
- static migrate(record: Record<string, any>) {
643
- record.role ??= "user"; // default for rows that predate the 'role' field
644
- }
645
- }, { pk: "id" });
644
+ email = E.field(E.string);
645
+ }, {
646
+ pk: "id",
647
+ unique: { email: "email" },
648
+ });
649
+ // Optional: declare a companion type so `let u: User` works.
650
+ // Not needed if you only use `new User()`, `User.find()`, etc.
651
+ type User = InstanceType<typeof User>;
646
652
  ```
647
653
 
648
- #### Model.get · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
649
-
650
- **Signature:** `(...args: any[]) => any`
651
-
652
- **Parameters:**
653
-
654
- - `args: any[]`
655
-
656
- #### Model.getLazy · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
657
-
658
- **Signature:** `(...args: any[]) => any`
659
-
660
- **Parameters:**
661
-
662
- - `args: any[]`
663
-
664
- #### Model.find · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
665
-
666
- **Signature:** `(opts?: any) => any`
654
+ #### ModelBase.migrate · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
667
655
 
668
- **Parameters:**
669
-
670
- - `opts?: any`
671
-
672
- #### Model.batchProcess · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
656
+ Optional migration function called when deserializing rows written with an older schema version.
657
+ Receives a plain record with all fields and should mutate it in-place to match the current schema.
658
+ It runs during lazy loading and during `runMigration()`. Changing this method creates a new schema version.
659
+ If it updates values used by secondary or unique indexes, those index entries are refreshed only by `runMigration()`.
673
660
 
674
- **Signature:** `(opts: any, callback?: any) => any`
661
+ **Signature:** `(record: Record<string, any>) => void`
675
662
 
676
663
  **Parameters:**
677
664
 
678
- - `opts: any`
679
- - `callback?: any`
680
-
681
- #### Model.replaceInto · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
682
-
683
- Load an existing instance by primary key and update it, or create a new one.
684
-
685
- The provided object must contain all primary key fields. If a matching row exists,
686
- the remaining properties from `obj` are set on the loaded instance. Otherwise a
687
- new instance is created with `obj` as its initial properties.
665
+ - `record: Record<string, any>` - A plain object containing the row's field values from the older schema version.
688
666
 
689
- **Signature:** `<T extends typeof Model<any>>(this: T, obj: Partial<Record<string, any>>) => InstanceType<T>`
690
-
691
- **Parameters:**
667
+ **Examples:**
692
668
 
693
- - `this: T`
694
- - `obj: Partial<Record<string, any>>` - - Partial model data that **must** include every primary key field.
669
+ ```typescript
670
+ const User = E.defineModel("User", class {
671
+ id = E.field(E.identifier);
672
+ name = E.field(E.string);
673
+ role = E.field(E.string);
695
674
 
696
- **Returns:** The loaded-and-updated or newly created instance.
675
+ static migrate(record: Record<string, any>) {
676
+ record.role ??= "user";
677
+ }
678
+ }, { pk: "id" });
679
+ ```
697
680
 
698
- #### model.preCommit · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
681
+ #### modelBase.preCommit · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
699
682
 
700
683
  Optional hook called on each modified instance right before the transaction commits.
701
684
  Runs before data is written to disk, so changes made here are included in the commit.
@@ -711,7 +694,7 @@ Common use cases:
711
694
  **Examples:**
712
695
 
713
696
  ```typescript
714
- const Post = E.defineModel(class {
697
+ const Post = E.defineModel("Post", class {
715
698
  id = E.field(E.identifier);
716
699
  title = E.field(E.string);
717
700
  slug = E.field(E.string);
@@ -722,19 +705,19 @@ const Post = E.defineModel(class {
722
705
  }, { pk: "id" });
723
706
  ```
724
707
 
725
- #### model.getPrimaryKey · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
708
+ #### modelBase.getPrimaryKey · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
726
709
 
727
710
  **Signature:** `() => Uint8Array<ArrayBufferLike>`
728
711
 
729
712
  **Returns:** The primary key for this instance.
730
713
 
731
- #### model.getPrimaryKeyHash · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
714
+ #### modelBase.getPrimaryKeyHash · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
732
715
 
733
716
  **Signature:** `() => number`
734
717
 
735
718
  **Returns:** A 53-bit positive integer non-cryptographic hash of the primary key, or undefined if not yet saved.
736
719
 
737
- #### model.isLazyField · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
720
+ #### modelBase.isLazyField · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
738
721
 
739
722
  **Signature:** `(field: keyof this) => boolean`
740
723
 
@@ -742,7 +725,7 @@ const Post = E.defineModel(class {
742
725
 
743
726
  - `field: keyof this`
744
727
 
745
- #### model.preventPersist · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
728
+ #### modelBase.preventPersist · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
746
729
 
747
730
  Prevent this instance from being persisted to the database.
748
731
 
@@ -758,7 +741,7 @@ user.name = "New Name";
758
741
  user.preventPersist(); // Changes won't be saved
759
742
  ```
760
743
 
761
- #### model.delete · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
744
+ #### modelBase.delete · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
762
745
 
763
746
  Delete this model instance from the database.
764
747
 
@@ -773,7 +756,7 @@ const user = User.get("user123");
773
756
  user.delete(); // Removes from database
774
757
  ```
775
758
 
776
- #### model.validate · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
759
+ #### modelBase.validate · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
777
760
 
778
761
  Validate all fields in this model instance.
779
762
 
@@ -781,7 +764,7 @@ Validate all fields in this model instance.
781
764
 
782
765
  **Parameters:**
783
766
 
784
- - `raise: boolean` (optional) - - If true, throw on first validation error.
767
+ - `raise: boolean` (optional) - If true, throw on first validation error.
785
768
 
786
769
  **Returns:** Array of validation errors (empty if valid).
787
770
 
@@ -795,7 +778,7 @@ if (errors.length > 0) {
795
778
  }
796
779
  ```
797
780
 
798
- #### model.isValid · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
781
+ #### modelBase.isValid · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
799
782
 
800
783
  Check if this model instance is valid.
801
784
 
@@ -810,19 +793,19 @@ const user = new User({name: "John"});
810
793
  if (!user.isValid()) shoutAtTheUser();
811
794
  ```
812
795
 
813
- #### model.getState · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
796
+ #### modelBase.getState · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
814
797
 
815
798
  **Signature:** `() => "created" | "deleted" | "loaded" | "lazy"`
816
799
 
817
- #### model.toString · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
800
+ #### modelBase.toString · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
818
801
 
819
802
  **Signature:** `() => string`
820
803
 
821
- #### model.[Symbol.for('nodejs.util.inspect.custom')] · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
804
+ #### modelBase.[Symbol.for('nodejs.util.inspect.custom')] · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
822
805
 
823
806
  **Signature:** `() => string`
824
807
 
825
- ### defineModel · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L164)
808
+ ### defineModel · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
826
809
 
827
810
  Register a model class with the Edinburgh ORM system.
828
811
 
@@ -840,12 +823,22 @@ typed fields, primary key access, and optional secondary and unique indexes.
840
823
 
841
824
  **Parameters:**
842
825
 
843
- - `cls: T` - - A plain class whose properties use E.field().
844
- - `opts?: { pk?: PK, unique?: UNIQUE, index?: INDEX, tableName?: string, override?: boolean }` - - Registration options.
826
+ - `tableName: string` - The database table name for this model.
827
+ - `cls: T` - A plain class whose properties use E.field().
828
+ - `opts?: { pk?: PK, unique?: UNIQUE, index?: INDEX, override?: boolean }` - Registration options.
845
829
 
846
830
  **Returns:** The enhanced model constructor.
847
831
 
848
- ### field · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L87)
832
+ ### deleteEverything · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
833
+
834
+ Delete every key/value entry in the database and reinitialize all registered models.
835
+
836
+ This clears rows, index metadata, and schema-version records. It is mainly useful
837
+ for tests, local resets, or tooling that needs a completely empty database.
838
+
839
+ **Signature:** `() => Promise<void>`
840
+
841
+ ### field · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L76)
849
842
 
850
843
  Create a field definition for a model property.
851
844
 
@@ -861,34 +854,27 @@ This allows for both runtime introspection and compile-time type safety.
861
854
 
862
855
  **Parameters:**
863
856
 
864
- - `type: TypeWrapper<T>` - - The type wrapper for this field.
865
- - `options: Partial<FieldConfig<T>>` (optional) - - Additional field configuration options.
857
+ - `type: TypeWrapper<T>` - The type wrapper for this field.
858
+ - `options: Partial<FieldConfig<T>>` (optional) - Additional field configuration options.
866
859
 
867
860
  **Returns:** The field value (typed as T, but actually returns FieldConfig<T>).
868
861
 
869
862
  **Examples:**
870
863
 
871
864
  ```typescript
872
- const User = E.defineModel(class {
865
+ const User = E.defineModel("User", class {
873
866
  name = E.field(E.string, {description: "User's full name"});
874
867
  age = E.field(E.opt(E.number), {description: "User's age", default: 25});
875
868
  });
876
869
  ```
877
870
 
878
- ### currentTxn · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L21)
879
-
880
- Returns the current transaction from AsyncLocalStorage.
881
- Throws if called outside a transact() callback.
882
-
883
- **Signature:** `() => Transaction`
884
-
885
- ### string · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
871
+ ### string · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
886
872
 
887
873
  Type wrapper instance for the string type.
888
874
 
889
875
  **Value:** `TypeWrapper<string>`
890
876
 
891
- ### orderedString · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
877
+ ### orderedString · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
892
878
 
893
879
  Type wrapper instance for the ordered string type, which is just like a string
894
880
  except that it sorts lexicographically in the database (instead of by incrementing
@@ -898,37 +884,37 @@ may not contain null characters.
898
884
 
899
885
  **Value:** `TypeWrapper<string>`
900
886
 
901
- ### number · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
887
+ ### number · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
902
888
 
903
889
  Type wrapper instance for the number type.
904
890
 
905
891
  **Value:** `TypeWrapper<number>`
906
892
 
907
- ### dateTime · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
893
+ ### dateTime · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
908
894
 
909
895
  Type wrapper instance for the date/time type. Stored without timezone info, rounded to whole seconds.
910
896
 
911
897
  **Value:** `TypeWrapper<Date>`
912
898
 
913
- ### boolean · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
899
+ ### boolean · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
914
900
 
915
901
  Type wrapper instance for the boolean type.
916
902
 
917
903
  **Value:** `TypeWrapper<boolean>`
918
904
 
919
- ### identifier · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
905
+ ### identifier · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
920
906
 
921
907
  Type wrapper instance for the identifier type.
922
908
 
923
909
  **Value:** `TypeWrapper<string>`
924
910
 
925
- ### undef · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
911
+ ### undef · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
926
912
 
927
913
  Type wrapper instance for the 'undefined' type.
928
914
 
929
915
  **Value:** `TypeWrapper<undefined>`
930
916
 
931
- ### opt · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
917
+ ### opt · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
932
918
 
933
919
  Create an optional type wrapper (allows undefined).
934
920
 
@@ -940,7 +926,7 @@ Create an optional type wrapper (allows undefined).
940
926
 
941
927
  **Parameters:**
942
928
 
943
- - `inner: T` - - The inner type to make optional.
929
+ - `inner: T` - The inner type to make optional.
944
930
 
945
931
  **Returns:** A union type that accepts the inner type or undefined.
946
932
 
@@ -951,7 +937,7 @@ const optionalString = E.opt(E.string);
951
937
  const optionalNumber = E.opt(E.number);
952
938
  ```
953
939
 
954
- ### or · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
940
+ ### or · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
955
941
 
956
942
  Create a union type wrapper from multiple type choices.
957
943
 
@@ -963,7 +949,7 @@ Create a union type wrapper from multiple type choices.
963
949
 
964
950
  **Parameters:**
965
951
 
966
- - `choices: T` - - The type choices for the union.
952
+ - `choices: T` - The type choices for the union.
967
953
 
968
954
  **Returns:** A union type instance.
969
955
 
@@ -974,7 +960,7 @@ const stringOrNumber = E.or(E.string, E.number);
974
960
  const status = E.or("active", "inactive", "pending");
975
961
  ```
976
962
 
977
- ### array · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
963
+ ### array · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
978
964
 
979
965
  Create an array type wrapper with optional length constraints.
980
966
 
@@ -986,8 +972,8 @@ Create an array type wrapper with optional length constraints.
986
972
 
987
973
  **Parameters:**
988
974
 
989
- - `inner: TypeWrapper<T>` - - Type wrapper for array elements.
990
- - `opts: {min?: number, max?: number}` (optional) - - Optional constraints (min/max length).
975
+ - `inner: TypeWrapper<T>` - Type wrapper for array elements.
976
+ - `opts: {min?: number, max?: number}` (optional) - Optional constraints (min/max length).
991
977
 
992
978
  **Returns:** An array type instance.
993
979
 
@@ -998,7 +984,7 @@ const stringArray = E.array(E.string);
998
984
  const boundedArray = E.array(E.number, {min: 1, max: 10});
999
985
  ```
1000
986
 
1001
- ### set · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
987
+ ### set · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
1002
988
 
1003
989
  Create a Set type wrapper with optional length constraints.
1004
990
 
@@ -1010,8 +996,8 @@ Create a Set type wrapper with optional length constraints.
1010
996
 
1011
997
  **Parameters:**
1012
998
 
1013
- - `inner: TypeWrapper<T>` - - Type wrapper for set elements.
1014
- - `opts: {min?: number, max?: number}` (optional) - - Optional constraints (min/max length).
999
+ - `inner: TypeWrapper<T>` - Type wrapper for set elements.
1000
+ - `opts: {min?: number, max?: number}` (optional) - Optional constraints (min/max length).
1015
1001
 
1016
1002
  **Returns:** A set type instance.
1017
1003
 
@@ -1022,7 +1008,7 @@ const stringSet = E.set(E.string);
1022
1008
  const boundedSet = E.set(E.number, {min: 1, max: 10});
1023
1009
  ```
1024
1010
 
1025
- ### record · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
1011
+ ### record · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
1026
1012
 
1027
1013
  Create a Record type wrapper for key-value objects with string or number keys.
1028
1014
 
@@ -1034,7 +1020,7 @@ Create a Record type wrapper for key-value objects with string or number keys.
1034
1020
 
1035
1021
  **Parameters:**
1036
1022
 
1037
- - `inner: TypeWrapper<T>` - - Type wrapper for record values.
1023
+ - `inner: TypeWrapper<T>` - Type wrapper for record values.
1038
1024
 
1039
1025
  **Returns:** A record type instance.
1040
1026
 
@@ -1044,7 +1030,7 @@ Create a Record type wrapper for key-value objects with string or number keys.
1044
1030
  const scores = E.record(E.number); // Record<string | number, number>
1045
1031
  ```
1046
1032
 
1047
- ### literal · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
1033
+ ### literal · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
1048
1034
 
1049
1035
  Create a literal type wrapper for a constant value.
1050
1036
 
@@ -1056,7 +1042,7 @@ Create a literal type wrapper for a constant value.
1056
1042
 
1057
1043
  **Parameters:**
1058
1044
 
1059
- - `value: T` - - The literal value.
1045
+ - `value: T` - The literal value.
1060
1046
 
1061
1047
  **Returns:** A literal type instance.
1062
1048
 
@@ -1067,7 +1053,7 @@ const statusType = E.literal("active");
1067
1053
  const countType = E.literal(42);
1068
1054
  ```
1069
1055
 
1070
- ### link · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
1056
+ ### link · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
1071
1057
 
1072
1058
  Create a link type wrapper for model relationships.
1073
1059
 
@@ -1079,155 +1065,215 @@ Create a link type wrapper for model relationships.
1079
1065
 
1080
1066
  **Parameters:**
1081
1067
 
1082
- - `TargetModel: T` - - The model class this link points to.
1068
+ - `TargetModel: T` - The model class this link points to.
1083
1069
 
1084
1070
  **Returns:** A link type instance.
1085
1071
 
1086
1072
  **Examples:**
1087
1073
 
1088
1074
  ```typescript
1089
- const Author = E.defineModel(class {
1075
+ const Author = E.defineModel("Author", class {
1090
1076
  id = E.field(E.identifier);
1091
1077
  posts = E.field(E.array(E.link(() => Book)));
1092
1078
  }, { pk: "id" });
1093
1079
 
1094
- const Book = E.defineModel(class {
1080
+ const Book = E.defineModel("Book", class {
1095
1081
  id = E.field(E.identifier);
1096
1082
  author = E.field(E.link(Author));
1097
1083
  }, { pk: "id" });
1098
1084
  ```
1099
1085
 
1100
- ### dump · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
1086
+ ### dump · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
1087
+
1088
+ **Signature:** `() => void`
1089
+
1090
+ ### FindOptions · [type](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L115)
1091
+
1092
+ Range-query options accepted by `find()`, `findBy()`, `batchProcess()`, and `batchProcessBy()`.
1093
+
1094
+ Supports exact-match lookups via `is`, inclusive bounds via `from` / `to`,
1095
+ exclusive bounds via `after` / `before`, and reverse scans.
1096
+
1097
+ For single-field indexes, values can be passed directly. For composite indexes,
1098
+ pass tuples or partial tuples for prefix matching.
1099
+
1100
+ **Type:** `(
1101
+ (
1102
+ {is: ArrayOrOnlyItem<ARG_TYPES>;} // Shortcut for setting `from` and `to` to the same value
1103
+ |
1104
+ (
1105
+ (
1106
+ {from: ArrayOrOnlyItem<ARG_TYPES>;}
1107
+ |
1108
+ {after: ArrayOrOnlyItem<ARG_TYPES>;}
1109
+ |
1110
+ {}
1111
+ )
1112
+ &
1113
+ (
1114
+ {to: ArrayOrOnlyItem<ARG_TYPES>;}
1115
+ |
1116
+ {before: ArrayOrOnlyItem<ARG_TYPES>;}
1117
+ |
1118
+ {}
1119
+ )
1120
+ )
1121
+ ) &
1122
+ {
1123
+ reverse?: boolean;
1124
+ }
1125
+ & (FETCH extends undefined ? { fetch?: undefined } : { fetch: FETCH })
1126
+ )`
1127
+
1128
+ ### IndexRangeIterator · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L73)
1129
+
1130
+ Iterator for range queries on indexes.
1131
+ Handles common iteration logic for both primary and unique indexes.
1132
+ Extends built-in Iterator to provide map/filter/reduce/toArray/etc.
1101
1133
 
1102
- Dump database contents for debugging.
1134
+ **Type Parameters:**
1103
1135
 
1104
- Prints all indexes and their data to the console for inspection.
1105
- This is primarily useful for development and debugging purposes.
1136
+ - `ITEM`
1106
1137
 
1107
- **Signature:** `() => void`
1138
+ #### indexRangeIterator.[Symbol.iterator] · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L78)
1139
+
1140
+ **Signature:** `() => this`
1141
+
1142
+ #### indexRangeIterator.next · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L80)
1143
+
1144
+ **Signature:** `() => IteratorResult<ITEM, any>`
1108
1145
 
1109
- ### BaseIndex · [abstract class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L130)
1146
+ #### indexRangeIterator.count · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L102)
1147
+
1148
+ **Signature:** `() => number`
1149
+
1150
+ #### indexRangeIterator.fetch · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L105)
1151
+
1152
+ **Signature:** `() => ITEM`
1153
+
1154
+ ### Change · [type](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L85)
1155
+
1156
+ **Type:** `Record<any, any> | "created" | "deleted"`
1110
1157
 
1111
- Base class for database indexes for efficient lookups on model fields.
1158
+ ### FieldConfig · [interface](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L37)
1112
1159
 
1113
- Indexes enable fast queries on specific field combinations and enforce uniqueness constraints.
1160
+ Configuration interface for model fields.
1114
1161
 
1115
1162
  **Type Parameters:**
1116
1163
 
1117
- - `M extends typeof Model` - The model class this index belongs to.
1118
- - `F extends readonly (keyof InstanceType<M> & string)[]` - The field names that make up this index.
1119
- - `ARGS extends readonly any[] = IndexArgTypes<M, F>`
1164
+ - `T` - The field type.
1120
1165
 
1121
- **Constructor Parameters:**
1166
+ #### fieldConfig.type · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L45)
1122
1167
 
1123
- - `MyModel`: - The model class this index belongs to.
1124
- - `_fieldNames`: - Array of field names that make up this index.
1168
+ The type wrapper that defines how this field is serialized/validated.
1125
1169
 
1126
- #### baseIndex.find · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
1170
+ **Type:** `TypeWrapper<T>`
1127
1171
 
1128
- **Signature:** `{ (opts?: FindOptions<ARGS, "first">): InstanceType<M>; (opts: FindOptions<ARGS, "single">): InstanceType<M>; (opts?: FindOptions<...>): IndexRangeIterator<...>; }`
1172
+ #### fieldConfig.description · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L47)
1129
1173
 
1130
- **Parameters:**
1174
+ Optional human-readable description of the field.
1131
1175
 
1132
- - `opts?: FindOptions<ARGS, 'first'>`
1176
+ **Type:** `string`
1133
1177
 
1134
- #### baseIndex.find · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
1178
+ #### fieldConfig.default · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L49)
1135
1179
 
1136
- **Signature:** `{ (opts?: FindOptions<ARGS, "first">): InstanceType<M>; (opts: FindOptions<ARGS, "single">): InstanceType<M>; (opts?: FindOptions<...>): IndexRangeIterator<...>; }`
1180
+ Optional default value or function that generates default values.
1137
1181
 
1138
- **Parameters:**
1182
+ **Type:** `T | ((model: Record<string, any>) => T)`
1139
1183
 
1140
- - `opts: FindOptions<ARGS, 'single'>`
1184
+ ### TypeWrapper · [abstract class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L33)
1141
1185
 
1142
- #### baseIndex.find · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
1186
+ **Type Parameters:**
1143
1187
 
1144
- **Signature:** `{ (opts?: FindOptions<ARGS, "first">): InstanceType<M>; (opts: FindOptions<ARGS, "single">): InstanceType<M>; (opts?: FindOptions<...>): IndexRangeIterator<...>; }`
1188
+ - `T` - The TypeScript type this wrapper represents.
1145
1189
 
1146
- **Parameters:**
1190
+ #### typeWrapper.kind · [abstract property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L47)
1147
1191
 
1148
- - `opts?: FindOptions<ARGS>`
1192
+ A string identifier for this type, used during serialization
1193
+
1194
+ **Type:** `string`
1149
1195
 
1150
- #### baseIndex.find · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
1196
+ #### typeWrapper.serialize · [abstract method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L52)
1151
1197
 
1152
- **Signature:** `{ (opts?: FindOptions<ARGS, "first">): InstanceType<M>; (opts: FindOptions<ARGS, "single">): InstanceType<M>; (opts?: FindOptions<...>): IndexRangeIterator<...>; }`
1198
+ Serialize a value from an object property to a Pack.
1199
+
1200
+ **Signature:** `(value: T, pack: DataPack) => void`
1153
1201
 
1154
1202
  **Parameters:**
1155
1203
 
1156
- - `opts: any` (optional)
1204
+ - `value: T` - The value to serialize.
1205
+ - `pack: DataPack` - The Pack instance to write to.
1157
1206
 
1158
- #### baseIndex.batchProcess · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
1207
+ #### typeWrapper.deserialize · [abstract method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L59)
1159
1208
 
1160
- [object Object],[object Object],[object Object]
1209
+ Deserialize a value from a Pack into an object property.
1161
1210
 
1162
- **Signature:** `(opts: FindOptions<ARGS, undefined> & { limitSeconds?: number; limitRows?: number; }, callback: (row: InstanceType<M>) => void | Promise<void>) => Promise<...>`
1211
+ **Signature:** `(pack: DataPack) => T`
1163
1212
 
1164
1213
  **Parameters:**
1165
1214
 
1166
- - `opts: FindOptions<ARGS> & { limitSeconds?: number; limitRows?: number }` (optional) - - Query options (same as `find()`), plus:
1167
- - `callback: (row: InstanceType<M>) => void | Promise<void>` - - Called for each matching row within a transaction
1215
+ - `pack: DataPack` - The Pack instance to read from.
1168
1216
 
1169
- #### baseIndex.toString · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
1217
+ #### typeWrapper.getError · [abstract method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L64)
1170
1218
 
1171
- **Signature:** `() => string`
1219
+ Validate a value.
1172
1220
 
1173
- ### NonPrimaryIndex · [abstract class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
1221
+ **Signature:** `(value: T) => void | DatabaseError`
1174
1222
 
1175
- Abstract base for all non-primary indexes (unique and secondary).
1176
- Provides shared key serialization, write/delete/update logic.
1223
+ **Parameters:**
1177
1224
 
1178
- **Type Parameters:**
1225
+ - `value: T` - The value to validate.
1179
1226
 
1180
- - `M extends typeof Model`
1181
- - `F extends readonly (keyof InstanceType<M> & string)[]`
1182
- - `ARGS extends readonly any[] = IndexArgTypes<M, F>`
1227
+ **Returns:** - A DatabaseError if validation fails.
1183
1228
 
1184
- ### UniqueIndex · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
1229
+ #### typeWrapper.serializeType · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L68)
1185
1230
 
1186
- Unique index that stores references to the primary key.
1231
+ Serialize type metadata to a Pack (for schema serialization).
1187
1232
 
1188
- **Type Parameters:**
1233
+ **Signature:** `(pack: DataPack) => void`
1234
+
1235
+ **Parameters:**
1189
1236
 
1190
- - `M extends typeof Model`
1191
- - `F extends readonly (keyof InstanceType<M> & string)[]`
1192
- - `ARGS extends readonly any[] = IndexArgTypes<M, F>`
1237
+ - `pack: DataPack` - The Pack instance to write to.
1193
1238
 
1194
- #### uniqueIndex.get · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
1239
+ #### typeWrapper.containsNull · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L77)
1195
1240
 
1196
- **Signature:** `(...args: ARGS) => InstanceType<M>`
1241
+ Check if indexing should be skipped for this field value.
1242
+
1243
+ **Signature:** `(value: T) => boolean`
1197
1244
 
1198
1245
  **Parameters:**
1199
1246
 
1200
- - `args: ARGS`
1247
+ - `value: T`
1201
1248
 
1202
- ### SecondaryIndex · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
1249
+ **Returns:** true if indexing should be skipped.
1203
1250
 
1204
- Secondary index for non-unique lookups.
1251
+ #### typeWrapper.toString · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L78)
1205
1252
 
1206
- **Type Parameters:**
1253
+ **Signature:** `() => string`
1207
1254
 
1208
- - `M extends typeof Model`
1209
- - `F extends readonly (keyof InstanceType<M> & string)[]`
1210
- - `ARGS extends readonly any[] = IndexArgTypes<M, F>`
1255
+ #### typeWrapper.clone · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L81)
1211
1256
 
1212
- ### Change · [type](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L94)
1257
+ **Signature:** `(value: T) => T`
1213
1258
 
1214
- **Type:** `Record<any, any> | "created" | "deleted"`
1259
+ **Parameters:**
1215
1260
 
1216
- ### Transaction · [interface](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L39)
1261
+ - `value: T`
1217
1262
 
1218
- #### transaction.id · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L41)
1263
+ #### typeWrapper.equals · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L85)
1219
1264
 
1220
- **Type:** `number`
1265
+ **Signature:** `(value1: T, value2: T) => boolean`
1221
1266
 
1222
- #### transaction.instances · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L42)
1267
+ **Parameters:**
1223
1268
 
1224
- **Type:** `Set<Model<unknown>>`
1269
+ - `value1: T`
1270
+ - `value2: T`
1225
1271
 
1226
- #### transaction.instancesByPk · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L44)
1272
+ #### typeWrapper.getLinkedModel · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L90)
1227
1273
 
1228
- **Type:** `Map<number, Model<unknown>>`
1274
+ **Signature:** `() => AnyModelClass`
1229
1275
 
1230
- ### DatabaseError · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L156)
1276
+ ### DatabaseError · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L166)
1231
1277
 
1232
1278
  The DatabaseError class is used to represent errors that occur during database operations.
1233
1279
  It extends the built-in Error class and has a machine readable error code string property.
@@ -1237,7 +1283,7 @@ Invalid function arguments will throw TypeError.
1237
1283
 
1238
1284
  **Value:** `DatabaseErrorConstructor`
1239
1285
 
1240
- ### runMigration · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L122)
1286
+ ### runMigration · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L121)
1241
1287
 
1242
1288
  Run database migration: populate secondary indexes for old-version rows,
1243
1289
  convert old primary indices, rewrite row data, and clean up orphaned indices.
@@ -1250,13 +1296,13 @@ convert old primary indices, rewrite row data, and clean up orphaned indices.
1250
1296
 
1251
1297
  ### MigrationOptions · [interface](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L13)
1252
1298
 
1253
- #### migrationOptions.tables · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L17)
1299
+ #### migrationOptions.tables · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L18)
1254
1300
 
1255
1301
  Limit migration to specific table names.
1256
1302
 
1257
1303
  **Type:** `string[]`
1258
1304
 
1259
- #### migrationOptions.populateSecondaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L21)
1305
+ #### migrationOptions.populateSecondaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L22)
1260
1306
 
1261
1307
  Populate secondary indexes for rows at old schema versions (default: true).
1262
1308
 
@@ -1268,27 +1314,27 @@ Convert old primary indices when primary key fields changed (default: true).
1268
1314
 
1269
1315
  **Type:** `boolean`
1270
1316
 
1271
- #### migrationOptions.rewriteData · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L37)
1317
+ #### migrationOptions.rewriteData · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L38)
1272
1318
 
1273
1319
  Rewrite all row data to the latest schema version (default: false).
1274
1320
 
1275
1321
  **Type:** `boolean`
1276
1322
 
1277
- #### migrationOptions.removeOrphans · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L44)
1323
+ #### migrationOptions.removeOrphans · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L45)
1278
1324
 
1279
1325
  Delete orphaned secondary/unique index entries (default: true).
1280
1326
 
1281
1327
  **Type:** `boolean`
1282
1328
 
1283
- #### migrationOptions.onProgress · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L44)
1329
+ #### migrationOptions.onProgress · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L45)
1284
1330
 
1285
1331
  Progress callback.
1286
1332
 
1287
1333
  **Type:** `(info: ProgressInfo) => void`
1288
1334
 
1289
- ### MigrationResult · [interface](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L49)
1335
+ ### MigrationResult · [interface](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L50)
1290
1336
 
1291
- #### migrationResult.secondaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L50)
1337
+ #### migrationResult.secondaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L52)
1292
1338
 
1293
1339
  Per-table counts of secondary index entries populated.
1294
1340
 
@@ -1300,21 +1346,35 @@ Per-table counts of old primary rows migrated.
1300
1346
 
1301
1347
  **Type:** `Record<string, number>`
1302
1348
 
1303
- #### migrationResult.conversionFailures · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L57)
1349
+ #### migrationResult.conversionFailures · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L59)
1304
1350
 
1305
1351
  Per-table conversion failure counts by reason.
1306
1352
 
1307
1353
  **Type:** `Record<string, Record<string, number>>`
1308
1354
 
1309
- #### migrationResult.rewritten · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L60)
1355
+ #### migrationResult.rewritten · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L63)
1310
1356
 
1311
1357
  Per-table counts of rows rewritten to latest version.
1312
1358
 
1313
1359
  **Type:** `Record<string, number>`
1314
1360
 
1315
- #### migrationResult.orphans · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L65)
1361
+ #### migrationResult.orphans · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L67)
1316
1362
 
1317
1363
  Number of orphaned index entries deleted.
1318
1364
 
1319
1365
  **Type:** `number`
1320
1366
 
1367
+ ### Transaction · [interface](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L54)
1368
+
1369
+ #### transaction.id · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L55)
1370
+
1371
+ **Type:** `number`
1372
+
1373
+ #### transaction.instances · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L56)
1374
+
1375
+ **Type:** `Map<number, ModelBase>`
1376
+
1377
+ ### txnStorage · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L59)
1378
+
1379
+ **Value:** `AsyncLocalStorage<Transaction>`
1380
+