edinburgh 0.3.0 → 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, lazy loading 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
- - 🔗 **Relationships**: Model instances can reference each other, and will be lazy-loaded on access
11
- - 📦 **Embedded Database**: Negligible query latency, due to a blazing fast embedded database (LMDB)
12
- - 📊 **Custom Indexing**: Efficient querying with primary, unique, and multi-value indexes
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#L89)
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
 
@@ -94,7 +153,6 @@ times.
94
153
  ```typescript
95
154
  const paid = await E.transact(() => {
96
155
  const user = User.pk.get("john_doe");
97
- // This is concurrency-safe - the function will rerun if it is raced by another transaction
98
156
  if (user.credits > 0) {
99
157
  user.credits--;
100
158
  return true;
@@ -105,32 +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
- ### setOnSaveCallback · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L146)
171
+ ### setMaxRetryCount · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L206)
172
+
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`
177
+
178
+ **Parameters:**
179
+
180
+ - `count: number` - The maximum number of retries for a transaction.
181
+
182
+ ### setOnSaveCallback · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L220)
114
183
 
115
184
  Set a callback function to be called after a model is saved and committed.
116
185
 
117
- **Signature:** `(callback: (commitId: number, items: ChangedModel[]) => void) => void`
186
+ **Signature:** `(callback: (commitId: number, items: Map<Model<any>, Change>) => void) => void`
118
187
 
119
188
  **Parameters:**
120
189
 
121
- - `callback: ((commitId: number, items: ChangedModel[]) => void) | undefined` - [object Object],[object Object],[object Object]
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.
122
194
 
123
- ### deleteEverything · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L151)
195
+ ### deleteEverything · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L225)
124
196
 
125
197
  **Signature:** `() => Promise<void>`
126
198
 
127
- ### Model · [abstract class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
128
-
129
- Base class for all database models in the Edinburgh ORM.
199
+ ### Model · [abstract class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L218)
130
200
 
131
- Models represent database entities with typed fields, automatic serialization,
132
- change tracking, and relationship management. All model classes should extend
133
- this base class and be decorated with `@registerModel`.
201
+ [object Object],[object Object],[object Object],[object Object],[object Object]
134
202
 
135
203
  **Type Parameters:**
136
204
 
@@ -141,29 +209,70 @@ this base class and be decorated with `@registerModel`.
141
209
  ```typescript
142
210
  ⁣@E.registerModel
143
211
  class User extends E.Model<User> {
144
- static pk = E.index(User, ["id"], "primary");
212
+ static pk = E.primary(User, "id");
145
213
 
146
214
  id = E.field(E.identifier);
147
215
  name = E.field(E.string);
148
216
  email = E.field(E.string);
149
217
 
150
- static byEmail = E.index(User, "email", "unique");
218
+ static byEmail = E.unique(User, "email");
151
219
  }
152
220
  ```
153
221
 
154
- #### Model.tableName · [static property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
222
+ #### Model.tableName · [static property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L226)
155
223
 
156
224
  The database table name (defaults to class name).
157
225
 
158
226
  **Type:** `string`
159
227
 
160
- #### Model.fields · [static property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
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)
161
235
 
162
236
  Field configuration metadata.
163
237
 
164
238
  **Type:** `Record<string | number | symbol, FieldConfig<unknown>>`
165
239
 
