edinburgh 0.4.4 → 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 (53) hide show
  1. package/README.md +58 -101
  2. package/build/src/migrate-cli.d.ts +1 -16
  3. package/build/src/migrate-cli.js +56 -42
  4. package/build/src/migrate-cli.js.map +1 -1
  5. package/build/src/migrate.d.ts +15 -11
  6. package/build/src/migrate.js +53 -39
  7. package/build/src/migrate.js.map +1 -1
  8. package/build/src/models.d.ts +1 -1
  9. package/build/src/models.js +2 -2
  10. package/build/src/models.js.map +1 -1
  11. package/package.json +6 -4
  12. package/skill/BaseIndex.md +16 -0
  13. package/skill/BaseIndex_batchProcess.md +10 -0
  14. package/skill/BaseIndex_find.md +7 -0
  15. package/skill/DatabaseError.md +9 -0
  16. package/skill/Model.md +22 -0
  17. package/skill/Model_delete.md +14 -0
  18. package/skill/Model_findAll.md +12 -0
  19. package/skill/Model_getPrimaryKeyHash.md +5 -0
  20. package/skill/Model_isValid.md +14 -0
  21. package/skill/Model_migrate.md +34 -0
  22. package/skill/Model_preCommit.md +28 -0
  23. package/skill/Model_preventPersist.md +15 -0
  24. package/skill/Model_replaceInto.md +16 -0
  25. package/skill/Model_validate.md +21 -0
  26. package/skill/PrimaryIndex.md +8 -0
  27. package/skill/PrimaryIndex_get.md +17 -0
  28. package/skill/PrimaryIndex_getLazy.md +13 -0
  29. package/skill/SKILL.md +90 -720
  30. package/skill/SecondaryIndex.md +9 -0
  31. package/skill/UniqueIndex.md +9 -0
  32. package/skill/UniqueIndex_get.md +17 -0
  33. package/skill/array.md +23 -0
  34. package/skill/dump.md +8 -0
  35. package/skill/field.md +29 -0
  36. package/skill/index.md +32 -0
  37. package/skill/init.md +17 -0
  38. package/skill/link.md +27 -0
  39. package/skill/literal.md +22 -0
  40. package/skill/opt.md +22 -0
  41. package/skill/or.md +22 -0
  42. package/skill/primary.md +26 -0
  43. package/skill/record.md +21 -0
  44. package/skill/registerModel.md +26 -0
  45. package/skill/runMigration.md +10 -0
  46. package/skill/set.md +23 -0
  47. package/skill/setMaxRetryCount.md +10 -0
  48. package/skill/setOnSaveCallback.md +12 -0
  49. package/skill/transact.md +49 -0
  50. package/skill/unique.md +32 -0
  51. package/src/migrate-cli.ts +44 -46
  52. package/src/migrate.ts +64 -46
  53. package/src/models.ts +2 -2
