edinburgh 0.4.2 → 0.4.5

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 (73) hide show
  1. package/README.md +251 -170
  2. package/build/src/datapack.d.ts +17 -1
  3. package/build/src/datapack.js +44 -5
  4. package/build/src/datapack.js.map +1 -1
  5. package/build/src/edinburgh.d.ts +1 -1
  6. package/build/src/edinburgh.js +1 -1
  7. package/build/src/edinburgh.js.map +1 -1
  8. package/build/src/indexes.d.ts +34 -17
  9. package/build/src/indexes.js +126 -55
  10. package/build/src/indexes.js.map +1 -1
  11. package/build/src/migrate-cli.d.ts +1 -16
  12. package/build/src/migrate-cli.js +56 -42
  13. package/build/src/migrate-cli.js.map +1 -1
  14. package/build/src/migrate.d.ts +15 -11
  15. package/build/src/migrate.js +62 -32
  16. package/build/src/migrate.js.map +1 -1
  17. package/build/src/models.d.ts +1 -1
  18. package/build/src/models.js +20 -7
  19. package/build/src/models.js.map +1 -1
  20. package/build/src/types.d.ts +13 -1
  21. package/build/src/types.js +89 -1
  22. package/build/src/types.js.map +1 -1
  23. package/build/src/utils.d.ts +2 -0
  24. package/build/src/utils.js +12 -0
  25. package/build/src/utils.js.map +1 -1
  26. package/package.json +7 -4
  27. package/skill/BaseIndex.md +16 -0
  28. package/skill/BaseIndex_batchProcess.md +10 -0
  29. package/skill/BaseIndex_find.md +7 -0
  30. package/skill/DatabaseError.md +9 -0
  31. package/skill/Model.md +22 -0
  32. package/skill/Model_delete.md +14 -0
  33. package/skill/Model_findAll.md +12 -0
  34. package/skill/Model_getPrimaryKeyHash.md +5 -0
  35. package/skill/Model_isValid.md +14 -0
  36. package/skill/Model_migrate.md +34 -0
  37. package/skill/Model_preCommit.md +28 -0
  38. package/skill/Model_preventPersist.md +15 -0
  39. package/skill/Model_replaceInto.md +16 -0
  40. package/skill/Model_validate.md +21 -0
  41. package/skill/PrimaryIndex.md +8 -0
  42. package/skill/PrimaryIndex_get.md +17 -0
  43. package/skill/PrimaryIndex_getLazy.md +13 -0
  44. package/skill/SKILL.md +158 -664
  45. package/skill/SecondaryIndex.md +9 -0
  46. package/skill/UniqueIndex.md +9 -0
  47. package/skill/UniqueIndex_get.md +17 -0
  48. package/skill/array.md +23 -0
  49. package/skill/dump.md +8 -0
  50. package/skill/field.md +29 -0
  51. package/skill/index.md +32 -0
  52. package/skill/init.md +17 -0
  53. package/skill/link.md +27 -0
  54. package/skill/literal.md +22 -0
  55. package/skill/opt.md +22 -0
  56. package/skill/or.md +22 -0
  57. package/skill/primary.md +26 -0
  58. package/skill/record.md +21 -0
  59. package/skill/registerModel.md +26 -0
  60. package/skill/runMigration.md +10 -0
  61. package/skill/set.md +23 -0
  62. package/skill/setMaxRetryCount.md +10 -0
  63. package/skill/setOnSaveCallback.md +12 -0
  64. package/skill/transact.md +49 -0
  65. package/skill/unique.md +32 -0
  66. package/src/datapack.ts +49 -7
  67. package/src/edinburgh.ts +2 -0
  68. package/src/indexes.ts +143 -71
  69. package/src/migrate-cli.ts +44 -46
  70. package/src/migrate.ts +71 -39
  71. package/src/models.ts +19 -7
  72. package/src/types.ts +97 -1
  73. package/src/utils.ts +12 -0
package/README.md CHANGED
@@ -117,6 +117,8 @@ Instance fields are declared with `E.field(type, options?)`. Available types:
117
117
  | `E.or(A, B, ...)` | `A \| B \| ...` | Union type; args can be types or literal values |
118
118
  | `E.literal(v)` | literal type | Constant value; defaults to that value |
119
119
  | `E.array(T)` | `T[]` | Optional `{min, max}` constraints |
120
+ | `E.set(T)` | `Set<T>` | Optional `{min, max}` constraints |
121
+ | `E.record(T)` | `Record<string \| number, T>` | Key-value object with string/number keys |
120
122
  | `E.link(Model)` | `Model` | Foreign key, lazy-loaded on access |
121
123
 
122
124
  #### Defaults
@@ -250,6 +252,67 @@ await E.transact(() => {
250
252
  });
