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/skill/SKILL.md CHANGED
@@ -26,85 +26,82 @@ import * as E from "edinburgh";
26
26
  // Initialize the database (optional, defaults to ".edinburgh")
27
27
  E.init("./my-database");
28
28
 
29
- // Define a model
30
- @E.registerModel
31
- class User extends E.Model<User> {
32
- // Define a primary key (optional, defaults to using the "id" field)
33
- static pk = E.primary(User, "id");
34
- // Define a unique index on the email field
35
- static byEmail = E.unique(User, "email");
36
-
37
- // Define fields with simple types -- they will be type-checked at compile time and validated at runtime.
29
+ const User = E.defineModel("User", class {
38
30
  id = E.field(E.identifier);
39
31
  name = E.field(E.string);
40
32
  age = E.field(E.number);
41
- email = E.field(E.opt(E.string)); // TypeScript: undefined | string
42
-
43
- // Link to another instance of this model
44
- supervisor = E.field(E.opt(E.link(User)));
45
-
46
- // A field with a more elaborate type. In TypeScript: `User | User[] | "unknown" | "whatever"`
47
- something = E.field(E.or(E.link(User), E.array(E.link(User)), E.literal("unknown"), E.literal("whatever")), { default: "unknown" });
48
- }
33
+ email = E.field(E.opt(E.string));
34
+ // Optional link to another instance of this model (needs a function as `User` is not defined yet at this point)
35
+ supervisor = E.field(E.opt(E.link(() => User)));
36
+ // A field with a more elaborate type. In TypeScript: `User | User[] | "unknown" | "whatever"`, defaulting to "unknown".
37
+ something = E.field(
38
+ E.or(
39
+ E.link(() => User),
40
+ E.array(E.link(() => User)),
41
+ E.literal("unknown"),
42
+ E.literal("whatever")
43
+ ),
44
+ { default: "unknown" }
45
+ );
46
+ }, {
47
+ pk: "id",
48
+ unique: {
49
+ email: "email",
50
+ },
51
+ });
49
52
 