package/skill/SKILL.md CHANGED
@@ -392,13 +392,9 @@ await Product.byCategory.batchProcess({is: "old"}, (product) => {
392
392
  // Commits every ~1 second or 4096 rows (configurable via limitSeconds, limitRows)
393
393
  ```
394
394
 
395
- ### Schema Evolution
395
+ ### Lazy Schema Migrations
396
396
 
397
- Edinburgh handles schema changes automatically:
398
-
399
- - **Adding/removing fields**: Old rows are lazily migrated on read. New fields use their default value.
400
- - **Changing field types**: Requires a `static migrate()` function.
401
- - **Adding/removing indexes**: Requires running `npx migrate-edinburgh`.
397
+ 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.
402
398
 
403
399
  ```typescript
404
400
  @E.registerModel
@@ -406,15 +402,40 @@ class User extends E.Model<User> {
406
402
  static pk = E.primary(User, "id");
407
403
  id = E.field(E.identifier);
408
404
  name = E.field(E.string);
409
- role = E.field(E.string, {default: "user"}); // new field
405
+ role = E.field(E.string); // newly added field
410
406
 
411
407
  static migrate(record: Record<string, any>) {
412
- record.role ??= "user"; // provide value for old rows
408
+ record.role ??= record.name.indexOf("admin") >= 0 ? "admin" : "user"; // set role based on name for old records
413
409
  }
414
410
  }
415
411
  ```
416
412
 
417
- Run `npx migrate-edinburgh` (or call `E.runMigration()`) after adding/removing indexes, changing index field types, or when a `migrate()` function affects indexed fields.
413
+ 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...
414
+ - Is idempotent (meaning it can be safely run multiple times on the same row without changing the result after the first run), and
415
+ - Should perform *all* transformation steps starting from the oldest version that could possibly still be in the database. (See the next section.)
416
+
417
+ While lazy migration is convenient and often sufficient, in some cases you need migrations to happen immediately...
418
+
419
+ ### Forced Schema Migrations
420
+
421
+ The `migrate-edinburgh` CLI tool will scan the entire database, pro-actively performing the following migrations:
422
+ - **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).
423
+ - **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.
424
+ - **Remove orphaned indexes**: If you removed or changed an index, the stale data will be deleted from the database.
425
+ - **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.
426
+
427
+ ```bash
428
+ npx migrate-edinburgh ./src/models.ts
429
+ ```
430
+
431
+ Run `npx migrate-edinburgh` without arguments to see all options. You can also call `runMigration()` programmatically:
432
+
433
+ ```typescript
434
+ import { runMigration } from "edinburgh";
435
+
436
+ const result = await runMigration({ tables: ["User"] });
437
+ console.log(result.secondaries); // { User: 1500 }
438
+ ```
418
439
 
419
440
  ### preCommit Hook
420
441
 
@@ -476,125 +497,33 @@ The following is auto-generated from `src/edinburgh.ts`:
476
497
 
477
498
  **Signature:** `() => void`
478
499
 
479
- ### init · function
500
+ ### [init](init.md) · function
480
501
 
481
502
  Initialize the database with the specified directory path.
482
503
  This function may be called multiple times with the same parameters. If it is not called before the first transact(),
483
504
  the database will be automatically initialized with the default directory.
484
505
 
485
- **Signature:** `(dbDir: string) => void`
486
-
487
- **Parameters:**
488
-
489
- - `dbDir: string`
490
-
491
- **Examples:**
492
-
493
- ```typescript
494
- init("./my-database");
495
- ```
496
-
497
- ### transact · function
506
+ ### [transact](transact.md) · function
498
507
 
499
508
  Executes a function within a database transaction context.
500
509
 
501
- Loading models (also through links in other models) and changing models can only be done from
502
- within a transaction.
503
-
504
- Transactions have a consistent view of the database, and changes made within a transaction are
505
- isolated from other transactions until they are committed. In case a commit clashes with changes
506
- made by another transaction, the transaction function will automatically be re-executed up to 6
507
- times.
508
-
509
- **Signature:** `<T>(fn: () => T) => Promise<T>`
510
-
511
- **Type Parameters:**
512
-
513
- - `T` - The return type of the transaction function.
514
-
515
- **Parameters:**
516
-
517
- - `fn: () => T` - - The function to execute within the transaction context. Receives a Transaction instance.
518
-
519
- **Returns:** A promise that resolves with the function's return value.
520
-
521
- **Throws:**
522
-
523
- - With code "RACING_TRANSACTION" if the transaction fails after retries due to conflicts.
524
- - With code "TXN_LIMIT" if maximum number of transactions is reached.
525
- - With code "LMDB-{code}" for LMDB-specific errors.
526
-
527
- **Examples:**
528
-
529
- ```typescript
530
- const paid = await E.transact(() => {
531
- const user = User.pk.get("john_doe");
532
- if (user.credits > 0) {
533
- user.credits--;
534
- return true;
535
- }
536
- return false;
537
- });
538
- ```
539
- ```typescript
540
- // Transaction with automatic retry on conflicts
541
- await E.transact(() => {
542
- const counter = Counter.pk.get("global") || new Counter({id: "global", value: 0});
543
- counter.value++;
544
- });
545
- ```
546
-
547
- ### setMaxRetryCount · function
510
+ ### [setMaxRetryCount](setMaxRetryCount.md) · function
548
511
 
549
512
  Set the maximum number of retries for a transaction in case of conflicts.
550
513
  The default value is 6. Setting it to 0 will disable retries and cause transactions to fail immediately on conflict.
551
514
 
552
- **Signature:** `(count: number) => void`
553
-
554
- **Parameters:**
555
-
556
- - `count: number` - The maximum number of retries for a transaction.
557
-
558
- ### setOnSaveCallback · function
515
+ ### [setOnSaveCallback](setOnSaveCallback.md) · function
559
516
 
560
517
  Set a callback function to be called after a model is saved and committed.
561
518
 
562
- **Signature:** `(callback: (commitId: number, items: Map<Model<any>, Change>) => void) => void`
563
-
564
- **Parameters:**
565
-
566
- - `callback: ((commitId: number, items: Map<Model<any>, Change>) => void) | undefined` - The callback function to set. It gets called after each successful
567
- `transact()` commit that has changes, with the following arguments:
568
- - A sequential number. Higher numbers have been committed after lower numbers.
569
- - A map of model instances to their changes. The change can be "created", "deleted", or an object containing the old values.
570
-
571
519
  ### deleteEverything · function
572
520
 
573
521
  **Signature:** `() => Promise<void>`
574
522
 
575
- ### Model · abstract class
523
+ ### [Model](Model.md) · abstract class
576
524
 
577
525
  [object Object],[object Object],[object Object],[object Object],[object Object]
578
526
 
579
- **Type Parameters:**
580
-
581
- - `SUB` - The concrete model subclass (for proper typing).
582
-
583
- **Examples:**
584
-
585
- ```typescript
586
- ⁣@E.registerModel
587
- class User extends E.Model<User> {
588
- static pk = E.primary(User, "id");
589
-
590
- id = E.field(E.identifier);
591
- name = E.field(E.string);
592
- email = E.field(E.string);
593
-
594
- static byEmail = E.unique(User, "email");
595
- }
596
- ```
597
-
598
527
  #### Model.tableName · static property
599
528
 
600
529
  The database table name (defaults to class name).
@@ -613,120 +542,32 @@ Field configuration metadata.
613
542
 
614
543
  **Type:** `Record<string | number | symbol, FieldConfig<unknown>>`
615
544
 
616
- #### Model.migrate · static method
545
+ #### [Model.migrate](Model_migrate.md) · static method
617
546
 
618
547
  Optional migration function called when deserializing rows written with an older schema version.
619
548
  Receives a plain record with all fields (primary key fields + value fields) and should mutate it
620
549
  in-place to match the current schema.
621
550
 
622
- This is called both during lazy loading (when a row is read from disk) and during batch
623
- migration (via `runMigration()` / `npx migrate-edinburgh`). The function's source code is hashed
624
- to detect changes. Modifying `migrate()` triggers a new schema version.
625
-
626
- If `migrate()` changes values of fields used in secondary or unique indexes, those indexes
627
- will only be updated when `runMigration()` is run (not during lazy loading).
628
-
629
- **Signature:** `(record: Record<string, any>) => void`
630
-
631
- **Parameters:**
632
-
633
- - `record: Record<string, any>` - - A plain object with all field values from the old schema version.
634
-
635
- **Examples:**
636
-
637
- ```typescript
638
- ⁣@E.registerModel
639
- class User extends E.Model<User> {
640
- static pk = E.primary(User, "id");
641
- id = E.field(E.identifier);
642
- name = E.field(E.string);
643
- role = E.field(E.string); // new field
644
-
645
- static migrate(record: Record<string, any>) {
646
- record.role ??= "user"; // default for rows that predate the 'role' field
647
- }
648
- }
649
- ```
650
-
651
- #### Model.findAll · static method
551
+ #### [Model.findAll](Model_findAll.md) · static method
652
552
 
653
553
  Find all instances of this model in the database, ordered by primary key.
654
554
 
655
- **Signature:** `<T extends typeof Model<unknown>>(this: T, opts?: { reverse?: boolean; }) => IndexRangeIterator<T>`
656
-
657
- **Parameters:**
658
-
659
- - `this: T`
660
- - `opts?: {reverse?: boolean}` - - Optional parameters.
661
-
662
- **Returns:** An iterator.
663
-
664
- #### Model.replaceInto · static method
555
+ #### [Model.replaceInto](Model_replaceInto.md) · static method
665
556
 
666
557
  Load an existing instance by primary key and update it, or create a new one.
667
558
 
668
- The provided object must contain all primary key fields. If a matching row exists,
669
- the remaining properties from `obj` are set on the loaded instance. Otherwise a
670
- new instance is created with `obj` as its initial properties.
671
-
672
- **Signature:** `<T extends typeof Model<any>>(this: T, obj: Partial<Omit<InstanceType<T>, "constructor">>) => InstanceType<T>`
673
-
674
- **Parameters:**
675
-
676
- - `this: T`
677
- - `obj: Partial<Omit<InstanceType<T>, "constructor">>` - - Partial model data that **must** include every primary key field.
678
-
679
- **Returns:** The loaded-and-updated or newly created instance.
680
-
681
- #### model.preCommit · method
559
+ #### [model.preCommit](Model_preCommit.md) · method
682
560
 
683
561
  Optional hook called on each modified instance right before the transaction commits.
684
562
  Runs before data is written to disk, so changes made here are included in the commit.
685
563
 
686
- Common use cases:
687
- - Computing derived or denormalized fields
688
- - Enforcing cross-field validation rules
689
- - Creating or updating related model instances (newly created instances will also
690
- have their `preCommit()` called)
691
-
692
- **Signature:** `() => void`
693
-
694
- **Parameters:**
695
-
696
-
697
- **Examples:**
698
-
699
- ```typescript
700
- ⁣@E.registerModel
701
- class Post extends E.Model<Post> {
702
- static pk = E.primary(Post, "id");
703
- id = E.field(E.identifier);
704
- title = E.field(E.string);
705
- slug = E.field(E.string);
706
-
707
- preCommit() {
708
- this.slug = this.title.toLowerCase().replace(/\s+/g, "-");
709
- }
710
- }
711
- ```
712
-
713
564
  #### model.getPrimaryKey · method
714
565
 
715
566
  **Signature:** `() => Uint8Array<ArrayBufferLike>`
716
567
 
717
- **Parameters:**
718
-
719
-
720
568
  **Returns:** The primary key for this instance.
721
569
 
722
- #### model.getPrimaryKeyHash · method
723
-
724
- **Signature:** `() => number`
725
-
726
- **Parameters:**
727
-
728
-
729
- **Returns:** A 53-bit positive integer non-cryptographic hash of the primary key, or undefined if not yet saved.
570
+ #### [model.getPrimaryKeyHash](Model_getPrimaryKeyHash.md) · method
730
571
 
731
572
  #### model.isLazyField · method
732
573
 
@@ -736,161 +577,42 @@ class Post extends E.Model<Post> {
736
577
 
737
578
  - `field: keyof this`
738
579
 
739
- #### model.preventPersist · method
580
+ #### [model.preventPersist](Model_preventPersist.md) · method
740
581
 
741
582
  Prevent this instance from being persisted to the database.
742
583
 
743
- **Signature:** `() => this`
744
-
745
- **Parameters:**
746
-
747
-
748
- **Returns:** This model instance for chaining.
749
-
750
- **Examples:**
751
-
752
- ```typescript
753
- const user = User.load("user123");
754
- user.name = "New Name";
755
- user.preventPersist(); // Changes won't be saved
756
- ```
757
-
758
- #### model.delete · method
584
+ #### [model.delete](Model_delete.md) · method
759
585
 
760
586
  Delete this model instance from the database.
761
587
 
762
- Removes the instance and all its index entries from the database and prevents further persistence.
763
-
764
- **Signature:** `() => void`
765
-
766
- **Parameters:**
767
-
768
-
769
- **Examples:**
770
-
771
- ```typescript
772
- const user = User.load("user123");
773
- user.delete(); // Removes from database
774
- ```
775
-
776
- #### model.validate · method
588
+ #### [model.validate](Model_validate.md) · method
777
589
 
778
590
  Validate all fields in this model instance.
779
591
 
780
- **Signature:** `(raise?: boolean) => Error[]`
781
-
782
- **Parameters:**
783
-
784
- - `raise: boolean` (optional) - - If true, throw on first validation error.
785
-
786
- **Returns:** Array of validation errors (empty if valid).
787
-
788
- **Examples:**
789
-
790
- ```typescript
791
- const user = new User();
792
- const errors = user.validate();
793
- if (errors.length > 0) {
794
- console.log("Validation failed:", errors);
795
- }
796
- ```
797
-
798
- #### model.isValid · method
592
+ #### [model.isValid](Model_isValid.md) · method
799
593
 
800
594
  Check if this model instance is valid.
801
595
 
802
- **Signature:** `() => boolean`
803
-
804
- **Parameters:**
805
-
806
-
807
- **Returns:** true if all validations pass.
808
-
809
- **Examples:**
810
-
811
- ```typescript
812
- const user = new User({name: "John"});
813
- if (!user.isValid()) shoutAtTheUser();
814
- ```
815
-
816
596
  #### model.getState · method
817
597
 
818
598
  **Signature:** `() => "created" | "deleted" | "loaded" | "lazy"`
819
599
 
820
- **Parameters:**
821
-
822
-
823
600
  #### model.toString · method
824
601
 
825
602
  **Signature:** `() => string`
826
603
 
827
- **Parameters:**
828
-
829
-
830
604
  #### model.[Symbol.for('nodejs.util.inspect.custom')] · method
831
605
 
832
606
  **Signature:** `() => string`
833
607
 
834
- **Parameters:**
835
-
836
-
837
- ### registerModel · function
608
+ ### [registerModel](registerModel.md) · function
838
609
 
839
610
  Register a model class with the Edinburgh ORM system.
840
611
 
841
- **Signature:** `<T extends typeof Model<unknown>>(MyModel: T) => T`
842
-
843
- **Type Parameters:**
844
-
845
- - `T extends typeof Model<unknown>` - The model class type.
846
-
847
- **Parameters:**
848
-
849
- - `MyModel: T` - - The model class to register.
850
-
851
- **Returns:** The enhanced model class with ORM capabilities.
852
-
853
- **Examples:**
854
-
855
- ```typescript
856
- ⁣@E.registerModel
857
- class User extends E.Model<User> {
858
- static pk = E.index(User, ["id"], "primary");
859
- id = E.field(E.identifier);
860
- name = E.field(E.string);
861
- }
862
- ```
863
-
864
- ### field · function
612
+ ### [field](field.md) · function
865
613
 
866
614
  Create a field definition for a model property.
867
615
 
868
- This function uses TypeScript magic to return the field configuration object
869
- while appearing to return the actual field value type to the type system.
870
- This allows for both runtime introspection and compile-time type safety.
871
-
872
- **Signature:** `<T>(type: TypeWrapper<T>, options?: Partial<FieldConfig<T>>) => T`
873
-
874
- **Type Parameters:**
875
-
876
- - `T` - The field type.
877
-
878
- **Parameters:**
879
-
880
- - `type: TypeWrapper<T>` - - The type wrapper for this field.
881
- - `options: Partial<FieldConfig<T>>` (optional) - - Additional field configuration options.
882
-
883
- **Returns:** The field value (typed as T, but actually returns FieldConfig<T>).
884
-
885
- **Examples:**
886
-
887
- ```typescript
888
- class User extends E.Model<User> {
889
- name = E.field(E.string, {description: "User's full name"});
890
- age = E.field(E.opt(E.number), {description: "User's age", default: 25});
891
- }
892
- ```
893
-
894
616
  ### string · constant
895
617
 
896
618
  Type wrapper instance for the string type.
@@ -937,397 +659,90 @@ Type wrapper instance for the 'undefined' type.
937
659
 
938
660
  **Value:** `TypeWrapper<undefined>`
939
661
 
940
- ### opt · function
662
+ ### [opt](opt.md) · function
941
663
 
942
664
  Create an optional type wrapper (allows undefined).
943
665
 
944
- **Signature:** `<const T extends TypeWrapper<unknown> | BasicType>(inner: T) => TypeWrapper<T extends TypeWrapper<infer U> ? U : T>`
945
-
946
- **Type Parameters:**
947
-
948
- - `T extends TypeWrapper<unknown>|BasicType` - Type wrapper or basic type to make optional.
949
-
950
- **Parameters:**
951
-
952
- - `inner: T` - - The inner type to make optional.
953
-
954
- **Returns:** A union type that accepts the inner type or undefined.
955
-
956
- **Examples:**
957
-
958
- ```typescript
959
- const optionalString = E.opt(E.string);
960
- const optionalNumber = E.opt(E.number);
961
- ```
962
-
963
- ### or · function
666
+ ### [or](or.md) · function
964
667
 
965
668
  Create a union type wrapper from multiple type choices.
966
669
 
967
- **Signature:** `<const T extends (TypeWrapper<unknown> | BasicType)[]>(...choices: T) => TypeWrapper<UnwrapTypes<T>>`
968
-
969
- **Type Parameters:**
970
-
971
- - `T extends (TypeWrapper<unknown>|BasicType)[]` - Array of type wrapper or basic types.
972
-
973
- **Parameters:**
974
-
975
- - `choices: T` - - The type choices for the union.
976
-
977
- **Returns:** A union type instance.
978
-
979
- **Examples:**
980
-
981
- ```typescript
982
- const stringOrNumber = E.or(E.string, E.number);
983
- const status = E.or("active", "inactive", "pending");
984
- ```
985
-
986
- ### array · function
670
+ ### [array](array.md) · function
987
671
 
988
672
  Create an array type wrapper with optional length constraints.
989
673
 
990
- **Signature:** `<const T>(inner: TypeWrapper<T>, opts?: { min?: number; max?: number; }) => TypeWrapper<T[]>`
991
-
992
- **Type Parameters:**
993
-
994
- - `T` - The element type.
995
-
996
- **Parameters:**
997
-
998
- - `inner: TypeWrapper<T>` - - Type wrapper for array elements.
999
- - `opts: {min?: number, max?: number}` (optional) - - Optional constraints (min/max length).
1000
-
1001
- **Returns:** An array type instance.
1002
-
1003
- **Examples:**
1004
-
1005
- ```typescript
1006
- const stringArray = E.array(E.string);
1007
- const boundedArray = E.array(E.number, {min: 1, max: 10});
1008
- ```
1009
-
1010
- ### set · function
674
+ ### [set](set.md) · function
1011
675
 
1012
676
  Create a Set type wrapper with optional length constraints.
1013
677
 
1014
- **Signature:** `<const T>(inner: TypeWrapper<T>, opts?: { min?: number; max?: number; }) => TypeWrapper<Set<T>>`
1015
-
1016
- **Type Parameters:**
1017
-
1018
- - `T` - The element type.
1019
-
1020
- **Parameters:**
1021
-
1022
- - `inner: TypeWrapper<T>` - - Type wrapper for set elements.
1023
- - `opts: {min?: number, max?: number}` (optional) - - Optional constraints (min/max length).
1024
-
1025
- **Returns:** A set type instance.
1026
-
1027
- **Examples:**
1028
-
1029
- ```typescript
1030
- const stringSet = E.set(E.string);
1031
- const boundedSet = E.set(E.number, {min: 1, max: 10});
1032
- ```
1033
-
1034
- ### record · function
678
+ ### [record](record.md) · function
1035
679
 
1036
680
  Create a Record type wrapper for key-value objects with string or number keys.
1037
681
 
1038
- **Signature:** `<const T>(inner: TypeWrapper<T>) => TypeWrapper<Record<string | number, T>>`
1039
-
1040
- **Type Parameters:**
1041
-
1042
- - `T` - The value type.
1043
-
1044
- **Parameters:**
1045
-
1046
- - `inner: TypeWrapper<T>` - - Type wrapper for record values.
1047
-
1048
- **Returns:** A record type instance.
1049
-
1050
- **Examples:**
1051
-
1052
- ```typescript
1053
- const scores = E.record(E.number); // Record<string | number, number>
1054
- ```
1055
-
1056
- ### literal · function
682
+ ### [literal](literal.md) · function
1057
683
 
1058
684
  Create a literal type wrapper for a constant value.
1059
685
 
1060
- **Signature:** `<const T>(value: T) => TypeWrapper<T>`
1061
-
1062
- **Type Parameters:**
1063
-
1064
- - `T` - The literal type.
1065
-
1066
- **Parameters:**
1067
-
1068
- - `value: T` - - The literal value.
1069
-
1070
- **Returns:** A literal type instance.
1071
-
1072
- **Examples:**
1073
-
1074
- ```typescript
1075
- const statusType = E.literal("active");
1076
- const countType = E.literal(42);
1077
- ```
1078
-
1079
- ### link · function
686
+ ### [link](link.md) · function
1080
687
 
1081
688
  Create a link type wrapper for model relationships.
1082
689
 
1083
- **Signature:** `<const T extends typeof Model<any>>(TargetModel: T) => TypeWrapper<InstanceType<T>>`
1084
-
1085
- **Type Parameters:**
1086
-
1087
- - `T extends typeof Model<any>` - The target model class.
1088
-
1089
- **Parameters:**
1090
-
1091
- - `TargetModel: T` - - The model class this link points to.
1092
-
1093
- **Returns:** A link type instance.
1094
-
1095
- **Examples:**
1096
-
1097
- ```typescript
1098
- class User extends E.Model<User> {
1099
- posts = E.field(E.array(E.link(Post, 'author')));
1100
- }
1101
-
1102
- class Post extends E.Model<Post> {
1103
- author = E.field(E.link(User));
1104
- }
1105
- ```
1106
-
1107
- ### index · function
690
+ ### [index](index.md) · function
1108
691
 
1109
692
  Create a secondary index on model fields, or a computed secondary index using a function.
1110
693
 
1111
- For field-based indexes, pass a field name or array of field names.
1112
- For computed indexes, pass a function that takes a model instance and returns an array of
1113
- index keys. Return `[]` to skip indexing for that instance. Each array element creates a
1114
- separate index entry, enabling multi-value indexes (e.g., indexing by each word in a name).
1115
-
1116
- **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)[]>(...`
1117
-
1118
- **Type Parameters:**
1119
-
1120
- - `M extends typeof Model` - The model class.
1121
- - `V` - The computed index value type (for function-based indexes).
1122
-
1123
- **Parameters:**
1124
-
1125
- - `MyModel: M` - - The model class to create the index for.
1126
- - `fn: (instance: InstanceType<M>) => V[]`
1127
-
1128
- **Returns:** A new SecondaryIndex instance.
1129
-
1130
- **Examples:**
1131
-
1132
- ```typescript
1133
- class User extends E.Model<User> {
1134
- static byAge = E.index(User, "age");
1135
- static byTagsDate = E.index(User, ["tags", "createdAt"]);
1136
- static byWord = E.index(User, (u: User) => u.name.split(" "));
1137
- }
1138
- ```
1139
-
1140
- ### primary · function
694
+ ### [primary](primary.md) · function
1141
695
 
1142
696
  Create a primary index on model fields.
1143
697
 
1144
- **Signature:** `{ <M extends typeof Model, const F extends (keyof InstanceType<M> & string)>(MyModel: M, field: F): PrimaryIndex<M, [F]>; <M extends typeof Model, const FS extends readonly (keyof InstanceType<M> & string)[]>(MyModel: M, fields: FS): PrimaryIndex<...>; }`
1145
-
1146
- **Type Parameters:**
1147
-
1148
- - `M extends typeof Model` - The model class.
1149
- - `F extends (keyof InstanceType<M> & string)` - The field name (for single field index).
1150
-
1151
- **Parameters:**
1152
-
1153
- - `MyModel: M` - - The model class to create the index for.
1154
- - `field: F` - - Single field name for simple indexes.
1155
-
1156
- **Returns:** A new PrimaryIndex instance.
1157
-
1158
- **Examples:**
1159
-
1160
- ```typescript
1161
- class User extends E.Model<User> {
1162
- static pk = E.primary(User, ["id"]);
1163
- static pkSingle = E.primary(User, "id");
1164
- }
1165
- ```
1166
-
1167
- ### unique · function
698
+ ### [unique](unique.md) · function
1168
699
 
1169
700
  Create a unique index on model fields, or a computed unique index using a function.
1170
701
 
1171
- For field-based indexes, pass a field name or array of field names.
1172
- For computed indexes, pass a function that takes a model instance and returns an array of
1173
- index keys. Return `[]` to skip indexing for that instance. Each array element creates a
1174
- separate index entry, enabling multi-value indexes (e.g., indexing by each word in a name).
1175
-
1176
- **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...`
1177
-
1178
- **Type Parameters:**
1179
-
1180
- - `M extends typeof Model` - The model class.
1181
- - `V` - The computed index value type (for function-based indexes).
1182
-
1183
- **Parameters:**
1184
-
1185
- - `MyModel: M` - - The model class to create the index for.
1186
- - `fn: (instance: InstanceType<M>) => V[]`
1187
-
1188
- **Returns:** A new UniqueIndex instance.
1189
-
1190
- **Examples:**
1191
-
1192
- ```typescript
1193
- class User extends E.Model<User> {
1194
- static byEmail = E.unique(User, "email");
1195
- static byNameAge = E.unique(User, ["name", "age"]);
1196
- static byFullName = E.unique(User, (u: User) => [`${u.firstName} ${u.lastName}`]);
1197
- }
1198
- ```
1199
-
1200
- ### dump · function
702
+ ### [dump](dump.md) · function
1201
703
 
1202
704
  Dump database contents for debugging.
1203
705
 
1204
- Prints all indexes and their data to the console for inspection.
1205
- This is primarily useful for development and debugging purposes.
1206
-
1207
- **Signature:** `() => void`
1208
-
1209
- ### BaseIndex · abstract class
706
+ ### [BaseIndex](BaseIndex.md) · abstract class
1210
707
 
1211
708
  Base class for database indexes for efficient lookups on model fields.
1212
709
 
1213
- Indexes enable fast queries on specific field combinations and enforce uniqueness constraints.
1214
-
1215
- **Type Parameters:**
1216
-
1217
- - `M extends typeof Model` - The model class this index belongs to.
1218
- - `F extends readonly (keyof InstanceType<M> & string)[]` - The field names that make up this index.
1219
- - `ARGS extends readonly any[] = IndexArgTypes<M, F>`
1220
-
1221
- **Constructor Parameters:**
710
+ #### [baseIndex.find](BaseIndex_find.md) · method
1222
711
 
1223
- - `MyModel`: - The model class this index belongs to.
1224
- - `_fieldNames`: - Array of field names that make up this index.
1225
-
1226
- #### baseIndex.find · method
1227
-
1228
- **Signature:** `(opts?: FindOptions<ARGS>) => IndexRangeIterator<M>`
1229
-
1230
- **Parameters:**
1231
-
1232
- - `opts: FindOptions<ARGS>` (optional)
1233
-
1234
- #### baseIndex.batchProcess · method
712
+ #### [baseIndex.batchProcess](BaseIndex_batchProcess.md) · method
1235
713
 
1236
714
  [object Object],[object Object],[object Object]
1237
715
 
1238
- **Signature:** `(opts: FindOptions<ARGS> & { limitSeconds?: number; limitRows?: number; }, callback: (row: InstanceType<M>) => void | Promise<void>) => Promise<...>`
1239
-
1240
- **Parameters:**
1241
-
1242
- - `opts: FindOptions<ARGS> & { limitSeconds?: number; limitRows?: number }` (optional) - - Query options (same as `find()`), plus:
1243
- - `callback: (row: InstanceType<M>) => void | Promise<void>` - - Called for each matching row within a transaction
1244
-
1245
716
  #### baseIndex.toString · method
1246
717
 
1247
718
  **Signature:** `() => string`
1248
719
 
1249
- **Parameters:**
1250
-
1251
-
1252
- ### UniqueIndex · class
720
+ ### [UniqueIndex](UniqueIndex.md) · class
1253
721
 
1254
722
  Unique index that stores references to the primary key.
1255
723
 
1256
- **Type Parameters:**
1257
-
1258
- - `M extends typeof Model` - The model class this index belongs to.
1259
- - `F extends readonly (keyof InstanceType<M> & string)[]` - The field names that make up this index.
1260
- - `ARGS extends readonly any[] = IndexArgTypes<M, F>`
1261
-
1262
- #### uniqueIndex.get · method
724
+ #### [uniqueIndex.get](UniqueIndex_get.md) · method
1263
725
 
1264
726
  Get a model instance by unique index key values.
1265
727
 
1266
- **Signature:** `(...args: ARGS) => InstanceType<M>`
1267
-
1268
- **Parameters:**
1269
-
1270
- - `args: ARGS` - - The unique index key values.
1271
-
1272
- **Returns:** The model instance if found, undefined otherwise.
1273
-
1274
- **Examples:**
1275
-
1276
- ```typescript
1277
- const userByEmail = User.byEmail.get("john@example.com");
1278
- ```
1279
-
1280
- ### PrimaryIndex · class
728
+ ### [PrimaryIndex](PrimaryIndex.md) · class
1281
729
 
1282
730
  Primary index that stores the actual model data.
1283
731
 
1284
- **Type Parameters:**
1285
-
1286
- - `M extends typeof Model` - The model class this index belongs to.
1287
- - `F extends readonly (keyof InstanceType<M> & string)[]` - The field names that make up this index.
1288
-
1289
- #### primaryIndex.get · method
732
+ #### [primaryIndex.get](PrimaryIndex_get.md) · method
1290
733
 
1291
734
  Get a model instance by primary key values.
1292
735
 
1293
- **Signature:** `(...args: IndexArgTypes<M, F>) => InstanceType<M>`
1294
-
1295
- **Parameters:**
1296
-
1297
- - `args: IndexArgTypes<M, F>` - - The primary key values.
1298
-
1299
- **Returns:** The model instance if found, undefined otherwise.
1300
-
1301
- **Examples:**
1302
-
1303
- ```typescript
1304
- const user = User.pk.get("john_doe");
1305
- ```
1306
-
1307
- #### primaryIndex.getLazy · method
736
+ #### [primaryIndex.getLazy](PrimaryIndex_getLazy.md) · method
1308
737
 
1309
738
  Does the same as as `get()`, but will delay loading the instance from disk until the first
1310
739
  property access. In case it turns out the instance doesn't exist, an error will be thrown
1311
740
  at that time.
1312
741
 
1313
- **Signature:** `(...args: IndexArgTypes<M, F>) => InstanceType<M>`
1314
-
1315
- **Parameters:**
1316
-
1317
- - `args: IndexArgTypes<M, F>` - Primary key field values. (Or a single Uint8Array containing the key.)
1318
-
1319
- **Returns:** The (lazily loaded) model instance.
1320
-
1321
- ### SecondaryIndex · class
742
+ ### [SecondaryIndex](SecondaryIndex.md) · class
1322
743
 
1323
744
  Secondary index for non-unique lookups.
1324
745
 
1325
- **Type Parameters:**
1326
-
1327
- - `M extends typeof Model` - The model class this index belongs to.
1328
- - `F extends readonly (keyof InstanceType<M> & string)[]` - The field names that make up this index.
1329
- - `ARGS extends readonly any[] = IndexArgTypes<M, F>`
1330
-
1331
746
  ### Change · type
1332
747
 
1333
748
  **Type:** `Record<any, any> | "created" | "deleted"`
@@ -1346,26 +761,15 @@ Secondary index for non-unique lookups.
1346
761
 
1347
762
  **Type:** `Map<number, Model<unknown>>`
1348
763
 
1349
- ### DatabaseError · constant
764
+ ### [DatabaseError](DatabaseError.md) · constant
1350
765
 
1351
766
  The DatabaseError class is used to represent errors that occur during database operations.
1352
767
  It extends the built-in Error class and has a machine readable error code string property.
1353
768
 
1354
- The lowlevel API will throw DatabaseError instances for all database-related errors.
1355
- Invalid function arguments will throw TypeError.
1356
-
1357
- **Value:** `DatabaseErrorConstructor`
1358
-
1359
- ### runMigration · function
1360
-
1361
- Run database migration: upgrade all rows to the latest schema version,
1362
- convert old primary indices, and clean up orphaned secondary indices.
1363
-
1364
- **Signature:** `(options?: MigrationOptions) => Promise<MigrationResult>`
1365
-
1366
- **Parameters:**
769
+ ### [runMigration](runMigration.md) · function
1367
770
 
1368
- - `options: MigrationOptions` (optional)
771
+ Run database migration: populate secondary indexes for old-version rows,
772
+ convert old primary indices, rewrite row data, and clean up orphaned indices.
1369
773
 
1370
774
  ### MigrationOptions · interface
1371
775
 
@@ -1375,21 +779,27 @@ Limit migration to specific table names.
1375
779
 
1376
780
  **Type:** `string[]`
1377
781
 
1378
- #### migrationOptions.convertOldPrimaries · member
782
+ #### migrationOptions.populateSecondaries · member
1379
783
 
1380
- Whether to convert old primary indices for known tables (default: true).
784
+ Populate secondary indexes for rows at old schema versions (default: true).
1381
785
 
1382
786
  **Type:** `boolean`
1383
787
 
1384
- #### migrationOptions.deleteOrphanedIndexes · member
788
+ #### migrationOptions.migratePrimaries · member
1385
789
 
1386
- Whether to delete orphaned secondary/unique indices (default: true).
790
+ Convert old primary indices when primary key fields changed (default: true).
1387
791
 
1388
792
  **Type:** `boolean`
1389
793
 
1390
- #### migrationOptions.upgradeVersions · member
794
+ #### migrationOptions.rewriteData · member
1391
795
 
1392
- Whether to upgrade rows to the latest version (default: true).
796
+ Rewrite all row data to the latest schema version (default: false).
797
+
798
+ **Type:** `boolean`
799
+
800
+ #### migrationOptions.removeOrphans · member
801
+
802
+ Delete orphaned secondary/unique index entries (default: true).
1393
803
 
1394
804
  **Type:** `boolean`
1395
805
 
@@ -1403,13 +813,13 @@ Progress callback.
1403
813
 
1404
814
  #### migrationResult.secondaries · member
1405
815
 
1406
- Per-table stats for row upgrades.
816
+ Per-table counts of secondary index entries populated.
1407
817
 
1408
818
  **Type:** `Record<string, number>`
1409
819
 
1410
820
  #### migrationResult.primaries · member
1411
821
 
1412
- Per-table stats for old primary conversions.
822
+ Per-table counts of old primary rows migrated.
1413
823
 
1414
824
  **Type:** `Record<string, number>`
1415
825
 
@@ -1419,55 +829,15 @@ Per-table conversion failure counts by reason.
1419
829
 
1420
830
  **Type:** `Record<string, Record<string, number>>`
1421
831
 
1422
- #### migrationResult.orphaned · member
832
+ #### migrationResult.rewritten · member
1423
833
 
1424
- Number of orphaned index entries deleted.
834
+ Per-table counts of rows rewritten to latest version.
1425
835
 
1426
- **Type:** `number`
1427
-
1428
- ## Schema Migrations
1429
-
1430
- 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.
1431
-
1432
- ### What happens automatically (lazy migration)
1433
-
1434
- 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.
1435
-
1436
- ```typescript
1437
- @E.registerModel
1438
- class User extends E.Model<User> {
1439
- static pk = E.primary(User, "id");
1440
- id = E.field(E.identifier);
1441
- name = E.field(E.string);
1442
- role = E.field(E.string); // newly added field
1443
-
1444
- static migrate(record: Record<string, any>) {
1445
- record.role ??= "user"; // provide a default for old rows
1446
- }
1447
- }
1448
- ```
1449
-
1450
- ### What requires `migrate-edinburgh`
1451
-
1452
- The `migrate-edinburgh` CLI tool (or the `runMigration()` API) must be run when:
1453
-
1454
- - **Adding or removing** secondary or unique indexes
1455
- - **Changing the fields or types** of an existing index
1456
- - A **`migrate()` function changes values** that are used in index fields
1457
-
1458
- 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.
1459
-
1460
- ```bash
1461
- npx migrate-edinburgh --import ./src/models.ts
1462
- ```
836
+ **Type:** `Record<string, number>`
1463
837
 
1464
- Run `npx migrate-edinburgh` to see all of its options.
838
+ #### migrationResult.orphans · member
1465
839
 
1466
- You can also call `runMigration()` programmatically:
840
+ Number of orphaned index entries deleted.
1467
841
 
1468
- ```typescript
1469
- import { runMigration } from "edinburgh";
842
+ **Type:** `number`
1470
843
 
1471
- const result = await runMigration({ tables: ["User"] });
1472
- console.log(result.upgraded); // { User: 1500 }
1473
- ```