edinburgh 0.4.2 → 0.4.4

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.
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:
@@ -409,7 +472,7 @@ The following is auto-generated from `src/edinburgh.ts`:
409
472
 
410
473
  **Signature:** `() => void`
411
474
 
412
- ### init · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L65)
475
+ ### init · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L67)
413
476
 
414
477
  Initialize the database with the specified directory path.
415
478
  This function may be called multiple times with the same parameters. If it is not called before the first transact(),
@@ -427,7 +490,7 @@ the database will be automatically initialized with the default directory.
427
490
  init("./my-database");
428
491
  ```
429
492
 
430
- ### transact · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L116)
493
+ ### transact · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L118)
431
494
 
432
495
  Executes a function within a database transaction context.
433
496
 
@@ -477,7 +540,7 @@ await E.transact(() => {
477
540
  });
478
541
  ```
479
542
 
480
- ### setMaxRetryCount · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L206)
543
+ ### setMaxRetryCount · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L208)
481
544
 
482
545
  Set the maximum number of retries for a transaction in case of conflicts.
483
546
  The default value is 6. Setting it to 0 will disable retries and cause transactions to fail immediately on conflict.
@@ -488,7 +551,7 @@ The default value is 6. Setting it to 0 will disable retries and cause transacti
488
551
 
489
552
  - `count: number` - The maximum number of retries for a transaction.
490
553
 
491
- ### setOnSaveCallback · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L220)
554
+ ### setOnSaveCallback · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L222)
492
555
 
493
556
  Set a callback function to be called after a model is saved and committed.
494
557
 
@@ -501,11 +564,11 @@ Set a callback function to be called after a model is saved and committed.
501
564
  - A sequential number. Higher numbers have been committed after lower numbers.
502
565
  - A map of model instances to their changes. The change can be "created", "deleted", or an object containing the old values.
503
566
 
504
- ### deleteEverything · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L225)
567
+ ### deleteEverything · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L227)
505
568
 
506
569
  **Signature:** `() => Promise<void>`
507
570
 
508
- ### Model · [abstract class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L218)
571
+ ### Model · [abstract class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L220)
509
572
 
510
573
  [object Object],[object Object],[object Object],[object Object],[object Object]
511
574
 
@@ -528,25 +591,25 @@ class User extends E.Model<User> {
528
591
  }
529
592
  ```
530
593
 
531
- #### Model.tableName · [static property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L225)
594
+ #### Model.tableName · [static property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L227)
532
595
 
533
596
  The database table name (defaults to class name).
534
597
 
535
598
  **Type:** `string`
536
599
 
537
- #### Model.override · [static property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L229)
600
+ #### Model.override · [static property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L231)
538
601
 
539
602
  When true, registerModel replaces an existing model with the same tableName.
540
603
 
541
604
  **Type:** `boolean`
542
605
 
543
- #### Model.fields · [static property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L230)
606
+ #### Model.fields · [static property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L232)
544
607
 
545
608
  Field configuration metadata.
546
609
 
547
610
  **Type:** `Record<string | number | symbol, FieldConfig<unknown>>`
548
611
 
549
- #### Model.migrate · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
612
+ #### Model.migrate · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
550
613
 
551
614
  Optional migration function called when deserializing rows written with an older schema version.
552
615
  Receives a plain record with all fields (primary key fields + value fields) and should mutate it
@@ -581,7 +644,7 @@ class User extends E.Model<User> {
581
644
  }
582
645
  ```
583
646
 
584
- #### Model.findAll · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
647
+ #### Model.findAll · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
585
648
 
586
649
  Find all instances of this model in the database, ordered by primary key.
587
650
 
@@ -594,7 +657,7 @@ Find all instances of this model in the database, ordered by primary key.
594
657
 
595
658
  **Returns:** An iterator.
596
659
 
597
- #### Model.replaceInto · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
660
+ #### Model.replaceInto · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
598
661
 