50
- // Use in transactions
51
53
  await E.transact(() => {
52
- const boss = new User({
53
- name: "Big Boss",
54
- age: 50,
55
- });
56
- const john = new User({ // Unique 'id' is automatically generated if not provided
57
- name: "John Doe",
54
+ // Unique 'id' values are auto-generated if not provided
55
+ const boss = new User({ name: "Big Boss", age: 50 });
56
+ new User({
57
+ name: "John Doe",
58
58
  age: 41,
59
59
  email: "john@example.com",
60
60
  supervisor: boss, // Link to another model instance
61
61
  });
62
+ // Newly instantiated models are automatically saved to the database on transaction commit
62
63
  });
63
64
 
64
65
  await E.transact(() => {
65
66
  // Query by unique index
66
- const john = User.byEmail.get("john@example.com")!;
67
+ const john = User.getBy("email", "john@example.com")!;
67
68
 
68
69
  // The transaction will retry if there's a conflict, such as another transaction
69
70
  // modifying the same user (from another async function or another process)
70
71
  john.age++;
71
72
 
72
73
  // The supervisor object is lazy loaded on first access
73
- console.log(`${john.supervisor!.name} is ${john.name}'s supervisor`);
74
+ console.log(`${john.supervisor!.name} is ${john.name}'s supervisor`);
74
75
  });
75
76
  ```
76
77
 
77
78
  ## Tutorial
78
79
 
79
- ### TypeScript Configuration
80
-
81
- When using TypeScript to transpile to JavaScript, make sure to enable the following options in your `tsconfig.json`:
82
-
83
- ```json
84
- {
85
- "compilerOptions": {
86
- "target": "es2022",
87
- "experimentalDecorators": true
88
- }
89
- }
90
- ```
91
80
 
92
81
  ### Defining Models
93
82
 
94
- Models are classes that extend `E.Model<Self>` and use the `@E.registerModel` decorator:
83
+ A model is defined using the `E.defineModel()` function by passing it..
84
+ - a consistent table name,
85
+ - an (anonymous) class containing `E.field` database properties and optionally regular properties/methods, and
86
+ - optional key/index configuration.
95
87
 
96
88
  ```typescript
97
89
  import * as E from "edinburgh";
98
90
 
99
- @E.registerModel
100
- class User extends E.Model<User> {
101
- static pk = E.primary(User, "id");
102
-
91
+ const User = E.defineModel("User", class {
103
92
  id = E.field(E.identifier);
104
93
  name = E.field(E.string);
105
94
  email = E.field(E.string);
106
95
  age = E.field(E.number);
107
- }
96
+ }, {
97
+ pk: "id",
98
+ unique: {
99
+ email: "email",
100
+ },
101
+ });
102
+ // Add this if you want to use User as a type annotation (e.g. `let u: User`).
103
+ // Not needed just to call User.get(), User.find(), new User(), etc.
104
+ type User = InstanceType<typeof User>;
108
105
  ```
109
106
 
110
107
  Instance fields are declared with `E.field(type, options?)`. Available types:
@@ -128,16 +125,13 @@ Instance fields are declared with `E.field(type, options?)`. Available types:
128
125
  #### Defaults
129
126
 
130
127
  ```typescript
131
- @E.registerModel
132
- class Post extends E.Model<Post> {
133
- static pk = E.primary(Post, "id");
134
-
135
- id = E.field(E.identifier); // auto-generated
128
+ const Post = E.defineModel("Post", class {
129
+ id = E.field(E.identifier); // auto-generated
136
130
  title = E.field(E.string);
137
131
  status = E.field(E.or("draft", "published"), {default: "draft"});
138
- tags = E.field(E.array(E.string), {default: () => []}); // use function for mutable defaults
139
- createdAt = E.field(E.dateTime); // dateTime defaults to new Date()
140
- }
132
+ tags = E.field(E.array(E.string), {default: () => []}); // use function for mutable defaults
133
+ createdAt = E.field(E.dateTime); // dateTime defaults to new Date()
134
+ }, { pk: "id" });
141
135
  ```
142
136
 
143
137
  ### Transactions
@@ -150,19 +144,19 @@ E.init("./my-database");
150
144
 
151
145
  // Create
152
146
  await E.transact(() => {
153
- const user = new User({name: "Alice", email: "alice@example.com", age: 30});
154
- // user.id is auto-generated
147
+ // User.id is auto-generated
148
+ new User({name: "Alice", email: "alice@example.com", age: 30});
155
149
  });
156
150
 
157
151
  // Read + Update
158
152
  await E.transact(() => {
159
- const user = User.byEmail.get("alice@example.com");
153
+ const user = User.getBy("email", "alice@example.com");
160
154
  if (user) user.age++;
161
155
  });
162
156
 
163
157
  // Return values from transactions
164
158
  const name = await E.transact(() => {
165
- const user = User.byEmail.get("alice@example.com");
159
+ const user = User.getBy("email", "alice@example.com");
166
160
  return user?.name;
167
161
  });
168
162
  ```
@@ -174,30 +168,29 @@ Transactions auto-retry on conflict (up to 6 times by default). Keep transaction
174
168
  Edinburgh supports three index types:
175
169
 
176
170
  ```typescript
177
- @E.registerModel
178
- class Product extends E.Model<Product> {
179
- static pk = E.primary(Product, "sku"); // primary: one per model, stores data
180
- static byName = E.unique(Product, "name"); // unique: enforces uniqueness + fast lookup
181
- static byCategory = E.index(Product, "category");// secondary: non-unique, for queries
182
-
171
+ const Product = E.defineModel("Product", class {
183
172
  sku = E.field(E.string);
184
173
  name = E.field(E.string);
185
174
  category = E.field(E.string);
186
175
  price = E.field(E.number);
187
- }
176
+ }, {
177
+ pk: "sku",
178
+ unique: { name: "name" },
179
+ index: { category: "category" },
180
+ });
188
181
  ```
189
182
 
190
- If no `E.primary()` is declared, Edinburgh auto-creates one on an `id` field (adding `E.identifier` if missing).
183
+ If no `pk` is provided, Edinburgh auto-creates one on an `id` field, adding it as an `E.identifier` field if needed.
191
184
 
192
185
  #### Lookups
193
186
 
194
187
  ```typescript
195
188
  await E.transact(() => {
196
189
  // Primary key lookup
197
- const p = Product.pk.get("SKU-001");
190
+ const p1 = Product.get("SKU-001");
198
191
 
199
192
  // Unique index lookup
200
- const p2 = Product.byName.get("Widget");
193
+ const p2 = Product.getBy("name", "Widget");
201
194
 
202
195
  // All return undefined if not found
203
196
  });
@@ -205,54 +198,53 @@ await E.transact(() => {
205
198
 
206
199
  #### Range Queries
207
200
 
208
- All index types support `.find()` for range iteration:
201
+ Primary-key queries use `.find()`. Named unique and secondary indexes use `.findBy(name, ...)`:
209
202
 
210
203
  ```typescript
211
204
  await E.transact(() => {
212
205
  // Exact match
213
- for (const p of Product.byCategory.find({is: "electronics"})) {
206
+ for (const p of Product.findBy("category", {is: "electronics"})) {
214
207
  console.log(p.name);
215
208
  }
216
209
 
217
210
  // Range (inclusive)
218
- for (const p of Product.pk.find({from: "A", to: "M"})) {
211
+ for (const p of Product.find({from: "A", to: "M"})) {
219
212
  console.log(p.sku);
220
213
  }
221
214
 
222
215
  // Exclusive bounds
223
- for (const p of Product.pk.find({after: "A", before: "M"})) { ... }
216
+ for (const p of Product.find({after: "A", before: "M"})) { ... }
224
217
 
225
218
  // Open-ended
226
- for (const p of Product.pk.find({from: "M"})) { ... }
219
+ for (const p of Product.find({from: "M"})) { ... }
227
220
 
228
221
  // Reverse
229
- for (const p of Product.pk.find({reverse: true})) { ... }
222
+ for (const p of Product.find({reverse: true})) { ... }
230
223
 
231
224
  // Count and fetch helpers
232
- const count = Product.byCategory.find({is: "electronics"}).count();
233
- const first = Product.byCategory.find({is: "electronics"}).fetch(); // first match or undefined
225
+ const count = Product.findBy("category", {is: "electronics"}).count();
226
+ const first = Product.findBy("category", {is: "electronics"}).fetch(); // first match or undefined
234
227
  });
235
228
  ```
236
229
 
237
- #### Composite Indexes
230
+ #### Composite Primary Keys
238
231
 
239
232
  ```typescript
240
- @E.registerModel
241
- class Event extends E.Model<Event> {
242
- static pk = E.primary(Event, ["year", "month", "id"]);
243
-
233
+ const Event = E.defineModel("Event", class {
244
234
  year = E.field(E.number);
245
235
  month = E.field(E.number);
246
236
  id = E.field(E.identifier);
247
237
  title = E.field(E.string);
248
- }
238
+ }, {
239
+ pk: ["year", "month", "id"] as const,
240
+ });
249
241
 
250
242
  await E.transact(() => {
251
243
  // Prefix matching — find all events in 2025
252
- for (const e of Event.pk.find({is: [2025]})) { ... }
244
+ for (const e of Event.find({is: [2025]})) { ... }
253
245
 
254
246
  // Find events in March 2025
255
- for (const e of Event.pk.find({is: [2025, 3]})) { ... }
247
+ for (const e of Event.find({is: [2025, 3]})) { ... }
256
248
  });
257
249
  ```
258
250
 
@@ -261,10 +253,7 @@ await E.transact(() => {
261
253
  You can freely add regular methods, getters, and other non-persistent properties to model classes. These work normally in JavaScript but are **not stored in the database** and **not synchronized** across transactions or processes.
262
254
 
263
255
  ```typescript
264
- @E.registerModel
265
- class User extends E.Model<User> {
266
- static pk = E.primary(User, "id");
267
- id = E.field(E.identifier);
256
+ const User = E.defineModel("User", class {
268
257
  firstName = E.field(E.string);
269
258
  lastName = E.field(E.string);
270
259
 
@@ -279,70 +268,68 @@ class User extends E.Model<User> {
279
268
  greet(): string {
280
269
  return `Hello, ${this.fullName}!`;
281
270
  }
282
- }
271
+ });
283
272
  ```
284
273
 
285
274
  #### Computed Indexes
286
275
 
287
- 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).
276
+ Instead of naming fields, you can pass a function as an index specification. The function receives a model instance and returns an **array** of index key values. Each element creates a separate index entry, enabling multi-value indexes. Return `[]` to skip indexing for that instance (partial index).
288
277
 
289
278
  ```typescript
290
- @E.registerModel
291
- class Article extends E.Model<Article> {
292
- static pk = E.primary(Article, "id");
293
- static byFullName = E.unique(Article, (a: Article) => [`${a.firstName} ${a.lastName}`]);
294
- static byWord = E.index(Article, (a: Article) => a.title.toLowerCase().split(" "));
295
- static byDomain = E.index(Article, (a: Article) => a.email ? [a.email.split("@")[1]] : []);
296
-
279
+ const Article = E.defineModel("Article", class {
297
280
  id = E.field(E.identifier);
298
281
  firstName = E.field(E.string);
299
282
  lastName = E.field(E.string);
300
283
  title = E.field(E.string);
301
284
  email = E.field(E.opt(E.string));
302
- }
285
+ }, {
286
+ pk: "id",
287
+ unique: {
288
+ fullName: (a: any) => [`${a.firstName} ${a.lastName}`], // computed covering unique index
289
+ },
290
+ index: {
291
+ domain: (a: any) => a.email ? [a.email.split("@")[1]] : [], // computed partial index
292
+ word: (a: any) => a.title.toLowerCase().split(" "), // computed multi-index
293
+ },
294
+ });
303
295
 
304
296
  await E.transact(() => {
305
297
  new Article({ firstName: "Jane", lastName: "Doe", title: "Hello World", email: "jane@acme.com" });
306
298
 
307
299
  // Lookup via computed unique index
308
- const jane = Article.byFullName.get("Jane Doe");
300
+ const jane = Article.getBy("fullName", "Jane Doe");
309
301
 
310
302
  // Multi-value: each word in the title is indexed separately
311
- for (const a of Article.byWord.find({is: "hello"})) { ... }
303
+ for (const a of Article.findBy("word", {is: "hello"})) { ... }
312
304
 
313
305
  // Partial index: articles without email are skipped
314
- for (const a of Article.byDomain.find({is: "acme.com"})) { ... }
306
+ for (const a of Article.findBy("domain", {is: "acme.com"})) { ... }
315
307
  });
316
308
  ```
317
309
 
318
- Computed indexes also support `find()` range queries and `batchProcess()`, just like field-based indexes.
319
-
320
- ### Relationships (Links)
310
+ ### Relationships
321
311
 
322
- Use `E.link(Model)` for foreign keys:
312
+ Use `E.link(Model)` for foreign keys. Use a thunk (a function that just returns a value) for forward references when needed:
323
313
 
324
314
  ```typescript
325
- @E.registerModel
326
- class Author extends E.Model<Author> {
327
- static pk = E.primary(Author, "id");
315
+ const Author = E.defineModel("Author", class {
328
316
  id = E.field(E.identifier);
329
317
  name = E.field(E.string);
330
- }
318
+ }, { pk: "id" });
331
319
 
332
- @E.registerModel
333
- class Book extends E.Model<Book> {
334
- static pk = E.primary(Book, "id");
320
+ const Book = E.defineModel("Book", class {
335
321
  id = E.field(E.identifier);
336
322
  title = E.field(E.string);
337
323
  author = E.field(E.link(Author));
338
- }
324
+ }, { pk: "id" });
339
325
 
340
326
  await E.transact(() => {
341
327
  const author = new Author({name: "Tolkien"});
342
328
  const book = new Book({title: "The Hobbit", author});
343
329
 
344
330
  // Later: linked models are lazy-loaded on property access
345
- const b = Book.pk.get(book.id)!;
331
+ const b = Book.get(book.id)!;
332
+ console.log(b.author.id); // no need to load yet..
346
333
  console.log(b.author.name); // loads Author automatically (~1µs)
347
334
  });
348
335
  ```
@@ -351,7 +338,7 @@ await E.transact(() => {
351
338
 
352
339
  ```typescript
353
340
  await E.transact(() => {
354
- const user = User.pk.get(someId);
341
+ const user = User.get(someId);
355
342
  if (user) user.delete();
356
343
  });
357
344
  ```
@@ -369,10 +356,16 @@ await E.transact(() => {
369
356
  user.preventPersist(); // exclude from commit
370
357
  });
371
358
 
372
- // findAll iterates all instances
359
+ // find() iterates all instances (or use range options)
373
360
  await E.transact(() => {
374
- for (const user of User.findAll()) { ... }
375
- for (const user of User.findAll({reverse: true})) { ... }
361
+ for (const user of User.find()) { ... }
362
+ for (const user of User.find({reverse: true})) { ... }
363
+
364
+ // {fetch: 'first'} returns a single instance or undefined
365
+ const first = User.find({fetch: 'first'});
366
+
367
+ // {fetch: 'single'} returns a single instance, or throws if there are none or more than one
368
+ const only = User.find({fetch: 'single'});
376
369
  });
377
370
 
378
371
  // replaceInto: upsert by primary key
@@ -386,7 +379,7 @@ await E.transact(() => {
386
379
  For large datasets, `batchProcess` auto-commits in batches:
387
380
 
388
381
  ```typescript
389
- await Product.byCategory.batchProcess({is: "old"}, (product) => {
382
+ await Product.batchProcess({ limitRows: 1000 }, (product) => {
390
383
  product.category = "archived";
391
384
  });
392
385
  // Commits every ~1 second or 4096 rows (configurable via limitSeconds, limitRows)
@@ -394,12 +387,10 @@ await Product.byCategory.batchProcess({is: "old"}, (product) => {
394
387
 
395
388
  ### Lazy Schema Migrations
396
389
 
397
- When you change a model's schema, Edinburgh will lazily try to migrate old records on access. This allows you to deploy code changes without downtime or a separate migration step. Optionally, you may provide a `static migrate(record: Record<string, any>)` function on the model to transform old records during lazy migration. If there is a migration error (like a new field without a default value, or an incompatible type change), a run-time error is thrown when loading the affected model instance.
390
+ When you change a model's schema, Edinburgh lazily migrates old records on access. You can provide a `static migrate(record)` function to transform old rows:
398
391
 
399
392
  ```typescript
400
- @E.registerModel
401
- class User extends E.Model<User> {
402
- static pk = E.primary(User, "id");
393
+ const UserV2 = E.defineModel("User", class {
403
394
  id = E.field(E.identifier);
404
395
  name = E.field(E.string);
405
396
  role = E.field(E.string); // newly added field
@@ -407,7 +398,7 @@ class User extends E.Model<User> {
407
398
  static migrate(record: Record<string, any>) {
408
399
  record.role ??= record.name.indexOf("admin") >= 0 ? "admin" : "user"; // set role based on name for old records
409
400
  }
410
- }
401
+ })
411
402
  ```
412
403
 
413
404
  Edinburgh will lazily (re)run the `migrate` function on an instance whenever its implementation (the literal function code) has changed. For robustness, make sure that your `migrate` function...
@@ -442,9 +433,7 @@ console.log(result.secondaries); // { User: 1500 }
442
433
  Compute derived fields before data is written:
443
434
 
444
435
  ```typescript
445
- @E.registerModel
446
- class Article extends E.Model<Article> {
447
- static pk = E.primary(Article, "id");
436
+ const Article = E.defineModel("Article", class {
448
437
  id = E.field(E.identifier);
449
438
  title = E.field(E.string);
450
439
  slug = E.field(E.string);
@@ -452,7 +441,7 @@ class Article extends E.Model<Article> {
452
441
  preCommit() {
453
442
  this.slug = this.title.toLowerCase().replace(/\s+/g, "-");
454
443
  }
455
- }
444
+ });
456
445
  ```
457
446
 
458
447
  ### Change Tracking
@@ -493,9 +482,12 @@ ln -s ../../node_modules/edinburgh/skill .claude/skills/edinburgh
493
482
 
494
483
  The following is auto-generated from `src/edinburgh.ts`:
495
484
 
496
- ### scheduleInit · function
485
+ ### currentTxn · function
497
486
 
498
- **Signature:** `() => void`
487
+ Returns the current transaction from AsyncLocalStorage.
488
+ Throws if called outside a transact() callback.
489
+
490
+ **Signature:** `() => Transaction`
499
491
 
500
492
  ### [init](init.md) · function
501
493
 
@@ -516,72 +508,53 @@ The default value is 6. Setting it to 0 will disable retries and cause transacti
516
508
 
517
509
  Set a callback function to be called after a model is saved and committed.
518
510
 
519
- ### deleteEverything · function
520
-
521
- **Signature:** `() => Promise<void>`
511
+ ### Model · class
522
512
 
523
- ### [Model](Model.md) · abstract class
513
+ **Type:** `typeof ModelBase`
524
514
 
525
- [object Object],[object Object],[object Object],[object Object],[object Object]
515
+ ### [ModelClass](ModelClass.md) · class
526
516
 
527
- #### Model.tableName · static property
517
+ Runtime base constructor for model classes returned by `defineModel()`.
528
518
 
529
- The database table name (defaults to class name).
519
+ ### [AnyModelClass](AnyModelClass.md) · type
530
520
 
531
- **Type:** `string`
521
+ A model constructor with its generic information erased.
532
522
 
533
- #### Model.override · static property
523
+ ### [ModelBase](ModelBase.md) · abstract class
534
524
 
535
- When true, registerModel replaces an existing model with the same tableName.
525
+ Base class for all database models in the Edinburgh ORM.
536
526
 
537
- **Type:** `boolean`
527
+ ### [Schema Evolution](Schema Evolution.md)
538
528
 
539
- #### Model.fields · static property
529
+ Edinburgh tracks the schema version of each model automatically. When you add, remove, or
530
+ change the types of fields, or add/remove indexes, Edinburgh detects the new schema version.
540
531
 
541
- Field configuration metadata.
532
+ ### [Lifecycle Hooks](Lifecycle Hooks.md)
542
533
 
543
- **Type:** `Record<string | number | symbol, FieldConfig<unknown>>`
534
+ - **`static migrate(record)`**: Called when deserializing rows written with an older schema
535
+ version. Receives a plain record object; mutate it in-place to match the current schema.
544
536
 
545
- #### [Model.migrate](Model_migrate.md) · static method
537
+ #### [ModelBase.migrate](Lifecycle Hooks_migrate.md) · static method
546
538
 
547
539
  Optional migration function called when deserializing rows written with an older schema version.
548
- Receives a plain record with all fields (primary key fields + value fields) and should mutate it
549
- in-place to match the current schema.
550
-
551
- #### Model.initFields · static method
552
-
553
- Transform the model's `E.field` properties into the appropriate JavaScript properties. Normally this is done
554
- automatically when using `transact()`, but in case you need to access `Model.fields` directly before the first
555
- transaction, you can call this method manually.
556
-
557
- **Signature:** `(reset?: boolean) => void`
558
-
559
- **Parameters:**
560
-
561
- - `reset?: boolean`
540
+ Receives a plain record with all fields and should mutate it in-place to match the current schema.
541
+ It runs during lazy loading and during `runMigration()`. Changing this method creates a new schema version.
542
+ If it updates values used by secondary or unique indexes, those index entries are refreshed only by `runMigration()`.
562
543
 
563
- #### [Model.findAll](Model_findAll.md) · static method
564
-
565
- Find all instances of this model in the database, ordered by primary key.
566
-
567
- #### [Model.replaceInto](Model_replaceInto.md) · static method
568
-
569
- Load an existing instance by primary key and update it, or create a new one.
570
-
571
- #### [model.preCommit](Model_preCommit.md) · method
544
+ #### [modelBase.preCommit](Lifecycle Hooks_preCommit.md) · method
572
545
 
573
546
  Optional hook called on each modified instance right before the transaction commits.
574
547
  Runs before data is written to disk, so changes made here are included in the commit.
575
548
 
576
- #### model.getPrimaryKey · method
549
+ #### modelBase.getPrimaryKey · method
577
550
 
578
551
  **Signature:** `() => Uint8Array<ArrayBufferLike>`
579
552
 
580
553
  **Returns:** The primary key for this instance.
581
554
 
582
- #### [model.getPrimaryKeyHash](Model_getPrimaryKeyHash.md) · method
555
+ #### [modelBase.getPrimaryKeyHash](Lifecycle Hooks_getPrimaryKeyHash.md) · method
583
556
 
584
- #### model.isLazyField · method
557
+ #### modelBase.isLazyField · method
585
558
 
586
559
  **Signature:** `(field: keyof this) => boolean`
587
560
 
@@ -589,38 +562,42 @@ Runs before data is written to disk, so changes made here are included in the co
589
562
 
590
563
  - `field: keyof this`
591
564
 
592
- #### [model.preventPersist](Model_preventPersist.md) · method
565
+ #### [modelBase.preventPersist](Lifecycle Hooks_preventPersist.md) · method
593
566
 
594
567
  Prevent this instance from being persisted to the database.
595
568
 
596
- #### [model.delete](Model_delete.md) · method
569
+ #### [modelBase.delete](Lifecycle Hooks_delete.md) · method
597
570
 
598
571
  Delete this model instance from the database.
599
572
 
600
- #### [model.validate](Model_validate.md) · method
573
+ #### [modelBase.validate](Lifecycle Hooks_validate.md) · method
601
574
 
602
575
  Validate all fields in this model instance.
603
576
 
604
- #### [model.isValid](Model_isValid.md) · method
577
+ #### [modelBase.isValid](Lifecycle Hooks_isValid.md) · method
605
578
 
606
579
  Check if this model instance is valid.
607
580
 
608
- #### model.getState · method
581
+ #### modelBase.getState · method
609
582
 
610
583
  **Signature:** `() => "created" | "deleted" | "loaded" | "lazy"`
611
584
 
612
- #### model.toString · method
585
+ #### modelBase.toString · method
613
586
 
614
587
  **Signature:** `() => string`
615
588
 
616
- #### model.[Symbol.for('nodejs.util.inspect.custom')] · method
589
+ #### modelBase.[Symbol.for('nodejs.util.inspect.custom')] · method
617
590
 
618
591
  **Signature:** `() => string`
619
592
 
620
- ### [registerModel](registerModel.md) · function
593
+ ### [defineModel](defineModel.md) · function
621
594
 
622
595
  Register a model class with the Edinburgh ORM system.
623
596
 
597
+ ### [deleteEverything](deleteEverything.md) · function
598
+
599
+ Delete every key/value entry in the database and reinitialize all registered models.
600
+
624
601
  ### [field](field.md) · function
625
602
 
626
603
  Create a field definition for a model property.
@@ -699,79 +676,126 @@ Create a literal type wrapper for a constant value.
699
676
 
700
677
  Create a link type wrapper for model relationships.
701
678
 
702
- ### [index](index.md) · function
679
+ ### dump · function
703
680
 
704
- Create a secondary index on model fields, or a computed secondary index using a function.
681
+ **Signature:** `() => void`
705
682
 
706
- ### [primary](primary.md) · function
683
+ ### [FindOptions](FindOptions.md) · type
707
684
 
708
- Create a primary index on model fields.
685
+ Range-query options accepted by `find()`, `findBy()`, `batchProcess()`, and `batchProcessBy()`.
709
686
 
710
- ### [unique](unique.md) · function
687
+ ### IndexRangeIterator · class
711
688
 
712
- Create a unique index on model fields, or a computed unique index using a function.
689
+ Iterator for range queries on indexes.
690
+ Handles common iteration logic for both primary and unique indexes.
691
+ Extends built-in Iterator to provide map/filter/reduce/toArray/etc.
713
692
 
714
- ### [dump](dump.md) · function
693
+ **Type Parameters:**
715
694
 
716
- Dump database contents for debugging.
695
+ - `ITEM`
717
696
 
718
- ### [BaseIndex](BaseIndex.md) · abstract class
697
+ #### indexRangeIterator.[Symbol.iterator] · method
719
698
 
720
- Base class for database indexes for efficient lookups on model fields.
699
+ **Signature:** `() => this`
721
700
 
722
- #### [baseIndex.find](BaseIndex_find.md) · method
701
+ #### indexRangeIterator.next · method
723
702
 
724
- #### [baseIndex.batchProcess](BaseIndex_batchProcess.md) · method
703
+ **Signature:** `() => IteratorResult<ITEM, any>`
725
704
 
726
- [object Object],[object Object],[object Object]
705
+ #### indexRangeIterator.count · method
727
706
 
728
- #### baseIndex.toString · method
707
+ **Signature:** `() => number`
729
708
 
730
- **Signature:** `() => string`
709
+ #### indexRangeIterator.fetch · method
731
710
 
732
- ### [UniqueIndex](UniqueIndex.md) · class
711
+ **Signature:** `() => ITEM`
733
712
 
734
- Unique index that stores references to the primary key.
713
+ ### Change · type
735
714
 
736
- #### [uniqueIndex.get](UniqueIndex_get.md) · method
715
+ **Type:** `Record<any, any> | "created" | "deleted"`
737
716
 
738
- Get a model instance by unique index key values.
717
+ ### FieldConfig · interface
739
718
 
740
- ### [PrimaryIndex](PrimaryIndex.md) · class
719
+ Configuration interface for model fields.
741
720
 
742
- Primary index that stores the actual model data.
721
+ **Type Parameters:**
743
722
 
744
- #### [primaryIndex.get](PrimaryIndex_get.md) · method
723
+ - `T` - The field type.
745
724
 
746
- Get a model instance by primary key values.
725
+ #### fieldConfig.type · member
747
726
 
748
- #### [primaryIndex.getLazy](PrimaryIndex_getLazy.md) · method
727
+ The type wrapper that defines how this field is serialized/validated.
749
728
 
750
- Does the same as as `get()`, but will delay loading the instance from disk until the first
751
- property access. In case it turns out the instance doesn't exist, an error will be thrown
752
- at that time.
729
+ **Type:** `TypeWrapper<T>`
753
730
 
754
- ### [SecondaryIndex](SecondaryIndex.md) · class
731
+ #### fieldConfig.description · member
755
732
 
756
- Secondary index for non-unique lookups.
733
+ Optional human-readable description of the field.
757
734
 
758
- ### Change · type
735
+ **Type:** `string`
759
736
 
760
- **Type:** `Record<any, any> | "created" | "deleted"`
737
+ #### fieldConfig.default · member
761
738
 
762
- ### Transaction · interface
739
+ Optional default value or function that generates default values.
763
740
 
764
- #### transaction.id · member
741
+ **Type:** `T | ((model: Record<string, any>) => T)`
765
742
 
766
- **Type:** `number`
743
+ ### TypeWrapper · abstract class
767
744
 
768
- #### transaction.instances · member
745
+ **Type Parameters:**
746
+
747
+ - `T` - The TypeScript type this wrapper represents.
748
+
749
+ #### typeWrapper.kind · abstract property
750
+
751
+ A string identifier for this type, used during serialization
752
+
753
+ **Type:** `string`
754
+
755
+ #### [typeWrapper.serialize](TypeWrapper_serialize.md) · abstract method
756
+
757
+ Serialize a value from an object property to a Pack.
758
+
759
+ #### [typeWrapper.deserialize](TypeWrapper_deserialize.md) · abstract method
760
+
761
+ Deserialize a value from a Pack into an object property.
762
+
763
+ #### [typeWrapper.getError](TypeWrapper_getError.md) · abstract method
764
+
765
+ Validate a value.
766
+
767
+ #### [typeWrapper.serializeType](TypeWrapper_serializeType.md) · method
768
+
769
+ Serialize type metadata to a Pack (for schema serialization).
770
+
771
+ #### [typeWrapper.containsNull](TypeWrapper_containsNull.md) · method
772
+
773
+ Check if indexing should be skipped for this field value.
774
+
775
+ #### typeWrapper.toString · method
769
776
 
770
- **Type:** `Set<Model<unknown>>`
777
+ **Signature:** `() => string`
778
+
779
+ #### typeWrapper.clone · method
780
+
781
+ **Signature:** `(value: T) => T`
782
+
783
+ **Parameters:**
784
+
785
+ - `value: T`
786
+
787
+ #### typeWrapper.equals · method
788
+
789
+ **Signature:** `(value1: T, value2: T) => boolean`
790
+
791
+ **Parameters:**
792
+
793
+ - `value1: T`
794
+ - `value2: T`
771
795
 
772
- #### transaction.instancesByPk · member
796
+ #### typeWrapper.getLinkedModel · method
773
797
 
774
- **Type:** `Map<number, Model<unknown>>`
798
+ **Signature:** `() => AnyModelClass`
775
799
 
776
800
  ### [DatabaseError](DatabaseError.md) · constant
777
801
 
@@ -853,3 +877,17 @@ Number of orphaned index entries deleted.
853
877
 
854
878
  **Type:** `number`
855
879
 
880
+ ### Transaction · interface
881
+
882
+ #### transaction.id · member
883
+
884
+ **Type:** `number`
885
+
886
+ #### transaction.instances · member
887
+
888
+ **Type:** `Map<number, ModelBase>`
889
+
890
+ ### txnStorage · constant
891
+
892
+ **Value:** `AsyncLocalStorage<Transaction>`
893
+