edinburgh 0.1.3 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,63 +1,124 @@
1
1
  # Edinburgh
2
- **Very fast object persistence for TypeScript, supporting optimistic transactions, on-demand reference loading, automatic back-references and indexes.**
2
+ **TypeScript objects that live in the database.**
3
3
 
4
- Edinburgh is a high-performance ORM built on [OLMDB](https://github.com/vanviegen/olmdb), providing type-safe model definitions with automatic field validation, ACID transactions, and efficient LMDB-based storage.
4
+ Edinburgh blurs the line between in-memory objects and database records. Define a model class, and its instances *are* the database rows. They are read directly from a memory-mapped [LMDB](http://www.lmdb.tech/doc/) store on first access, and mutations are written back in an ACID transaction on commit. There is no SQL layer, no query builder, no network round-trip, and no result-set marshalling. A primary-key lookup completes in about 1 µs.
5
5
 
6
- **Features:**
6
+ This makes problems like n+1 queries irrelevant: traversing `post.author.department.manager` is just a chain of microsecond memory-mapped reads, not a cascade of network calls.
7
7
 
8
- - 🚀 **Type-Safe Models**: Define models with automatic TypeScript type inference and runtime validation
9
- - 🔒 **ACID Transactions**: Optimistic locking with automatic retry on conflicts
10
- - 🔗 **Relationship Management**: Automatic back-references and link handling between models
11
- - 📊 **Custom Indexing**: Efficient querying with primary, unique, and multi-value indexes
12
- - 📦 **Zero Dependencies**: Minimal footprint, built on LMDB for maximum performance
8
+ Built on [OLMDB](https://github.com/vanviegen/olmdb) (an optimistic-locking wrapper around LMDB).
9
+
10
+ - **Objects are records**: model fields are backed by memory-mapped storage; no serialization boundary between your code and the database
11
+ - **Sub-microsecond reads**: embedded B+ tree in the same process, no network hop, no query parsing
12
+ - **Type-safe at every layer**: TypeScript inference at compile time, runtime validation at write time
13
+ - **First-class relationships**: `E.link(OtherModel)` fields load lazily and transparently on access
14
+ - **Indexes**: primary, unique, and secondary indexes with efficient range queries
15
+ - **ACID transactions**: optimistic locking with automatic retry on conflict (up to 6 attempts)
16
+ - **Zero-downtime schema evolution**: old rows are lazily migrated on read; no batch DDL required
13
17
 
14
18
  ## Quick Demo
15
19
  ```typescript
16
20
  import * as E from "edinburgh";
17
21
 
18
- // Initialize the database (optional, defaults to "./.olmdb")
22
+ // Initialize the database (optional, defaults to ".edinburgh")
19
23
  E.init("./my-database");
20
24
 
21
25
  // Define a model
22
26
  @E.registerModel
23
27
  class User extends E.Model<User> {
24
- // Define a primary key (optional, defaults to using the "id" field)
25
- static pk = E.index(User, ["id"], "primary");
26
- // Define a unique index on the email field
27
- static byEmail = E.index(User, "email", "unique");
28
-
29
- // Define fields with simple types -- they will be type-checked at compile time and validated at runtime.
30
- id = E.field(E.identifier);
31
- name = E.field(E.string);
32
- email = E.field(E.string);
33
- age = E.field(E.opt(E.number));
34
-
35
- // A field with a more elaborate type. In Typescript: `User | User[] | "self" | "spouse"`
36
- supervisor = E.field(E.choice(E.link(User), E.array(E.link(User)), E.literal("self"), E.literal("spouse")));
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.
34
+ id = E.field(E.identifier);
35
+ name = E.field(E.string);
36
+ 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" });
37
44
  }
38
45
 
39
46
  // Use in transactions
40
47
  await E.transact(() => {
41
- const user = new User({
42
- name: "John Doe",
43
- email: "john@example.com"
44
- });
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",
54
+ age: 41,
55
+ email: "john@example.com",
56
+ supervisor: boss, // Link to another model instance
57
+ });
45
58
  });
46
59
 
47
60
  await E.transact(() => {
48
- // Query by unique index
49
- const user = User.byEmail.get("john@example.com")!;
50
- // The transaction will retry if there's a conflict, such as another transaction
51
- // modifying the same user (from another async function or another process)
52
- user.age++;
53
- });
61
+ // Query by unique index
62
+ const john = User.byEmail.get("john@example.com")!;
63
+
64
+ // The transaction will retry if there's a conflict, such as another transaction
65
+ // modifying the same user (from another async function or another process)
66
+ john.age++;
67
+
68
+ // The supervisor object is lazy loaded on first access
69
+ console.log(`${john.supervisor!.name} is ${john.name}'s supervisor`);
70
+ });
71
+ ```
72
+
73
+ ## TypeScript Configuration
74
+
75
+ When using TypeScript to transpile to JavaScript, make sure to enable the following options in your `tsconfig.json`:
76
+
77
+ ```json
78
+ {
79
+ "compilerOptions": {
80
+ "target": "es2022",
81
+ "experimentalDecorators": true
82
+ }
83
+ }
54
84
  ```
55
85
 
86
+ ## Logging
87
+
88
+ You can enable debug logging to stdout by setting the `EDINBURGH_LOG_LEVEL` environment variable to a number from 0 to 3. Higher numbers produce more verbose logs, including model-level operations, updates, and reads.
89
+
90
+ - 0: no logging (default)
91
+ - 1: model-level logs
92
+ - 2: + update logs
93
+ - 3: + read logs
94
+
56
95
  ## API Reference
57
96
 
58
97
  The following is auto-generated from `src/edinburgh.ts`:
59
98
 
60
- ### transact · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L83)
99
+ ### scheduleInit · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L6)
100
+
101
+ **Signature:** `() => void`
102
+
103
+ ### init · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L65)
104
+
105
+ Initialize the database with the specified directory path.
106
+ This function may be called multiple times with the same parameters. If it is not called before the first transact(),
107
+ the database will be automatically initialized with the default directory.
108
+
109
+ **Signature:** `(dbDir: string) => void`
110
+
111
+ **Parameters:**
112
+
113
+ - `dbDir: string`
114
+
115
+ **Examples:**
116
+
117
+ ```typescript
118
+ init("./my-database");
119
+ ```
120
+
121
+ ### transact · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L116)
61
122
 
62
123
  Executes a function within a database transaction context.
63
124
 
@@ -66,7 +127,7 @@ within a transaction.
66
127
 
67
128
  Transactions have a consistent view of the database, and changes made within a transaction are
68
129
  isolated from other transactions until they are committed. In case a commit clashes with changes
69
- made by another transaction, the transaction function will automatically be re-executed up to 10
130
+ made by another transaction, the transaction function will automatically be re-executed up to 6
70
131
  times.
71
132
 
72
133
  **Signature:** `<T>(fn: () => T) => Promise<T>`
@@ -77,15 +138,13 @@ times.
77
138
 
78
139
  **Parameters:**
79
140
 
80
- - `fn: () => T` - - The function to execute within the transaction context.
141
+ - `fn: () => T` - - The function to execute within the transaction context. Receives a Transaction instance.
81
142
 
82
143
  **Returns:** A promise that resolves with the function's return value.
83
144
 
84
145
  **Throws:**
85
146
 
86
- - If nested transactions are attempted.
87
147
  - With code "RACING_TRANSACTION" if the transaction fails after retries due to conflicts.
88
- - With code "TRANSACTION_FAILED" if the transaction fails for other reasons.
89
148
  - With code "TXN_LIMIT" if maximum number of transactions is reached.
90
149
  - With code "LMDB-{code}" for LMDB-specific errors.
91
150
 
@@ -93,8 +152,7 @@ times.
93
152
 
94
153
  ```typescript
95
154
  const paid = await E.transact(() => {
96
- const user = User.load("john_doe");
97
- // This is concurrency-safe - the function will rerun if it is raced by another transaction
155
+ const user = User.pk.get("john_doe");
98
156
  if (user.credits > 0) {
99
157
  user.credits--;
100
158
  return true;
@@ -105,22 +163,42 @@ const paid = await E.transact(() => {
105
163
  ```typescript
106
164
  // Transaction with automatic retry on conflicts
107
165
  await E.transact(() => {
108
- const counter = Counter.load("global") || new Counter({id: "global", value: 0});
166
+ const counter = Counter.pk.get("global") || new Counter({id: "global", value: 0});
109
167
  counter.value++;
110
168
  });
111
169
  ```
112
170
 
113
- ### deleteEverything · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L112)
171
+ ### setMaxRetryCount · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L206)
114
172
 
115
- **Signature:** `() => Promise<void>`
173
+ Set the maximum number of retries for a transaction in case of conflicts.
174
+ The default value is 6. Setting it to 0 will disable retries and cause transactions to fail immediately on conflict.
175
+
176
+ **Signature:** `(count: number) => void`
116
177
 
117
- ### Model · [abstract class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L120)
178
+ **Parameters:**
118
179
 
119
- Base class for all database models in the Edinburgh ORM.
180
+ - `count: number` - The maximum number of retries for a transaction.
120
181
 
121
- Models represent database entities with typed fields, automatic serialization,
122
- change tracking, and relationship management. All model classes should extend
123
- this base class and be decorated with `@registerModel`.
182
+ ### setOnSaveCallback · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L220)
183
+
184
+ Set a callback function to be called after a model is saved and committed.
185
+
186
+ **Signature:** `(callback: (commitId: number, items: Map<Model<any>, Change>) => void) => void`
187
+
188
+ **Parameters:**
189
+
190
+ - `callback: ((commitId: number, items: Map<Model<any>, Change>) => void) | undefined` - The callback function to set. It gets called after each successful
191
+ `transact()` commit that has changes, with the following arguments:
192
+ - A sequential number. Higher numbers have been committed after lower numbers.
193
+ - A map of model instances to their changes. The change can be "created", "deleted", or an object containing the old values.
194
+
195
+ ### deleteEverything · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L225)
196
+
197
+ **Signature:** `() => Promise<void>`
198
+
199
+ ### Model · [abstract class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L218)
200
+
201
+ [object Object],[object Object],[object Object],[object Object],[object Object]
124
202
 
125
203
  **Type Parameters:**
126
204
 
@@ -131,54 +209,160 @@ this base class and be decorated with `@registerModel`.
131
209
  ```typescript
132
210
  ⁣@E.registerModel
133
211
  class User extends E.Model<User> {
134
- static pk = E.index(User, ["id"], "primary");
212
+ static pk = E.primary(User, "id");
135
213
 
136
214
  id = E.field(E.identifier);
137
215
  name = E.field(E.string);
138
216
  email = E.field(E.string);
139
217
 
140
- static byEmail = E.index(User, "email", "unique");
218
+ static byEmail = E.unique(User, "email");
141
219
  }
142
220
  ```
143
221
 
144
- #### Model.tableName · [static property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L120)
222
+ #### Model.tableName · [static property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L226)
145
223
 
146
224
  The database table name (defaults to class name).
147
225
 
148
226
  **Type:** `string`
149
227
 
150
- #### Model.fields · [static property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L120)
228
+ #### Model.override · [static property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L230)
229
+
230
+ When true, registerModel replaces an existing model with the same tableName.
231
+
232
+ **Type:** `boolean`
233
+
234
+ #### Model.fields · [static property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L231)
151
235
 
152
236
  Field configuration metadata.
153
237
 
154
- **Type:** `Record<string, FieldConfig<unknown>>`
238
+ **Type:** `Record<string | number | symbol, FieldConfig<unknown>>`
239
+
240
+ #### Model.migrate · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
155
241
 
156
- #### Model.load · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L120)
242
+ Optional migration function called when deserializing rows written with an older schema version.
243
+ Receives a plain record with all fields (primary key fields + value fields) and should mutate it
244
+ in-place to match the current schema.
157
245
 
158
- Load a model instance by primary key.
246
+ This is called both during lazy loading (when a row is read from disk) and during batch
247
+ migration (via `runMigration()` / `npx migrate-edinburgh`). The function's source code is hashed
248
+ to detect changes. Modifying `migrate()` triggers a new schema version.
159
249
 
160
- **Signature:** `<SUB>(this: typeof Model<SUB>, ...args: any[]) => SUB`
250
+ If `migrate()` changes values of fields used in secondary or unique indexes, those indexes
251
+ will only be updated when `runMigration()` is run (not during lazy loading).
252
+
253
+ **Signature:** `(record: Record<string, any>) => void`
161
254
 
162
255
  **Parameters:**
163
256
 
164
- - `this: typeof Model<SUB>`
165
- - `args: any[]` - - Primary key field values.
257
+ - `record: Record<string, any>` - - A plain object with all field values from the old schema version.
258
+
259
+ **Examples:**
260
+
261
+ ```typescript
262
+ ⁣@E.registerModel
263
+ class User extends E.Model<User> {
264
+ static pk = E.primary(User, "id");
265
+ id = E.field(E.identifier);
266
+ name = E.field(E.string);
267
+ role = E.field(E.string); // new field
268
+
269
+ static migrate(record: Record<string, any>) {
270
+ record.role ??= "user"; // default for rows that predate the 'role' field
271
+ }
272
+ }
273
+ ```
274
+
275
+ #### Model.findAll · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
276
+
277
+ Find all instances of this model in the database, ordered by primary key.
278
+
279
+ **Signature:** `<T extends typeof Model<unknown>>(this: T, opts?: { reverse?: boolean; }) => IndexRangeIterator<T>`
280
+
281
+ **Parameters:**
282
+
283
+ - `this: T`
284
+ - `opts?: {reverse?: boolean}` - - Optional parameters.
285
+
286
+ **Returns:** An iterator.
287
+
288
+ #### Model.replaceInto · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
289
+
290
+ Load an existing instance by primary key and update it, or create a new one.
291
+
292
+ The provided object must contain all primary key fields. If a matching row exists,
293
+ the remaining properties from `obj` are set on the loaded instance. Otherwise a
294
+ new instance is created with `obj` as its initial properties.
295
+
296
+ **Signature:** `<T extends typeof Model<any>>(this: T, obj: Partial<Omit<InstanceType<T>, "constructor">>) => InstanceType<T>`
297
+
298
+ **Parameters:**
299
+
300
+ - `this: T`
301
+ - `obj: Partial<Omit<InstanceType<T>, "constructor">>` - - Partial model data that **must** include every primary key field.
302
+
303
+ **Returns:** The loaded-and-updated or newly created instance.
304
+
305
+ #### model.preCommit · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
306
+
307
+ Optional hook called on each modified instance right before the transaction commits.
308
+ Runs before data is written to disk, so changes made here are included in the commit.
309
+
310
+ Common use cases:
311
+ - Computing derived or denormalized fields
312
+ - Enforcing cross-field validation rules
313
+ - Creating or updating related model instances (newly created instances will also
314
+ have their `preCommit()` called)
315
+
316
+ **Signature:** `() => void`
317
+
318
+ **Parameters:**
166
319
 
167
- **Returns:** The model instance if found, undefined otherwise.
168
320
 
169
321
  **Examples:**
170
322
 
171
323
  ```typescript
172
- const user = User.load("user123");
173
- const post = Post.load("post456", "en");
324
+ ⁣@E.registerModel
325
+ class Post extends E.Model<Post> {
326
+ static pk = E.primary(Post, "id");
327
+ id = E.field(E.identifier);
328
+ title = E.field(E.string);
329
+ slug = E.field(E.string);
330
+
331
+ preCommit() {
332
+ this.slug = this.title.toLowerCase().replace(/\s+/g, "-");
333
+ }
334
+ }
174
335
  ```
175
336
 
176
- #### model.preventPersist · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L120)
337
+ #### model.getPrimaryKey · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
177
338
 
178
- Prevent this instance from being persisted to the database.
339
+ **Signature:** `() => Uint8Array<ArrayBufferLike>`
340
+
341
+ **Parameters:**
342
+
343
+
344
+ **Returns:** The primary key for this instance.
345
+
346
+ #### model.getPrimaryKeyHash · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
347
+
348
+ **Signature:** `() => number`
349
+
350
+ **Parameters:**
351
+
352
+
353
+ **Returns:** A 53-bit positive integer non-cryptographic hash of the primary key, or undefined if not yet saved.
179
354
 
180
- Removes the instance from the modified instances set and disables
181
- automatic persistence at transaction commit.
355
+ #### model.isLazyField · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
356
+
357
+ **Signature:** `(field: keyof this) => boolean`
358
+
359
+ **Parameters:**
360
+
361
+ - `field: keyof this`
362
+
363
+ #### model.preventPersist · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
364
+
365
+ Prevent this instance from being persisted to the database.
182
366
 
183
367
  **Signature:** `() => this`
184
368
 
@@ -195,7 +379,7 @@ user.name = "New Name";
195
379
  user.preventPersist(); // Changes won't be saved
196
380
  ```
197
381
 
198
- #### model.delete · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L120)
382
+ #### model.delete · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
199
383
 
200
384
  Delete this model instance from the database.
201
385
 
@@ -213,11 +397,11 @@ const user = User.load("user123");
213
397
  user.delete(); // Removes from database
214
398
  ```
215
399
 
216
- #### model.validate · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L120)
400
+ #### model.validate · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
217
401
 
218
402
  Validate all fields in this model instance.
219
403
 
220
- **Signature:** `(raise?: boolean) => DatabaseError[]`
404
+ **Signature:** `(raise?: boolean) => Error[]`
221
405
 
222
406
  **Parameters:**
223
407
 
@@ -235,7 +419,7 @@ if (errors.length > 0) {
235
419
  }
236
420
  ```
237
421
 
238
- #### model.isValid · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L120)
422
+ #### model.isValid · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
239
423
 
240
424
  Check if this model instance is valid.
241
425
 
@@ -253,13 +437,30 @@ const user = new User({name: "John"});
253
437
  if (!user.isValid()) shoutAtTheUser();
254
438
  ```
255
439
 
256
- ### registerModel · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L120)
440
+ #### model.getState · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
441
+
442
+ **Signature:** `() => "created" | "deleted" | "loaded" | "lazy"`
443
+
444
+ **Parameters:**
445
+
446
+
447
+ #### model.toString · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
448
+
449
+ **Signature:** `() => string`
450
+
451
+ **Parameters:**
452
+
453
+
454
+ #### model.[Symbol.for('nodejs.util.inspect.custom')] · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
455
+
456
+ **Signature:** `() => string`
457
+
458
+ **Parameters:**
257
459
 
258
- Register a model class with the Edinburgh ORM system.
259
460
 
260
- This decorator function transforms the model class to use a proxy-based constructor
261
- that enables change tracking and automatic field initialization. It also extracts
262
- field metadata and sets up default values on the prototype.
461
+ ### registerModel · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L111)
462
+
463
+ Register a model class with the Edinburgh ORM system.
263
464
 
264
465
  **Signature:** `<T extends typeof Model<unknown>>(MyModel: T) => T`
265
466
 
@@ -284,7 +485,7 @@ class User extends E.Model<User> {
284
485
  }
285
486
  ```
286
487
 
287
- ### field · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L55)
488
+ ### field · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L87)
288
489
 
289
490
  Create a field definition for a model property.
290
491
 
@@ -314,51 +515,57 @@ class User extends E.Model<User> {
314
515
  }
315
516
  ```
316
517
 
317
- ### setOnSaveCallback · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L85)
518
+ ### string · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
318
519
 
319
- Set a callback function to be called after a model is saved and committed.
520
+ Type wrapper instance for the string type.
320
521
 
321
- **Signature:** `(callback: OnSaveType) => void`
522
+ **Value:** `TypeWrapper<string>`
322
523
 
323
- **Parameters:**
524
+ ### orderedString · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
324
525
 
325
- - `callback: OnSaveType | undefined` - The callback function to set. As arguments, it receives the model instance, the new key (undefined in case of a delete), and the old key (undefined in case of a create).
526
+ Type wrapper instance for the ordered string type, which is just like a string
527
+ except that it sorts lexicographically in the database (instead of by incrementing
528
+ length first), making it suitable for index fields that want lexicographic range
529
+ scans. Ordered strings are implemented as null-terminated UTF-8 strings, so they
530
+ may not contain null characters.
326
531
 
327
- ### string · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L120)
532
+ **Value:** `TypeWrapper<string>`
328
533
 
329
- Constant representing the string type.
534
+ ### number · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
330
535
 
331
- **Value:** `StringType`
536
+ Type wrapper instance for the number type.
332
537
 
333
- ### number · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L120)
538
+ **Value:** `TypeWrapper<number>`
334
539
 
335
- Constant representing the number type.
540
+ ### dateTime · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
336
541
 
337
- **Value:** `NumberType`
542
+ Type wrapper instance for the date/time type.
338
543
 
339
- ### boolean · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L120)
544
+ **Value:** `TypeWrapper<Date>`
340
545
 
341
- Constant representing the boolean type.
546
+ ### boolean · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
342
547
 
343
- **Value:** `BooleanType`
548
+ Type wrapper instance for the boolean type.
344
549
 
345
- ### identifier · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L120)
550
+ **Value:** `TypeWrapper<boolean>`
346
551
 
347
- Constant representing the identifier type.
552
+ ### identifier · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
348
553
 
349
- **Value:** `IdentifierType`
554
+ Type wrapper instance for the identifier type.
350
555
 
351
- ### undef · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L120)
556
+ **Value:** `TypeWrapper<string>`
352
557
 
353
- Constant representing the 'undefined' type.
558
+ ### undef · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
354
559
 
355
- **Value:** `LiteralType<any>`
560
+ Type wrapper instance for the 'undefined' type.
356
561
 
357
- ### opt · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L120)
562
+ **Value:** `TypeWrapper<undefined>`
563
+
564
+ ### opt · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
358
565
 
359
566
  Create an optional type wrapper (allows undefined).
360
567
 
361
- **Signature:** `<const T extends TypeWrapper<unknown> | BasicType>(inner: T) => OrType<any>`
568
+ **Signature:** `<const T extends TypeWrapper<unknown> | BasicType>(inner: T) => TypeWrapper<T extends TypeWrapper<infer U> ? U : T>`
362
569
 
363
570
  **Type Parameters:**
364
571
 
@@ -377,11 +584,11 @@ const optionalString = E.opt(E.string);
377
584
  const optionalNumber = E.opt(E.number);
378
585
  ```
379
586
 
380
- ### or · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L120)
587
+ ### or · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
381
588
 
382
589
  Create a union type wrapper from multiple type choices.
383
590
 
384
- **Signature:** `<const T extends (TypeWrapper<unknown> | BasicType)[]>(...choices: T) => OrType<UnwrapTypes<T>>`
591
+ **Signature:** `<const T extends (TypeWrapper<unknown> | BasicType)[]>(...choices: T) => TypeWrapper<UnwrapTypes<T>>`
385
592
 
386
593
  **Type Parameters:**
387
594
 
@@ -400,11 +607,11 @@ const stringOrNumber = E.or(E.string, E.number);
400
607
  const status = E.or("active", "inactive", "pending");
401
608
  ```
402
609
 
403
- ### array · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L120)
610
+ ### array · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
404
611
 
405
612
  Create an array type wrapper with optional length constraints.
406
613
 
407
- **Signature:** `<const T>(inner: TypeWrapper<T>, opts?: { min?: number; max?: number; }) => ArrayType<T>`
614
+ **Signature:** `<const T>(inner: TypeWrapper<T>, opts?: { min?: number; max?: number; }) => TypeWrapper<T[]>`
408
615
 
409
616
  **Type Parameters:**
410
617
 
@@ -424,11 +631,11 @@ const stringArray = E.array(E.string);
424
631
  const boundedArray = E.array(E.number, {min: 1, max: 10});
425
632
  ```
426
633
 
427
- ### literal · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L120)
634
+ ### literal · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
428
635
 
429
636
  Create a literal type wrapper for a constant value.
430
637
 
431
- **Signature:** `<const T>(value: T) => LiteralType<T>`
638
+ **Signature:** `<const T>(value: T) => TypeWrapper<T>`
432
639
 
433
640
  **Type Parameters:**
434
641
 
@@ -447,11 +654,11 @@ const statusType = E.literal("active");
447
654
  const countType = E.literal(42);
448
655
  ```
449
656
 
450
- ### link · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L120)
657
+ ### link · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
451
658
 
452
659
  Create a link type wrapper for model relationships.
453
660
 
454
- **Signature:** `<const T extends typeof Model<any>>(TargetModel: T) => LinkType<T>`
661
+ **Signature:** `<const T extends typeof Model<any>>(TargetModel: T) => TypeWrapper<InstanceType<T>>`
455
662
 
456
663
  **Type Parameters:**
457
664
 
@@ -475,7 +682,7 @@ class Post extends E.Model<Post> {
475
682
  }
476
683
  ```
477
684
 
478
- ### index · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L120)
685
+ ### index · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
479
686
 
480
687
  Create a secondary index on model fields.
481
688
 
@@ -502,7 +709,7 @@ class User extends E.Model<User> {
502
709
  }
503
710
  ```
504
711
 
505
- ### primary · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L120)
712
+ ### primary · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
506
713
 
507
714
  Create a primary index on model fields.
508
715
 
@@ -529,7 +736,7 @@ class User extends E.Model<User> {
529
736
  }
530
737
  ```
531
738
 
532
- ### unique · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L120)
739
+ ### unique · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
533
740
 
534
741
  Create a unique index on model fields.
535
742
 
@@ -556,7 +763,7 @@ class User extends E.Model<User> {
556
763
  }
557
764
  ```
558
765
 
559
- ### dump · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L120)
766
+ ### dump · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
560
767
 
561
768
  Dump database contents for debugging.
562
769
 
@@ -565,7 +772,7 @@ This is primarily useful for development and debugging purposes.
565
772
 
566
773
  **Signature:** `() => void`
567
774
 
568
- ### BaseIndex · [abstract class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L104)
775
+ ### BaseIndex · [abstract class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L128)
569
776
 
570
777
  Base class for database indexes for efficient lookups on model fields.
571
778
 
@@ -581,66 +788,33 @@ Indexes enable fast queries on specific field combinations and enforce uniquenes
581
788
  - `MyModel`: - The model class this index belongs to.
582
789
  - `_fieldNames`: - Array of field names that make up this index.
583
790
 
584
- #### baseIndex.find · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L120)
585
-
586
- Find model instances using flexible range query options.
587
-
588
- Supports exact matches, inclusive/exclusive range queries, and reverse iteration.
589
- For single-field indexes, you can pass values directly or in arrays.
590
- For multi-field indexes, pass arrays or partial arrays for prefix matching.
791
+ #### baseIndex.find · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
591
792
 
592
- **Signature:** `(opts?: FindOptions<IndexArgTypes<M, F>>) => IndexRangeIterator<M, F>`
793
+ **Signature:** `(opts?: FindOptions<IndexArgTypes<M, F>>) => IndexRangeIterator<M>`
593
794
 
594
795
  **Parameters:**
595
796
 
596
- - `opts: FindOptions<IndexArgTypes<M, F>>` (optional) - - Query options object
797
+ - `opts: FindOptions<IndexArgTypes<M, F>>` (optional)
597
798
 
598
- **Returns:** An iterable of model instances matching the query
799
+ #### baseIndex.batchProcess · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
599
800
 
600
- **Examples:**
801
+ [object Object],[object Object],[object Object]
601
802
 
602
- ```typescript
603
- // Exact match
604
- for (const user of User.byEmail.find({is: "john@example.com"})) {
605
- console.log(user.name);
606
- }
803
+ **Signature:** `(opts: FindOptions<IndexArgTypes<M, F>> & { limitSeconds?: number; limitRows?: number; }, callback: (row: InstanceType<M>) => void | Promise<...>) => Promise<...>`
607
804
 
608
- // Range query (inclusive)
609
- for (const user of User.byEmail.find({from: "a@", to: "m@"})) {
610
- console.log(user.email);
611
- }
612
-
613
- // Range query (exclusive)
614
- for (const user of User.byEmail.find({after: "a@", before: "m@"})) {
615
- console.log(user.email);
616
- }
805
+ **Parameters:**
617
806
 
618
- // Open-ended ranges
619
- for (const user of User.byEmail.find({from: "m@"})) { // m@ and later
620
- console.log(user.email);
621
- }
807
+ - `opts: FindOptions<IndexArgTypes<M, F>> & { limitSeconds?: number; limitRows?: number }` (optional) - - Query options (same as `find()`), plus:
808
+ - `callback: (row: InstanceType<M>) => void | Promise<void>` - - Called for each matching row within a transaction
622
809
 
623
- for (const user of User.byEmail.find({to: "m@"})) { // up to and including m@
624
- console.log(user.email);
625
- }
810
+ #### baseIndex.toString · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
626
811
 
627
- // Reverse iteration
628
- for (const user of User.byEmail.find({reverse: true})) {
629
- console.log(user.email); // Z to A order
630
- }
812
+ **Signature:** `() => string`
631
813
 
632
- // Multi-field index prefix matching
633
- for (const item of CompositeModel.pk.find({from: ["electronics", "phones"]})) {
634
- console.log(item.name); // All electronics/phones items
635
- }
814
+ **Parameters:**
636
815
 
637
- // For single-field indexes, you can use the value directly
638
- for (const user of User.byEmail.find({is: "john@example.com"})) {
639
- console.log(user.name);
640
- }
641
- ```
642
816
 
643
- ### UniqueIndex · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L120)
817
+ ### UniqueIndex · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
644
818
 
645
819
  Unique index that stores references to the primary key.
646
820
 
@@ -649,7 +823,7 @@ Unique index that stores references to the primary key.
649
823
  - `M extends typeof Model` - The model class this index belongs to.
650
824
  - `F extends readonly (keyof InstanceType<M> & string)[]` - The field names that make up this index.
651
825
 
652
- #### uniqueIndex.get · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L120)
826
+ #### uniqueIndex.get · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
653
827
 
654
828
  Get a model instance by unique index key values.
655
829
 
@@ -667,7 +841,7 @@ Get a model instance by unique index key values.
667
841
  const userByEmail = User.byEmail.get("john@example.com");
668
842
  ```
669
843
 
670
- ### PrimaryIndex · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L120)
844
+ ### PrimaryIndex · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
671
845
 
672
846
  Primary index that stores the actual model data.
673
847
 
@@ -676,7 +850,7 @@ Primary index that stores the actual model data.
676
850
  - `M extends typeof Model` - The model class this index belongs to.
677
851
  - `F extends readonly (keyof InstanceType<M> & string)[]` - The field names that make up this index.
678
852
 
679
- #### primaryIndex.get · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L120)
853
+ #### primaryIndex.get · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
680
854
 
681
855
  Get a model instance by primary key values.
682
856
 
@@ -694,111 +868,169 @@ Get a model instance by primary key values.
694
868
  const user = User.pk.get("john_doe");
695
869
  ```
696
870
 
697
- ### init · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L120)
871
+ #### primaryIndex.getLazy · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
698
872
 
699
- Initialize the database with the specified directory path.
700
- This function may only be called once. If it is not called before the first transact(),
701
- the database will be automatically initialized with the default directory.
873
+ Does the same as as `get()`, but will delay loading the instance from disk until the first
874
+ property access. In case it turns out the instance doesn't exist, an error will be thrown
875
+ at that time.
702
876
 
703
- **Signature:** `(dbDir?: string) => void`
877
+ **Signature:** `(...args: IndexArgTypes<M, F>) => InstanceType<M>`
704
878
 
705
879
  **Parameters:**
706
880
 
707
- - `dbDir?: string` - - Optional directory path for the database (defaults to environment variable $OLMDB_DIR or "./.olmdb").
881
+ - `args: IndexArgTypes<M, F>` - Primary key field values. (Or a single Uint8Array containing the key.)
708
882
 
709
- **Throws:**
883
+ **Returns:** The (lazily loaded) model instance.
710
884
 
711
- - With code "DUP_INIT" if database is already initialized.
712
- - With code "CREATE_DIR_FAILED" if directory creation fails.
713
- - With code "LMDB-{code}" for LMDB-specific errors.
885
+ ### SecondaryIndex · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
714
886
 
715
- **Examples:**
887
+ Secondary index for non-unique lookups.
716
888
 
717
- ```typescript
718
- init("./my-database");
719
- ```
889
+ **Type Parameters:**
720
890
 
721
- ### onCommit · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L120)
891
+ - `M extends typeof Model` - The model class this index belongs to.
892
+ - `F extends readonly (keyof InstanceType<M> & string)[]` - The field names that make up this index.
722
893
 
723
- Registers a callback to be executed when the current transaction commits successfully.
724
- The callback will be executed outside of transaction context.
894
+ ### Change · [type](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L94)
725
895
 
726
- **Signature:** `(callback: (commitSeq: number) => void) => void`
896
+ **Type:** `Record<any, any> | "created" | "deleted"`
727
897
 
728
- **Parameters:**
898
+ ### Transaction · [interface](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L38)
729
899
 
730
- - `callback: (commitSeq: number) => void` - - Function to execute when transaction commits. It receives the commit sequence, which is an always-increasing number that provides a global ordering of commits, as an argument.
900
+ #### transaction.id · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L41)
731
901
 
732
- **Throws:**
902
+ **Type:** `number`
733
903
 
734
- - If called outside of a transaction context
904
+ #### transaction.instances · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L42)
735
905
 
736
- ### onRevert · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L120)
906
+ **Type:** `Set<Model<unknown>>`
737
907
 
738
- Registers a callback to be executed when the current transaction is reverted (aborted due to error).
739
- The callback will be executed outside of transaction context.
908
+ #### transaction.instancesByPk · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L44)
740
909
 
741
- **Signature:** `(callback: (commitSeq: number) => void) => void`
910
+ **Type:** `Map<number, Model<unknown>>`
742
911
 
743
- **Parameters:**
912
+ ### DatabaseError · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L156)
744
913
 
745
- - `callback: (commitSeq: number) => void` - - Function to execute when transaction is reverted. It receives the dummy (always 0) commit sequence indicating failure as an argument.
914
+ The DatabaseError class is used to represent errors that occur during database operations.
915
+ It extends the built-in Error class and has a machine readable error code string property.
746
916
 
747
- **Throws:**
917
+ The lowlevel API will throw DatabaseError instances for all database-related errors.
918
+ Invalid function arguments will throw TypeError.
748
919
 
749
- - If called outside of a transaction context
920
+ **Value:** `DatabaseErrorConstructor`
750
921
 
751
- ### getTransactionData · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L51)
922
+ ### runMigration · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L116)
752
923
 
753
- Retrieves data from the current transaction context.
924
+ Run database migration: upgrade all rows to the latest schema version,
925
+ convert old primary indices, and clean up orphaned secondary indices.
754
926
 
755
- **Signature:** `(key: symbol) => any`
927
+ **Signature:** `(options?: MigrationOptions) => Promise<MigrationResult>`
756
928
 
757
929
  **Parameters:**
758
930
 
759
- - `key: symbol` - - A symbol key to retrieve data from the current transaction context.
931
+ - `options: MigrationOptions` (optional)
760
932
 
761
- **Returns:** - The value associated with the key, or undefined if not set.
933
+ ### MigrationOptions · [interface](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L16)
762
934
 
763
- **Throws:**
935
+ #### migrationOptions.tables · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L18)
764
936
 
765
- - If called outside of a transaction context.
937
+ Limit migration to specific table names.
766
938
 
767
- ### setTransactionData · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L44)
939
+ **Type:** `string[]`
768
940
 
769
- Attach some arbitrary user data to the current transaction context, which is
770
- attached to the currently running (async) task.
941
+ #### migrationOptions.convertOldPrimaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L22)
771
942
 
772
- **Signature:** `(key: symbol, value: any) => void`
943
+ Whether to convert old primary indices for known tables (default: true).
773
944
 
774
- **Parameters:**
945
+ **Type:** `boolean`
775
946
 
776
- - `key: symbol` - - A symbol key to store data in the current transaction context.
777
- - `value: any` - - The value to store.
947
+ #### migrationOptions.deleteOrphanedIndexes · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L30)
778
948
 
779
- **Throws:**
949
+ Whether to delete orphaned secondary/unique indices (default: true).
780
950
 
781
- - If called outside of a transaction context.
951
+ **Type:** `boolean`
782
952
 
783
- **Examples:**
953
+ #### migrationOptions.upgradeVersions · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L38)
954
+
955
+ Whether to upgrade rows to the latest version (default: true).
956
+
957
+ **Type:** `boolean`
958
+
959
+ #### migrationOptions.onProgress · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L42)
960
+
961
+ Progress callback.
962
+
963
+ **Type:** `(info: ProgressInfo) => void`
964
+
965
+ ### MigrationResult · [interface](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L47)
966
+
967
+ #### migrationResult.secondaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L49)
968
+
969
+ Per-table stats for row upgrades.
970
+
971
+ **Type:** `Record<string, number>`
972
+
973
+ #### migrationResult.primaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L50)
974
+
975
+ Per-table stats for old primary conversions.
976
+
977
+ **Type:** `Record<string, number>`
978
+
979
+ #### migrationResult.conversionFailures · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L56)
980
+
981
+ Per-table conversion failure counts by reason.
982
+
983
+ **Type:** `Record<string, Record<string, number>>`
984
+
985
+ #### migrationResult.orphaned · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L57)
986
+
987
+ Number of orphaned index entries deleted.
988
+
989
+ **Type:** `number`
990
+
991
+ ## Schema Migrations
992
+
993
+ Edinburgh automatically tracks the schema version of each model. When you change fields, field types, indexes, or the `migrate()` function, Edinburgh detects a new schema version.
994
+
995
+ ### What happens automatically (lazy migration)
996
+
997
+ Changes to regular (non-index) field values are migrated lazily. When a row with an old schema version is loaded from disk, it is deserialized using the old field types and transformed by the optional static `migrate()` function. This is transparent and requires no downtime.
784
998
 
785
999
  ```typescript
786
- const MY_SYMBOL = Symbol("myKey");
787
- await transact(async () => {
788
- setTransactionData(MY_SYMBOL, "myValue");
789
- await somethingAsync(); // Can be interleaved with other transactions
790
- const value = getTransactionData(MY_SYMBOL);
791
- console.log(value); // "myValue"
792
- });
1000
+ @E.registerModel
1001
+ class User extends E.Model<User> {
1002
+ static pk = E.primary(User, "id");
1003
+ id = E.field(E.identifier);
1004
+ name = E.field(E.string);
1005
+ role = E.field(E.string); // newly added field
1006
+
1007
+ static migrate(record: Record<string, any>) {
1008
+ record.role ??= "user"; // provide a default for old rows
1009
+ }
1010
+ }
793
1011
  ```
794
1012
 
795
- ### DatabaseError · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L120)
1013
+ ### What requires `migrate-edinburgh`
796
1014
 
797
- The DatabaseError class is used to represent errors that occur during database operations.
798
- It extends the built-in Error class and has a machine readable error code string property.
1015
+ The `migrate-edinburgh` CLI tool (or the `runMigration()` API) must be run when:
799
1016
 
800
- The lowlevel API will throw DatabaseError instances for all database-related errors.
801
- Invalid function arguments will throw TypeError.
1017
+ - **Adding or removing** secondary or unique indexes
1018
+ - **Changing the fields or types** of an existing index
1019
+ - A **`migrate()` function changes values** that are used in index fields
802
1020
 
803
- **Value:** `DatabaseErrorConstructor`
1021
+ The tool populates new indexes, removes orphaned ones, and updates index entries whose values were changed by `migrate()`. It does *not* rewrite primary data rows - lazy migration handles that on read.
804
1022
 
1023
+ ```bash
1024
+ npx migrate-edinburgh --import ./src/models.ts
1025
+ ```
1026
+
1027
+ Run `npx migrate-edinburgh` to see all of its options.
1028
+
1029
+ You can also call `runMigration()` programmatically:
1030
+
1031
+ ```typescript
1032
+ import { runMigration } from "edinburgh";
1033
+
1034
+ const result = await runMigration({ tables: ["User"] });
1035
+ console.log(result.upgraded); // { User: 1500 }
1036
+ ```