599
662
  Load an existing instance by primary key and update it, or create a new one.
600
663
 
@@ -611,7 +674,7 @@ new instance is created with `obj` as its initial properties.
611
674
 
612
675
  **Returns:** The loaded-and-updated or newly created instance.
613
676
 
614
- #### model.preCommit · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
677
+ #### model.preCommit · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
615
678
 
616
679
  Optional hook called on each modified instance right before the transaction commits.
617
680
  Runs before data is written to disk, so changes made here are included in the commit.
@@ -643,7 +706,7 @@ class Post extends E.Model<Post> {
643
706
  }
644
707
  ```
645
708
 
646
- #### model.getPrimaryKey · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
709
+ #### model.getPrimaryKey · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
647
710
 
648
711
  **Signature:** `() => Uint8Array<ArrayBufferLike>`
649
712
 
@@ -652,7 +715,7 @@ class Post extends E.Model<Post> {
652
715
 
653
716
  **Returns:** The primary key for this instance.
654
717
 
655
- #### model.getPrimaryKeyHash · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
718
+ #### model.getPrimaryKeyHash · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
656
719
 
657
720
  **Signature:** `() => number`
658
721
 
@@ -661,7 +724,7 @@ class Post extends E.Model<Post> {
661
724
 
662
725
  **Returns:** A 53-bit positive integer non-cryptographic hash of the primary key, or undefined if not yet saved.
663
726
 
664
- #### model.isLazyField · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
727
+ #### model.isLazyField · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
665
728
 
666
729
  **Signature:** `(field: keyof this) => boolean`
667
730
 
@@ -669,7 +732,7 @@ class Post extends E.Model<Post> {
669
732
 
670
733
  - `field: keyof this`
671
734
 
672
- #### model.preventPersist · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
735
+ #### model.preventPersist · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
673
736
 
674
737
  Prevent this instance from being persisted to the database.
675
738
 
@@ -688,7 +751,7 @@ user.name = "New Name";
688
751
  user.preventPersist(); // Changes won't be saved
689
752
  ```
690
753
 
691
- #### model.delete · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
754
+ #### model.delete · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
692
755
 
693
756
  Delete this model instance from the database.
694
757
 
@@ -706,7 +769,7 @@ const user = User.load("user123");
706
769
  user.delete(); // Removes from database
707
770
  ```
708
771
 
709
- #### model.validate · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
772
+ #### model.validate · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
710
773
 
711
774
  Validate all fields in this model instance.
712
775
 
@@ -728,7 +791,7 @@ if (errors.length > 0) {
728
791
  }
729
792
  ```
730
793
 
731
- #### model.isValid · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
794
+ #### model.isValid · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
732
795
 
733
796
  Check if this model instance is valid.
734
797
 
@@ -746,28 +809,28 @@ const user = new User({name: "John"});
746
809
  if (!user.isValid()) shoutAtTheUser();
747
810
  ```
748
811
 
749
- #### model.getState · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
812
+ #### model.getState · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
750
813
 
751
814
  **Signature:** `() => "created" | "deleted" | "loaded" | "lazy"`
752
815
 
753
816
  **Parameters:**
754
817
 
755
818
 
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
823
  **Parameters:**
761
824
 
762
825
 
763
- #### model.[Symbol.for('nodejs.util.inspect.custom')] · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
826
+ #### model.[Symbol.for('nodejs.util.inspect.custom')] · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
764
827
 
765
828
  **Signature:** `() => string`
766
829
 
767
830
  **Parameters:**
768
831
 
769
832
 
770
- ### registerModel · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L111)
833
+ ### registerModel · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L113)
771
834
 
772
835
  Register a model class with the Edinburgh ORM system.
773
836
 
@@ -794,7 +857,7 @@ class User extends E.Model<User> {
794
857
  }
795
858
  ```
796
859
 