166
- #### Model.findAll · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
240
+ #### Model.migrate · [static method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
241
+
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.
245
+
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.
249
+
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`
254
+
255
+ **Parameters:**
256
+
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)
167
276
 
168
277
  Find all instances of this model in the database, ordered by primary key.
169
278
 
@@ -176,27 +285,74 @@ Find all instances of this model in the database, ordered by primary key.
176
285
 
177
286
  **Returns:** An iterator.
178
287
 
179
- #### model.changed · [property](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
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)
180
306
 
181
- This property can be used in `setOnSave` callbacks to determine how a model instance has changed.
182
- If the value is undefined, the instance has been created. If it's "deleted" the instance has
183
- been deleted. If its an object, the instance has been modified and the object contains the old values.
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.
184
309
 
185
- Note: this property should **not** be accessed *during* a `transact()` -- it's state is an implementation
186
- detail that may change semantics at any minor release.
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)
187
315
 
188
- **Type:** `Record<any, any> | "deleted" | "created"`
316
+ **Signature:** `() => void`
317
+
318
+ **Parameters:**
189
319
 
190
- #### model.getPrimaryKey · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
320
+
321
+ **Examples:**
322
+
323
+ ```typescript
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
+ }
335
+ ```
336
+
337
+ #### model.getPrimaryKey · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
191
338
 
192
339
  **Signature:** `() => Uint8Array<ArrayBufferLike>`
193
340
 
194
341
  **Parameters:**
195
342
 
196
343
 
197
- **Returns:** The primary key for this instance, or undefined if not yet saved.
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.
198
354
 
199
- #### model.isLazyField · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
355
+ #### model.isLazyField · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
200
356
 
201
357
  **Signature:** `(field: keyof this) => boolean`
202
358
 
@@ -204,7 +360,7 @@ detail that may change semantics at any minor release.
204
360
 
205
361
  - `field: keyof this`
206
362
 
207
- #### model.preventPersist · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
363
+ #### model.preventPersist · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
208
364
 
209
365
  Prevent this instance from being persisted to the database.
210
366
 
@@ -223,7 +379,7 @@ user.name = "New Name";
223
379
  user.preventPersist(); // Changes won't be saved
224
380
  ```
225
381
 
226
- #### model.delete · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
382
+ #### model.delete · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
227
383
 
228
384
  Delete this model instance from the database.
229
385
 
@@ -241,7 +397,7 @@ const user = User.load("user123");
241
397
  user.delete(); // Removes from database
242
398
  ```
243
399
 
244
- #### model.validate · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
400
+ #### model.validate · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
245
401
 
246
402
  Validate all fields in this model instance.
247
403
 
@@ -263,7 +419,7 @@ if (errors.length > 0) {
263
419
  }
264
420
  ```
265
421
 
266
- #### model.isValid · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
422
+ #### model.isValid · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
267
423
 
268
424
  Check if this model instance is valid.
269
425
 
@@ -281,7 +437,28 @@ const user = new User({name: "John"});
281
437
  if (!user.isValid()) shoutAtTheUser();
282
438
  ```
283
439
 
284
- ### registerModel · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L91)
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:**
459
+
460
+
461
+ ### registerModel · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L111)
285
462
 
286
463
  Register a model class with the Edinburgh ORM system.
287
464
 
@@ -308,7 +485,7 @@ class User extends E.Model<User> {
308
485
  }
309
486
  ```
310
487
 
311
- ### field · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L60)
488
+ ### field · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L87)
312
489
 
313
490
  Create a field definition for a model property.
314
491
 
@@ -338,13 +515,13 @@ class User extends E.Model<User> {
338
515
  }
339
516
  ```
340
517
 
341
- ### string · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
518
+ ### string · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
342
519
 
343
520
  Type wrapper instance for the string type.
344
521
 
345
522
  **Value:** `TypeWrapper<string>`
346
523
 
347
- ### orderedString · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
524
+ ### orderedString · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
348
525
 
349
526
  Type wrapper instance for the ordered string type, which is just like a string
350
527
  except that it sorts lexicographically in the database (instead of by incrementing
@@ -354,37 +531,37 @@ may not contain null characters.
354
531
 
355
532
  **Value:** `TypeWrapper<string>`
356
533
 
357
- ### number · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
534
+ ### number · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
358
535
 
359
536
  Type wrapper instance for the number type.
360
537
 
361
538
  **Value:** `TypeWrapper<number>`
362
539
 
363
- ### dateTime · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
540
+ ### dateTime · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
364
541
 
365
542
  Type wrapper instance for the date/time type.
366
543
 
367
544
  **Value:** `TypeWrapper<Date>`
368
545
 
369
- ### boolean · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
546
+ ### boolean · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
370
547
 
371
548
  Type wrapper instance for the boolean type.
372
549
 
373
550
  **Value:** `TypeWrapper<boolean>`
374
551
 
375
- ### identifier · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
552
+ ### identifier · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
376
553
 
377
554
  Type wrapper instance for the identifier type.
378
555
 
379
556
  **Value:** `TypeWrapper<string>`
380
557
 
381
- ### undef · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
558
+ ### undef · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
382
559
 
383
560
  Type wrapper instance for the 'undefined' type.
384
561
 
385
562
  **Value:** `TypeWrapper<undefined>`
386
563
 
387
- ### opt · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
564
+ ### opt · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
388
565
 
389
566
  Create an optional type wrapper (allows undefined).
390
567
 
@@ -407,7 +584,7 @@ const optionalString = E.opt(E.string);
407
584
  const optionalNumber = E.opt(E.number);
408
585
  ```
