edinburgh 0.5.0 → 0.6.1

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 +309 -246
  2. package/build/src/datapack.d.ts +9 -9
  3. package/build/src/datapack.js +9 -9
  4. package/build/src/edinburgh.d.ts +21 -7
  5. package/build/src/edinburgh.js +53 -67
  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 +2 -2
  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 +5 -2
  51. package/skill/transact.md +1 -1
  52. package/src/datapack.ts +9 -9
  53. package/src/edinburgh.ts +68 -68
  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#L232)
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,124 @@ 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#L249)
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
+ The callback is called within a new transaction context, allowing lazy-loads to happen. However, any
581
+ changes made to Edinburgh models will not be saved.
573
582
 
574
- **Signature:** `() => Promise<void>`
575
-
576
- ### Model · [abstract class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
583
+ ### Model · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
577
584
 
578
- [object Object],[object Object],[object Object],[object Object],[object Object]
585
+ **Type:** `typeof ModelBase`
579
586
 
580
- **Type Parameters:**
587
+ ### ModelClass · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
581
588
 
582
- - `SUB` - The concrete model subclass (for proper typing).
589
+ Runtime base constructor for model classes returned by `defineModel()`.
583
590
 
584
- **Examples:**
585
-
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
- ```
591
+ Prefer the `ModelClass` type alias for annotations and the result of
592
+ `defineModel()` for concrete model classes.
596
593
 
597
- #### Model.tableName · [static property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
594
+ **Type:** `typeof ModelClassRuntime`
598
595
 
599
- The database table name (defaults to class name).
596
+ ### AnyModelClass · [type](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L111)
600
597
 
601
- **Type:** `string`
598
+ A model constructor with its generic information erased.
602
599
 
603
- #### Model.override · [static property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
600
+ Useful when accepting or storing arbitrary registered model classes.
604
601
 
605
- When true, defineModel replaces an existing model with the same tableName.
602
+ **Type:** `ModelClass<new () => any, readonly any[], any, any>`
606
603
 
607
- **Type:** `boolean`
604
+ ### ModelBase · [abstract class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
608
605
 
609
- #### Model.fields · [static property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
606
+ Base class for all database models in the Edinburgh ORM.
610
607
 
611
- Field configuration metadata.
608
+ Models represent database entities with typed fields, automatic serialization,
609
+ change tracking, and relationship management. Model classes are created using
610
+ `E.defineModel()`.
612
611
 
613
- **Type:** `Record<string | number | symbol, FieldConfig<unknown>>`
612
+ ### Schema Evolution
614
613
 
615
- #### Model.migrate · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
614
+ Edinburgh tracks the schema version of each model automatically. When you add, remove, or
615
+ change the types of fields, or add/remove indexes, Edinburgh detects the new schema version.
616
616
 
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.
617
+ **Lazy migration:** Changes to non-key field values are migrated lazily, when a row with an
618
+ old schema version is read from disk, it is deserialized using the old schema and optionally
619
+ transformed by the static `migrate()` function. This happens transparently on every read
620
+ and requires no downtime or batch processing.
620
621
 
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.
622
+ **Batch migration (via `npx migrate-edinburgh` or `runMigration()`):** Certain schema changes
623
+ require an explicit migration run:
624
+ - Adding or removing secondary/unique indexes
625
+ - Changing the fields or types of an existing index
626
+ - A `migrate()` function that changes values used in secondary index fields
624
627
 
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).
628
+ The batch migration tool populates new indexes, deletes orphaned ones, and updates index
629
+ entries whose values were changed by `migrate()`. It does *not* rewrite primary data rows
630
+ (lazy migration handles that).
627
631
 
628
- **Signature:** `(record: Record<string, any>) => void`
632
+ ### Lifecycle Hooks
629
633
 
630
- **Parameters:**
634
+ - **`static migrate(record)`**: Called when deserializing rows written with an older schema
635
+ version. Receives a plain record object; mutate it in-place to match the current schema.
631
636
 
632
- - `record: Record<string, any>` - - A plain object with all field values from the old schema version.
637
+ - **`preCommit()`**: Called on each modified instance right before the transaction commits.
638
+ Useful for computing derived fields, enforcing cross-field invariants, or creating related
639
+ instances.
633
640
 
634
641
  **Examples:**
635
642
 
636
643
  ```typescript