797
- ### field · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L87)
860
+ ### field · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L89)
798
861
 
799
862
  Create a field definition for a model property.
800
863
 
@@ -824,13 +887,13 @@ class User extends E.Model<User> {
824
887
  }
825
888
  ```
826
889
 
827
- ### string · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
890
+ ### string · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
828
891
 
829
892
  Type wrapper instance for the string type.
830
893
 
831
894
  **Value:** `TypeWrapper<string>`
832
895
 
833
- ### orderedString · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
896
+ ### orderedString · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
834
897
 
835
898
  Type wrapper instance for the ordered string type, which is just like a string
836
899
  except that it sorts lexicographically in the database (instead of by incrementing
@@ -840,37 +903,37 @@ may not contain null characters.
840
903
 
841
904
  **Value:** `TypeWrapper<string>`
842
905
 
843
- ### number · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
906
+ ### number · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
844
907
 
845
908
  Type wrapper instance for the number type.
846
909
 
847
910
  **Value:** `TypeWrapper<number>`
848
911
 
849
- ### dateTime · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
912
+ ### dateTime · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
850
913
 
851
- Type wrapper instance for the date/time type.
914
+ Type wrapper instance for the date/time type. Stored without timezone info, rounded to whole seconds.
852
915
 
853
916
  **Value:** `TypeWrapper<Date>`
854
917
 
855
- ### boolean · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
918
+ ### boolean · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
856
919
 
857
920
  Type wrapper instance for the boolean type.
858
921
 
859
922
  **Value:** `TypeWrapper<boolean>`
860
923
 
861
- ### identifier · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
924
+ ### identifier · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
862
925
 
863
926
  Type wrapper instance for the identifier type.
864
927
 
865
928
  **Value:** `TypeWrapper<string>`
866
929
 
867
- ### undef · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
930
+ ### undef · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
868
931
 
869
932
  Type wrapper instance for the 'undefined' type.
870
933
 
871
934
  **Value:** `TypeWrapper<undefined>`
872
935
 
873
- ### opt · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
936
+ ### opt · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
874
937
 
875
938
  Create an optional type wrapper (allows undefined).
876
939
 
@@ -893,7 +956,7 @@ const optionalString = E.opt(E.string);
893
956
  const optionalNumber = E.opt(E.number);
894
957
  ```
895
958
 
896
- ### or · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
959
+ ### or · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
897
960
 
898
961
  Create a union type wrapper from multiple type choices.
899
962
 
@@ -916,7 +979,7 @@ const stringOrNumber = E.or(E.string, E.number);
916
979
  const status = E.or("active", "inactive", "pending");
917
980
  ```
918
981
 
919
- ### array · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
982
+ ### array · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
920
983
 
921
984
  Create an array type wrapper with optional length constraints.
922
985
 
@@ -940,7 +1003,53 @@ const stringArray = E.array(E.string);
940
1003
  const boundedArray = E.array(E.number, {min: 1, max: 10});
941
1004
  ```
942
1005
 
943
- ### literal · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
1006
+ ### set · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
1007
+
1008
+ Create a Set type wrapper with optional length constraints.
1009
+
1010
+ **Signature:** `<const T>(inner: TypeWrapper<T>, opts?: { min?: number; max?: number; }) => TypeWrapper<Set<T>>`
1011
+
1012
+ **Type Parameters:**
1013
+
1014
+ - `T` - The element type.
1015
+
1016
+ **Parameters:**
1017
+
1018
+ - `inner: TypeWrapper<T>` - - Type wrapper for set elements.
1019
+ - `opts: {min?: number, max?: number}` (optional) - - Optional constraints (min/max length).
1020
+
1021
+ **Returns:** A set type instance.
1022
+
1023
+ **Examples:**
1024
+
1025
+ ```typescript
1026
+ const stringSet = E.set(E.string);
1027
+ const boundedSet = E.set(E.number, {min: 1, max: 10});
1028
+ ```
1029
+
1030
+ ### record · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
1031
+
1032
+ Create a Record type wrapper for key-value objects with string or number keys.
1033
+
1034
+ **Signature:** `<const T>(inner: TypeWrapper<T>) => TypeWrapper<Record<string | number, T>>`
1035
+
1036
+ **Type Parameters:**
1037
+
1038
+ - `T` - The value type.
1039
+
1040
+ **Parameters:**
1041
+
1042
+ - `inner: TypeWrapper<T>` - - Type wrapper for record values.
1043
+
1044
+ **Returns:** A record type instance.
1045
+
1046
+ **Examples:**
1047
+
1048
+ ```typescript
1049
+ const scores = E.record(E.number); // Record<string | number, number>
1050
+ ```
1051
+
1052
+ ### literal · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
944
1053
 