251
253
  ```
252
254
 
255
+ #### Non-Persistent Properties
256
+
257
+ 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.
258
+
259
+ ```typescript
260
+ @E.registerModel
261
+ class User extends E.Model<User> {
262
+ static pk = E.primary(User, "id");
263
+ id = E.field(E.identifier);
264
+ firstName = E.field(E.string);
265
+ lastName = E.field(E.string);
266
+
267
+ // Non-persisted property
268
+ cachedFullName?: string;
269
+
270
+ get fullName(): string {
271
+ this.cachedFullName ??= `${this.firstName} ${this.lastName}`;
272
+ return this.cachedFullName;
273
+ }
274
+
275
+ greet(): string {
276
+ return `Hello, ${this.fullName}!`;
277
+ }
278
+ }
279
+ ```
280
+
281
+ #### Computed Indexes
282
+
283
+ Instead of naming fields, you can pass a **function** to `E.unique()` or `E.index()`. 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).
284
+
285
+ ```typescript
286
+ @E.registerModel
287
+ class Article extends E.Model<Article> {
288
+ static pk = E.primary(Article, "id");
289
+ static byFullName = E.unique(Article, (a: Article) => [`${a.firstName} ${a.lastName}`]);
290
+ static byWord = E.index(Article, (a: Article) => a.title.toLowerCase().split(" "));
291
+ static byDomain = E.index(Article, (a: Article) => a.email ? [a.email.split("@")[1]] : []);
292
+
293
+ id = E.field(E.identifier);
294
+ firstName = E.field(E.string);
295
+ lastName = E.field(E.string);
296
+ title = E.field(E.string);
297
+ email = E.field(E.opt(E.string));
298
+ }
299
+
300
+ await E.transact(() => {
301
+ new Article({ firstName: "Jane", lastName: "Doe", title: "Hello World", email: "jane@acme.com" });
302
+
303
+ // Lookup via computed unique index
304
+ const jane = Article.byFullName.get("Jane Doe");
305
+
306
+ // Multi-value: each word in the title is indexed separately
307
+ for (const a of Article.byWord.find({is: "hello"})) { ... }
308
+
309
+ // Partial index: articles without email are skipped
310
+ for (const a of Article.byDomain.find({is: "acme.com"})) { ... }
311
+ });
312
+ ```
313
+
314
+ Computed indexes also support `find()` range queries and `batchProcess()`, just like field-based indexes.
315
+
253
316
  ### Relationships (Links)
254
317
 
255
318
  Use `E.link(Model)` for foreign keys:
@@ -325,13 +388,9 @@ await Product.byCategory.batchProcess({is: "old"}, (product) => {
325
388
  // Commits every ~1 second or 4096 rows (configurable via limitSeconds, limitRows)
326
389
  ```
327
390
 
328
- ### Schema Evolution
329
-
330
- Edinburgh handles schema changes automatically:
391
+ ### Lazy Schema Migrations
331
392
 
332
- - **Adding/removing fields**: Old rows are lazily migrated on read. New fields use their default value.
333
- - **Changing field types**: Requires a `static migrate()` function.
334
- - **Adding/removing indexes**: Requires running `npx migrate-edinburgh`.
393
+ When you change a model's schema, Edinburgh will lazily try to migrate old records on access. This allows you to deploy code changes without downtime or a separate migration step. Optionally, you may provide a `static migrate(record: Record<string, any>)` function on the model to transform old records during lazy migration. If there is a migration error (like a new field without a default value, or an incompatible type change), a run-time error is thrown when loading the affected model instance.
335
394
 
336
395
  ```typescript
337
396
  @E.registerModel
@@ -339,15 +398,40 @@ class User extends E.Model<User> {
339
398
  static pk = E.primary(User, "id");
340
399
  id = E.field(E.identifier);
341
400
  name = E.field(E.string);
342
- role = E.field(E.string, {default: "user"}); // new field
401
+ role = E.field(E.string); // newly added field
343
402
 
344
403
  static migrate(record: Record<string, any>) {
345
- record.role ??= "user"; // provide value for old rows
404
+ record.role ??= record.name.indexOf("admin") >= 0 ? "admin" : "user"; // set role based on name for old records
346
405
  }
347
406
  }
348
407
  ```
349
408
 
350
- Run `npx migrate-edinburgh` (or call `E.runMigration()`) after adding/removing indexes, changing index field types, or when a `migrate()` function affects indexed fields.
409
+ 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...
410
+ - Is idempotent (meaning it can be safely run multiple times on the same row without changing the result after the first run), and
411
+ - Should perform *all* transformation steps starting from the oldest version that could possibly still be in the database. (See the next section.)
412
+
413
+ While lazy migration is convenient and often sufficient, in some cases you need migrations to happen immediately...
414
+
415
+ ### Forced Schema Migrations
416
+
417
+ The `migrate-edinburgh` CLI tool will scan the entire database, pro-actively performing the following migrations:
418
+ - **Populate secondary indexes**: If you added or changed secondary indexes, it will build them. Until you do, the indexes will be empty (or only contain instances that have been saved since the index was created).
419
+ - **Migrate primary indexes**: In case you changed the primary key fields or field types (not recommended!) of a model, it will build the new primary index, as well as all secondary indexes (to point at the new primary keys). Until you do, all of your old data will appear to be missing! Note that this may fail on duplicates.
420
+ - **Remove orphaned indexes**: If you removed or changed an index, the stale data will be deleted from the database.
421
+ - **Rewrite primary data**: These are the types of migrations that would normally be done lazily on instance access. As there's usually not much benefit to doing this forcibly, and it can be very time-consuming (and generates a lot of I/O), this is *not* done by default. It may however be useful if you want to clean up the contents of your `migrate()` function, if you have control over all application deployments. Use the `--rewrite-data` flag to enable this.
422
+
423
+ ```bash
424
+ npx migrate-edinburgh ./src/models.ts
425
+ ```
426
+
427
+ Run `npx migrate-edinburgh` without arguments to see all options. You can also call `runMigration()` programmatically:
428
+
429
+ ```typescript
430
+ import { runMigration } from "edinburgh";
431
+
432
+ const result = await runMigration({ tables: ["User"] });
433
+ console.log(result.secondaries); // { User: 1500 }
434
+ ```
351
435
 