409
586
 
410
- ### or · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
587
+ ### or · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
411
588
 
412
589
  Create a union type wrapper from multiple type choices.
413
590
 
@@ -430,7 +607,7 @@ const stringOrNumber = E.or(E.string, E.number);
430
607
  const status = E.or("active", "inactive", "pending");
431
608
  ```
432
609
 
433
- ### array · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
610
+ ### array · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
434
611
 
435
612
  Create an array type wrapper with optional length constraints.
436
613
 
@@ -454,7 +631,7 @@ const stringArray = E.array(E.string);
454
631
  const boundedArray = E.array(E.number, {min: 1, max: 10});
455
632
  ```
456
633
 
457
- ### literal · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
634
+ ### literal · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
458
635
 
459
636
  Create a literal type wrapper for a constant value.
460
637
 
@@ -477,7 +654,7 @@ const statusType = E.literal("active");
477
654
  const countType = E.literal(42);
478
655
  ```
479
656
 
480
- ### link · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
657
+ ### link · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
481
658
 
482
659
  Create a link type wrapper for model relationships.
483
660
 
@@ -505,7 +682,7 @@ class Post extends E.Model<Post> {
505
682
  }
506
683
  ```
507
684
 
508
- ### index · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
685
+ ### index · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
509
686
 
510
687
  Create a secondary index on model fields.
511
688
 
@@ -532,7 +709,7 @@ class User extends E.Model<User> {
532
709
  }
533
710
  ```
534
711
 
535
- ### primary · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
712
+ ### primary · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
536
713
 
537
714
  Create a primary index on model fields.
538
715
 
@@ -559,7 +736,7 @@ class User extends E.Model<User> {
559
736
  }
560
737
  ```
561
738
 
562
- ### unique · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
739
+ ### unique · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
563
740
 
564
741
  Create a unique index on model fields.
565
742
 
@@ -586,7 +763,7 @@ class User extends E.Model<User> {
586
763
  }