945
1054
  Create a literal type wrapper for a constant value.
946
1055
 
@@ -963,7 +1072,7 @@ const statusType = E.literal("active");
963
1072
  const countType = E.literal(42);
964
1073
  ```
965
1074
 
966
- ### link · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
1075
+ ### link · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
967
1076
 
968
1077
  Create a link type wrapper for model relationships.
969
1078
 
@@ -991,21 +1100,26 @@ class Post extends E.Model<Post> {
991
1100
  }
992
1101
  ```
993
1102
 
994
- ### index · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
1103
+ ### index · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
1104
+
1105
+ Create a secondary index on model fields, or a computed secondary index using a function.
995
1106
 
996
- Create a secondary index on model fields.
1107
+ For field-based indexes, pass a field name or array of field names.
1108
+ For computed indexes, pass a function that takes a model instance and returns an array of
1109
+ index keys. Return `[]` to skip indexing for that instance. Each array element creates a
1110
+ separate index entry, enabling multi-value indexes (e.g., indexing by each word in a name).
997
1111
 
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<...>; }`
1112
+ **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
1113
 
1000
1114
  **Type Parameters:**
1001
1115
 
1002
1116
  - `M extends typeof Model` - The model class.
1003
- - `F extends (keyof InstanceType<M> & string)` - The field name (for single field index).
1117
+ - `V` - The computed index value type (for function-based indexes).
1004
1118
 
1005
1119
  **Parameters:**
1006
1120
 
1007
1121
  - `MyModel: M` - - The model class to create the index for.
1008
- - `field: F` - - Single field name for simple indexes.
1122
+ - `fn: (instance: InstanceType<M>) => V[]`
1009
1123
 
1010
1124
  **Returns:** A new SecondaryIndex instance.
1011
1125
 
@@ -1015,10 +1129,11 @@ Create a secondary index on model fields.
1015
1129
  class User extends E.Model<User> {
1016
1130
  static byAge = E.index(User, "age");
1017
1131
  static byTagsDate = E.index(User, ["tags", "createdAt"]);
1132
+ static byWord = E.index(User, (u: User) => u.name.split(" "));
1018
1133
  }
1019
1134
  ```
1020
1135
 
1021
- ### primary · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
1136
+ ### primary · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
1022
1137
 
1023
1138
  Create a primary index on model fields.
1024
1139
 
@@ -1045,21 +1160,26 @@ class User extends E.Model<User> {
1045
1160
  }
1046
1161
  ```
1047
1162
 
1048
- ### unique · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
1163
+ ### unique · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
1049
1164
 
1050
- Create a unique index on model fields.
1165
+ Create a unique index on model fields, or a computed unique index using a function.
1051
1166
 
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<...>; }`
1167
+ For field-based indexes, pass a field name or array of field names.
1168
+ For computed indexes, pass a function that takes a model instance and returns an array of
1169
+ index keys. Return `[]` to skip indexing for that instance. Each array element creates a
1170
+ separate index entry, enabling multi-value indexes (e.g., indexing by each word in a name).
1171
+
1172
+ **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
1173
 
