edinburgh 0.4.6 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +403 -461
  2. package/build/src/datapack.d.ts +9 -9
  3. package/build/src/datapack.js +10 -10
  4. package/build/src/datapack.js.map +1 -1
  5. package/build/src/edinburgh.d.ts +21 -10
  6. package/build/src/edinburgh.js +33 -55
  7. package/build/src/edinburgh.js.map +1 -1
  8. package/build/src/indexes.d.ts +99 -288
  9. package/build/src/indexes.js +253 -636
  10. package/build/src/indexes.js.map +1 -1
  11. package/build/src/migrate.js +17 -39
  12. package/build/src/migrate.js.map +1 -1
  13. package/build/src/models.d.ts +177 -113
  14. package/build/src/models.js +487 -259
  15. package/build/src/models.js.map +1 -1
  16. package/build/src/types.d.ts +41 -51
  17. package/build/src/types.js +39 -52
  18. package/build/src/types.js.map +1 -1
  19. package/build/src/utils.d.ts +4 -4
  20. package/build/src/utils.js +4 -4
  21. package/package.json +1 -3
  22. package/skill/AnyModelClass.md +7 -0
  23. package/skill/FindOptions.md +37 -0
  24. package/skill/Lifecycle Hooks.md +24 -0
  25. package/skill/{Model_delete.md → Lifecycle Hooks_delete.md } +2 -2
  26. package/skill/{Model_getPrimaryKeyHash.md → Lifecycle Hooks_getPrimaryKeyHash.md } +1 -1
  27. package/skill/{Model_isValid.md → Lifecycle Hooks_isValid.md } +1 -1
  28. package/skill/Lifecycle Hooks_migrate.md +26 -0
  29. package/skill/{Model_preCommit.md → Lifecycle Hooks_preCommit.md } +3 -5
  30. package/skill/{Model_preventPersist.md → Lifecycle Hooks_preventPersist.md } +2 -2
  31. package/skill/{Model_validate.md → Lifecycle Hooks_validate.md } +2 -2
  32. package/skill/ModelBase.md +7 -0
  33. package/skill/ModelClass.md +8 -0
  34. package/skill/SKILL.md +253 -215
  35. package/skill/Schema Evolution.md +19 -0
  36. package/skill/TypeWrapper_containsNull.md +11 -0
  37. package/skill/TypeWrapper_deserialize.md +9 -0
  38. package/skill/TypeWrapper_getError.md +11 -0
  39. package/skill/TypeWrapper_serialize.md +10 -0
  40. package/skill/TypeWrapper_serializeType.md +9 -0
  41. package/skill/array.md +2 -2
  42. package/skill/defineModel.md +23 -0
  43. package/skill/deleteEverything.md +8 -0
  44. package/skill/field.md +4 -4
  45. package/skill/link.md +12 -10
  46. package/skill/literal.md +1 -1
  47. package/skill/opt.md +1 -1
  48. package/skill/or.md +1 -1
  49. package/skill/record.md +1 -1
  50. package/skill/set.md +2 -2
  51. package/skill/setOnSaveCallback.md +2 -2
  52. package/skill/transact.md +3 -3
  53. package/src/datapack.ts +10 -10
  54. package/src/edinburgh.ts +46 -58
  55. package/src/indexes.ts +338 -802
  56. package/src/migrate.ts +15 -37
  57. package/src/models.ts +617 -314
  58. package/src/types.ts +61 -54
  59. package/src/utils.ts +4 -4
  60. package/skill/BaseIndex.md +0 -16
  61. package/skill/BaseIndex_batchProcess.md +0 -10
  62. package/skill/BaseIndex_find.md +0 -7
  63. package/skill/Model.md +0 -22
  64. package/skill/Model_findAll.md +0 -12
  65. package/skill/Model_migrate.md +0 -34
  66. package/skill/Model_replaceInto.md +0 -16
  67. package/skill/PrimaryIndex.md +0 -8
  68. package/skill/PrimaryIndex_get.md +0 -17
  69. package/skill/PrimaryIndex_getLazy.md +0 -13
  70. package/skill/SecondaryIndex.md +0 -9
  71. package/skill/UniqueIndex.md +0 -9
  72. package/skill/UniqueIndex_get.md +0 -17
  73. package/skill/dump.md +0 -8
  74. package/skill/index.md +0 -32
  75. package/skill/primary.md +0 -26
  76. package/skill/registerModel.md +0 -26
  77. package/skill/unique.md +0 -32
package/README.md CHANGED
@@ -22,85 +22,82 @@ import * as E from "edinburgh";
22
22
  // Initialize the database (optional, defaults to ".edinburgh")
23
23
  E.init("./my-database");
24
24
 
25
- // Define a model
26
- @E.registerModel
27
- class User extends E.Model<User> {
28
- // Define a primary key (optional, defaults to using the "id" field)
29
- static pk = E.primary(User, "id");
30
- // Define a unique index on the email field
31
- static byEmail = E.unique(User, "email");
32
-
33
- // Define fields with simple types -- they will be type-checked at compile time and validated at runtime.
25
+ const User = E.defineModel("User", class {
34
26
  id = E.field(E.identifier);
35
27
  name = E.field(E.string);
36
28
  age = E.field(E.number);
37
- email = E.field(E.opt(E.string)); // TypeScript: undefined | string
38
-
39
- // Link to another instance of this model
40
- supervisor = E.field(E.opt(E.link(User)));
41
-
42
- // A field with a more elaborate type. In TypeScript: `User | User[] | "unknown" | "whatever"`
43
- something = E.field(E.or(E.link(User), E.array(E.link(User)), E.literal("unknown"), E.literal("whatever")), { default: "unknown" });
44
- }
29
+ email = E.field(E.opt(E.string));
30
+ // Optional link to another instance of this model (needs a function as `User` is not defined yet at this point)
31
+ supervisor = E.field(E.opt(E.link(() => User)));
32
+ // A field with a more elaborate type. In TypeScript: `User | User[] | "unknown" | "whatever"`, defaulting to "unknown".
33
+ something = E.field(
34
+ E.or(
35
+ E.link(() => User),
36
+ E.array(E.link(() => User)),
37
+ E.literal("unknown"),
38
+ E.literal("whatever")
39
+ ),
40
+ { default: "unknown" }
41
+ );
42
+ }, {
43
+ pk: "id",
44
+ unique: {
45
+ email: "email",
46
+ },
47
+ });
45
48
 