352
436
  ### preCommit Hook
353
437
 
@@ -409,7 +493,7 @@ The following is auto-generated from `src/edinburgh.ts`:
409
493
 
410
494
  **Signature:** `() => void`
411
495
 
412
- ### init · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L65)
496
+ ### init · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L67)
413
497
 
414
498
  Initialize the database with the specified directory path.
415
499
  This function may be called multiple times with the same parameters. If it is not called before the first transact(),
@@ -427,7 +511,7 @@ the database will be automatically initialized with the default directory.
427
511
  init("./my-database");
428
512
  ```
429
513
 
430
- ### transact · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L116)
514
+ ### transact · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L118)
431
515
 
432
516
  Executes a function within a database transaction context.
433
517
 
@@ -477,7 +561,7 @@ await E.transact(() => {
477
561
  });
478
562
  ```
479
563
 
480
- ### setMaxRetryCount · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L206)
564
+ ### setMaxRetryCount · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L208)
481
565
 
482
566
  Set the maximum number of retries for a transaction in case of conflicts.
483
567
  The default value is 6. Setting it to 0 will disable retries and cause transactions to fail immediately on conflict.
@@ -488,7 +572,7 @@ The default value is 6. Setting it to 0 will disable retries and cause transacti
488
572
 
489
573
  - `count: number` - The maximum number of retries for a transaction.
490
574
 
491
- ### setOnSaveCallback · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L220)
575
+ ### setOnSaveCallback · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L222)
492
576
 
493
577
  Set a callback function to be called after a model is saved and committed.
494
578
 
@@ -501,11 +585,11 @@ Set a callback function to be called after a model is saved and committed.
501
585
  - A sequential number. Higher numbers have been committed after lower numbers.
502
586
  - A map of model instances to their changes. The change can be "created", "deleted", or an object containing the old values.
503
587
 
504
- ### deleteEverything · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L225)
588
+ ### deleteEverything · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L227)
505
589
 
506
590
  **Signature:** `() => Promise<void>`
507
591
 
508
- ### Model · [abstract class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L218)
592
+ ### Model · [abstract class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L220)
509
593
 
510
594
  [object Object],[object Object],[object Object],[object Object],[object Object]
511
595
 
@@ -528,25 +612,25 @@ class User extends E.Model<User> {
528
612
  }
529
613
  ```
530
614
 
531
- #### Model.tableName · [static property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L225)
615
+ #### Model.tableName · [static property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L227)
532
616
 
533
617
  The database table name (defaults to class name).
534
618
 
535
619
  **Type:** `string`
536
620
 
537
- #### Model.override · [static property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L229)
621
+ #### Model.override · [static property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L231)
538
622
 
539
623
  When true, registerModel replaces an existing model with the same tableName.
540
624
 
541
625
  **Type:** `boolean`
542
626
 
543
- #### Model.fields · [static property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L230)
627
+ #### Model.fields · [static property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L232)
544
628
 
545
629
  Field configuration metadata.
546
630
 
547
631
  **Type:** `Record<string | number | symbol, FieldConfig<unknown>>`
548
632
 
549
- #### Model.migrate · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
633
+ #### Model.migrate · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
550
634
 
551
635
  Optional migration function called when deserializing rows written with an older schema version.
552
636
  Receives a plain record with all fields (primary key fields + value fields) and should mutate it
@@ -581,7 +665,7 @@ class User extends E.Model<User> {
581
665
  }
582
666
  ```
583
667
 
584
- #### Model.findAll · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
668
+ #### Model.findAll · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
585
669
 
586
670
  Find all instances of this model in the database, ordered by primary key.
587
671
 
@@ -594,7 +678,7 @@ Find all instances of this model in the database, ordered by primary key.
594
678
 
595
679
  **Returns:** An iterator.
596
680
 
597
- #### Model.replaceInto · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
681
+ #### Model.replaceInto · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
598
682
 
599
683
  Load an existing instance by primary key and update it, or create a new one.
600
684
 
@@ -611,7 +695,7 @@ new instance is created with `obj` as its initial properties.
611
695
 
612
696
  **Returns:** The loaded-and-updated or newly created instance.
613
697
 
614
- #### model.preCommit · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
698
+ #### model.preCommit · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
615
699
 
616
700
  Optional hook called on each modified instance right before the transaction commits.
617
701
  Runs before data is written to disk, so changes made here are included in the commit.
@@ -624,9 +708,6 @@ Common use cases:
624
708
 
625
709
  **Signature:** `() => void`
626
710
 
627
- **Parameters:**
628
-
629
-
630
711
  **Examples:**
631
712
 
632
713
  ```typescript
@@ -643,25 +724,19 @@ class Post extends E.Model<Post> {
643
724
  }
644
725
  ```
645
726
 
646
- #### model.getPrimaryKey · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
727
+ #### model.getPrimaryKey · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
647
728
 
648
729
  **Signature:** `() => Uint8Array<ArrayBufferLike>`
649
730
 
650
- **Parameters:**
651
-
652
-
653
731
  **Returns:** The primary key for this instance.
654
732
 
655
- #### model.getPrimaryKeyHash · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
733
+ #### model.getPrimaryKeyHash · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
656
734
 
657
735
  **Signature:** `() => number`
658
736
 
659
- **Parameters:**
660
-
661
-
662
737
  **Returns:** A 53-bit positive integer non-cryptographic hash of the primary key, or undefined if not yet saved.