1054
1174
  **Type Parameters:**
1055
1175
 
1056
1176
  - `M extends typeof Model` - The model class.
1057
- - `F extends (keyof InstanceType<M> & string)` - The field name (for single field index).
1177
+ - `V` - The computed index value type (for function-based indexes).
1058
1178
 
1059
1179
  **Parameters:**
1060
1180
 
1061
1181
  - `MyModel: M` - - The model class to create the index for.
1062
- - `field: F` - - Single field name for simple indexes.
1182
+ - `fn: (instance: InstanceType<M>) => V[]`
1063
1183
 
1064
1184
  **Returns:** A new UniqueIndex instance.
1065
1185
 
@@ -1069,10 +1189,11 @@ Create a unique index on model fields.
1069
1189
  class User extends E.Model<User> {
1070
1190
  static byEmail = E.unique(User, "email");
1071
1191
  static byNameAge = E.unique(User, ["name", "age"]);
1192
+ static byFullName = E.unique(User, (u: User) => [`${u.firstName} ${u.lastName}`]);
1072
1193
  }
1073
1194
  ```
1074
1195
 
1075
- ### dump · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
1196
+ ### dump · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
1076
1197
 
1077
1198
  Dump database contents for debugging.
1078
1199
 
@@ -1081,7 +1202,7 @@ This is primarily useful for development and debugging purposes.
1081
1202
 
1082
1203
  **Signature:** `() => void`
1083
1204
 
1084
- ### BaseIndex · [abstract class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L127)
1205
+ ### BaseIndex · [abstract class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L125)
1085
1206
 
1086
1207
  Base class for database indexes for efficient lookups on model fields.
1087
1208
 
@@ -1091,39 +1212,40 @@ Indexes enable fast queries on specific field combinations and enforce uniquenes
1091
1212
 
1092
1213
  - `M extends typeof Model` - The model class this index belongs to.
1093
1214
  - `F extends readonly (keyof InstanceType<M> & string)[]` - The field names that make up this index.
1215
+ - `ARGS extends readonly any[] = IndexArgTypes<M, F>`
1094
1216
 
1095
1217
  **Constructor Parameters:**
1096
1218
 
1097
1219
  - `MyModel`: - The model class this index belongs to.
1098
1220
  - `_fieldNames`: - Array of field names that make up this index.
1099
1221
 
1100
- #### baseIndex.find · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
1222
+ #### baseIndex.find · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
1101
1223
 
1102
- **Signature:** `(opts?: FindOptions<IndexArgTypes<M, F>>) => IndexRangeIterator<M>`
1224
+ **Signature:** `(opts?: FindOptions<ARGS>) => IndexRangeIterator<M>`
1103
1225
 
1104
1226
  **Parameters:**
1105
1227
 
1106
- - `opts: FindOptions<IndexArgTypes<M, F>>` (optional)
1228
+ - `opts: FindOptions<ARGS>` (optional)
1107
1229
 
1108
- #### baseIndex.batchProcess · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
1230
+ #### baseIndex.batchProcess · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
1109
1231
 
1110
1232
  [object Object],[object Object],[object Object]
1111
1233
 
1112
- **Signature:** `(opts: FindOptions<IndexArgTypes<M, F>> & { limitSeconds?: number; limitRows?: number; }, callback: (row: InstanceType<M>) => void | Promise<...>) => Promise<...>`
1234
+ **Signature:** `(opts: FindOptions<ARGS> & { limitSeconds?: number; limitRows?: number; }, callback: (row: InstanceType<M>) => void | Promise<void>) => Promise<...>`
1113
1235
 
1114
1236
  **Parameters:**
1115
1237
 
1116
- - `opts: FindOptions<IndexArgTypes<M, F>> & { limitSeconds?: number; limitRows?: number }` (optional) - - Query options (same as `find()`), plus:
1238
+ - `opts: FindOptions<ARGS> & { limitSeconds?: number; limitRows?: number }` (optional) - - Query options (same as `find()`), plus:
1117
1239
  - `callback: (row: InstanceType<M>) => void | Promise<void>` - - Called for each matching row within a transaction
1118
1240
 
1119
- #### baseIndex.toString · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
1241
+ #### baseIndex.toString · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
1120
1242
 
1121
1243
  **Signature:** `() => string`
1122
1244
 
1123
1245
  **Parameters:**
1124
1246
 
1125
1247
 
1126
- ### UniqueIndex · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
1248
+ ### UniqueIndex · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
1127
1249
 
1128
1250
  Unique index that stores references to the primary key.
1129
1251
 
@@ -1131,16 +1253,17 @@ Unique index that stores references to the primary key.
1131
1253
 
1132
1254
  - `M extends typeof Model` - The model class this index belongs to.
1133
1255
  - `F extends readonly (keyof InstanceType<M> & string)[]` - The field names that make up this index.
1256
+ - `ARGS extends readonly any[] = IndexArgTypes<M, F>`
1134
1257
 
1135
- #### uniqueIndex.get · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
1258
+ #### uniqueIndex.get · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
1136
1259
 
1137
1260
  Get a model instance by unique index key values.
1138
1261
 
1139
- **Signature:** `(...args: IndexArgTypes<M, F>) => InstanceType<M>`
1262
+ **Signature:** `(...args: ARGS) => InstanceType<M>`
1140
1263
 
1141
1264
  **Parameters:**
1142
1265
 
1143
- - `args: IndexArgTypes<M, F>` - - The unique index key values.
1266
+ - `args: ARGS` - - The unique index key values.
1144
1267
 
1145
1268
  **Returns:** The model instance if found, undefined otherwise.
1146
1269
 
@@ -1150,7 +1273,7 @@ Get a model instance by unique index key values.
1150
1273
  const userByEmail = User.byEmail.get("john@example.com");
1151
1274
  ```