587
764
  ```
588
765
 
589
- ### dump · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
766
+ ### dump · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
590
767
 
591
768
  Dump database contents for debugging.
592
769
 
@@ -595,18 +772,7 @@ This is primarily useful for development and debugging purposes.
595
772
 
596
773
  **Signature:** `() => void`
597
774
 
598
- ### setLogLevel · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L55)
599
-
600
- Global log level for debugging output.
601
- 0 = no logging, 1 = model-level logs, 2 = update logs, 3 = read logs.
602
-
603
- **Signature:** `(level: number) => void`
604
-
605
- **Parameters:**
606
-
607
- - `level: number`
608
-
609
- ### BaseIndex · [abstract class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L129)
775
+ ### BaseIndex · [abstract class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L128)
610
776
 
611
777
  Base class for database indexes for efficient lookups on model fields.
612
778
 
@@ -622,73 +788,33 @@ Indexes enable fast queries on specific field combinations and enforce uniquenes
622
788
  - `MyModel`: - The model class this index belongs to.
623
789
  - `_fieldNames`: - Array of field names that make up this index.
624
790
 
625
- #### baseIndex.find · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
626
-
627
- Find model instances using flexible range query options.
628
-
629
- Supports exact matches, inclusive/exclusive range queries, and reverse iteration.
630
- For single-field indexes, you can pass values directly or in arrays.
631
- 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)
632
792
 
633
793
  **Signature:** `(opts?: FindOptions<IndexArgTypes<M, F>>) => IndexRangeIterator<M>`
634
794
 
635
795
  **Parameters:**
636
796
 
637
- - `opts: FindOptions<IndexArgTypes<M, F>>` (optional) - - Query options object
638
-
639
- **Returns:** An iterable of model instances matching the query
797
+ - `opts: FindOptions<IndexArgTypes<M, F>>` (optional)
640
798
 
641
- **Examples:**
799
+ #### baseIndex.batchProcess · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
642
800
 
643
- ```typescript
644
- // Exact match
645
- for (const user of User.byEmail.find({is: "john@example.com"})) {
646
- console.log(user.name);
647
- }
801
+ [object Object],[object Object],[object Object]
648
802
 
649
- // Range query (inclusive)
650
- for (const user of User.byEmail.find({from: "a@", to: "m@"})) {
651
- console.log(user.email);
652
- }
803
+ **Signature:** `(opts: FindOptions<IndexArgTypes<M, F>> & { limitSeconds?: number; limitRows?: number; }, callback: (row: InstanceType<M>) => void | Promise<...>) => Promise<...>`
653
804
 
654
- // Range query (exclusive)
655
- for (const user of User.byEmail.find({after: "a@", before: "m@"})) {
656
- console.log(user.email);
657
- }
658
-
659
- // Open-ended ranges
660
- for (const user of User.byEmail.find({from: "m@"})) { // m@ and later
661
- console.log(user.email);
662
- }
663
-
664
- for (const user of User.byEmail.find({to: "m@"})) { // up to and including m@
665
- console.log(user.email);
666
- }
667
-
668
- // Reverse iteration
669
- for (const user of User.byEmail.find({reverse: true})) {
670
- console.log(user.email); // Z to A order
671
- }
672
-
673
- // Multi-field index prefix matching
674
- for (const item of CompositeModel.pk.find({from: ["electronics", "phones"]})) {
675
- console.log(item.name); // All electronics/phones items
676
- }
805
+ **Parameters:**
677
806
 
678
- // For single-field indexes, you can use the value directly
679
- for (const user of User.byEmail.find({is: "john@example.com"})) {
680
- console.log(user.name);
681
- }
682
- ```
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
683
809
 
684
- #### baseIndex.toString · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
810
+ #### baseIndex.toString · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
685
811
 
686
812
  **Signature:** `() => string`
687
813
 
688
814
  **Parameters:**
689
815
 
690
816
 
691
- ### UniqueIndex · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
817
+ ### UniqueIndex · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
692
818
 
693
819
  Unique index that stores references to the primary key.
694
820
 
@@ -697,7 +823,7 @@ Unique index that stores references to the primary key.
697
823
  - `M extends typeof Model` - The model class this index belongs to.
698
824
  - `F extends readonly (keyof InstanceType<M> & string)[]` - The field names that make up this index.
699
825
 
700
- #### uniqueIndex.get · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
826
+ #### uniqueIndex.get · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
701
827
 
702
828
  Get a model instance by unique index key values.
703
829
 
@@ -715,7 +841,7 @@ Get a model instance by unique index key values.
715
841
  const userByEmail = User.byEmail.get("john@example.com");
716
842
  ```
717
843
 
718
- ### PrimaryIndex · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
844
+ ### PrimaryIndex · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
719
845
 
720
846
  Primary index that stores the actual model data.
721
847
 
@@ -724,15 +850,15 @@ Primary index that stores the actual model data.
724
850
  - `M extends typeof Model` - The model class this index belongs to.
725
851
  - `F extends readonly (keyof InstanceType<M> & string)[]` - The field names that make up this index.