637
- const User = E.defineModel(class {
644
+ const User = E.defineModel("User", class {
638
645
  id = E.field(E.identifier);
639
646
  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" });
647
+ email = E.field(E.string);
648
+ }, {
649
+ pk: "id",
650
+ unique: { email: "email" },
651
+ });
652
+ // Optional: declare a companion type so `let u: User` works.
653
+ // Not needed if you only use `new User()`, `User.find()`, etc.
654
+ type User = InstanceType<typeof User>;
646
655
  ```
647
656
 
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`
657
+ #### ModelBase.migrate · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
667
658
 
668
- **Parameters:**
669
-
670
- - `opts?: any`
671
-
672
- #### Model.batchProcess · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
659
+ Optional migration function called when deserializing rows written with an older schema version.
660
+ Receives a plain record with all fields and should mutate it in-place to match the current schema.
661
+ It runs during lazy loading and during `runMigration()`. Changing this method creates a new schema version.
662
+ If it updates values used by secondary or unique indexes, those index entries are refreshed only by `runMigration()`.
673
663
 
674
- **Signature:** `(opts: any, callback?: any) => any`
664
+ **Signature:** `(record: Record<string, any>) => void`
675
665
 
676
666
  **Parameters:**
677
667
 
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.
668
+ - `record: Record<string, any>` - A plain object containing the row's field values from the older schema version.
688
669
 
689
- **Signature:** `<T extends typeof Model<any>>(this: T, obj: Partial<Record<string, any>>) => InstanceType<T>`
690
-
691
- **Parameters:**
670
+ **Examples:**
692
671
 
693
- - `this: T`
694
- - `obj: Partial<Record<string, any>>` - - Partial model data that **must** include every primary key field.
672
+ ```typescript
673
+ const User = E.defineModel("User", class {
674
+ id = E.field(E.identifier);
675
+ name = E.field(E.string);
676
+ role = E.field(E.string);
695
677
 
696
- **Returns:** The loaded-and-updated or newly created instance.
678
+ static migrate(record: Record<string, any>) {
679
+ record.role ??= "user";
680
+ }
681
+ }, { pk: "id" });
682
+ ```
697
683
 
698
- #### model.preCommit · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
684
+ #### modelBase.preCommit · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
699
685
 
700
686
  Optional hook called on each modified instance right before the transaction commits.
701
687
  Runs before data is written to disk, so changes made here are included in the commit.
@@ -711,7 +697,7 @@ Common use cases:
711
697
  **Examples:**
712
698
 
713
699
  ```typescript
714
- const Post = E.defineModel(class {
700
+ const Post = E.defineModel("Post", class {
715
701
  id = E.field(E.identifier);
716
702
  title = E.field(E.string);
717
703
  slug = E.field(E.string);
@@ -722,19 +708,19 @@ const Post = E.defineModel(class {
722
708
  }, { pk: "id" });
723
709
  ```
724
710
 
725
- #### model.getPrimaryKey · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
711
+ #### modelBase.getPrimaryKey · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
726
712
 
727
713
  **Signature:** `() => Uint8Array<ArrayBufferLike>`
728
714
 
729
715
  **Returns:** The primary key for this instance.
730
716
 
731
- #### model.getPrimaryKeyHash · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
717
+ #### modelBase.getPrimaryKeyHash · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
732
718
 
733
719
  **Signature:** `() => number`
734
720
 
735
721
  **Returns:** A 53-bit positive integer non-cryptographic hash of the primary key, or undefined if not yet saved.
736
722
 
737
- #### model.isLazyField · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
723
+ #### modelBase.isLazyField · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
738
724
 
739
725
  **Signature:** `(field: keyof this) => boolean`
740
726
 
@@ -742,7 +728,7 @@ const Post = E.defineModel(class {
742
728
 
743
729
  - `field: keyof this`
744
730
 
745
- #### model.preventPersist · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
731
+ #### modelBase.preventPersist · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
746
732
 
747
733
  Prevent this instance from being persisted to the database.
748
734
 
@@ -758,7 +744,7 @@ user.name = "New Name";
758
744
  user.preventPersist(); // Changes won't be saved
759
745
  ```
760
746
 
761
- #### model.delete · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
747
+ #### modelBase.delete · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
762
748
 
763
749
  Delete this model instance from the database.
764
750
 
@@ -773,7 +759,7 @@ const user = User.get("user123");
773
759
  user.delete(); // Removes from database
774
760
  ```
775
761
 
776
- #### model.validate · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
762
+ #### modelBase.validate · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
777
763
 
778
764
  Validate all fields in this model instance.
779
765
 
@@ -781,7 +767,7 @@ Validate all fields in this model instance.
781
767
 
782
768
  **Parameters:**
783
769
 
784
- - `raise: boolean` (optional) - - If true, throw on first validation error.
770
+ - `raise: boolean` (optional) - If true, throw on first validation error.
785
771
 
786
772
  **Returns:** Array of validation errors (empty if valid).
787
773
 
@@ -795,7 +781,7 @@ if (errors.length > 0) {
795
781
  }
796
782
  ```
797
783
 
798
- #### model.isValid · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
784
+ #### modelBase.isValid · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
799
785
 
800
786
  Check if this model instance is valid.
801
787
 
@@ -810,19 +796,19 @@ const user = new User({name: "John"});
810
796
  if (!user.isValid()) shoutAtTheUser();
811
797
  ```
812
798
 
813
- #### model.getState · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
799
+ #### modelBase.getState · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
814
800
 
815
801
  **Signature:** `() => "created" | "deleted" | "loaded" | "lazy"`
816
802
 
817
- #### model.toString · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
803
+ #### modelBase.toString · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
818
804
 
819
805
  **Signature:** `() => string`
820
806
 
821
- #### model.[Symbol.for('nodejs.util.inspect.custom')] · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
807
+ #### modelBase.[Symbol.for('nodejs.util.inspect.custom')] · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
822
808
 
823
809
  **Signature:** `() => string`
824
810
 
825
- ### defineModel · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L164)
811
+ ### defineModel · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
826
812
 
827
813
  Register a model class with the Edinburgh ORM system.
828
814
 
@@ -840,12 +826,22 @@ typed fields, primary key access, and optional secondary and unique indexes.
840
826
 
841
827
  **Parameters:**
842
828
 
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.
829
+ - `tableName: string` - The database table name for this model.
830
+ - `cls: T` - A plain class whose properties use E.field().
831
+ - `opts?: { pk?: PK, unique?: UNIQUE, index?: INDEX, override?: boolean }` - Registration options.
845
832
 
846
833
  **Returns:** The enhanced model constructor.
847
834
 
848
- ### field · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L87)
835
+ ### deleteEverything · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
836
+
837
+ Delete every key/value entry in the database and reinitialize all registered models.
838
+
839
+ This clears rows, index metadata, and schema-version records. It is mainly useful
840
+ for tests, local resets, or tooling that needs a completely empty database.
841
+
842
+ **Signature:** `() => Promise<void>`
843
+
844
+ ### field · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L76)
849
845
 
850
846
  Create a field definition for a model property.
851
847
 
@@ -861,27 +857,20 @@ This allows for both runtime introspection and compile-time type safety.
861
857
 
862
858
  **Parameters:**
863
859
 
864
- - `type: TypeWrapper<T>` - - The type wrapper for this field.
865
- - `options: Partial<FieldConfig<T>>` (optional) - - Additional field configuration options.
860
+ - `type: TypeWrapper<T>` - The type wrapper for this field.
861
+ - `options: Partial<FieldConfig<T>>` (optional) - Additional field configuration options.
866
862
 
867
863
  **Returns:** The field value (typed as T, but actually returns FieldConfig<T>).
868
864
 
869
865
  **Examples:**
870
866
 
871
867
  ```typescript
872
- const User = E.defineModel(class {
868
+ const User = E.defineModel("User", class {
873
869
  name = E.field(E.string, {description: "User's full name"});
874
870
  age = E.field(E.opt(E.number), {description: "User's age", default: 25});
875
871
  });
876
872
  ```
877
873
 
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
874
  ### string · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
886
875
 
887
876
  Type wrapper instance for the string type.
@@ -940,7 +929,7 @@ Create an optional type wrapper (allows undefined).
940
929
 
941
930
  **Parameters:**
942
931
 
943
- - `inner: T` - - The inner type to make optional.
932
+ - `inner: T` - The inner type to make optional.
944
933
 
945
934
  **Returns:** A union type that accepts the inner type or undefined.
946
935
 
@@ -963,7 +952,7 @@ Create a union type wrapper from multiple type choices.
963
952
 
964
953
  **Parameters:**
965
954
 
966
- - `choices: T` - - The type choices for the union.
955
+ - `choices: T` - The type choices for the union.
967
956
 
968
957
  **Returns:** A union type instance.
969
958
 
@@ -986,8 +975,8 @@ Create an array type wrapper with optional length constraints.
986
975
 
987
976
  **Parameters:**
988
977
 
989
- - `inner: TypeWrapper<T>` - - Type wrapper for array elements.
990
- - `opts: {min?: number, max?: number}` (optional) - - Optional constraints (min/max length).
978
+ - `inner: TypeWrapper<T>` - Type wrapper for array elements.
979
+ - `opts: {min?: number, max?: number}` (optional) - Optional constraints (min/max length).
991
980
 
992
981
  **Returns:** An array type instance.
993
982
 
@@ -1010,8 +999,8 @@ Create a Set type wrapper with optional length constraints.
1010
999
 
1011
1000
  **Parameters:**
1012
1001
 
1013
- - `inner: TypeWrapper<T>` - - Type wrapper for set elements.
1014
- - `opts: {min?: number, max?: number}` (optional) - - Optional constraints (min/max length).
1002
+ - `inner: TypeWrapper<T>` - Type wrapper for set elements.
1003
+ - `opts: {min?: number, max?: number}` (optional) - Optional constraints (min/max length).
1015
1004
 
1016
1005
  **Returns:** A set type instance.
1017
1006
 
@@ -1034,7 +1023,7 @@ Create a Record type wrapper for key-value objects with string or number keys.
1034
1023
 
1035
1024
  **Parameters:**
1036
1025
 
1037
- - `inner: TypeWrapper<T>` - - Type wrapper for record values.
1026
+ - `inner: TypeWrapper<T>` - Type wrapper for record values.
1038
1027
 
1039
1028
  **Returns:** A record type instance.
1040
1029
 
@@ -1056,7 +1045,7 @@ Create a literal type wrapper for a constant value.
1056
1045
 
1057
1046
  **Parameters:**
1058
1047
 
1059
- - `value: T` - - The literal value.
1048
+ - `value: T` - The literal value.
1060
1049
 
1061
1050
  **Returns:** A literal type instance.
1062
1051
 
@@ -1079,19 +1068,19 @@ Create a link type wrapper for model relationships.
1079
1068
 
1080
1069
  **Parameters:**
1081
1070
 
1082
- - `TargetModel: T` - - The model class this link points to.
1071
+ - `TargetModel: T` - The model class this link points to.
1083
1072
 
1084
1073
  **Returns:** A link type instance.
1085
1074
 
1086
1075
  **Examples:**
1087
1076
 
1088
1077
  ```typescript
1089
- const Author = E.defineModel(class {
1078
+ const Author = E.defineModel("Author", class {
1090
1079
  id = E.field(E.identifier);
1091
1080
  posts = E.field(E.array(E.link(() => Book)));
1092
1081
  }, { pk: "id" });
1093
1082
 
1094
- const Book = E.defineModel(class {
1083
+ const Book = E.defineModel("Book", class {
1095
1084
  id = E.field(E.identifier);
1096
1085
  author = E.field(E.link(Author));
1097
1086
  }, { pk: "id" });
@@ -1099,135 +1088,195 @@ const Book = E.defineModel(class {
1099
1088
 
1100
1089
  ### dump · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
1101
1090
 
1102
- Dump database contents for debugging.
1091
+ **Signature:** `() => void`
1092
+
1093
+ ### FindOptions · [type](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L115)
1094
+
1095
+ Range-query options accepted by `find()`, `findBy()`, `batchProcess()`, and `batchProcessBy()`.
1096
+
1097
+ Supports exact-match lookups via `is`, inclusive bounds via `from` / `to`,
1098
+ exclusive bounds via `after` / `before`, and reverse scans.
1099
+
1100
+ For single-field indexes, values can be passed directly. For composite indexes,
1101
+ pass tuples or partial tuples for prefix matching.
1102
+
1103
+ **Type:** `(
1104
+ (
1105
+ {is: ArrayOrOnlyItem<ARG_TYPES>;} // Shortcut for setting `from` and `to` to the same value
1106
+ |
1107
+ (
1108
+ (
1109
+ {from: ArrayOrOnlyItem<ARG_TYPES>;}
1110
+ |
1111
+ {after: ArrayOrOnlyItem<ARG_TYPES>;}
1112
+ |
1113
+ {}
1114
+ )
1115
+ &
1116
+ (
1117
+ {to: ArrayOrOnlyItem<ARG_TYPES>;}
1118
+ |
1119
+ {before: ArrayOrOnlyItem<ARG_TYPES>;}
1120
+ |
1121
+ {}
1122
+ )
1123
+ )
1124
+ ) &
1125
+ {
1126
+ reverse?: boolean;
1127
+ }
1128
+ & (FETCH extends undefined ? { fetch?: undefined } : { fetch: FETCH })
1129
+ )`
1130
+
1131
+ ### IndexRangeIterator · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L73)
1132
+
1133
+ Iterator for range queries on indexes.
1134
+ Handles common iteration logic for both primary and unique indexes.
1135
+ Extends built-in Iterator to provide map/filter/reduce/toArray/etc.
1136
+
1137
+ **Type Parameters:**
1103
1138
 
1104
- Prints all indexes and their data to the console for inspection.
1105
- This is primarily useful for development and debugging purposes.
1139
+ - `ITEM`
1106
1140
 
1107
- **Signature:** `() => void`
1141
+ #### indexRangeIterator.[Symbol.iterator] · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L78)
1142
+
1143
+ **Signature:** `() => this`
1144
+
1145
+ #### indexRangeIterator.next · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L80)
1146
+
1147
+ **Signature:** `() => IteratorResult<ITEM, any>`
1108
1148
 
1109
- ### BaseIndex · [abstract class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L130)
1149
+ #### indexRangeIterator.count · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L102)
1150
+
1151
+ **Signature:** `() => number`
1152
+
1153
+ #### indexRangeIterator.fetch · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L105)
1154
+
1155
+ **Signature:** `() => ITEM`
1156
+
1157
+ ### Change · [type](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L85)
1158
+
1159
+ **Type:** `Record<any, any> | "created" | "deleted"`
1110
1160
 
1111
- Base class for database indexes for efficient lookups on model fields.
1161
+ ### FieldConfig · [interface](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L37)
1112
1162
 
1113
- Indexes enable fast queries on specific field combinations and enforce uniqueness constraints.
1163
+ Configuration interface for model fields.
1114
1164
 
1115
1165
  **Type Parameters:**
1116
1166
 
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>`
1167
+ - `T` - The field type.
1120
1168
 
1121
- **Constructor Parameters:**
1169
+ #### fieldConfig.type · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L45)
1122
1170
 
1123
- - `MyModel`: - The model class this index belongs to.
1124
- - `_fieldNames`: - Array of field names that make up this index.
1171
+ The type wrapper that defines how this field is serialized/validated.
1125
1172
 
1126
- #### baseIndex.find · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
1173
+ **Type:** `TypeWrapper<T>`
1127
1174
 
1128
- **Signature:** `{ (opts?: FindOptions<ARGS, "first">): InstanceType<M>; (opts: FindOptions<ARGS, "single">): InstanceType<M>; (opts?: FindOptions<...>): IndexRangeIterator<...>; }`
1175
+ #### fieldConfig.description · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L47)
1129
1176
 
1130
- **Parameters:**
1177
+ Optional human-readable description of the field.
1131
1178
 
1132
- - `opts?: FindOptions<ARGS, 'first'>`
1179
+ **Type:** `string`
1133
1180
 
1134
- #### baseIndex.find · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
1181
+ #### fieldConfig.default · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L49)
1135
1182
 
1136
- **Signature:** `{ (opts?: FindOptions<ARGS, "first">): InstanceType<M>; (opts: FindOptions<ARGS, "single">): InstanceType<M>; (opts?: FindOptions<...>): IndexRangeIterator<...>; }`
1183
+ Optional default value or function that generates default values.
1137
1184
 
1138
- **Parameters:**
1185
+ **Type:** `T | ((model: Record<string, any>) => T)`
1139
1186
 
1140
- - `opts: FindOptions<ARGS, 'single'>`
1187
+ ### TypeWrapper · [abstract class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L33)
1141
1188
 
1142
- #### baseIndex.find · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
1189
+ **Type Parameters:**
1143
1190
 
1144
- **Signature:** `{ (opts?: FindOptions<ARGS, "first">): InstanceType<M>; (opts: FindOptions<ARGS, "single">): InstanceType<M>; (opts?: FindOptions<...>): IndexRangeIterator<...>; }`
1191
+ - `T` - The TypeScript type this wrapper represents.
1145
1192
 
1146
- **Parameters:**
1193
+ #### typeWrapper.kind · [abstract property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L47)
1147
1194
 
1148
- - `opts?: FindOptions<ARGS>`
1195
+ A string identifier for this type, used during serialization
1196
+
1197
+ **Type:** `string`
1149
1198
 
1150
- #### baseIndex.find · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
1199
+ #### typeWrapper.serialize · [abstract method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L52)
1151
1200
 
1152
- **Signature:** `{ (opts?: FindOptions<ARGS, "first">): InstanceType<M>; (opts: FindOptions<ARGS, "single">): InstanceType<M>; (opts?: FindOptions<...>): IndexRangeIterator<...>; }`
1201
+ Serialize a value from an object property to a Pack.
1202
+
1203
+ **Signature:** `(value: T, pack: DataPack) => void`
1153
1204
 
1154
1205
  **Parameters:**
1155
1206
 
1156
- - `opts: any` (optional)
1207
+ - `value: T` - The value to serialize.
1208
+ - `pack: DataPack` - The Pack instance to write to.
1157
1209
 
1158
- #### baseIndex.batchProcess · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
1210
+ #### typeWrapper.deserialize · [abstract method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L59)
1159
1211
 
1160
- [object Object],[object Object],[object Object]
1212
+ Deserialize a value from a Pack into an object property.
1161
1213
 
1162
- **Signature:** `(opts: FindOptions<ARGS, undefined> & { limitSeconds?: number; limitRows?: number; }, callback: (row: InstanceType<M>) => void | Promise<void>) => Promise<...>`
1214
+ **Signature:** `(pack: DataPack) => T`
1163
1215
 
1164
1216
  **Parameters:**
1165
1217
 
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
1218
+ - `pack: DataPack` - The Pack instance to read from.
1168
1219
 
1169
- #### baseIndex.toString · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
1220
+ #### typeWrapper.getError · [abstract method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L64)
1170
1221
 
1171
- **Signature:** `() => string`
1222
+ Validate a value.
1172
1223
 
1173
- ### NonPrimaryIndex · [abstract class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
1224
+ **Signature:** `(value: T) => void | DatabaseError`
1174
1225
 
1175
- Abstract base for all non-primary indexes (unique and secondary).
1176
- Provides shared key serialization, write/delete/update logic.
1226
+ **Parameters:**
1177
1227
 
1178
- **Type Parameters:**
1228
+ - `value: T` - The value to validate.
1179
1229
 
1180
- - `M extends typeof Model`
1181
- - `F extends readonly (keyof InstanceType<M> & string)[]`
1182
- - `ARGS extends readonly any[] = IndexArgTypes<M, F>`
1230
+ **Returns:** - A DatabaseError if validation fails.
1183
1231
 
1184
- ### UniqueIndex · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
1232
+ #### typeWrapper.serializeType · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L68)
1185
1233
 
1186
- Unique index that stores references to the primary key.
1234
+ Serialize type metadata to a Pack (for schema serialization).
1187
1235
 
1188
- **Type Parameters:**
1236
+ **Signature:** `(pack: DataPack) => void`
1237
+
1238
+ **Parameters:**
1189
1239
 
1190
- - `M extends typeof Model`
1191
- - `F extends readonly (keyof InstanceType<M> & string)[]`
1192
- - `ARGS extends readonly any[] = IndexArgTypes<M, F>`
1240
+ - `pack: DataPack` - The Pack instance to write to.
1193
1241
 
1194
- #### uniqueIndex.get · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
1242
+ #### typeWrapper.containsNull · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L77)
1195
1243
 
1196
- **Signature:** `(...args: ARGS) => InstanceType<M>`
1244
+ Check if indexing should be skipped for this field value.
1245
+
1246
+ **Signature:** `(value: T) => boolean`
1197
1247
 
1198
1248
  **Parameters:**
1199
1249
 
1200
- - `args: ARGS`
1250
+ - `value: T`
1201
1251
 
1202
- ### SecondaryIndex · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L252)
1252
+ **Returns:** true if indexing should be skipped.
1203
1253
 
1204
- Secondary index for non-unique lookups.
1254
+ #### typeWrapper.toString · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L78)
1205
1255
 
1206
- **Type Parameters:**
1256
+ **Signature:** `() => string`
1207
1257
 
1208
- - `M extends typeof Model`
1209
- - `F extends readonly (keyof InstanceType<M> & string)[]`
1210
- - `ARGS extends readonly any[] = IndexArgTypes<M, F>`
1258
+ #### typeWrapper.clone · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L81)
1211
1259
 
1212
- ### Change · [type](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L94)
1260
+ **Signature:** `(value: T) => T`
1213
1261
 
1214
- **Type:** `Record<any, any> | "created" | "deleted"`
1262
+ **Parameters:**
1215
1263
 
1216
- ### Transaction · [interface](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L39)
1264
+ - `value: T`
1217
1265
 
1218
- #### transaction.id · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L41)
1266
+ #### typeWrapper.equals · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L85)
1219
1267
 
1220
- **Type:** `number`
1268
+ **Signature:** `(value1: T, value2: T) => boolean`
1221
1269
 
1222
- #### transaction.instances · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L42)
1270
+ **Parameters:**
1223
1271
 
1224
- **Type:** `Set<Model<unknown>>`
1272
+ - `value1: T`
1273
+ - `value2: T`
1225
1274
 
1226
- #### transaction.instancesByPk · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L44)
1275
+ #### typeWrapper.getLinkedModel · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L90)
1227
1276
 
1228
- **Type:** `Map<number, Model<unknown>>`
1277
+ **Signature:** `() => AnyModelClass`
1229
1278
 
1230
- ### DatabaseError · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L156)
1279
+ ### DatabaseError · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L174)
1231
1280
 
1232
1281
  The DatabaseError class is used to represent errors that occur during database operations.
1233
1282
  It extends the built-in Error class and has a machine readable error code string property.
@@ -1237,7 +1286,7 @@ Invalid function arguments will throw TypeError.
1237
1286
 
1238
1287
  **Value:** `DatabaseErrorConstructor`
1239
1288
 
1240
- ### runMigration · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L122)
1289
+ ### runMigration · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L121)
1241
1290
 
1242
1291
  Run database migration: populate secondary indexes for old-version rows,
1243
1292
  convert old primary indices, rewrite row data, and clean up orphaned indices.
@@ -1250,13 +1299,13 @@ convert old primary indices, rewrite row data, and clean up orphaned indices.
1250
1299
 
1251
1300
  ### MigrationOptions · [interface](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L13)
1252
1301
 
1253
- #### migrationOptions.tables · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L17)
1302
+ #### migrationOptions.tables · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L18)
1254
1303
 
1255
1304
  Limit migration to specific table names.
1256
1305
 
1257
1306
  **Type:** `string[]`
1258
1307
 
1259
- #### migrationOptions.populateSecondaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L21)
1308
+ #### migrationOptions.populateSecondaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L22)
1260
1309
 
1261
1310
  Populate secondary indexes for rows at old schema versions (default: true).
1262
1311
 
@@ -1268,27 +1317,27 @@ Convert old primary indices when primary key fields changed (default: true).
1268
1317
 
1269
1318
  **Type:** `boolean`
1270
1319
 
1271
- #### migrationOptions.rewriteData · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L37)
1320
+ #### migrationOptions.rewriteData · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L38)
1272
1321
 
1273
1322
  Rewrite all row data to the latest schema version (default: false).
1274
1323
 
1275
1324
  **Type:** `boolean`
1276
1325
 
1277
- #### migrationOptions.removeOrphans · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L44)
1326
+ #### migrationOptions.removeOrphans · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L45)
1278
1327
 
1279
1328
  Delete orphaned secondary/unique index entries (default: true).
1280
1329
 
1281
1330
  **Type:** `boolean`
1282
1331
 
1283
- #### migrationOptions.onProgress · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L44)
1332
+ #### migrationOptions.onProgress · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L45)
1284
1333
 
1285
1334
  Progress callback.
1286
1335
 
1287
1336
  **Type:** `(info: ProgressInfo) => void`
1288
1337
 
1289
- ### MigrationResult · [interface](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L49)
1338
+ ### MigrationResult · [interface](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L50)
1290
1339
 
1291
- #### migrationResult.secondaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L50)
1340
+ #### migrationResult.secondaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L52)
1292
1341
 
1293
1342
  Per-table counts of secondary index entries populated.
1294
1343
 
@@ -1300,21 +1349,35 @@ Per-table counts of old primary rows migrated.
1300
1349
 
1301
1350
  **Type:** `Record<string, number>`
1302
1351
 
1303
- #### migrationResult.conversionFailures · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L57)
1352
+ #### migrationResult.conversionFailures · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L59)
1304
1353
 
1305
1354
  Per-table conversion failure counts by reason.
1306
1355
 
1307
1356
  **Type:** `Record<string, Record<string, number>>`
1308
1357
 
1309
- #### migrationResult.rewritten · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L60)
1358
+ #### migrationResult.rewritten · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L63)
1310
1359
 
1311
1360
  Per-table counts of rows rewritten to latest version.
1312
1361
 
1313
1362
  **Type:** `Record<string, number>`
1314
1363
 
1315
- #### migrationResult.orphans · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L65)
1364
+ #### migrationResult.orphans · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L67)
1316
1365
 
1317
1366
  Number of orphaned index entries deleted.
1318
1367
 
1319
1368
  **Type:** `number`
1320
1369
 
1370
+ ### Transaction · [interface](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L54)
1371
+
1372
+ #### transaction.id · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L55)
1373
+
1374
+ **Type:** `number`
1375
+
1376
+ #### transaction.instances · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L56)
1377
+
1378
+ **Type:** `Map<number, ModelBase>`
1379
+
1380
+ ### txnStorage · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L59)
1381
+
1382
+ **Value:** `AsyncLocalStorage<Transaction>`
1383
+