1152
1275
 
1153
- ### PrimaryIndex · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
1276
+ ### PrimaryIndex · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
1154
1277
 
1155
1278
  Primary index that stores the actual model data.
1156
1279
 
@@ -1159,7 +1282,7 @@ Primary index that stores the actual model data.
1159
1282
  - `M extends typeof Model` - The model class this index belongs to.
1160
1283
  - `F extends readonly (keyof InstanceType<M> & string)[]` - The field names that make up this index.
1161
1284
 
1162
- #### primaryIndex.get · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
1285
+ #### primaryIndex.get · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
1163
1286
 
1164
1287
  Get a model instance by primary key values.
1165
1288
 
@@ -1177,7 +1300,7 @@ Get a model instance by primary key values.
1177
1300
  const user = User.pk.get("john_doe");
1178
1301
  ```
1179
1302
 
1180
- #### primaryIndex.getLazy · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
1303
+ #### primaryIndex.getLazy · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
1181
1304
 
1182
1305
  Does the same as as `get()`, but will delay loading the instance from disk until the first
1183
1306
  property access. In case it turns out the instance doesn't exist, an error will be thrown
@@ -1191,7 +1314,7 @@ at that time.
1191
1314
 
1192
1315
  **Returns:** The (lazily loaded) model instance.
1193
1316
 
1194
- ### SecondaryIndex · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
1317
+ ### SecondaryIndex · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L253)
1195
1318
 
1196
1319
  Secondary index for non-unique lookups.
1197
1320
 
@@ -1199,8 +1322,9 @@ Secondary index for non-unique lookups.
1199
1322
 
1200
1323
  - `M extends typeof Model` - The model class this index belongs to.
1201
1324
  - `F extends readonly (keyof InstanceType<M> & string)[]` - The field names that make up this index.
1325
+ - `ARGS extends readonly any[] = IndexArgTypes<M, F>`
1202
1326
 
1203
- ### Change · [type](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L94)
1327
+ ### Change · [type](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L95)
1204
1328
 
1205
1329
  **Type:** `Record<any, any> | "created" | "deleted"`
1206
1330
 
@@ -1214,11 +1338,11 @@ Secondary index for non-unique lookups.
1214
1338
 
1215
1339
  **Type:** `Set<Model<unknown>>`
1216
1340
 
1217
- #### transaction.instancesByPk · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L44)
1341
+ #### transaction.instancesByPk · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L46)
1218
1342
 
1219
1343
  **Type:** `Map<number, Model<unknown>>`
1220
1344
 
1221
- ### DatabaseError · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L156)
1345
+ ### DatabaseError · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L157)
1222
1346
 
1223
1347
  The DatabaseError class is used to represent errors that occur during database operations.
1224
1348
  It extends the built-in Error class and has a machine readable error code string property.
@@ -1228,7 +1352,7 @@ Invalid function arguments will throw TypeError.
1228
1352
 
1229
1353
  **Value:** `DatabaseErrorConstructor`
1230
1354
 
1231
- ### runMigration · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L116)
1355
+ ### runMigration · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L118)
1232
1356
 
1233
1357
  Run database migration: upgrade all rows to the latest schema version,
1234
1358
  convert old primary indices, and clean up orphaned secondary indices.
@@ -1247,51 +1371,51 @@ Limit migration to specific table names.
1247
1371
 
1248
1372
  **Type:** `string[]`
1249
1373
 
1250
- #### migrationOptions.convertOldPrimaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L22)
1374
+ #### migrationOptions.convertOldPrimaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L23)
1251
1375
 
1252
1376
  Whether to convert old primary indices for known tables (default: true).
1253
1377
 
1254
1378
  **Type:** `boolean`
1255
1379
 
1256
- #### migrationOptions.deleteOrphanedIndexes · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L30)
1380
+ #### migrationOptions.deleteOrphanedIndexes · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L31)
1257
1381
 
1258
1382
  Whether to delete orphaned secondary/unique indices (default: true).
1259
1383
 
1260
1384
  **Type:** `boolean`
1261
1385
 
1262
- #### migrationOptions.upgradeVersions · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L38)
1386
+ #### migrationOptions.upgradeVersions · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L39)
1263
1387
 
1264
1388
  Whether to upgrade rows to the latest version (default: true).
1265
1389
 
1266
1390
  **Type:** `boolean`
1267
1391
 
1268
- #### migrationOptions.onProgress · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L42)
1392
+ #### migrationOptions.onProgress · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L44)
1269
1393
 
1270
1394
  Progress callback.
1271
1395
 
1272
1396
  **Type:** `(info: ProgressInfo) => void`
1273
1397
 
1274
- ### MigrationResult · [interface](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L47)
1398
+ ### MigrationResult · [interface](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L49)
1275
1399
 
1276
- #### migrationResult.secondaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L49)
1400
+ #### migrationResult.secondaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L51)
1277
1401
 
1278
1402
  Per-table stats for row upgrades.
1279
1403
 
1280
1404
  **Type:** `Record<string, number>`
1281
1405
 
1282
- #### migrationResult.primaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L50)
1406
+ #### migrationResult.primaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L52)
1283
1407
 
1284
1408
  Per-table stats for old primary conversions.
1285
1409
 
1286
1410
  **Type:** `Record<string, number>`
1287
1411
 
1288
- #### migrationResult.conversionFailures · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L56)
1412
+ #### migrationResult.conversionFailures · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L58)
1289
1413
 
1290
1414
  Per-table conversion failure counts by reason.
1291
1415
 
1292
1416
  **Type:** `Record<string, Record<string, number>>`
1293
1417
 
1294
- #### migrationResult.orphaned · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L57)
1418
+ #### migrationResult.orphaned · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L59)
1295
1419
 
1296
1420
  Number of orphaned index entries deleted.
1297
1421