726
852
 
727
- #### primaryIndex.get · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
853
+ #### primaryIndex.get · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
728
854
 
729
855
  Get a model instance by primary key values.
730
856
 
731
- **Signature:** `(...args: IndexArgTypes<M, F> | [Uint8Array<ArrayBufferLike>]) => InstanceType<M>`
857
+ **Signature:** `(...args: IndexArgTypes<M, F>) => InstanceType<M>`
732
858
 
733
859
  **Parameters:**
734
860
 
735
- - `args: IndexArgTypes<M, F> | [Uint8Array]` - - The primary key values.
861
+ - `args: IndexArgTypes<M, F>` - - The primary key values.
736
862
 
737
863
  **Returns:** The model instance if found, undefined otherwise.
738
864
 
@@ -742,125 +868,169 @@ Get a model instance by primary key values.
742
868
  const user = User.pk.get("john_doe");
743
869
  ```
744
870
 
745
- #### primaryIndex.getLazy · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
871
+ #### primaryIndex.getLazy · [method](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
746
872
 
747
873
  Does the same as as `get()`, but will delay loading the instance from disk until the first
748
874
  property access. In case it turns out the instance doesn't exist, an error will be thrown
749
875
  at that time.
750
876
 
751
- **Signature:** `(...args: IndexArgTypes<M, F> | [Uint8Array<ArrayBufferLike>]) => InstanceType<M>`
877
+ **Signature:** `(...args: IndexArgTypes<M, F>) => InstanceType<M>`
752
878
 
753
879
  **Parameters:**
754
880
 
755
- - `args: IndexArgTypes<M, F> | [Uint8Array]` - Primary key field values. (Or a single Uint8Array containing the key.)
881
+ - `args: IndexArgTypes<M, F>` - Primary key field values. (Or a single Uint8Array containing the key.)
756
882
 
757
883
  **Returns:** The (lazily loaded) model instance.
758
884
 
759
- ### init · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L141)
885
+ ### SecondaryIndex · [class](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L251)
760
886
 
761
- Initialize the database with the specified directory path.
762
- This function may only be called once. If it is not called before the first transact(),
763
- the database will be automatically initialized with the default directory.
887
+ Secondary index for non-unique lookups.
764
888
 
765
- **Signature:** `(dbDir?: string) => void`
889
+ **Type Parameters:**
766
890
 
767
- **Parameters:**
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.
768
893
 
769
- - `dbDir?: string` - - Optional directory path for the database (defaults to environment variable $OLMDB_DIR or "./.olmdb").
894
+ ### Change · [type](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L94)
770
895
 
771
- **Throws:**
896
+ **Type:** `Record<any, any> | "created" | "deleted"`
772
897
 
773
- - With code "DUP_INIT" if database is already initialized.
774
- - With code "CREATE_DIR_FAILED" if directory creation fails.
775
- - With code "LMDB-{code}" for LMDB-specific errors.
898
+ ### Transaction · [interface](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L38)
776
899
 
777
- **Examples:**
900
+ #### transaction.id · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L41)
778
901
 
779
- ```typescript
780
- init("./my-database");
781
- ```
902
+ **Type:** `number`
782
903
 
783
- ### onCommit · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L159)
904
+ #### transaction.instances · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L42)
784
905
 
785
- Registers a callback to be executed when the current transaction commits successfully.
786
- The callback will be executed outside of transaction context.
906
+ **Type:** `Set<Model<unknown>>`
787
907
 
788
- **Signature:** `(callback: (commitSeq: number) => void) => void`
908
+ #### transaction.instancesByPk · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L44)
789
909
 
790
- **Parameters:**
910
+ **Type:** `Map<number, Model<unknown>>`
791
911
 
792
- - `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.
912
+ ### DatabaseError · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L156)
793
913
 
794
- **Throws:**
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.
795
916
 
796
- - If called outside of a transaction context
917
+ The lowlevel API will throw DatabaseError instances for all database-related errors.
918
+ Invalid function arguments will throw TypeError.
797
919
 