663
738
 
664
- #### model.isLazyField · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
739
+ #### model.isLazyField · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
665
740
 
666
741
  **Signature:** `(field: keyof this) => boolean`
667
742
 
@@ -669,15 +744,12 @@ class Post extends E.Model<Post> {
669
744
 
670
745
  - `field: keyof this`
671
746
 
672
- #### model.preventPersist · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
747
+ #### model.preventPersist · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
673
748
 
674
749
  Prevent this instance from being persisted to the database.
675
750
 
676
751
  **Signature:** `() => this`
677
752
 
678
- **Parameters:**
679
-
680
-
681
753
  **Returns:** This model instance for chaining.
682
754
 
683
755
  **Examples:**
@@ -688,7 +760,7 @@ user.name = "New Name";
688
760
  user.preventPersist(); // Changes won't be saved
689
761
  ```
690
762
 
691
- #### model.delete · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
763
+ #### model.delete · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
692
764
 
693
765
  Delete this model instance from the database.
694
766
 
@@ -696,9 +768,6 @@ Removes the instance and all its index entries from the database and prevents fu
696
768
 
697
769
  **Signature:** `() => void`
698
770
 
699
- **Parameters:**
700
-
701
-
702
771
  **Examples:**
703
772
 
704
773
  ```typescript
@@ -706,7 +775,7 @@ const user = User.load("user123");
706
775
  user.delete(); // Removes from database
707
776
  ```
708
777
 
709
- #### model.validate · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
778
+ #### model.validate · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
710
779
 
711
780
  Validate all fields in this model instance.
712
781
 
@@ -728,15 +797,12 @@ if (errors.length > 0) {
728
797
  }
729
798
  ```
730
799
 
731
- #### model.isValid · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
800
+ #### model.isValid · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
732
801
 
733
802
  Check if this model instance is valid.
734
803
 
735
804
  **Signature:** `() => boolean`
736
805
 
737
- **Parameters:**
738
-
739
-
740
806
  **Returns:** true if all validations pass.
741
807
 
742
808
  **Examples:**
@@ -746,28 +812,19 @@ const user = new User({name: "John"});
746
812
  if (!user.isValid()) shoutAtTheUser();
747
813
  ```
748
814
 
749
- #### model.getState · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
815
+ #### model.getState · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
750
816
 
751
817
  **Signature:** `() => "created" | "deleted" | "loaded" | "lazy"`
752
818
 
753
- **Parameters:**
754
-
755
-
756
- #### model.toString · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
819
+ #### model.toString · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
757
820
 
758
821
  **Signature:** `() => string`
759
822
 
760
- **Parameters:**
761
-
762
-
763
- #### model.[Symbol.for('nodejs.util.inspect.custom')] · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
823
+ #### model.[Symbol.for('nodejs.util.inspect.custom')] · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
764
824
 
765
825
  **Signature:** `() => string`
766
826
 
767
- **Parameters:**
768
-
769
-
770
- ### registerModel · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L111)
827
+ ### registerModel · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L113)
771
828
 
772
829
  Register a model class with the Edinburgh ORM system.
773
830
 
@@ -794,7 +851,7 @@ class User extends E.Model<User> {
794
851
  }
795
852
  ```
796
853
 
797
- ### field · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L87)
854
+ ### field · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L89)
798
855
 
799
856
  Create a field definition for a model property.
800
857
 
@@ -824,13 +881,13 @@ class User extends E.Model<User> {
824
881
  }
825
882
  ```
826
883
 
827
- ### string · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
884
+ ### string · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
828
885
 
829
886
  Type wrapper instance for the string type.
830
887
 
831
888
  **Value:** `TypeWrapper<string>`
832
889
 
833
- ### orderedString · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
890
+ ### orderedString · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
834
891
 
835
892
  Type wrapper instance for the ordered string type, which is just like a string
836
893
  except that it sorts lexicographically in the database (instead of by incrementing
@@ -840,37 +897,37 @@ may not contain null characters.
840
897
 
841
898
  **Value:** `TypeWrapper<string>`
842
899
 
843
- ### number · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
900
+ ### number · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
844
901
 
845
902
  Type wrapper instance for the number type.
846
903
 
847
904
  **Value:** `TypeWrapper<number>`
848
905
 
849
- ### dateTime · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
906
+ ### dateTime · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
850
907
 
851
- Type wrapper instance for the date/time type.
908
+ Type wrapper instance for the date/time type. Stored without timezone info, rounded to whole seconds.
852
909
 
853
910
  **Value:** `TypeWrapper<Date>`
854
911
 
855
- ### boolean · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
912
+ ### boolean · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
856
913
 
857
914
  Type wrapper instance for the boolean type.
858
915
 
859
916
  **Value:** `TypeWrapper<boolean>`
860
917
 
861
- ### identifier · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
918
+ ### identifier · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
862
919
 
863
920
  Type wrapper instance for the identifier type.
864
921
 
865
922
  **Value:** `TypeWrapper<string>`
866
923
 
867
- ### undef · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
924
+ ### undef · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
868
925
 
869
926
  Type wrapper instance for the 'undefined' type.
870
927
 
871
928
  **Value:** `TypeWrapper<undefined>`
872
929
 
873
- ### opt · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
930
+ ### opt · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
874
931
 
875
932
  Create an optional type wrapper (allows undefined).
876
933
 
@@ -893,7 +950,7 @@ const optionalString = E.opt(E.string);
893
950
  const optionalNumber = E.opt(E.number);
894
951
  ```
895
952
 
896
- ### or · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
953
+ ### or · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
897
954
 
898
955
  Create a union type wrapper from multiple type choices.
899
956
 
@@ -916,7 +973,7 @@ const stringOrNumber = E.or(E.string, E.number);
916
973
  const status = E.or("active", "inactive", "pending");
917
974
  ```
918
975
 
919
- ### array · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
976
+ ### array · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
920
977
 
921
978
  Create an array type wrapper with optional length constraints.
922
979
 
@@ -940,7 +997,53 @@ const stringArray = E.array(E.string);
940
997
  const boundedArray = E.array(E.number, {min: 1, max: 10});
941
998
  ```
942
999
 
943
- ### literal · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
1000
+ ### set · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
1001
+
1002
+ Create a Set type wrapper with optional length constraints.
1003
+
1004
+ **Signature:** `<const T>(inner: TypeWrapper<T>, opts?: { min?: number; max?: number; }) => TypeWrapper<Set<T>>`
1005
+
1006
+ **Type Parameters:**
1007
+
1008
+ - `T` - The element type.
1009
+
1010
+ **Parameters:**
1011
+
1012
+ - `inner: TypeWrapper<T>` - - Type wrapper for set elements.
1013
+ - `opts: {min?: number, max?: number}` (optional) - - Optional constraints (min/max length).
1014
+
1015
+ **Returns:** A set type instance.
1016
+
1017
+ **Examples:**
1018
+
1019
+ ```typescript
1020
+ const stringSet = E.set(E.string);
1021
+ const boundedSet = E.set(E.number, {min: 1, max: 10});
1022
+ ```
1023
+
1024
+ ### record · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
1025
+
1026
+ Create a Record type wrapper for key-value objects with string or number keys.
1027
+
1028
+ **Signature:** `<const T>(inner: TypeWrapper<T>) => TypeWrapper<Record<string | number, T>>`
1029
+
1030
+ **Type Parameters:**
1031
+
1032
+ - `T` - The value type.
1033
+
1034
+ **Parameters:**
1035
+
1036
+ - `inner: TypeWrapper<T>` - - Type wrapper for record values.
1037
+
1038
+ **Returns:** A record type instance.
1039
+
1040
+ **Examples:**
1041
+
1042
+ ```typescript
1043
+ const scores = E.record(E.number); // Record<string | number, number>
1044
+ ```
1045
+
1046
+ ### literal · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
944
1047
 
945
1048
  Create a literal type wrapper for a constant value.
946
1049
 
@@ -963,7 +1066,7 @@ const statusType = E.literal("active");
963
1066
  const countType = E.literal(42);
964
1067
  ```
965
1068
 
966
- ### link · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
1069
+ ### link · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
967
1070
 
968
1071
  Create a link type wrapper for model relationships.
969
1072
 
@@ -991,21 +1094,26 @@ class Post extends E.Model<Post> {
991
1094
  }
992
1095
  ```
993
1096
 
994
- ### index · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
1097
+ ### index · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
995
1098
 
996
- Create a secondary index on model fields.
1099
+ Create a secondary index on model fields, or a computed secondary index using a function.
997
1100
 
998
- **Signature:** `{ <M extends typeof Model, const F extends (keyof InstanceType<M> & string)>(MyModel: M, field: F): SecondaryIndex<M, [F]>; <M extends typeof Model, const FS extends readonly (keyof InstanceType<M> & string)[]>(MyModel: M, fields: FS): SecondaryIndex<...>; }`
1101
+ For field-based indexes, pass a field name or array of field names.
1102
+ For computed indexes, pass a function that takes a model instance and returns an array of
1103
+ index keys. Return `[]` to skip indexing for that instance. Each array element creates a
1104
+ separate index entry, enabling multi-value indexes (e.g., indexing by each word in a name).
1105
+
1106
+ **Signature:** `{ <M extends typeof Model, V>(MyModel: M, fn: (instance: InstanceType<M>) => V[]): SecondaryIndex<M, [], [V]>; <M extends typeof Model, const F extends (keyof InstanceType<M> & string)>(MyModel: M, field: F): SecondaryIndex<...>; <M extends typeof Model, const FS extends readonly (keyof InstanceType<M> & string)[]>(...`
999
1107
 
1000
1108
  **Type Parameters:**
1001
1109
 
1002
1110
  - `M extends typeof Model` - The model class.
1003
- - `F extends (keyof InstanceType<M> & string)` - The field name (for single field index).
1111
+ - `V` - The computed index value type (for function-based indexes).
1004
1112
 
1005
1113
  **Parameters:**
1006
1114
 
1007
1115
  - `MyModel: M` - - The model class to create the index for.
1008
- - `field: F` - - Single field name for simple indexes.
1116
+ - `fn: (instance: InstanceType<M>) => V[]`
1009
1117
 
1010
1118
  **Returns:** A new SecondaryIndex instance.
1011
1119
 
@@ -1015,10 +1123,11 @@ Create a secondary index on model fields.
1015
1123
  class User extends E.Model<User> {
1016
1124
  static byAge = E.index(User, "age");
1017
1125
  static byTagsDate = E.index(User, ["tags", "createdAt"]);
1126
+ static byWord = E.index(User, (u: User) => u.name.split(" "));
1018
1127
  }
1019
1128
  ```
1020
1129
 
1021
- ### primary · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
1130
+ ### primary · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
1022
1131
 
1023
1132
  Create a primary index on model fields.
1024
1133
 
@@ -1045,21 +1154,26 @@ class User extends E.Model<User> {
1045
1154
  }
1046
1155
  ```
1047
1156
 
1048
- ### unique · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
1157
+ ### unique · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
1049
1158
 
1050
- Create a unique index on model fields.
1159
+ Create a unique index on model fields, or a computed unique index using a function.
1051
1160
 
1052
- **Signature:** `{ <M extends typeof Model, const F extends (keyof InstanceType<M> & string)>(MyModel: M, field: F): UniqueIndex<M, [F]>; <M extends typeof Model, const FS extends readonly (keyof InstanceType<M> & string)[]>(MyModel: M, fields: FS): UniqueIndex<...>; }`
1161
+ For field-based indexes, pass a field name or array of field names.
1162
+ For computed indexes, pass a function that takes a model instance and returns an array of
1163
+ index keys. Return `[]` to skip indexing for that instance. Each array element creates a
1164
+ separate index entry, enabling multi-value indexes (e.g., indexing by each word in a name).
1165
+
1166
+ **Signature:** `{ <M extends typeof Model, V>(MyModel: M, fn: (instance: InstanceType<M>) => V[]): UniqueIndex<M, [], [V]>; <M extends typeof Model, const F extends (keyof InstanceType<M> & string)>(MyModel: M, field: F): UniqueIndex<...>; <M extends typeof Model, const FS extends readonly (keyof InstanceType<M> & string)[]>(MyMode...`
1053
1167
 
1054
1168
  **Type Parameters:**
1055
1169
 
1056
1170
  - `M extends typeof Model` - The model class.
1057
- - `F extends (keyof InstanceType<M> & string)` - The field name (for single field index).
1171
+ - `V` - The computed index value type (for function-based indexes).
1058
1172
 
1059
1173
  **Parameters:**
1060
1174
 
1061
1175
  - `MyModel: M` - - The model class to create the index for.
1062
- - `field: F` - - Single field name for simple indexes.
1176
+ - `fn: (instance: InstanceType<M>) => V[]`
1063
1177
 
1064
1178
  **Returns:** A new UniqueIndex instance.
1065
1179
 
@@ -1069,10 +1183,11 @@ Create a unique index on model fields.
1069
1183
  class User extends E.Model<User> {
1070
1184
  static byEmail = E.unique(User, "email");
1071
1185
  static byNameAge = E.unique(User, ["name", "age"]);
1186
+ static byFullName = E.unique(User, (u: User) => [`${u.firstName} ${u.lastName}`]);
1072
1187
  }
1073
1188
  ```
1074
1189
 
1075
- ### dump · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
1190
+ ### dump · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
1076
1191
 
1077
1192
  Dump database contents for debugging.
1078
1193
 
@@ -1081,7 +1196,7 @@ This is primarily useful for development and debugging purposes.
1081
1196
 
1082
1197
  **Signature:** `() => void`
1083
1198
 
1084
- ### BaseIndex · [abstract class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L127)
1199
+ ### BaseIndex · [abstract class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L125)
1085
1200
 
1086
1201
  Base class for database indexes for efficient lookups on model fields.
1087
1202
 
@@ -1091,39 +1206,37 @@ Indexes enable fast queries on specific field combinations and enforce uniquenes
1091
1206
 
1092
1207
  - `M extends typeof Model` - The model class this index belongs to.
1093
1208
  - `F extends readonly (keyof InstanceType<M> & string)[]` - The field names that make up this index.
1209
+ - `ARGS extends readonly any[] = IndexArgTypes<M, F>`
1094
1210
 
1095
1211
  **Constructor Parameters:**
1096
1212
 
1097
1213
  - `MyModel`: - The model class this index belongs to.
1098
1214
  - `_fieldNames`: - Array of field names that make up this index.
1099
1215
 
1100
- #### baseIndex.find · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
1216
+ #### baseIndex.find · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
1101
1217
 
1102
- **Signature:** `(opts?: FindOptions<IndexArgTypes<M, F>>) => IndexRangeIterator<M>`
1218
+ **Signature:** `(opts?: FindOptions<ARGS>) => IndexRangeIterator<M>`
1103
1219
 
1104
1220
  **Parameters:**
1105
1221
 
1106
- - `opts: FindOptions<IndexArgTypes<M, F>>` (optional)
1222
+ - `opts: FindOptions<ARGS>` (optional)
1107
1223
 
1108
- #### baseIndex.batchProcess · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
1224
+ #### baseIndex.batchProcess · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
1109
1225
 
1110
1226
  [object Object],[object Object],[object Object]
1111
1227
 
1112
- **Signature:** `(opts: FindOptions<IndexArgTypes<M, F>> & { limitSeconds?: number; limitRows?: number; }, callback: (row: InstanceType<M>) => void | Promise<...>) => Promise<...>`
1228
+ **Signature:** `(opts: FindOptions<ARGS> & { limitSeconds?: number; limitRows?: number; }, callback: (row: InstanceType<M>) => void | Promise<void>) => Promise<...>`
1113
1229
 
1114
1230
  **Parameters:**
1115
1231
 
1116
- - `opts: FindOptions<IndexArgTypes<M, F>> & { limitSeconds?: number; limitRows?: number }` (optional) - - Query options (same as `find()`), plus:
1232
+ - `opts: FindOptions<ARGS> & { limitSeconds?: number; limitRows?: number }` (optional) - - Query options (same as `find()`), plus:
1117
1233
  - `callback: (row: InstanceType<M>) => void | Promise<void>` - - Called for each matching row within a transaction
1118
1234
 
1119
- #### baseIndex.toString · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
1235
+ #### baseIndex.toString · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
1120
1236
 
1121
1237
  **Signature:** `() => string`
1122
1238
 
1123
- **Parameters:**
1124
-
1125
-
1126
- ### UniqueIndex · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
1239
+ ### UniqueIndex · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
1127
1240
 
1128
1241
  Unique index that stores references to the primary key.
1129
1242
 
@@ -1131,16 +1244,17 @@ Unique index that stores references to the primary key.
1131
1244
 
1132
1245
  - `M extends typeof Model` - The model class this index belongs to.
1133
1246
  - `F extends readonly (keyof InstanceType<M> & string)[]` - The field names that make up this index.
1247
+ - `ARGS extends readonly any[] = IndexArgTypes<M, F>`
1134
1248
 
1135
- #### uniqueIndex.get · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
1249
+ #### uniqueIndex.get · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
1136
1250
 
1137
1251
  Get a model instance by unique index key values.
1138
1252
 
1139
- **Signature:** `(...args: IndexArgTypes<M, F>) => InstanceType<M>`
1253
+ **Signature:** `(...args: ARGS) => InstanceType<M>`
1140
1254
 
1141
1255
  **Parameters:**
1142
1256
 
1143
- - `args: IndexArgTypes<M, F>` - - The unique index key values.
1257
+ - `args: ARGS` - - The unique index key values.
1144
1258
 
1145
1259
  **Returns:** The model instance if found, undefined otherwise.
1146
1260
 
@@ -1150,7 +1264,7 @@ Get a model instance by unique index key values.
1150
1264
  const userByEmail = User.byEmail.get("john@example.com");
1151
1265
  ```
1152
1266
 
1153
- ### PrimaryIndex · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
1267
+ ### PrimaryIndex · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
1154
1268
 
1155
1269
  Primary index that stores the actual model data.
1156
1270
 
@@ -1159,7 +1273,7 @@ Primary index that stores the actual model data.
1159
1273
  - `M extends typeof Model` - The model class this index belongs to.
1160
1274
  - `F extends readonly (keyof InstanceType<M> & string)[]` - The field names that make up this index.
1161
1275
 
1162
- #### primaryIndex.get · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
1276
+ #### primaryIndex.get · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
1163
1277
 
1164
1278
  Get a model instance by primary key values.
1165
1279
 
@@ -1177,7 +1291,7 @@ Get a model instance by primary key values.
1177
1291
  const user = User.pk.get("john_doe");
1178
1292
  ```
1179
1293
 
1180
- #### primaryIndex.getLazy · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
1294
+ #### primaryIndex.getLazy · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
1181
1295
 
1182
1296
  Does the same as as `get()`, but will delay loading the instance from disk until the first
1183
1297
  property access. In case it turns out the instance doesn't exist, an error will be thrown
@@ -1191,7 +1305,7 @@ at that time.
1191
1305
 
1192
1306
  **Returns:** The (lazily loaded) model instance.
1193
1307
 
1194
- ### SecondaryIndex · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
1308
+ ### SecondaryIndex · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
1195
1309
 
1196
1310
  Secondary index for non-unique lookups.
1197
1311
 
@@ -1199,8 +1313,9 @@ Secondary index for non-unique lookups.
1199
1313
 
1200
1314
  - `M extends typeof Model` - The model class this index belongs to.
1201
1315
  - `F extends readonly (keyof InstanceType<M> & string)[]` - The field names that make up this index.
1316
+ - `ARGS extends readonly any[] = IndexArgTypes<M, F>`
1202
1317
 
1203
- ### Change · [type](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L94)
1318
+ ### Change · [type](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L95)
1204
1319
 
1205
1320
  **Type:** `Record<any, any> | "created" | "deleted"`
1206
1321
 
@@ -1214,11 +1329,11 @@ Secondary index for non-unique lookups.
1214
1329
 
1215
1330
  **Type:** `Set<Model<unknown>>`
1216
1331
 
1217
- #### transaction.instancesByPk · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L44)
1332
+ #### transaction.instancesByPk · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L46)
1218
1333
 
1219
1334
  **Type:** `Map<number, Model<unknown>>`
1220
1335
 
1221
- ### DatabaseError · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L156)
1336
+ ### DatabaseError · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L157)
1222
1337
 
1223
1338
  The DatabaseError class is used to represent errors that occur during database operations.
1224
1339
  It extends the built-in Error class and has a machine readable error code string property.
@@ -1228,10 +1343,10 @@ Invalid function arguments will throw TypeError.
1228
1343
 
1229
1344
  **Value:** `DatabaseErrorConstructor`
1230
1345
 
1231
- ### runMigration · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L116)
1346
+ ### runMigration · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L124)
1232
1347
 
1233
- Run database migration: upgrade all rows to the latest schema version,
1234
- convert old primary indices, and clean up orphaned secondary indices.
1348
+ Run database migration: populate secondary indexes for old-version rows,
1349
+ convert old primary indices, rewrite row data, and clean up orphaned indices.
1235
1350
 
1236
1351
  **Signature:** `(options?: MigrationOptions) => Promise<MigrationResult>`
1237
1352
 
@@ -1247,99 +1362,65 @@ Limit migration to specific table names.
1247
1362
 
1248
1363
  **Type:** `string[]`
1249
1364
 
1250
- #### migrationOptions.convertOldPrimaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L22)
1365
+ #### migrationOptions.populateSecondaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L23)
1366
+
1367
+ Populate secondary indexes for rows at old schema versions (default: true).
1368
+
1369
+ **Type:** `boolean`
1370
+
1371
+ #### migrationOptions.migratePrimaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L32)
1251
1372
 
1252
- Whether to convert old primary indices for known tables (default: true).
1373
+ Convert old primary indices when primary key fields changed (default: true).
1253
1374
 
1254
1375
  **Type:** `boolean`
1255
1376
 
1256
- #### migrationOptions.deleteOrphanedIndexes · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L30)
1377
+ #### migrationOptions.rewriteData · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L40)
1257
1378
 
1258
- Whether to delete orphaned secondary/unique indices (default: true).
1379
+ Rewrite all row data to the latest schema version (default: false).
1259
1380
 
1260
1381
  **Type:** `boolean`
1261
1382
 
1262
- #### migrationOptions.upgradeVersions · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L38)
1383
+ #### migrationOptions.removeOrphans · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L46)
1263
1384
 
1264
- Whether to upgrade rows to the latest version (default: true).
1385
+ Delete orphaned secondary/unique index entries (default: true).
1265
1386
 
1266
1387
  **Type:** `boolean`
1267
1388
 
1268
- #### migrationOptions.onProgress · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L42)
1389
+ #### migrationOptions.onProgress · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L48)
1269
1390
 
1270
1391
  Progress callback.
1271
1392
 
1272
1393
  **Type:** `(info: ProgressInfo) => void`
1273
1394
 
1274
- ### MigrationResult · [interface](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L47)
1395
+ ### MigrationResult · [interface](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L51)
1275
1396
 
1276
- #### migrationResult.secondaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L49)
1397
+ #### migrationResult.secondaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L54)
1277
1398
 
1278
- Per-table stats for row upgrades.
1399
+ Per-table counts of secondary index entries populated.
1279
1400
 
1280
1401
  **Type:** `Record<string, number>`
1281
1402
 
1282
- #### migrationResult.primaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L50)
1403
+ #### migrationResult.primaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L59)
1283
1404
 
1284
- Per-table stats for old primary conversions.
1405
+ Per-table counts of old primary rows migrated.
1285
1406
 
1286
1407
  **Type:** `Record<string, number>`
1287
1408
 
1288
- #### migrationResult.conversionFailures · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L56)
1409
+ #### migrationResult.conversionFailures · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L59)
1289
1410
 
1290
1411
  Per-table conversion failure counts by reason.
1291
1412
 
1292
1413
  **Type:** `Record<string, Record<string, number>>`
1293
1414
 
1294
- #### migrationResult.orphaned · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L57)
1295
-
1296
- Number of orphaned index entries deleted.
1297
-
1298
- **Type:** `number`
1299
-
1300
- ## Schema Migrations
1301
-
1302
- Edinburgh automatically tracks the schema version of each model. When you change fields, field types, indexes, or the `migrate()` function, Edinburgh detects a new schema version.
1303
-
1304
- ### What happens automatically (lazy migration)
1305
-
1306
- Changes to regular (non-index) field values are migrated lazily. When a row with an old schema version is loaded from disk, it is deserialized using the old field types and transformed by the optional static `migrate()` function. This is transparent and requires no downtime.
1307
-
1308
- ```typescript
1309
- @E.registerModel
1310
- class User extends E.Model<User> {
1311
- static pk = E.primary(User, "id");
1312
- id = E.field(E.identifier);
1313
- name = E.field(E.string);
1314
- role = E.field(E.string); // newly added field
1315
-
1316
- static migrate(record: Record<string, any>) {
1317
- record.role ??= "user"; // provide a default for old rows
1318
- }
1319
- }
1320
- ```
1321
-
1322
- ### What requires `migrate-edinburgh`
1323
-
1324
- The `migrate-edinburgh` CLI tool (or the `runMigration()` API) must be run when:
1325
-
1326
- - **Adding or removing** secondary or unique indexes
1327
- - **Changing the fields or types** of an existing index
1328
- - A **`migrate()` function changes values** that are used in index fields
1415
+ #### migrationResult.rewritten · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L64)
1329
1416
 
1330
- The tool populates new indexes, removes orphaned ones, and updates index entries whose values were changed by `migrate()`. It does *not* rewrite primary data rows - lazy migration handles that on read.
1417
+ Per-table counts of rows rewritten to latest version.
1331
1418
 
1332
- ```bash
1333
- npx migrate-edinburgh --import ./src/models.ts
1334
- ```
1419
+ **Type:** `Record<string, number>`
1335
1420
 
1336
- Run `npx migrate-edinburgh` to see all of its options.
1421
+ #### migrationResult.orphans · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L68)
1337
1422
 
1338
- You can also call `runMigration()` programmatically:
1423
+ Number of orphaned index entries deleted.
1339
1424
 
1340
- ```typescript
1341
- import { runMigration } from "edinburgh";
1425
+ **Type:** `number`
1342
1426
 
1343
- const result = await runMigration({ tables: ["User"] });
1344
- console.log(result.upgraded); // { User: 1500 }
1345
- ```