46
- // Use in transactions
47
49
  await E.transact(() => {
48
- const boss = new User({
49
- name: "Big Boss",
50
- age: 50,
51
- });
52
- const john = new User({ // Unique 'id' is automatically generated if not provided
53
- name: "John Doe",
50
+ // Unique 'id' values are auto-generated if not provided
51
+ const boss = new User({ name: "Big Boss", age: 50 });
52
+ new User({
53
+ name: "John Doe",
54
54
  age: 41,
55
55
  email: "john@example.com",
56
56
  supervisor: boss, // Link to another model instance
57
57
  });
58
+ // Newly instantiated models are automatically saved to the database on transaction commit
58
59
  });
59
60
 
60
61
  await E.transact(() => {
61
62
  // Query by unique index
62
- const john = User.byEmail.get("john@example.com")!;
63
+ const john = User.getBy("email", "john@example.com")!;
63
64
 
64
65
  // The transaction will retry if there's a conflict, such as another transaction
65
66
  // modifying the same user (from another async function or another process)
66
67
  john.age++;
67
68
 
68
69
  // The supervisor object is lazy loaded on first access
69
- console.log(`${john.supervisor!.name} is ${john.name}'s supervisor`);
70
+ console.log(`${john.supervisor!.name} is ${john.name}'s supervisor`);
70
71
  });
71
72
  ```
72
73
 
73
74
  ## Tutorial
74
75
 
75
- ### TypeScript Configuration
76
-
77
- When using TypeScript to transpile to JavaScript, make sure to enable the following options in your `tsconfig.json`:
78
-
79
- ```json
80
- {
81
- "compilerOptions": {
82
- "target": "es2022",
83
- "experimentalDecorators": true
84
- }
85
- }
86
- ```
87
76
 
88
77
  ### Defining Models
89
78
 
90
- Models are classes that extend `E.Model<Self>` and use the `@E.registerModel` decorator:
79
+ A model is defined using the `E.defineModel()` function by passing it..
80
+ - a consistent table name,
81
+ - an (anonymous) class containing `E.field` database properties and optionally regular properties/methods, and
82
+ - optional key/index configuration.
91
83
 
92
84
  ```typescript
93
85
  import * as E from "edinburgh";
94
86
 
95
- @E.registerModel
96
- class User extends E.Model<User> {
97
- static pk = E.primary(User, "id");
98
-
87
+ const User = E.defineModel("User", class {
99
88
  id = E.field(E.identifier);
100
89
  name = E.field(E.string);
101
90
  email = E.field(E.string);
102
91
  age = E.field(E.number);
103
- }
92
+ }, {
93
+ pk: "id",
94
+ unique: {
95
+ email: "email",
96
+ },
97
+ });
98
+ // Add this if you want to use User as a type annotation (e.g. `let u: User`).
99
+ // Not needed just to call User.get(), User.find(), new User(), etc.
100
+ type User = InstanceType<typeof User>;
104
101
  ```
105
102
 
106
103
  Instance fields are declared with `E.field(type, options?)`. Available types:
@@ -124,16 +121,13 @@ Instance fields are declared with `E.field(type, options?)`. Available types:
124
121
  #### Defaults
125
122
 
126
123
  ```typescript
127
- @E.registerModel
128
- class Post extends E.Model<Post> {
129
- static pk = E.primary(Post, "id");
130
-
131
- id = E.field(E.identifier); // auto-generated
124
+ const Post = E.defineModel("Post", class {
125
+ id = E.field(E.identifier); // auto-generated
132
126
  title = E.field(E.string);
133
127
  status = E.field(E.or("draft", "published"), {default: "draft"});
134
- tags = E.field(E.array(E.string), {default: () => []}); // use function for mutable defaults
135
- createdAt = E.field(E.dateTime); // dateTime defaults to new Date()
136
- }
128
+ tags = E.field(E.array(E.string), {default: () => []}); // use function for mutable defaults
129
+ createdAt = E.field(E.dateTime); // dateTime defaults to new Date()
130
+ }, { pk: "id" });
137
131
  ```
138
132
 
139
133
  ### Transactions
@@ -146,19 +140,19 @@ E.init("./my-database");
146
140
 
147
141
  // Create
148
142
  await E.transact(() => {
149
- const user = new User({name: "Alice", email: "alice@example.com", age: 30});
150
- // user.id is auto-generated
143
+ // User.id is auto-generated
144
+ new User({name: "Alice", email: "alice@example.com", age: 30});
151
145
  });
152
146
 
153
147
  // Read + Update
154
148
  await E.transact(() => {
155
- const user = User.byEmail.get("alice@example.com");
149
+ const user = User.getBy("email", "alice@example.com");
156
150
  if (user) user.age++;
157
151
  });
158
152
 
159
153
  // Return values from transactions
160
154
  const name = await E.transact(() => {
161
- const user = User.byEmail.get("alice@example.com");
155
+ const user = User.getBy("email", "alice@example.com");
162
156
  return user?.name;
163
157
  });
164
158
  ```
@@ -170,30 +164,29 @@ Transactions auto-retry on conflict (up to 6 times by default). Keep transaction
170
164
  Edinburgh supports three index types:
171
165
 
172
166
  ```typescript
173
- @E.registerModel
174
- class Product extends E.Model<Product> {
175
- static pk = E.primary(Product, "sku"); // primary: one per model, stores data
176
- static byName = E.unique(Product, "name"); // unique: enforces uniqueness + fast lookup
177
- static byCategory = E.index(Product, "category");// secondary: non-unique, for queries
178
-
167
+ const Product = E.defineModel("Product", class {
179
168
  sku = E.field(E.string);
180
169
  name = E.field(E.string);
181
170
  category = E.field(E.string);
182
171
  price = E.field(E.number);
183
- }
172
+ }, {
173
+ pk: "sku",
174
+ unique: { name: "name" },
175
+ index: { category: "category" },
176
+ });
184
177
  ```
185
178
 
186
- If no `E.primary()` is declared, Edinburgh auto-creates one on an `id` field (adding `E.identifier` if missing).
179
+ If no `pk` is provided, Edinburgh auto-creates one on an `id` field, adding it as an `E.identifier` field if needed.
187
180
 
188
181
  #### Lookups
189
182
 
190
183
  ```typescript
191
184
  await E.transact(() => {
192
185
  // Primary key lookup
193
- const p = Product.pk.get("SKU-001");
186
+ const p1 = Product.get("SKU-001");
194
187
 
195
188
  // Unique index lookup
196
- const p2 = Product.byName.get("Widget");
189
+ const p2 = Product.getBy("name", "Widget");
197
190
 
198
191
  // All return undefined if not found
199
192
  });
@@ -201,54 +194,53 @@ await E.transact(() => {
201
194
 
202
195
  #### Range Queries
203
196
 
204
- All index types support `.find()` for range iteration:
197
+ Primary-key queries use `.find()`. Named unique and secondary indexes use `.findBy(name, ...)`:
205
198
 
206
199
  ```typescript
207
200
  await E.transact(() => {
208
201
  // Exact match
209
- for (const p of Product.byCategory.find({is: "electronics"})) {
202
+ for (const p of Product.findBy("category", {is: "electronics"})) {
210
203
  console.log(p.name);
211
204
  }
212
205
 
213
206
  // Range (inclusive)
214
- for (const p of Product.pk.find({from: "A", to: "M"})) {
207
+ for (const p of Product.find({from: "A", to: "M"})) {
215
208
  console.log(p.sku);
216
209
  }
217
210
 
218
211
  // Exclusive bounds
219
- for (const p of Product.pk.find({after: "A", before: "M"})) { ... }
212
+ for (const p of Product.find({after: "A", before: "M"})) { ... }
220
213
 
221
214
  // Open-ended
222
- for (const p of Product.pk.find({from: "M"})) { ... }
215
+ for (const p of Product.find({from: "M"})) { ... }
223
216
 
224
217
  // Reverse
225
- for (const p of Product.pk.find({reverse: true})) { ... }
218
+ for (const p of Product.find({reverse: true})) { ... }
226
219
 
227
220
  // Count and fetch helpers
228
- const count = Product.byCategory.find({is: "electronics"}).count();
229
- const first = Product.byCategory.find({is: "electronics"}).fetch(); // first match or undefined
221
+ const count = Product.findBy("category", {is: "electronics"}).count();
222
+ const first = Product.findBy("category", {is: "electronics"}).fetch(); // first match or undefined
230
223
  });
231
224
  ```
232
225
 
233
- #### Composite Indexes
226
+ #### Composite Primary Keys
234
227
 
235
228
  ```typescript
236
- @E.registerModel
237
- class Event extends E.Model<Event> {
238
- static pk = E.primary(Event, ["year", "month", "id"]);
239
-
229
+ const Event = E.defineModel("Event", class {
240
230
  year = E.field(E.number);
241
231
  month = E.field(E.number);
242
232
  id = E.field(E.identifier);
243
233
  title = E.field(E.string);
244
- }
234
+ }, {
235
+ pk: ["year", "month", "id"] as const,
236
+ });
245
237
 
246
238
  await E.transact(() => {
247
239
  // Prefix matching — find all events in 2025
248
- for (const e of Event.pk.find({is: [2025]})) { ... }
240
+ for (const e of Event.find({is: [2025]})) { ... }
249
241
 
250
242
  // Find events in March 2025
251
- for (const e of Event.pk.find({is: [2025, 3]})) { ... }
243
+ for (const e of Event.find({is: [2025, 3]})) { ... }
252
244
  });
253
245
  ```
254
246
 
@@ -257,10 +249,7 @@ await E.transact(() => {
257
249
  You can freely add regular methods, getters, and other non-persistent properties to model classes. These work normally in JavaScript but are **not stored in the database** and **not synchronized** across transactions or processes.
258
250
 
259
251
  ```typescript
260
- @E.registerModel
261
- class User extends E.Model<User> {
262
- static pk = E.primary(User, "id");
263
- id = E.field(E.identifier);
252
+ const User = E.defineModel("User", class {
264
253
  firstName = E.field(E.string);
265
254
  lastName = E.field(E.string);
266
255
 
@@ -275,70 +264,68 @@ class User extends E.Model<User> {
275
264
  greet(): string {
276
265
  return `Hello, ${this.fullName}!`;
277
266
  }
278
- }
267
+ });
279
268
  ```
280
269
 
281
270
  #### Computed Indexes
282
271
 
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).
272
+ Instead of naming fields, you can pass a function as an index specification. The function receives a model instance and returns an **array** of index key values. Each element creates a separate index entry, enabling multi-value indexes. Return `[]` to skip indexing for that instance (partial index).
284
273
 
285
274
  ```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
-
275
+ const Article = E.defineModel("Article", class {
293
276
  id = E.field(E.identifier);
294
277
  firstName = E.field(E.string);
295
278
  lastName = E.field(E.string);
296
279
  title = E.field(E.string);
297
280
  email = E.field(E.opt(E.string));
298
- }
281
+ }, {
282
+ pk: "id",
283
+ unique: {
284
+ fullName: (a: any) => [`${a.firstName} ${a.lastName}`], // computed covering unique index
285
+ },
286
+ index: {
287
+ domain: (a: any) => a.email ? [a.email.split("@")[1]] : [], // computed partial index
288
+ word: (a: any) => a.title.toLowerCase().split(" "), // computed multi-index
289
+ },
290
+ });
299
291
 
300
292
  await E.transact(() => {
301
293
  new Article({ firstName: "Jane", lastName: "Doe", title: "Hello World", email: "jane@acme.com" });
302
294
 
303
295
  // Lookup via computed unique index
304
- const jane = Article.byFullName.get("Jane Doe");
296
+ const jane = Article.getBy("fullName", "Jane Doe");
305
297
 
306
298
  // Multi-value: each word in the title is indexed separately
307
- for (const a of Article.byWord.find({is: "hello"})) { ... }
299
+ for (const a of Article.findBy("word", {is: "hello"})) { ... }
308
300
 
309
301
  // Partial index: articles without email are skipped
310
- for (const a of Article.byDomain.find({is: "acme.com"})) { ... }
302
+ for (const a of Article.findBy("domain", {is: "acme.com"})) { ... }
311
303
  });
312
304
  ```
313
305
 
314
- Computed indexes also support `find()` range queries and `batchProcess()`, just like field-based indexes.
315
-
316
- ### Relationships (Links)
306
+ ### Relationships
317
307
 
318
- Use `E.link(Model)` for foreign keys:
308
+ Use `E.link(Model)` for foreign keys. Use a thunk (a function that just returns a value) for forward references when needed:
319
309
 
320
310
  ```typescript
321
- @E.registerModel
322
- class Author extends E.Model<Author> {
323
- static pk = E.primary(Author, "id");
311
+ const Author = E.defineModel("Author", class {
324
312
  id = E.field(E.identifier);
325
313
  name = E.field(E.string);
326
- }
314
+ }, { pk: "id" });
327
315
 
328
- @E.registerModel
329
- class Book extends E.Model<Book> {
330
- static pk = E.primary(Book, "id");
316
+ const Book = E.defineModel("Book", class {
331
317
  id = E.field(E.identifier);
332
318
  title = E.field(E.string);
333
319
  author = E.field(E.link(Author));
334
- }
320
+ }, { pk: "id" });
335
321
 
336
322
  await E.transact(() => {
337
323
  const author = new Author({name: "Tolkien"});
338
324
  const book = new Book({title: "The Hobbit", author});
339
325
 
340
326
  // Later: linked models are lazy-loaded on property access
341
- const b = Book.pk.get(book.id)!;
327
+ const b = Book.get(book.id)!;
328
+ console.log(b.author.id); // no need to load yet..
342
329
  console.log(b.author.name); // loads Author automatically (~1µs)
343
330
  });
344
331
  ```
@@ -347,7 +334,7 @@ await E.transact(() => {
347
334
 
348
335
  ```typescript
349
336
  await E.transact(() => {
350
- const user = User.pk.get(someId);
337
+ const user = User.get(someId);
351
338
  if (user) user.delete();
352
339
  });
353
340
  ```
@@ -365,10 +352,16 @@ await E.transact(() => {
365
352
  user.preventPersist(); // exclude from commit
366
353
  });
367
354
 
368
- // findAll iterates all instances
355
+ // find() iterates all instances (or use range options)
369
356
  await E.transact(() => {
370
- for (const user of User.findAll()) { ... }
371
- for (const user of User.findAll({reverse: true})) { ... }
357
+ for (const user of User.find()) { ... }
358
+ for (const user of User.find({reverse: true})) { ... }
359
+
360
+ // {fetch: 'first'} returns a single instance or undefined
361
+ const first = User.find({fetch: 'first'});
362
+
363
+ // {fetch: 'single'} returns a single instance, or throws if there are none or more than one
364
+ const only = User.find({fetch: 'single'});
372
365
  });
373
366
 
374
367
  // replaceInto: upsert by primary key
@@ -382,7 +375,7 @@ await E.transact(() => {
382
375
  For large datasets, `batchProcess` auto-commits in batches:
383
376
 
384
377
  ```typescript
385
- await Product.byCategory.batchProcess({is: "old"}, (product) => {
378
+ await Product.batchProcess({ limitRows: 1000 }, (product) => {
386
379
  product.category = "archived";
387
380
  });
388
381
  // Commits every ~1 second or 4096 rows (configurable via limitSeconds, limitRows)
@@ -390,12 +383,10 @@ await Product.byCategory.batchProcess({is: "old"}, (product) => {
390
383
 
391
384
  ### Lazy Schema Migrations
392
385
 
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.
386
+ When you change a model's schema, Edinburgh lazily migrates old records on access. You can provide a `static migrate(record)` function to transform old rows:
394
387
 
395
388
  ```typescript
396
- @E.registerModel
397
- class User extends E.Model<User> {
398
- static pk = E.primary(User, "id");
389
+ const UserV2 = E.defineModel("User", class {
399
390
  id = E.field(E.identifier);
400
391
  name = E.field(E.string);
401
392
  role = E.field(E.string); // newly added field
@@ -403,7 +394,7 @@ class User extends E.Model<User> {
403
394
  static migrate(record: Record<string, any>) {
404
395
  record.role ??= record.name.indexOf("admin") >= 0 ? "admin" : "user"; // set role based on name for old records
405
396
  }
406
- }
397
+ })
407
398
  ```
408
399
 
409
400
  Edinburgh will lazily (re)run the `migrate` function on an instance whenever its implementation (the literal function code) has changed. For robustness, make sure that your `migrate` function...
@@ -438,9 +429,7 @@ console.log(result.secondaries); // { User: 1500 }
438
429
  Compute derived fields before data is written:
439
430
 
440
431
  ```typescript
441
- @E.registerModel
442
- class Article extends E.Model<Article> {
443
- static pk = E.primary(Article, "id");
432
+ const Article = E.defineModel("Article", class {
444
433
  id = E.field(E.identifier);
445
434
  title = E.field(E.string);
446
435
  slug = E.field(E.string);
@@ -448,7 +437,7 @@ class Article extends E.Model<Article> {
448
437
  preCommit() {
449
438
  this.slug = this.title.toLowerCase().replace(/\s+/g, "-");
450
439
  }
451
- }
440
+ });
452
441
  ```
453
442
 
454
443
  ### Change Tracking
@@ -489,11 +478,14 @@ ln -s ../../node_modules/edinburgh/skill .claude/skills/edinburgh
489
478
 
490
479
  The following is auto-generated from `src/edinburgh.ts`:
491
480
 
492
- ### scheduleInit · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L6)
481
+ ### currentTxn · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L66)
493
482
 
494
- **Signature:** `() => void`
483
+ Returns the current transaction from AsyncLocalStorage.
484
+ Throws if called outside a transact() callback.
495
485
 
496
- ### init · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L67)
486
+ **Signature:** `() => Transaction`
487
+
488
+ ### init · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L85)
497
489
 
498
490
  Initialize the database with the specified directory path.
499
491
  This function may be called multiple times with the same parameters. If it is not called before the first transact(),
@@ -511,7 +503,7 @@ the database will be automatically initialized with the default directory.
511
503
  init("./my-database");
512
504
  ```
513
505
 
514
- ### transact · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L118)
506
+ ### transact · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L136)
515
507
 
516
508
  Executes a function within a database transaction context.
517
509
 
@@ -531,7 +523,7 @@ times.
531
523
 
532
524
  **Parameters:**
533
525
 
534
- - `fn: () => T` - - The function to execute within the transaction context. Receives a Transaction instance.
526
+ - `fn: () => T` - The function to execute within the transaction context. Receives a Transaction instance.
535
527
 
536
528
  **Returns:** A promise that resolves with the function's return value.
537
529
 
@@ -545,7 +537,7 @@ times.
545
537
 
546
538
  ```typescript
547
539
  const paid = await E.transact(() => {
548
- const user = User.pk.get("john_doe");
540
+ const user = User.get("john_doe");
549
541
  if (user.credits > 0) {
550
542
  user.credits--;
551
543
  return true;
@@ -556,12 +548,12 @@ const paid = await E.transact(() => {
556
548
  ```typescript
557
549
  // Transaction with automatic retry on conflicts
558
550
  await E.transact(() => {
559
- const counter = Counter.pk.get("global") || new Counter({id: "global", value: 0});
551
+ const counter = Counter.get("global") || new Counter({id: "global", value: 0});
560
552
  counter.value++;
561
553
  });
562
554
  ```
563
555
 
564
- ### setMaxRetryCount · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L209)
556
+ ### setMaxRetryCount · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L226)
565
557
 
566
558
  Set the maximum number of retries for a transaction in case of conflicts.
567
559
  The default value is 6. Setting it to 0 will disable retries and cause transactions to fail immediately on conflict.
@@ -572,142 +564,121 @@ The default value is 6. Setting it to 0 will disable retries and cause transacti
572
564
 
573
565
  - `count: number` - The maximum number of retries for a transaction.
574
566
 
575
- ### setOnSaveCallback · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L223)
567
+ ### setOnSaveCallback · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L240)
576
568
 
577
569
  Set a callback function to be called after a model is saved and committed.
578
570
 
579
- **Signature:** `(callback: (commitId: number, items: Map<Model<any>, Change>) => void) => void`
571
+ **Signature:** `(callback: (commitId: number, items: Map<ModelBase, Change>) => void) => void`
580
572
 
581
573
  **Parameters:**
582
574
 
583
- - `callback: ((commitId: number, items: Map<Model<any>, Change>) => void) | undefined` - The callback function to set. It gets called after each successful
575
+ - `callback: ((commitId: number, items: Map<Model<unknown>, Change>) => void) | undefined` - The callback function to set. It gets called after each successful
584
576
  `transact()` commit that has changes, with the following arguments:
585
577
  - A sequential number. Higher numbers have been committed after lower numbers.
586
578
  - A map of model instances to their changes. The change can be "created", "deleted", or an object containing the old values.
587
579
 
588
- ### deleteEverything · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L228)
580
+ ### Model · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
589
581
 
590
- **Signature:** `() => Promise<void>`
582
+ **Type:** `typeof ModelBase`
591
583
 
592
- ### Model · [abstract class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L221)
584
+ ### ModelClass · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
593
585
 
594
- [object Object],[object Object],[object Object],[object Object],[object Object]
586
+ Runtime base constructor for model classes returned by `defineModel()`.
595
587
 
596
- **Type Parameters:**
588
+ Prefer the `ModelClass` type alias for annotations and the result of
589
+ `defineModel()` for concrete model classes.
597
590
 
598
- - `SUB` - The concrete model subclass (for proper typing).
591
+ **Type:** `typeof ModelClassRuntime`
599
592
 
600
- **Examples:**
593
+ ### AnyModelClass · [type](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L111)
601
594
 
602
- ```typescript
603
- ⁣@E.registerModel
604
- class User extends E.Model<User> {
605
- static pk = E.primary(User, "id");
606
-
607
- id = E.field(E.identifier);
608
- name = E.field(E.string);
609
- email = E.field(E.string);
610
-
611
- static byEmail = E.unique(User, "email");
612
- }
613
- ```
595
+ A model constructor with its generic information erased.
614
596
 
615
- #### Model.tableName · [static property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L225)
597
+ Useful when accepting or storing arbitrary registered model classes.
616
598
 
617
- The database table name (defaults to class name).
599
+ **Type:** `ModelClass<new () => any, readonly any[], any, any>`
618
600
 
619
- **Type:** `string`
601
+ ### ModelBase · [abstract class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
620
602
 
621
- #### Model.override · [static property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L231)
603
+ Base class for all database models in the Edinburgh ORM.
622
604
 
623
- When true, registerModel replaces an existing model with the same tableName.
605
+ Models represent database entities with typed fields, automatic serialization,
606
+ change tracking, and relationship management. Model classes are created using
607
+ `E.defineModel()`.
624
608
 
625
- **Type:** `boolean`
609
+ ### Schema Evolution
626
610
 
627
- #### Model.fields · [static property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L233)
611
+ Edinburgh tracks the schema version of each model automatically. When you add, remove, or
612
+ change the types of fields, or add/remove indexes, Edinburgh detects the new schema version.
628
613
 
629
- Field configuration metadata.
614
+ **Lazy migration:** Changes to non-key field values are migrated lazily, when a row with an
615
+ old schema version is read from disk, it is deserialized using the old schema and optionally
616
+ transformed by the static `migrate()` function. This happens transparently on every read
617
+ and requires no downtime or batch processing.
630
618
 
631
- **Type:** `Record<string | number | symbol, FieldConfig<unknown>>`
619
+ **Batch migration (via `npx migrate-edinburgh` or `runMigration()`):** Certain schema changes
620
+ require an explicit migration run:
621
+ - Adding or removing secondary/unique indexes
622
+ - Changing the fields or types of an existing index
623
+ - A `migrate()` function that changes values used in secondary index fields
632
624
 
633
- #### Model.migrate · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
625
+ The batch migration tool populates new indexes, deletes orphaned ones, and updates index
626
+ entries whose values were changed by `migrate()`. It does *not* rewrite primary data rows
627
+ (lazy migration handles that).
634
628
 
635
- Optional migration function called when deserializing rows written with an older schema version.
636
- Receives a plain record with all fields (primary key fields + value fields) and should mutate it
637
- in-place to match the current schema.
629
+ ### Lifecycle Hooks
638
630
 
639
- This is called both during lazy loading (when a row is read from disk) and during batch
640
- migration (via `runMigration()` / `npx migrate-edinburgh`). The function's source code is hashed
641
- to detect changes. Modifying `migrate()` triggers a new schema version.
631
+ - **`static migrate(record)`**: Called when deserializing rows written with an older schema
632
+ version. Receives a plain record object; mutate it in-place to match the current schema.
642
633
 
643
- If `migrate()` changes values of fields used in secondary or unique indexes, those indexes
644
- will only be updated when `runMigration()` is run (not during lazy loading).
645
-
646
- **Signature:** `(record: Record<string, any>) => void`
647
-
648
- **Parameters:**
649
-
650
- - `record: Record<string, any>` - - A plain object with all field values from the old schema version.
634
+ - **`preCommit()`**: Called on each modified instance right before the transaction commits.
635
+ Useful for computing derived fields, enforcing cross-field invariants, or creating related
636
+ instances.
651
637
 
652
638
  **Examples:**
653
639
 
654
640
  ```typescript
655
- ⁣@E.registerModel
656
- class User extends E.Model<User> {
657
- static pk = E.primary(User, "id");
641
+ const User = E.defineModel("User", class {
658
642
  id = E.field(E.identifier);
659
643
  name = E.field(E.string);
660
- role = E.field(E.string); // new field
661
-
662
- static migrate(record: Record<string, any>) {
663
- record.role ??= "user"; // default for rows that predate the 'role' field
664
- }
665
- }
644
+ email = E.field(E.string);
645
+ }, {
646
+ pk: "id",
647
+ unique: { email: "email" },
648
+ });
649
+ // Optional: declare a companion type so `let u: User` works.
650
+ // Not needed if you only use `new User()`, `User.find()`, etc.
651
+ type User = InstanceType<typeof User>;
666
652
  ```
667
653
 
668
- #### Model.initFields · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
669
-
670
- Transform the model's `E.field` properties into the appropriate JavaScript properties. Normally this is done
671
- automatically when using `transact()`, but in case you need to access `Model.fields` directly before the first
672
- transaction, you can call this method manually.
673
-
674
- **Signature:** `(reset?: boolean) => void`
675
-
676
- **Parameters:**
677
-
678
- - `reset?: boolean`
679
-
680
- #### Model.findAll · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
654
+ #### ModelBase.migrate · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
681
655
 
682
- Find all instances of this model in the database, ordered by primary key.
656
+ Optional migration function called when deserializing rows written with an older schema version.
657
+ Receives a plain record with all fields and should mutate it in-place to match the current schema.
658
+ It runs during lazy loading and during `runMigration()`. Changing this method creates a new schema version.
659
+ If it updates values used by secondary or unique indexes, those index entries are refreshed only by `runMigration()`.
683
660
 
684
- **Signature:** `<T extends typeof Model<unknown>>(this: T, opts?: { reverse?: boolean; }) => IndexRangeIterator<T>`
661
+ **Signature:** `(record: Record<string, any>) => void`
685
662
 
686
663
  **Parameters:**
687
664
 
688
- - `this: T`
689
- - `opts?: {reverse?: boolean}` - - Optional parameters.
690
-
691
- **Returns:** An iterator.
692
-
693
- #### Model.replaceInto · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
694
-
695
- Load an existing instance by primary key and update it, or create a new one.
696
-
697
- The provided object must contain all primary key fields. If a matching row exists,
698
- the remaining properties from `obj` are set on the loaded instance. Otherwise a
699
- new instance is created with `obj` as its initial properties.
665
+ - `record: Record<string, any>` - A plain object containing the row's field values from the older schema version.
700
666
 
701
- **Signature:** `<T extends typeof Model<any>>(this: T, obj: Partial<Omit<InstanceType<T>, "constructor">>) => InstanceType<T>`
702
-
703
- **Parameters:**
667
+ **Examples:**
704
668
 
705
- - `this: T`
706
- - `obj: Partial<Omit<InstanceType<T>, "constructor">>` - - Partial model data that **must** include every primary key field.
669
+ ```typescript
670
+ const User = E.defineModel("User", class {
671
+ id = E.field(E.identifier);
672
+ name = E.field(E.string);
673
+ role = E.field(E.string);
707
674
 
708
- **Returns:** The loaded-and-updated or newly created instance.
675
+ static migrate(record: Record<string, any>) {
676
+ record.role ??= "user";
677
+ }
678
+ }, { pk: "id" });
679
+ ```
709
680
 
710
- #### model.preCommit · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
681
+ #### modelBase.preCommit · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
711
682
 
712
683
  Optional hook called on each modified instance right before the transaction commits.
713
684
  Runs before data is written to disk, so changes made here are included in the commit.
@@ -723,9 +694,7 @@ Common use cases:
723
694
  **Examples:**
724
695
 
725
696
  ```typescript
726
- ⁣@E.registerModel
727
- class Post extends E.Model<Post> {
728
- static pk = E.primary(Post, "id");
697
+ const Post = E.defineModel("Post", class {
729
698
  id = E.field(E.identifier);
730
699
  title = E.field(E.string);
731
700
  slug = E.field(E.string);
@@ -733,22 +702,22 @@ class Post extends E.Model<Post> {
733
702
  preCommit() {
734
703
  this.slug = this.title.toLowerCase().replace(/\s+/g, "-");
735
704
  }
736
- }
705
+ }, { pk: "id" });
737
706
  ```
738
707
 
739
- #### model.getPrimaryKey · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
708
+ #### modelBase.getPrimaryKey · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
740
709
 
741
710
  **Signature:** `() => Uint8Array<ArrayBufferLike>`
742
711
 
743
712
  **Returns:** The primary key for this instance.
744
713
 
745
- #### model.getPrimaryKeyHash · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
714
+ #### modelBase.getPrimaryKeyHash · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
746
715
 
747
716
  **Signature:** `() => number`
748
717
 
749
718
  **Returns:** A 53-bit positive integer non-cryptographic hash of the primary key, or undefined if not yet saved.
750
719
 
751
- #### model.isLazyField · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
720
+ #### modelBase.isLazyField · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
752
721
 
753
722
  **Signature:** `(field: keyof this) => boolean`
754
723
 
@@ -756,7 +725,7 @@ class Post extends E.Model<Post> {
756
725
 
757
726
  - `field: keyof this`
758
727
 
759
- #### model.preventPersist · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
728
+ #### modelBase.preventPersist · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
760
729
 
761
730
  Prevent this instance from being persisted to the database.
762
731
 
@@ -767,12 +736,12 @@ Prevent this instance from being persisted to the database.
767
736
  **Examples:**
768
737
 
769
738
  ```typescript
770
- const user = User.load("user123");
739
+ const user = User.get("user123");
771
740
  user.name = "New Name";
772
741
  user.preventPersist(); // Changes won't be saved
773
742
  ```
774
743
 
775
- #### model.delete · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
744
+ #### modelBase.delete · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
776
745
 
777
746
  Delete this model instance from the database.
778
747
 
@@ -783,11 +752,11 @@ Removes the instance and all its index entries from the database and prevents fu
783
752
  **Examples:**
784
753
 
785
754
  ```typescript
786
- const user = User.load("user123");
755
+ const user = User.get("user123");
787
756
  user.delete(); // Removes from database
788
757
  ```
789
758
 
790
- #### model.validate · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
759
+ #### modelBase.validate · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
791
760
 
792
761
  Validate all fields in this model instance.
793
762
 
@@ -795,7 +764,7 @@ Validate all fields in this model instance.
795
764
 
796
765
  **Parameters:**
797
766
 
798
- - `raise: boolean` (optional) - - If true, throw on first validation error.
767
+ - `raise: boolean` (optional) - If true, throw on first validation error.
799
768
 
800
769
  **Returns:** Array of validation errors (empty if valid).
801
770
 
@@ -809,7 +778,7 @@ if (errors.length > 0) {
809
778
  }
810
779
  ```
811
780
 
812
- #### model.isValid · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
781
+ #### modelBase.isValid · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
813
782
 
814
783
  Check if this model instance is valid.
815
784
 
@@ -824,46 +793,52 @@ const user = new User({name: "John"});
824
793
  if (!user.isValid()) shoutAtTheUser();
825
794
  ```
826
795
 
827
- #### model.getState · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
796
+ #### modelBase.getState · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
828
797
 
829
798
  **Signature:** `() => "created" | "deleted" | "loaded" | "lazy"`
830
799
 
831
- #### model.toString · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
800
+ #### modelBase.toString · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
832
801
 
833
802
  **Signature:** `() => string`
834
803
 
835
- #### model.[Symbol.for('nodejs.util.inspect.custom')] · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
804
+ #### modelBase.[Symbol.for('nodejs.util.inspect.custom')] · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
836
805
 
837
806
  **Signature:** `() => string`
838
807
 
839
- ### registerModel · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L113)
808
+ ### defineModel · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
840
809
 
841
810
  Register a model class with the Edinburgh ORM system.
842
811
 
843
- **Signature:** `<T extends typeof Model<unknown>>(MyModel: T) => T`
812
+ Converts a plain class into a fully-featured model with database persistence,
813
+ typed fields, primary key access, and optional secondary and unique indexes.
814
+
815
+ **Signature:** `<T extends new () => any, const PK extends (keyof FieldsOf<T> & string) | readonly (keyof FieldsOf<T> & string)[], const UNIQUE extends Record<string, (keyof FieldsOf<T> & string) | readonly (keyof FieldsOf<T> & string)[] | ((instance: any) => any)>, const INDEX extends Record<string, (keyof FieldsOf<T> & string) | ...`
844
816
 
845
817
  **Type Parameters:**
846
818
 
847
- - `T extends typeof Model<unknown>` - The model class type.
819
+ - `T extends new () => any`
820
+ - `PK extends (keyof FieldsOf<T> & string) | readonly (keyof FieldsOf<T> & string)[]`
821
+ - `UNIQUE extends Record<string, (keyof FieldsOf<T> & string) | readonly (keyof FieldsOf<T> & string)[] | ((instance: any) => any)>`
822
+ - `INDEX extends Record<string, (keyof FieldsOf<T> & string) | readonly (keyof FieldsOf<T> & string)[] | ((instance: any) => any)>`
848
823
 
849
824
  **Parameters:**
850
825
 
851
- - `MyModel: T` - - The model class to register.
826
+ - `tableName: string` - The database table name for this model.
827
+ - `cls: T` - A plain class whose properties use E.field().
828
+ - `opts?: { pk?: PK, unique?: UNIQUE, index?: INDEX, override?: boolean }` - Registration options.
852
829
 
853
- **Returns:** The enhanced model class with ORM capabilities.
830
+ **Returns:** The enhanced model constructor.
854
831
 
855
- **Examples:**
832
+ ### deleteEverything · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
856
833
 
857
- ```typescript
858
- ⁣@E.registerModel
859
- class User extends E.Model<User> {
860
- static pk = E.index(User, ["id"], "primary");
861
- id = E.field(E.identifier);
862
- name = E.field(E.string);
863
- }
864
- ```
834
+ Delete every key/value entry in the database and reinitialize all registered models.
835
+
836
+ This clears rows, index metadata, and schema-version records. It is mainly useful
837
+ for tests, local resets, or tooling that needs a completely empty database.
865
838
 
866
- ### field · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L89)
839
+ **Signature:** `() => Promise<void>`
840
+
841
+ ### field · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L76)
867
842
 
868
843
  Create a field definition for a model property.
869
844
 
@@ -879,27 +854,27 @@ This allows for both runtime introspection and compile-time type safety.
879
854
 
880
855
  **Parameters:**
881
856
 
882
- - `type: TypeWrapper<T>` - - The type wrapper for this field.
883
- - `options: Partial<FieldConfig<T>>` (optional) - - Additional field configuration options.
857
+ - `type: TypeWrapper<T>` - The type wrapper for this field.
858
+ - `options: Partial<FieldConfig<T>>` (optional) - Additional field configuration options.
884
859
 
885
860
  **Returns:** The field value (typed as T, but actually returns FieldConfig<T>).
886
861
 
887
862
  **Examples:**
888
863
 
889
864
  ```typescript
890
- class User extends E.Model<User> {
865
+ const User = E.defineModel("User", class {
891
866
  name = E.field(E.string, {description: "User's full name"});
892
867
  age = E.field(E.opt(E.number), {description: "User's age", default: 25});
893
- }
868
+ });
894
869
  ```
895
870
 
896
- ### string · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
871
+ ### string · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
897
872
 
898
873
  Type wrapper instance for the string type.
899
874
 
900
875
  **Value:** `TypeWrapper<string>`
901
876
 
902
- ### orderedString · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
877
+ ### orderedString · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
903
878
 
904
879
  Type wrapper instance for the ordered string type, which is just like a string
905
880
  except that it sorts lexicographically in the database (instead of by incrementing
@@ -909,37 +884,37 @@ may not contain null characters.
909
884
 
910
885
  **Value:** `TypeWrapper<string>`
911
886
 
912
- ### number · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
887
+ ### number · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
913
888
 
914
889
  Type wrapper instance for the number type.
915
890
 
916
891
  **Value:** `TypeWrapper<number>`
917
892
 
918
- ### dateTime · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
893
+ ### dateTime · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
919
894
 
920
895
  Type wrapper instance for the date/time type. Stored without timezone info, rounded to whole seconds.
921
896
 
922
897
  **Value:** `TypeWrapper<Date>`
923
898
 
924
- ### boolean · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
899
+ ### boolean · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
925
900
 
926
901
  Type wrapper instance for the boolean type.
927
902
 
928
903
  **Value:** `TypeWrapper<boolean>`
929
904
 
930
- ### identifier · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
905
+ ### identifier · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
931
906
 
932
907
  Type wrapper instance for the identifier type.
933
908
 
934
909
  **Value:** `TypeWrapper<string>`
935
910
 
936
- ### undef · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
911
+ ### undef · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
937
912
 
938
913
  Type wrapper instance for the 'undefined' type.
939
914
 
940
915
  **Value:** `TypeWrapper<undefined>`
941
916
 
942
- ### opt · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
917
+ ### opt · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
943
918
 
944
919
  Create an optional type wrapper (allows undefined).
945
920
 
@@ -951,7 +926,7 @@ Create an optional type wrapper (allows undefined).
951
926
 
952
927
  **Parameters:**
953
928
 
954
- - `inner: T` - - The inner type to make optional.
929
+ - `inner: T` - The inner type to make optional.
955
930
 
956
931
  **Returns:** A union type that accepts the inner type or undefined.
957
932
 
@@ -962,7 +937,7 @@ const optionalString = E.opt(E.string);
962
937
  const optionalNumber = E.opt(E.number);
963
938
  ```
964
939
 
965
- ### or · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
940
+ ### or · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
966
941
 
967
942
  Create a union type wrapper from multiple type choices.
968
943
 
@@ -974,7 +949,7 @@ Create a union type wrapper from multiple type choices.
974
949
 
975
950
  **Parameters:**
976
951
 
977
- - `choices: T` - - The type choices for the union.
952
+ - `choices: T` - The type choices for the union.
978
953
 
979
954
  **Returns:** A union type instance.
980
955
 
@@ -985,7 +960,7 @@ const stringOrNumber = E.or(E.string, E.number);
985
960
  const status = E.or("active", "inactive", "pending");
986
961
  ```
987
962
 
988
- ### array · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
963
+ ### array · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
989
964
 
990
965
  Create an array type wrapper with optional length constraints.
991
966
 
@@ -997,8 +972,8 @@ Create an array type wrapper with optional length constraints.
997
972
 
998
973
  **Parameters:**
999
974
 
1000
- - `inner: TypeWrapper<T>` - - Type wrapper for array elements.
1001
- - `opts: {min?: number, max?: number}` (optional) - - Optional constraints (min/max length).
975
+ - `inner: TypeWrapper<T>` - Type wrapper for array elements.
976
+ - `opts: {min?: number, max?: number}` (optional) - Optional constraints (min/max length).
1002
977
 
1003
978
  **Returns:** An array type instance.
1004
979
 
@@ -1009,7 +984,7 @@ const stringArray = E.array(E.string);
1009
984
  const boundedArray = E.array(E.number, {min: 1, max: 10});
1010
985
  ```
1011
986
 
1012
- ### set · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
987
+ ### set · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
1013
988
 
1014
989
  Create a Set type wrapper with optional length constraints.
1015
990
 
@@ -1021,8 +996,8 @@ Create a Set type wrapper with optional length constraints.
1021
996
 
1022
997
  **Parameters:**
1023
998
 
1024
- - `inner: TypeWrapper<T>` - - Type wrapper for set elements.
1025
- - `opts: {min?: number, max?: number}` (optional) - - Optional constraints (min/max length).
999
+ - `inner: TypeWrapper<T>` - Type wrapper for set elements.
1000
+ - `opts: {min?: number, max?: number}` (optional) - Optional constraints (min/max length).
1026
1001
 
1027
1002
  **Returns:** A set type instance.
1028
1003
 
@@ -1033,7 +1008,7 @@ const stringSet = E.set(E.string);
1033
1008
  const boundedSet = E.set(E.number, {min: 1, max: 10});
1034
1009
  ```
1035
1010
 
1036
- ### record · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
1011
+ ### record · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
1037
1012
 
1038
1013
  Create a Record type wrapper for key-value objects with string or number keys.
1039
1014
 
@@ -1045,7 +1020,7 @@ Create a Record type wrapper for key-value objects with string or number keys.
1045
1020
 
1046
1021
  **Parameters:**
1047
1022
 
1048
- - `inner: TypeWrapper<T>` - - Type wrapper for record values.
1023
+ - `inner: TypeWrapper<T>` - Type wrapper for record values.
1049
1024
 
1050
1025
  **Returns:** A record type instance.
1051
1026
 
@@ -1055,7 +1030,7 @@ Create a Record type wrapper for key-value objects with string or number keys.
1055
1030
  const scores = E.record(E.number); // Record<string | number, number>
1056
1031
  ```
1057
1032
 
1058
- ### literal · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
1033
+ ### literal · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
1059
1034
 
1060
1035
  Create a literal type wrapper for a constant value.
1061
1036
 
@@ -1067,7 +1042,7 @@ Create a literal type wrapper for a constant value.
1067
1042
 
1068
1043
  **Parameters:**
1069
1044
 
1070
- - `value: T` - - The literal value.
1045
+ - `value: T` - The literal value.
1071
1046
 
1072
1047
  **Returns:** A literal type instance.
1073
1048
 
@@ -1078,274 +1053,227 @@ const statusType = E.literal("active");
1078
1053
  const countType = E.literal(42);
1079
1054
  ```
1080
1055
 
1081
- ### link · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
1056
+ ### link · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
1082
1057
 
1083
1058
  Create a link type wrapper for model relationships.
1084
1059
 
1085
- **Signature:** `<const T extends typeof Model<any>>(TargetModel: T) => TypeWrapper<InstanceType<T>>`
1060
+ **Signature:** `{ <const T extends new (...args: any[]) => Model<any>>(TargetModel: T): TypeWrapper<InstanceType<T>>; <const T extends new (...args: any[]) => Model<any>>(TargetModel: () => T): TypeWrapper<...>; }`
1086
1061
 
1087
1062
  **Type Parameters:**
1088
1063
 
1089
- - `T extends typeof Model<any>` - The target model class.
1064
+ - `T extends new (...args: any[]) => Model<any>` - The target model class.
1090
1065
 
1091
1066
  **Parameters:**
1092
1067
 
1093
- - `TargetModel: T` - - The model class this link points to.
1068
+ - `TargetModel: T` - The model class this link points to.
1094
1069
 
1095
1070
  **Returns:** A link type instance.
1096
1071
 
1097
1072
  **Examples:**
1098
1073
 
1099
1074
  ```typescript
1100
- class User extends E.Model<User> {
1101
- posts = E.field(E.array(E.link(Post, 'author')));
1102
- }
1075
+ const Author = E.defineModel("Author", class {
1076
+ id = E.field(E.identifier);
1077
+ posts = E.field(E.array(E.link(() => Book)));
1078
+ }, { pk: "id" });
1103
1079
 
1104
- class Post extends E.Model<Post> {
1105
- author = E.field(E.link(User));
1106
- }
1080
+ const Book = E.defineModel("Book", class {
1081
+ id = E.field(E.identifier);
1082
+ author = E.field(E.link(Author));
1083
+ }, { pk: "id" });
1107
1084
  ```
1108
1085
 
1109
- ### index · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
1110
-
1111
- Create a secondary index on model fields, or a computed secondary index using a function.
1112
-
1113
- For field-based indexes, pass a field name or array of field names.
1114
- For computed indexes, pass a function that takes a model instance and returns an array of
1115
- index keys. Return `[]` to skip indexing for that instance. Each array element creates a
1116
- separate index entry, enabling multi-value indexes (e.g., indexing by each word in a name).
1117
-
1118
- **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)[]>(...`
1119
-
1120
- **Type Parameters:**
1086
+ ### dump · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L243)
1121
1087
 
1122
- - `M extends typeof Model` - The model class.
1123
- - `V` - The computed index value type (for function-based indexes).
1124
-
1125
- **Parameters:**
1088
+ **Signature:** `() => void`
1126
1089
 
1127
- - `MyModel: M` - - The model class to create the index for.
1128
- - `fn: (instance: InstanceType<M>) => V[]`
1090
+ ### FindOptions · [type](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L115)
1091
+
1092
+ Range-query options accepted by `find()`, `findBy()`, `batchProcess()`, and `batchProcessBy()`.
1093
+
1094
+ Supports exact-match lookups via `is`, inclusive bounds via `from` / `to`,
1095
+ exclusive bounds via `after` / `before`, and reverse scans.
1096
+
1097
+ For single-field indexes, values can be passed directly. For composite indexes,
1098
+ pass tuples or partial tuples for prefix matching.
1099
+
1100
+ **Type:** `(
1101
+ (
1102
+ {is: ArrayOrOnlyItem<ARG_TYPES>;} // Shortcut for setting `from` and `to` to the same value
1103
+ |
1104
+ (
1105
+ (
1106
+ {from: ArrayOrOnlyItem<ARG_TYPES>;}
1107
+ |
1108
+ {after: ArrayOrOnlyItem<ARG_TYPES>;}
1109
+ |
1110
+ {}
1111
+ )
1112
+ &
1113
+ (
1114
+ {to: ArrayOrOnlyItem<ARG_TYPES>;}
1115
+ |
1116
+ {before: ArrayOrOnlyItem<ARG_TYPES>;}
1117
+ |
1118
+ {}
1119
+ )
1120
+ )
1121
+ ) &
1122
+ {
1123
+ reverse?: boolean;
1124
+ }
1125
+ & (FETCH extends undefined ? { fetch?: undefined } : { fetch: FETCH })
1126
+ )`
1129
1127
 
1130
- **Returns:** A new SecondaryIndex instance.
1128
+ ### IndexRangeIterator · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L73)
1131
1129
 
1132
- **Examples:**
1130
+ Iterator for range queries on indexes.
1131
+ Handles common iteration logic for both primary and unique indexes.
1132
+ Extends built-in Iterator to provide map/filter/reduce/toArray/etc.
1133
1133
 
1134
- ```typescript
1135
- class User extends E.Model<User> {
1136
- static byAge = E.index(User, "age");
1137
- static byTagsDate = E.index(User, ["tags", "createdAt"]);
1138
- static byWord = E.index(User, (u: User) => u.name.split(" "));
1139
- }
1140
- ```
1134
+ **Type Parameters:**
1141
1135
 
1142
- ### primary · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
1136
+ - `ITEM`
1143
1137
 
1144
- Create a primary index on model fields.
1138
+ #### indexRangeIterator.[Symbol.iterator] · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L78)
1145
1139
 
1146
- **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<...>; }`
1140
+ **Signature:** `() => this`
1147
1141
 
1148
- **Type Parameters:**
1142
+ #### indexRangeIterator.next · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L80)
1149
1143
 
1150
- - `M extends typeof Model` - The model class.
1151
- - `F extends (keyof InstanceType<M> & string)` - The field name (for single field index).
1144
+ **Signature:** `() => IteratorResult<ITEM, any>`
1152
1145
 
1153
- **Parameters:**
1146
+ #### indexRangeIterator.count · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L102)
1154
1147
 
1155
- - `MyModel: M` - - The model class to create the index for.
1156
- - `field: F` - - Single field name for simple indexes.
1148
+ **Signature:** `() => number`
1157
1149
 
1158
- **Returns:** A new PrimaryIndex instance.
1150
+ #### indexRangeIterator.fetch · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L105)
1159
1151
 
1160
- **Examples:**
1152
+ **Signature:** `() => ITEM`
1161
1153
 
1162
- ```typescript
1163
- class User extends E.Model<User> {
1164
- static pk = E.primary(User, ["id"]);
1165
- static pkSingle = E.primary(User, "id");
1166
- }
1167
- ```
1168
-
1169
- ### unique · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
1154
+ ### Change · [type](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L85)
1170
1155
 
1171
- Create a unique index on model fields, or a computed unique index using a function.
1156
+ **Type:** `Record<any, any> | "created" | "deleted"`
1172
1157
 
1173
- For field-based indexes, pass a field name or array of field names.
1174
- For computed indexes, pass a function that takes a model instance and returns an array of
1175
- index keys. Return `[]` to skip indexing for that instance. Each array element creates a
1176
- separate index entry, enabling multi-value indexes (e.g., indexing by each word in a name).
1158
+ ### FieldConfig · [interface](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L37)
1177
1159
 
1178
- **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...`
1160
+ Configuration interface for model fields.
1179
1161
 
1180
1162
  **Type Parameters:**
1181
1163
 
1182
- - `M extends typeof Model` - The model class.
1183
- - `V` - The computed index value type (for function-based indexes).
1184
-
1185
- **Parameters:**
1186
-
1187
- - `MyModel: M` - - The model class to create the index for.
1188
- - `fn: (instance: InstanceType<M>) => V[]`
1164
+ - `T` - The field type.
1189
1165
 
1190
- **Returns:** A new UniqueIndex instance.
1166
+ #### fieldConfig.type · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L45)
1191
1167
 
1192
- **Examples:**
1168
+ The type wrapper that defines how this field is serialized/validated.
1193
1169
 
1194
- ```typescript
1195
- class User extends E.Model<User> {
1196
- static byEmail = E.unique(User, "email");
1197
- static byNameAge = E.unique(User, ["name", "age"]);
1198
- static byFullName = E.unique(User, (u: User) => [`${u.firstName} ${u.lastName}`]);
1199
- }
1200
- ```
1170
+ **Type:** `TypeWrapper<T>`
1201
1171
 
1202
- ### dump · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
1172
+ #### fieldConfig.description · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L47)
1203
1173
 
1204
- Dump database contents for debugging.
1174
+ Optional human-readable description of the field.
1205
1175
 
1206
- Prints all indexes and their data to the console for inspection.
1207
- This is primarily useful for development and debugging purposes.
1176
+ **Type:** `string`
1208
1177
 
1209
- **Signature:** `() => void`
1178
+ #### fieldConfig.default · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L49)
1210
1179
 
1211
- ### BaseIndex · [abstract class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L123)
1180
+ Optional default value or function that generates default values.
1212
1181
 
1213
- Base class for database indexes for efficient lookups on model fields.
1182
+ **Type:** `T | ((model: Record<string, any>) => T)`
1214
1183
 
1215
- Indexes enable fast queries on specific field combinations and enforce uniqueness constraints.
1184
+ ### TypeWrapper · [abstract class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L33)
1216
1185
 
1217
1186
  **Type Parameters:**
1218
1187
 
1219
- - `M extends typeof Model` - The model class this index belongs to.
1220
- - `F extends readonly (keyof InstanceType<M> & string)[]` - The field names that make up this index.
1221
- - `ARGS extends readonly any[] = IndexArgTypes<M, F>`
1188
+ - `T` - The TypeScript type this wrapper represents.
1222
1189
 
1223
- **Constructor Parameters:**
1190
+ #### typeWrapper.kind · [abstract property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L47)
1224
1191
 
1225
- - `MyModel`: - The model class this index belongs to.
1226
- - `_fieldNames`: - Array of field names that make up this index.
1192
+ A string identifier for this type, used during serialization
1227
1193
 
1228
- #### baseIndex.find · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
1229
-
1230
- **Signature:** `(opts?: FindOptions<ARGS>) => IndexRangeIterator<M>`
1231
-
1232
- **Parameters:**
1233
-
1234
- - `opts: FindOptions<ARGS>` (optional)
1194
+ **Type:** `string`
1235
1195
 
1236
- #### baseIndex.batchProcess · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
1196
+ #### typeWrapper.serialize · [abstract method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L52)
1237
1197
 
1238
- [object Object],[object Object],[object Object]
1198
+ Serialize a value from an object property to a Pack.
1239
1199
 
1240
- **Signature:** `(opts: FindOptions<ARGS> & { limitSeconds?: number; limitRows?: number; }, callback: (row: InstanceType<M>) => void | Promise<void>) => Promise<...>`
1200
+ **Signature:** `(value: T, pack: DataPack) => void`
1241
1201
 
1242
1202
  **Parameters:**
1243
1203
 
1244
- - `opts: FindOptions<ARGS> & { limitSeconds?: number; limitRows?: number }` (optional) - - Query options (same as `find()`), plus:
1245
- - `callback: (row: InstanceType<M>) => void | Promise<void>` - - Called for each matching row within a transaction
1246
-
1247
- #### baseIndex.toString · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
1248
-
1249
- **Signature:** `() => string`
1250
-
1251
- ### UniqueIndex · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
1252
-
1253
- Unique index that stores references to the primary key.
1254
-
1255
- **Type Parameters:**
1256
-
1257
- - `M extends typeof Model` - The model class this index belongs to.
1258
- - `F extends readonly (keyof InstanceType<M> & string)[]` - The field names that make up this index.
1259
- - `ARGS extends readonly any[] = IndexArgTypes<M, F>`
1204
+ - `value: T` - The value to serialize.
1205
+ - `pack: DataPack` - The Pack instance to write to.
1260
1206
 
1261
- #### uniqueIndex.get · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
1207
+ #### typeWrapper.deserialize · [abstract method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L59)
1262
1208
 
1263
- Get a model instance by unique index key values.
1209
+ Deserialize a value from a Pack into an object property.
1264
1210
 
1265
- **Signature:** `(...args: ARGS) => InstanceType<M>`
1211
+ **Signature:** `(pack: DataPack) => T`
1266
1212
 
1267
1213
  **Parameters:**
1268
1214
 
1269
- - `args: ARGS` - - The unique index key values.
1215
+ - `pack: DataPack` - The Pack instance to read from.
1270
1216
 
1271
- **Returns:** The model instance if found, undefined otherwise.
1217
+ #### typeWrapper.getError · [abstract method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L64)
1272
1218
 
1273
- **Examples:**
1219
+ Validate a value.
1274
1220
 
1275
- ```typescript
1276
- const userByEmail = User.byEmail.get("john@example.com");
1277
- ```
1278
-
1279
- ### PrimaryIndex · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
1221
+ **Signature:** `(value: T) => void | DatabaseError`
1280
1222
 
1281
- Primary index that stores the actual model data.
1223
+ **Parameters:**
1282
1224
 
1283
- **Type Parameters:**
1225
+ - `value: T` - The value to validate.
1284
1226
 
1285
- - `M extends typeof Model` - The model class this index belongs to.
1286
- - `F extends readonly (keyof InstanceType<M> & string)[]` - The field names that make up this index.
1227
+ **Returns:** - A DatabaseError if validation fails.
1287
1228
 
1288
- #### primaryIndex.get · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
1229
+ #### typeWrapper.serializeType · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L68)
1289
1230
 
1290
- Get a model instance by primary key values.
1231
+ Serialize type metadata to a Pack (for schema serialization).
1291
1232
 
1292
- **Signature:** `(...args: IndexArgTypes<M, F>) => InstanceType<M>`
1233
+ **Signature:** `(pack: DataPack) => void`
1293
1234
 
1294
1235
  **Parameters:**
1295
1236
 
1296
- - `args: IndexArgTypes<M, F>` - - The primary key values.
1237
+ - `pack: DataPack` - The Pack instance to write to.
1297
1238
 
1298
- **Returns:** The model instance if found, undefined otherwise.
1239
+ #### typeWrapper.containsNull · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L77)
1299
1240
 
1300
- **Examples:**
1301
-
1302
- ```typescript
1303
- const user = User.pk.get("john_doe");
1304
- ```
1305
-
1306
- #### primaryIndex.getLazy · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
1307
-
1308
- Does the same as as `get()`, but will delay loading the instance from disk until the first
1309
- property access. In case it turns out the instance doesn't exist, an error will be thrown
1310
- at that time.
1241
+ Check if indexing should be skipped for this field value.
1311
1242
 
1312
- **Signature:** `(...args: IndexArgTypes<M, F>) => InstanceType<M>`
1243
+ **Signature:** `(value: T) => boolean`
1313
1244
 
1314
1245
  **Parameters:**
1315
1246
 
1316
- - `args: IndexArgTypes<M, F>` - Primary key field values. (Or a single Uint8Array containing the key.)
1247
+ - `value: T`
1317
1248
 
1318
- **Returns:** The (lazily loaded) model instance.
1249
+ **Returns:** true if indexing should be skipped.
1319
1250
 
1320
- ### SecondaryIndex · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L255)
1251
+ #### typeWrapper.toString · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L78)
1321
1252
 
1322
- Secondary index for non-unique lookups.
1253
+ **Signature:** `() => string`
1323
1254
 
1324
- **Type Parameters:**
1255
+ #### typeWrapper.clone · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L81)
1325
1256
 
1326
- - `M extends typeof Model` - The model class this index belongs to.
1327
- - `F extends readonly (keyof InstanceType<M> & string)[]` - The field names that make up this index.
1328
- - `ARGS extends readonly any[] = IndexArgTypes<M, F>`
1257
+ **Signature:** `(value: T) => T`
1329
1258
 
1330
- ### Change · [type](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L95)
1259
+ **Parameters:**
1331
1260
 
1332
- **Type:** `Record<any, any> | "created" | "deleted"`
1261
+ - `value: T`
1333
1262
 
1334
- ### Transaction · [interface](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L38)
1263
+ #### typeWrapper.equals · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L85)
1335
1264
 
1336
- #### transaction.id · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L41)
1265
+ **Signature:** `(value1: T, value2: T) => boolean`
1337
1266
 
1338
- **Type:** `number`
1339
-
1340
- #### transaction.instances · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L42)
1267
+ **Parameters:**
1341
1268
 
1342
- **Type:** `Set<Model<unknown>>`
1269
+ - `value1: T`
1270
+ - `value2: T`
1343
1271
 
1344
- #### transaction.instancesByPk · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L46)
1272
+ #### typeWrapper.getLinkedModel · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L90)
1345
1273
 
1346
- **Type:** `Map<number, Model<unknown>>`
1274
+ **Signature:** `() => AnyModelClass`
1347
1275
 
1348
- ### DatabaseError · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L158)
1276
+ ### DatabaseError · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L166)
1349
1277
 
1350
1278
  The DatabaseError class is used to represent errors that occur during database operations.
1351
1279
  It extends the built-in Error class and has a machine readable error code string property.
@@ -1355,7 +1283,7 @@ Invalid function arguments will throw TypeError.
1355
1283
 
1356
1284
  **Value:** `DatabaseErrorConstructor`
1357
1285
 
1358
- ### runMigration · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L124)
1286
+ ### runMigration · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L121)
1359
1287
 
1360
1288
  Run database migration: populate secondary indexes for old-version rows,
1361
1289
  convert old primary indices, rewrite row data, and clean up orphaned indices.
@@ -1366,7 +1294,7 @@ convert old primary indices, rewrite row data, and clean up orphaned indices.
1366
1294
 
1367
1295
  - `options: MigrationOptions` (optional)
1368
1296
 
1369
- ### MigrationOptions · [interface](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L16)
1297
+ ### MigrationOptions · [interface](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L13)
1370
1298
 
1371
1299
  #### migrationOptions.tables · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L18)
1372
1300
 
@@ -1374,45 +1302,45 @@ Limit migration to specific table names.
1374
1302
 
1375
1303
  **Type:** `string[]`
1376
1304
 
1377
- #### migrationOptions.populateSecondaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L23)
1305
+ #### migrationOptions.populateSecondaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L22)
1378
1306
 
1379
1307
  Populate secondary indexes for rows at old schema versions (default: true).
1380
1308
 
1381
1309
  **Type:** `boolean`
1382
1310
 
1383
- #### migrationOptions.migratePrimaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L32)
1311
+ #### migrationOptions.migratePrimaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L29)
1384
1312
 
1385
1313
  Convert old primary indices when primary key fields changed (default: true).
1386
1314
 
1387
1315
  **Type:** `boolean`
1388
1316
 
1389
- #### migrationOptions.rewriteData · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L40)
1317
+ #### migrationOptions.rewriteData · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L38)
1390
1318
 
1391
1319
  Rewrite all row data to the latest schema version (default: false).
1392
1320
 
1393
1321
  **Type:** `boolean`
1394
1322
 
1395
- #### migrationOptions.removeOrphans · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L46)
1323
+ #### migrationOptions.removeOrphans · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L45)
1396
1324
 
1397
1325
  Delete orphaned secondary/unique index entries (default: true).
1398
1326
 
1399
1327
  **Type:** `boolean`
1400
1328
 
1401
- #### migrationOptions.onProgress · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L48)
1329
+ #### migrationOptions.onProgress · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L45)
1402
1330
 
1403
1331
  Progress callback.
1404
1332
 
1405
1333
  **Type:** `(info: ProgressInfo) => void`
1406
1334
 
1407
- ### MigrationResult · [interface](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L51)
1335
+ ### MigrationResult · [interface](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L50)
1408
1336
 
1409
- #### migrationResult.secondaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L54)
1337
+ #### migrationResult.secondaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L52)
1410
1338
 
1411
1339
  Per-table counts of secondary index entries populated.
1412
1340
 
1413
1341
  **Type:** `Record<string, number>`
1414
1342
 
1415
- #### migrationResult.primaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L59)
1343
+ #### migrationResult.primaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L56)
1416
1344
 
1417
1345
  Per-table counts of old primary rows migrated.
1418
1346
 
@@ -1424,15 +1352,29 @@ Per-table conversion failure counts by reason.
1424
1352
 
1425
1353
  **Type:** `Record<string, Record<string, number>>`
1426
1354
 
1427
- #### migrationResult.rewritten · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L64)
1355
+ #### migrationResult.rewritten · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L63)
1428
1356
 
1429
1357
  Per-table counts of rows rewritten to latest version.
1430
1358
 
1431
1359
  **Type:** `Record<string, number>`
1432
1360
 
1433
- #### migrationResult.orphans · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L68)
1361
+ #### migrationResult.orphans · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L67)
1434
1362
 
1435
1363
  Number of orphaned index entries deleted.
1436
1364
 
1437
1365
  **Type:** `number`
1438
1366
 
1367
+ ### Transaction · [interface](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L54)
1368
+
1369
+ #### transaction.id · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L55)
1370
+
1371
+ **Type:** `number`
1372
+
1373
+ #### transaction.instances · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L56)
1374
+
1375
+ **Type:** `Map<number, ModelBase>`
1376
+
1377
+ ### txnStorage · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L59)
1378
+
1379
+ **Value:** `AsyncLocalStorage<Transaction>`
1380
+