798
- ### onRevert · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L151)
920
+ **Value:** `DatabaseErrorConstructor`
921
+
922
+ ### runMigration · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L116)
799
923
 
800
- Registers a callback to be executed when the current transaction is reverted (aborted due to error).
801
- The callback will be executed outside of 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.
802
926
 
803
- **Signature:** `(callback: (commitSeq: number) => void) => void`
927
+ **Signature:** `(options?: MigrationOptions) => Promise<MigrationResult>`
804
928
 
805
929
  **Parameters:**
806
930
 
807
- - `callback: (commitSeq: number) => void` - - Function to execute when transaction is reverted. It receives the dummy (always 0) commit sequence indicating failure as an argument.
931
+ - `options: MigrationOptions` (optional)
808
932
 
809
- **Throws:**
933
+ ### MigrationOptions · [interface](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L16)
810
934
 
811
- - If called outside of a transaction context
935
+ #### migrationOptions.tables · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L18)
812
936
 
813
- ### getTransactionData · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L55)
937
+ Limit migration to specific table names.
814
938
 
815
- Retrieves data from the current transaction context.
939
+ **Type:** `string[]`
816
940
 
817
- **Signature:** `(key: symbol) => any`
941
+ #### migrationOptions.convertOldPrimaries · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L22)
818
942
 
819
- **Parameters:**
943
+ Whether to convert old primary indices for known tables (default: true).
820
944
 
821
- - `key: symbol` - - A symbol key to retrieve data from the current transaction context.
945
+ **Type:** `boolean`
822
946
 
823
- **Returns:** - The value associated with the key, or undefined if not set.
947
+ #### migrationOptions.deleteOrphanedIndexes · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L30)
824
948
 
825
- **Throws:**
949
+ Whether to delete orphaned secondary/unique indices (default: true).
826
950
 
827
- - If called outside of a transaction context.
951
+ **Type:** `boolean`
828
952
 
829
- ### setTransactionData · [function](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L46)
953
+ #### migrationOptions.upgradeVersions · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L38)
830
954
 
831
- Attach some arbitrary user data to the current transaction context, which is
832
- attached to the currently running (async) task.
955
+ Whether to upgrade rows to the latest version (default: true).
833
956
 
834
- **Signature:** `(key: symbol, value: any) => void`
957
+ **Type:** `boolean`
835
958
 
836
- **Parameters:**
959
+ #### migrationOptions.onProgress · [member](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L42)
837
960
 
838
- - `key: symbol` - - A symbol key to store data in the current transaction context.
839
- - `value: any` - - The value to store.
961
+ Progress callback.
840
962
 
841
- **Throws:**
963
+ **Type:** `(info: ProgressInfo) => void`
842
964
 
843
- - If called outside of a transaction context.
965
+ ### MigrationResult · [interface](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L47)
844
966
 
845
- **Examples:**
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.
846
998
 
847
999
  ```typescript
848
- const MY_SYMBOL = Symbol("myKey");
849
- await transact(async () => {
850
- setTransactionData(MY_SYMBOL, "myValue");
851
- await somethingAsync(); // Can be interleaved with other transactions
852
- const value = getTransactionData(MY_SYMBOL);
853
- console.log(value); // "myValue"
854
- });
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
+ }
855
1011
  ```
856
1012
 
857
- ### DatabaseError · [constant](https://github.com/vanviegen/edinburgh/blob/main/src/edinburgh.ts#L142)
1013
+ ### What requires `migrate-edinburgh`
858
1014
 
859
- The DatabaseError class is used to represent errors that occur during database operations.
860
- 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:
861
1016
 
862
- The lowlevel API will throw DatabaseError instances for all database-related errors.
863
- 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
864
1020
 
865
- **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.
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:
866
1030
 
1031
+ ```typescript
1032
+ import { runMigration } from "edinburgh";
1033
+
1034
+ const result = await runMigration({ tables: ["User"] });
1035
+ console.log(result.upgraded); // { User: 1500 }
1036
+ ```