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/skill/SKILL.md CHANGED
@@ -26,7 +26,7 @@ import * as E from "edinburgh";
26
26
  // Initialize the database (optional, defaults to ".edinburgh")
27
27
  E.init("./my-database");
28
28
 
29
- const User = E.defineModel(class {
29
+ const User = E.defineModel("User", class {
30
30
  id = E.field(E.identifier);
31
31
  name = E.field(E.string);
32
32
  age = E.field(E.number);
@@ -46,9 +46,8 @@ const User = E.defineModel(class {
46
46
  }, {
47
47
  pk: "id",
48
48
  unique: {
49
- byEmail: "email",
49
+ email: "email",
50
50
  },
51
- tableName: "User",
52
51
  });
53
52
 
54
53
  await E.transact(() => {
@@ -65,7 +64,7 @@ await E.transact(() => {
65
64
 
66
65
  await E.transact(() => {
67
66
  // Query by unique index
68
- const john = User.byEmail.get("john@example.com")!;
67
+ const john = User.getBy("email", "john@example.com")!;
69
68
 
70
69
  // The transaction will retry if there's a conflict, such as another transaction
71
70
  // modifying the same user (from another async function or another process)
@@ -81,12 +80,15 @@ await E.transact(() => {
81
80
 
82
81
  ### Defining Models
83
82
 
84
- Models are plain, usually anonymous, classes passed to `E.defineModel()`:
83
+ A model is defined using the `E.defineModel()` function by passing it..
84
+ - a consistent table name,
85
+ - an (anonymous) class containing `E.field` database properties and optionally regular properties/methods, and
86
+ - optional key/index configuration.
85
87
 
86
88
  ```typescript
87
89
  import * as E from "edinburgh";
88
90
 
89
- const User = E.defineModel(class {
91
+ const User = E.defineModel("User", class {
90
92
  id = E.field(E.identifier);
91
93
  name = E.field(E.string);
92
94
  email = E.field(E.string);
@@ -94,9 +96,12 @@ const User = E.defineModel(class {
94
96
  }, {
95
97
  pk: "id",
96
98
  unique: {
97
- byEmail: "email",
99
+ email: "email",
98
100
  },
99
101
  });
102
+ // Add this if you want to use User as a type annotation (e.g. `let u: User`).
103
+ // Not needed just to call User.get(), User.find(), new User(), etc.
104
+ type User = InstanceType<typeof User>;
100
105
  ```
101
106
 
102
107
  Instance fields are declared with `E.field(type, options?)`. Available types:
@@ -120,7 +125,7 @@ Instance fields are declared with `E.field(type, options?)`. Available types:
120
125
  #### Defaults
121
126
 
122
127
  ```typescript
123
- const Post = E.defineModel(class {
128
+ const Post = E.defineModel("Post", class {
124
129
  id = E.field(E.identifier); // auto-generated
125
130
  title = E.field(E.string);
126
131
  status = E.field(E.or("draft", "published"), {default: "draft"});
@@ -145,13 +150,13 @@ await E.transact(() => {
145
150
 
146
151
  // Read + Update
147
152
  await E.transact(() => {
148
- const user = User.byEmail.get("alice@example.com");
153
+ const user = User.getBy("email", "alice@example.com");
149
154
  if (user) user.age++;
150
155
  });
151
156
 
152
157
  // Return values from transactions
153
158
  const name = await E.transact(() => {
154
- const user = User.byEmail.get("alice@example.com");
159
+ const user = User.getBy("email", "alice@example.com");
155
160
  return user?.name;
156
161
  });
157
162
  ```
@@ -163,15 +168,15 @@ Transactions auto-retry on conflict (up to 6 times by default). Keep transaction
163
168
  Edinburgh supports three index types:
164
169
 
165
170
  ```typescript
166
- const Product = E.defineModel(class {
171
+ const Product = E.defineModel("Product", class {
167
172
  sku = E.field(E.string);
168
173
  name = E.field(E.string);
169
174
  category = E.field(E.string);
170
175
  price = E.field(E.number);
171
176
  }, {
172
177
  pk: "sku",
173
- unique: { byName: "name" },
174
- index: { byCategory: "category" },
178
+ unique: { name: "name" },
179
+ index: { category: "category" },
175
180
  });
176
181
  ```
177
182
 
@@ -185,7 +190,7 @@ await E.transact(() => {
185
190
  const p1 = Product.get("SKU-001");
186
191
 
187
192
  // Unique index lookup
188
- const p2 = Product.byName.get("Widget");
193
+ const p2 = Product.getBy("name", "Widget");
189
194
 
190
195
  // All return undefined if not found
191
196
  });
@@ -193,12 +198,12 @@ await E.transact(() => {
193
198
 
194
199
  #### Range Queries
195
200
 
196
- All index types support `.find()` for range iteration:
201
+ Primary-key queries use `.find()`. Named unique and secondary indexes use `.findBy(name, ...)`:
197
202
 
198
203
  ```typescript
199
204
  await E.transact(() => {
200
205
  // Exact match
201
- for (const p of Product.byCategory.find({is: "electronics"})) {
206
+ for (const p of Product.findBy("category", {is: "electronics"})) {
202
207
  console.log(p.name);
203
208
  }
204
209
 
@@ -217,15 +222,15 @@ await E.transact(() => {
217
222
  for (const p of Product.find({reverse: true})) { ... }
218
223
 
219
224
  // Count and fetch helpers
220
- const count = Product.byCategory.find({is: "electronics"}).count();
221
- const first = Product.byCategory.find({is: "electronics"}).fetch(); // first match or undefined
225
+ const count = Product.findBy("category", {is: "electronics"}).count();
226
+ const first = Product.findBy("category", {is: "electronics"}).fetch(); // first match or undefined
222
227
  });
223
228
  ```
224
229
 
225
230
  #### Composite Primary Keys
226
231
 
227
232
  ```typescript
228
- const Event = E.defineModel(class {
233
+ const Event = E.defineModel("Event", class {
229
234
  year = E.field(E.number);
230
235
  month = E.field(E.number);
231
236
  id = E.field(E.identifier);
@@ -248,7 +253,7 @@ await E.transact(() => {
248
253
  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.
249
254
 
250
255
  ```typescript
251
- const User = E.defineModel(class {
256
+ const User = E.defineModel("User", class {
252
257
  firstName = E.field(E.string);
253
258
  lastName = E.field(E.string);
254
259
 
@@ -271,7 +276,7 @@ const User = E.defineModel(class {
271
276
  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).
272
277
 
273
278
  ```typescript
274
- const Article = E.defineModel(class {
279
+ const Article = E.defineModel("Article", class {
275
280
  id = E.field(E.identifier);
276
281
  firstName = E.field(E.string);
277
282
  lastName = E.field(E.string);
@@ -280,11 +285,11 @@ const Article = E.defineModel(class {
280
285
  }, {
281
286
  pk: "id",
282
287
  unique: {
283
- byFullName: (a: any) => [`${a.firstName} ${a.lastName}`], // computed covering unique index
288
+ fullName: (a: any) => [`${a.firstName} ${a.lastName}`], // computed covering unique index
284
289
  },
285
290
  index: {
286
- byDomain: (a: any) => a.email ? [a.email.split("@")[1]] : [], // computed partial index
287
- byWord: (a: any) => a.title.toLowerCase().split(" "), // computed multi-index
291
+ domain: (a: any) => a.email ? [a.email.split("@")[1]] : [], // computed partial index
292
+ word: (a: any) => a.title.toLowerCase().split(" "), // computed multi-index
288
293
  },
289
294
  });
290
295
 
@@ -292,13 +297,13 @@ await E.transact(() => {
292
297
  new Article({ firstName: "Jane", lastName: "Doe", title: "Hello World", email: "jane@acme.com" });
293
298
 
294
299
  // Lookup via computed unique index
295
- const jane = Article.byFullName.get("Jane Doe");
300
+ const jane = Article.getBy("fullName", "Jane Doe");
296
301
 
297
302
  // Multi-value: each word in the title is indexed separately
298
- for (const a of Article.byWord.find({is: "hello"})) { ... }
303
+ for (const a of Article.findBy("word", {is: "hello"})) { ... }
299
304
 
300
305
  // Partial index: articles without email are skipped
301
- for (const a of Article.byDomain.find({is: "acme.com"})) { ... }
306
+ for (const a of Article.findBy("domain", {is: "acme.com"})) { ... }
302
307
  });
303
308
  ```
304
309
 
@@ -307,12 +312,12 @@ await E.transact(() => {
307
312
  Use `E.link(Model)` for foreign keys. Use a thunk (a function that just returns a value) for forward references when needed:
308
313
 
309
314
  ```typescript
310
- const Author = E.defineModel(class {
315
+ const Author = E.defineModel("Author", class {
311
316
  id = E.field(E.identifier);
312
317
  name = E.field(E.string);
313
318
  }, { pk: "id" });
314
319
 
315
- const Book = E.defineModel(class {
320
+ const Book = E.defineModel("Book", class {
316
321
  id = E.field(E.identifier);
317
322
  title = E.field(E.string);
318
323
  author = E.field(E.link(Author));
@@ -385,7 +390,7 @@ await Product.batchProcess({ limitRows: 1000 }, (product) => {
385
390
  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:
386
391
 
387
392
  ```typescript
388
- const UserV2 = E.defineModel(class {
393
+ const UserV2 = E.defineModel("User", class {
389
394
  id = E.field(E.identifier);
390
395
  name = E.field(E.string);
391
396
  role = E.field(E.string); // newly added field
@@ -393,7 +398,7 @@ const UserV2 = E.defineModel(class {
393
398
  static migrate(record: Record<string, any>) {
394
399
  record.role ??= record.name.indexOf("admin") >= 0 ? "admin" : "user"; // set role based on name for old records
395
400
  }
396
- }
401
+ })
397
402
  ```
398
403
 
399
404
  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...
@@ -428,7 +433,7 @@ console.log(result.secondaries); // { User: 1500 }
428
433
  Compute derived fields before data is written:
429
434
 
430
435
  ```typescript
431
- const Article = E.defineModel(class {
436
+ const Article = E.defineModel("Article", class {
432
437
  id = E.field(E.identifier);
433
438
  title = E.field(E.string);
434
439
  slug = E.field(E.string);
@@ -436,7 +441,7 @@ const Article = E.defineModel(class {
436
441
  preCommit() {
437
442
  this.slug = this.title.toLowerCase().replace(/\s+/g, "-");
438
443
  }
439
- }, { pk: "id" });
444
+ });
440
445
  ```
441
446
 
442
447
  ### Change Tracking
@@ -477,9 +482,12 @@ ln -s ../../node_modules/edinburgh/skill .claude/skills/edinburgh
477
482
 
478
483
  The following is auto-generated from `src/edinburgh.ts`:
479
484
 
480
- ### scheduleInit · function
485
+ ### currentTxn · function
481
486
 
482
- **Signature:** `() => void`
487
+ Returns the current transaction from AsyncLocalStorage.
488
+ Throws if called outside a transact() callback.
489
+
490
+ **Signature:** `() => Transaction`
483
491
 
484
492
  ### [init](init.md) · function
485
493
 
@@ -500,82 +508,53 @@ The default value is 6. Setting it to 0 will disable retries and cause transacti
500
508
 
501
509
  Set a callback function to be called after a model is saved and committed.
502
510
 
503
- ### deleteEverything · function
504
-
505
- **Signature:** `() => Promise<void>`
511
+ ### Model · class
506
512
 
507
- ### [Model](Model.md) · abstract class
513
+ **Type:** `typeof ModelBase`
508
514
 
509
- [object Object],[object Object],[object Object],[object Object],[object Object]
515
+ ### [ModelClass](ModelClass.md) · class
510
516
 
511
- #### Model.tableName · static property
517
+ Runtime base constructor for model classes returned by `defineModel()`.
512
518
 
513
- The database table name (defaults to class name).
519
+ ### [AnyModelClass](AnyModelClass.md) · type
514
520
 
515
- **Type:** `string`
521
+ A model constructor with its generic information erased.
516
522
 
517
- #### Model.override · static property
523
+ ### [ModelBase](ModelBase.md) · abstract class
518
524
 
519
- When true, defineModel replaces an existing model with the same tableName.
525
+ Base class for all database models in the Edinburgh ORM.
520
526
 
521
- **Type:** `boolean`
527
+ ### [Schema Evolution](Schema Evolution.md)
522
528
 
523
- #### Model.fields · static property
529
+ Edinburgh tracks the schema version of each model automatically. When you add, remove, or
530
+ change the types of fields, or add/remove indexes, Edinburgh detects the new schema version.
524
531
 
525
- Field configuration metadata.
532
+ ### [Lifecycle Hooks](Lifecycle Hooks.md)
526
533
 
527
- **Type:** `Record<string | number | symbol, FieldConfig<unknown>>`
534
+ - **`static migrate(record)`**: Called when deserializing rows written with an older schema
535
+ version. Receives a plain record object; mutate it in-place to match the current schema.
528
536
 
529
- #### [Model.migrate](Model_migrate.md) · static method
537
+ #### [ModelBase.migrate](Lifecycle Hooks_migrate.md) · static method
530
538
 
531
539
  Optional migration function called when deserializing rows written with an older schema version.
532
- Receives a plain record with all fields (primary key fields + value fields) and should mutate it
533
- in-place to match the current schema.
534
-
535
- #### Model.get · static method
540
+ Receives a plain record with all fields and should mutate it in-place to match the current schema.
541
+ It runs during lazy loading and during `runMigration()`. Changing this method creates a new schema version.
542
+ If it updates values used by secondary or unique indexes, those index entries are refreshed only by `runMigration()`.
536
543
 
537
- **Signature:** `(...args: any[]) => any`
538
-
539
- **Parameters:**
540
-
541
- - `args: any[]`
542
-
543
- #### Model.getLazy · static method
544
-
545
- **Signature:** `(...args: any[]) => any`
546
-
547
- **Parameters:**
548
-
549
- - `args: any[]`
550
-
551
- #### Model.find · static method
552
-
553
- **Signature:** `(opts?: any) => any`
554
-
555
- **Parameters:**
556
-
557
- - `opts?: any`
558
-
559
- #### [Model.batchProcess](Model_batchProcess.md) · static method
560
-
561
- #### [Model.replaceInto](Model_replaceInto.md) · static method
562
-
563
- Load an existing instance by primary key and update it, or create a new one.
564
-
565
- #### [model.preCommit](Model_preCommit.md) · method
544
+ #### [modelBase.preCommit](Lifecycle Hooks_preCommit.md) · method
566
545
 
567
546
  Optional hook called on each modified instance right before the transaction commits.
568
547
  Runs before data is written to disk, so changes made here are included in the commit.
569
548
 
570
- #### model.getPrimaryKey · method
549
+ #### modelBase.getPrimaryKey · method
571
550
 
572
551
  **Signature:** `() => Uint8Array<ArrayBufferLike>`
573
552
 
574
553
  **Returns:** The primary key for this instance.
575
554
 
576
- #### [model.getPrimaryKeyHash](Model_getPrimaryKeyHash.md) · method
555
+ #### [modelBase.getPrimaryKeyHash](Lifecycle Hooks_getPrimaryKeyHash.md) · method
577
556
 
578
- #### model.isLazyField · method
557
+ #### modelBase.isLazyField · method
579
558
 
580
559
  **Signature:** `(field: keyof this) => boolean`
581
560
 
@@ -583,31 +562,31 @@ Runs before data is written to disk, so changes made here are included in the co
583
562
 
584
563
  - `field: keyof this`
585
564
 
586
- #### [model.preventPersist](Model_preventPersist.md) · method
565
+ #### [modelBase.preventPersist](Lifecycle Hooks_preventPersist.md) · method
587
566
 
588
567
  Prevent this instance from being persisted to the database.
589
568
 
590
- #### [model.delete](Model_delete.md) · method
569
+ #### [modelBase.delete](Lifecycle Hooks_delete.md) · method
591
570
 
592
571
  Delete this model instance from the database.
593
572
 
594
- #### [model.validate](Model_validate.md) · method
573
+ #### [modelBase.validate](Lifecycle Hooks_validate.md) · method
595
574
 
596
575
  Validate all fields in this model instance.
597
576
 
598
- #### [model.isValid](Model_isValid.md) · method
577
+ #### [modelBase.isValid](Lifecycle Hooks_isValid.md) · method
599
578
 
600
579
  Check if this model instance is valid.
601
580
 
602
- #### model.getState · method
581
+ #### modelBase.getState · method
603
582
 
604
583
  **Signature:** `() => "created" | "deleted" | "loaded" | "lazy"`
605
584
 
606
- #### model.toString · method
585
+ #### modelBase.toString · method
607
586
 
608
587
  **Signature:** `() => string`
609
588
 
610
- #### model.[Symbol.for('nodejs.util.inspect.custom')] · method
589
+ #### modelBase.[Symbol.for('nodejs.util.inspect.custom')] · method
611
590
 
612
591
  **Signature:** `() => string`
613
592
 
@@ -615,16 +594,13 @@ Check if this model instance is valid.
615
594
 
616
595
  Register a model class with the Edinburgh ORM system.
617
596
 
618
- ### [field](field.md) · function
619
-
620
- Create a field definition for a model property.
597
+ ### [deleteEverything](deleteEverything.md) · function
621
598
 
622
- ### currentTxn · function
599
+ Delete every key/value entry in the database and reinitialize all registered models.
623
600
 
624
- Returns the current transaction from AsyncLocalStorage.
625
- Throws if called outside a transact() callback.
601
+ ### [field](field.md) · function
626
602
 
627
- **Signature:** `() => Transaction`
603
+ Create a field definition for a model property.
628
604
 
629
605
  ### string · constant
630
606
 
@@ -700,68 +676,126 @@ Create a literal type wrapper for a constant value.
700
676
 
701
677
  Create a link type wrapper for model relationships.
702
678
 
703
- ### [dump](dump.md) · function
679
+ ### dump · function
704
680
 
705
- Dump database contents for debugging.
681
+ **Signature:** `() => void`
706
682
 
707
- ### [BaseIndex](BaseIndex.md) · abstract class
683
+ ### [FindOptions](FindOptions.md) · type
708
684
 
709
- Base class for database indexes for efficient lookups on model fields.
685
+ Range-query options accepted by `find()`, `findBy()`, `batchProcess()`, and `batchProcessBy()`.
710
686
 
711
- #### [baseIndex.find](BaseIndex_find.md) · method
687
+ ### IndexRangeIterator · class
712
688
 
713
- #### [baseIndex.find](BaseIndex_find_2.md) · method
689
+ Iterator for range queries on indexes.
690
+ Handles common iteration logic for both primary and unique indexes.
691
+ Extends built-in Iterator to provide map/filter/reduce/toArray/etc.
714
692
 
715
- #### [baseIndex.find](BaseIndex_find_3.md) · method
693
+ **Type Parameters:**
716
694
 
717
- #### [baseIndex.find](BaseIndex_find_4.md) · method
695
+ - `ITEM`
718
696
 
719
- #### [baseIndex.batchProcess](BaseIndex_batchProcess.md) · method
697
+ #### indexRangeIterator.[Symbol.iterator] · method
720
698
 
721
- [object Object],[object Object],[object Object]
699
+ **Signature:** `() => this`
722
700
 
723
- #### baseIndex.toString · method
701
+ #### indexRangeIterator.next · method
724
702
 
725
- **Signature:** `() => string`
703
+ **Signature:** `() => IteratorResult<ITEM, any>`
726
704
 
727
- ### [NonPrimaryIndex](NonPrimaryIndex.md) · abstract class
705
+ #### indexRangeIterator.count · method
728
706
 
729
- Abstract base for all non-primary indexes (unique and secondary).
730
- Provides shared key serialization, write/delete/update logic.
707
+ **Signature:** `() => number`
731
708
 
732
- ### [UniqueIndex](UniqueIndex.md) · class
709
+ #### indexRangeIterator.fetch · method
733
710
 
734
- Unique index that stores references to the primary key.
711
+ **Signature:** `() => ITEM`
735
712
 
736
- #### uniqueIndex.get · method
713
+ ### Change · type
737
714
 
738
- **Signature:** `(...args: ARGS) => InstanceType<M>`
715
+ **Type:** `Record<any, any> | "created" | "deleted"`
739
716
 
740
- **Parameters:**
717
+ ### FieldConfig · interface
741
718
 
742
- - `args: ARGS`
719
+ Configuration interface for model fields.
743
720
 
744
- ### [SecondaryIndex](SecondaryIndex.md) · class
721
+ **Type Parameters:**
745
722
 
746
- Secondary index for non-unique lookups.
723
+ - `T` - The field type.
747
724
 
748
- ### Change · type
725
+ #### fieldConfig.type · member
749
726
 
750
- **Type:** `Record<any, any> | "created" | "deleted"`
727
+ The type wrapper that defines how this field is serialized/validated.
751
728
 
752
- ### Transaction · interface
729
+ **Type:** `TypeWrapper<T>`
753
730
 
754
- #### transaction.id · member
731
+ #### fieldConfig.description · member
755
732
 
756
- **Type:** `number`
733
+ Optional human-readable description of the field.
757
734
 
758
- #### transaction.instances · member
735
+ **Type:** `string`
736
+
737
+ #### fieldConfig.default · member
738
+
739
+ Optional default value or function that generates default values.
740
+
741
+ **Type:** `T | ((model: Record<string, any>) => T)`
742
+
743
+ ### TypeWrapper · abstract class
744
+
745
+ **Type Parameters:**
746
+
747
+ - `T` - The TypeScript type this wrapper represents.
748
+
749
+ #### typeWrapper.kind · abstract property
750
+
751
+ A string identifier for this type, used during serialization
752
+
753
+ **Type:** `string`
754
+
755
+ #### [typeWrapper.serialize](TypeWrapper_serialize.md) · abstract method
756
+
757
+ Serialize a value from an object property to a Pack.
758
+
759
+ #### [typeWrapper.deserialize](TypeWrapper_deserialize.md) · abstract method
760
+
761
+ Deserialize a value from a Pack into an object property.
762
+
763
+ #### [typeWrapper.getError](TypeWrapper_getError.md) · abstract method
764
+
765
+ Validate a value.
766
+
767
+ #### [typeWrapper.serializeType](TypeWrapper_serializeType.md) · method
768
+
769
+ Serialize type metadata to a Pack (for schema serialization).
770
+
771
+ #### [typeWrapper.containsNull](TypeWrapper_containsNull.md) · method
772
+
773
+ Check if indexing should be skipped for this field value.
759
774
 
760
- **Type:** `Set<Model<unknown>>`
775
+ #### typeWrapper.toString · method
761
776
 
762
- #### transaction.instancesByPk · member
777
+ **Signature:** `() => string`
778
+
779
+ #### typeWrapper.clone · method
780
+
781
+ **Signature:** `(value: T) => T`
782
+
783
+ **Parameters:**
784
+
785
+ - `value: T`
786
+
787
+ #### typeWrapper.equals · method
788
+
789
+ **Signature:** `(value1: T, value2: T) => boolean`
790
+
791
+ **Parameters:**
792
+
793
+ - `value1: T`
794
+ - `value2: T`
795
+
796
+ #### typeWrapper.getLinkedModel · method
763
797
 
764
- **Type:** `Map<number, Model<unknown>>`
798
+ **Signature:** `() => AnyModelClass`
765
799
 
766
800
  ### [DatabaseError](DatabaseError.md) · constant
767
801
 
@@ -843,3 +877,17 @@ Number of orphaned index entries deleted.
843
877
 
844
878
  **Type:** `number`
845
879
 
880
+ ### Transaction · interface
881
+
882
+ #### transaction.id · member
883
+
884
+ **Type:** `number`
885
+
886
+ #### transaction.instances · member
887
+
888
+ **Type:** `Map<number, ModelBase>`
889
+
890
+ ### txnStorage · constant
891
+
892
+ **Value:** `AsyncLocalStorage<Transaction>`
893
+
@@ -0,0 +1,19 @@
1
+ ### Schema Evolution
2
+
3
+ Edinburgh tracks the schema version of each model automatically. When you add, remove, or
4
+ change the types of fields, or add/remove indexes, Edinburgh detects the new schema version.
5
+
6
+ **Lazy migration:** Changes to non-key field values are migrated lazily, when a row with an
7
+ old schema version is read from disk, it is deserialized using the old schema and optionally
8
+ transformed by the static `migrate()` function. This happens transparently on every read
9
+ and requires no downtime or batch processing.
10
+
11
+ **Batch migration (via `npx migrate-edinburgh` or `runMigration()`):** Certain schema changes
12
+ require an explicit migration run:
13
+ - Adding or removing secondary/unique indexes
14
+ - Changing the fields or types of an existing index
15
+ - A `migrate()` function that changes values used in secondary index fields
16
+
17
+ The batch migration tool populates new indexes, deletes orphaned ones, and updates index
18
+ entries whose values were changed by `migrate()`. It does *not* rewrite primary data rows
19
+ (lazy migration handles that).
@@ -0,0 +1,11 @@
1
+ #### typeWrapper.containsNull · method
2
+
3
+ Check if indexing should be skipped for this field value.
4
+
5
+ **Signature:** `(value: T) => boolean`
6
+
7
+ **Parameters:**
8
+
9
+ - `value: T`
10
+
11
+ **Returns:** true if indexing should be skipped.
@@ -0,0 +1,9 @@
1
+ #### typeWrapper.deserialize · abstract method
2
+
3
+ Deserialize a value from a Pack into an object property.
4
+
5
+ **Signature:** `(pack: DataPack) => T`
6
+
7
+ **Parameters:**
8
+
9
+ - `pack: DataPack` - The Pack instance to read from.
@@ -0,0 +1,11 @@
1
+ #### typeWrapper.getError · abstract method
2
+
3
+ Validate a value.
4
+
5
+ **Signature:** `(value: T) => void | DatabaseError`
6
+
7
+ **Parameters:**
8
+
9
+ - `value: T` - The value to validate.
10
+
11
+ **Returns:** - A DatabaseError if validation fails.
@@ -0,0 +1,10 @@
1
+ #### typeWrapper.serialize · abstract method
2
+
3
+ Serialize a value from an object property to a Pack.
4
+
5
+ **Signature:** `(value: T, pack: DataPack) => void`
6
+
7
+ **Parameters:**
8
+
9
+ - `value: T` - The value to serialize.
10
+ - `pack: DataPack` - The Pack instance to write to.
@@ -0,0 +1,9 @@
1
+ #### typeWrapper.serializeType · method
2
+
3
+ Serialize type metadata to a Pack (for schema serialization).
4
+
5
+ **Signature:** `(pack: DataPack) => void`
6
+
7
+ **Parameters:**
8
+
9
+ - `pack: DataPack` - The Pack instance to write to.