edinburgh 0.4.6 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +263 -381
  2. package/build/src/datapack.js +1 -1
  3. package/build/src/datapack.js.map +1 -1
  4. package/build/src/edinburgh.d.ts +5 -5
  5. package/build/src/edinburgh.js +6 -7
  6. package/build/src/edinburgh.js.map +1 -1
  7. package/build/src/indexes.d.ts +44 -113
  8. package/build/src/indexes.js +145 -175
  9. package/build/src/indexes.js.map +1 -1
  10. package/build/src/migrate.js +11 -31
  11. package/build/src/migrate.js.map +1 -1
  12. package/build/src/models.d.ts +73 -54
  13. package/build/src/models.js +110 -171
  14. package/build/src/models.js.map +1 -1
  15. package/build/src/types.d.ts +29 -21
  16. package/build/src/types.js +16 -30
  17. package/build/src/types.js.map +1 -1
  18. package/package.json +1 -3
  19. package/skill/BaseIndex_batchProcess.md +1 -1
  20. package/skill/BaseIndex_find.md +2 -2
  21. package/skill/BaseIndex_find_2.md +7 -0
  22. package/skill/BaseIndex_find_3.md +7 -0
  23. package/skill/BaseIndex_find_4.md +7 -0
  24. package/skill/Model.md +5 -7
  25. package/skill/Model_batchProcess.md +8 -0
  26. package/skill/Model_delete.md +1 -1
  27. package/skill/Model_migrate.md +2 -4
  28. package/skill/Model_preCommit.md +2 -4
  29. package/skill/Model_preventPersist.md +1 -1
  30. package/skill/Model_replaceInto.md +2 -2
  31. package/skill/NonPrimaryIndex.md +10 -0
  32. package/skill/SKILL.md +140 -150
  33. package/skill/SecondaryIndex.md +2 -2
  34. package/skill/UniqueIndex.md +2 -2
  35. package/skill/defineModel.md +22 -0
  36. package/skill/field.md +2 -2
  37. package/skill/link.md +11 -9
  38. package/skill/transact.md +2 -2
  39. package/src/datapack.ts +1 -1
  40. package/src/edinburgh.ts +6 -9
  41. package/src/indexes.ts +155 -271
  42. package/src/migrate.ts +9 -30
  43. package/src/models.ts +186 -180
  44. package/src/types.ts +31 -26
  45. package/skill/Model_findAll.md +0 -12
  46. package/skill/PrimaryIndex.md +0 -8
  47. package/skill/PrimaryIndex_get.md +0 -17
  48. package/skill/PrimaryIndex_getLazy.md +0 -13
  49. package/skill/UniqueIndex_get.md +0 -17
  50. package/skill/index.md +0 -32
  51. package/skill/primary.md +0 -26
  52. package/skill/registerModel.md +0 -26
  53. package/skill/unique.md +0 -32
package/skill/SKILL.md CHANGED
@@ -26,39 +26,41 @@ import * as E from "edinburgh";
26
26
  // Initialize the database (optional, defaults to ".edinburgh")
27
27
  E.init("./my-database");
28
28
 
29
- // Define a model
30
- @E.registerModel
31
- class User extends E.Model<User> {
32
- // Define a primary key (optional, defaults to using the "id" field)
33
- static pk = E.primary(User, "id");
34
- // Define a unique index on the email field
35
- static byEmail = E.unique(User, "email");
36
-
37
- // Define fields with simple types -- they will be type-checked at compile time and validated at runtime.
29
+ const User = E.defineModel(class {
38
30
  id = E.field(E.identifier);
39
31
  name = E.field(E.string);
40
32
  age = E.field(E.number);
41
- email = E.field(E.opt(E.string)); // TypeScript: undefined | string
42
-
43
- // Link to another instance of this model
44
- supervisor = E.field(E.opt(E.link(User)));
45
-
46
- // A field with a more elaborate type. In TypeScript: `User | User[] | "unknown" | "whatever"`
47
- something = E.field(E.or(E.link(User), E.array(E.link(User)), E.literal("unknown"), E.literal("whatever")), { default: "unknown" });
48
- }
33
+ email = E.field(E.opt(E.string));
34
+ // Optional link to another instance of this model (needs a function as `User` is not defined yet at this point)
35
+ supervisor = E.field(E.opt(E.link(() => User)));
36
+ // A field with a more elaborate type. In TypeScript: `User | User[] | "unknown" | "whatever"`, defaulting to "unknown".
37
+ something = E.field(
38
+ E.or(
39
+ E.link(() => User),
40
+ E.array(E.link(() => User)),
41
+ E.literal("unknown"),
42
+ E.literal("whatever")
43
+ ),
44
+ { default: "unknown" }
45
+ );
46
+ }, {
47
+ pk: "id",
48
+ unique: {
49
+ byEmail: "email",
50
+ },
51
+ tableName: "User",
52
+ });
49
53
 
50
- // Use in transactions
51
54
  await E.transact(() => {
52
- const boss = new User({
53
- name: "Big Boss",
54
- age: 50,
55
- });
56
- const john = new User({ // Unique 'id' is automatically generated if not provided
57
- name: "John Doe",
55
+ // Unique 'id' values are auto-generated if not provided
56
+ const boss = new User({ name: "Big Boss", age: 50 });
57
+ new User({
58
+ name: "John Doe",
58
59
  age: 41,
59
60
  email: "john@example.com",
60
61
  supervisor: boss, // Link to another model instance
61
62
  });
63
+ // Newly instantiated models are automatically saved to the database on transaction commit
62
64
  });
63
65
 
64
66
  await E.transact(() => {
@@ -70,41 +72,31 @@ await E.transact(() => {
70
72
  john.age++;
71
73
 
72
74
  // The supervisor object is lazy loaded on first access
73
- console.log(`${john.supervisor!.name} is ${john.name}'s supervisor`);
75
+ console.log(`${john.supervisor!.name} is ${john.name}'s supervisor`);
74
76
  });
75
77
  ```
76
78
 
77
79
  ## Tutorial
78
80
 
79
- ### TypeScript Configuration
80
-
81
- When using TypeScript to transpile to JavaScript, make sure to enable the following options in your `tsconfig.json`:
82
-
83
- ```json
84
- {
85
- "compilerOptions": {
86
- "target": "es2022",
87
- "experimentalDecorators": true
88
- }
89
- }
90
- ```
91
81
 
92
82
  ### Defining Models
93
83
 
94
- Models are classes that extend `E.Model<Self>` and use the `@E.registerModel` decorator:
84
+ Models are plain, usually anonymous, classes passed to `E.defineModel()`:
95
85
 
96
86
  ```typescript
97
87
  import * as E from "edinburgh";
98
88
 
99
- @E.registerModel
100
- class User extends E.Model<User> {
101
- static pk = E.primary(User, "id");
102
-
89
+ const User = E.defineModel(class {
103
90
  id = E.field(E.identifier);
104
91
  name = E.field(E.string);
105
92
  email = E.field(E.string);
106
93
  age = E.field(E.number);
107
- }
94
+ }, {
95
+ pk: "id",
96
+ unique: {
97
+ byEmail: "email",
98
+ },
99
+ });
108
100
  ```
109
101
 
110
102
  Instance fields are declared with `E.field(type, options?)`. Available types:
@@ -128,16 +120,13 @@ Instance fields are declared with `E.field(type, options?)`. Available types:
128
120
  #### Defaults
129
121
 
130
122
  ```typescript
131
- @E.registerModel
132
- class Post extends E.Model<Post> {
133
- static pk = E.primary(Post, "id");
134
-
135
- id = E.field(E.identifier); // auto-generated
123
+ const Post = E.defineModel(class {
124
+ id = E.field(E.identifier); // auto-generated
136
125
  title = E.field(E.string);
137
126
  status = E.field(E.or("draft", "published"), {default: "draft"});
138
- tags = E.field(E.array(E.string), {default: () => []}); // use function for mutable defaults
139
- createdAt = E.field(E.dateTime); // dateTime defaults to new Date()
140
- }
127
+ tags = E.field(E.array(E.string), {default: () => []}); // use function for mutable defaults
128
+ createdAt = E.field(E.dateTime); // dateTime defaults to new Date()
129
+ }, { pk: "id" });
141
130
  ```
142
131
 
143
132
  ### Transactions
@@ -150,8 +139,8 @@ E.init("./my-database");
150
139
 
151
140
  // Create
152
141
  await E.transact(() => {
153
- const user = new User({name: "Alice", email: "alice@example.com", age: 30});
154
- // user.id is auto-generated
142
+ // User.id is auto-generated
143
+ new User({name: "Alice", email: "alice@example.com", age: 30});
155
144
  });
156
145
 
157
146
  // Read + Update
@@ -174,27 +163,26 @@ Transactions auto-retry on conflict (up to 6 times by default). Keep transaction
174
163
  Edinburgh supports three index types:
175
164
 
176
165
  ```typescript
177
- @E.registerModel
178
- class Product extends E.Model<Product> {
179
- static pk = E.primary(Product, "sku"); // primary: one per model, stores data
180
- static byName = E.unique(Product, "name"); // unique: enforces uniqueness + fast lookup
181
- static byCategory = E.index(Product, "category");// secondary: non-unique, for queries
182
-
166
+ const Product = E.defineModel(class {
183
167
  sku = E.field(E.string);
184
168
  name = E.field(E.string);
185
169
  category = E.field(E.string);
186
170
  price = E.field(E.number);
187
- }
171
+ }, {
172
+ pk: "sku",
173
+ unique: { byName: "name" },
174
+ index: { byCategory: "category" },
175
+ });
188
176
  ```
189
177
 
190
- If no `E.primary()` is declared, Edinburgh auto-creates one on an `id` field (adding `E.identifier` if missing).
178
+ If no `pk` is provided, Edinburgh auto-creates one on an `id` field, adding it as an `E.identifier` field if needed.
191
179
 
192
180
  #### Lookups
193
181
 
194
182
  ```typescript
195
183
  await E.transact(() => {
196
184
  // Primary key lookup
197
- const p = Product.pk.get("SKU-001");
185
+ const p1 = Product.get("SKU-001");
198
186
 
199
187
  // Unique index lookup
200
188
  const p2 = Product.byName.get("Widget");
@@ -215,18 +203,18 @@ await E.transact(() => {
215
203
  }
216
204
 
217
205
  // Range (inclusive)
218
- for (const p of Product.pk.find({from: "A", to: "M"})) {
206
+ for (const p of Product.find({from: "A", to: "M"})) {
219
207
  console.log(p.sku);
220
208
  }
221
209
 
222
210
  // Exclusive bounds
223
- for (const p of Product.pk.find({after: "A", before: "M"})) { ... }
211
+ for (const p of Product.find({after: "A", before: "M"})) { ... }
224
212
 
225
213
  // Open-ended
226
- for (const p of Product.pk.find({from: "M"})) { ... }
214
+ for (const p of Product.find({from: "M"})) { ... }
227
215
 
228
216
  // Reverse
229
- for (const p of Product.pk.find({reverse: true})) { ... }
217
+ for (const p of Product.find({reverse: true})) { ... }
230
218
 
231
219
  // Count and fetch helpers
232
220
  const count = Product.byCategory.find({is: "electronics"}).count();
@@ -234,25 +222,24 @@ await E.transact(() => {
234
222
  });
235
223
  ```
236
224
 
237
- #### Composite Indexes
225
+ #### Composite Primary Keys
238
226
 
239
227
  ```typescript
240
- @E.registerModel
241
- class Event extends E.Model<Event> {
242
- static pk = E.primary(Event, ["year", "month", "id"]);
243
-
228
+ const Event = E.defineModel(class {
244
229
  year = E.field(E.number);
245
230
  month = E.field(E.number);
246
231
  id = E.field(E.identifier);
247
232
  title = E.field(E.string);
248
- }
233
+ }, {
234
+ pk: ["year", "month", "id"] as const,
235
+ });
249
236
 
250
237
  await E.transact(() => {
251
238
  // Prefix matching — find all events in 2025
252
- for (const e of Event.pk.find({is: [2025]})) { ... }
239
+ for (const e of Event.find({is: [2025]})) { ... }
253
240
 
254
241
  // Find events in March 2025
255
- for (const e of Event.pk.find({is: [2025, 3]})) { ... }
242
+ for (const e of Event.find({is: [2025, 3]})) { ... }
256
243
  });
257
244
  ```
258
245
 
@@ -261,10 +248,7 @@ await E.transact(() => {
261
248
  You can freely add regular methods, getters, and other non-persistent properties to model classes. These work normally in JavaScript but are **not stored in the database** and **not synchronized** across transactions or processes.
262
249
 
263
250
  ```typescript
264
- @E.registerModel
265
- class User extends E.Model<User> {
266
- static pk = E.primary(User, "id");
267
- id = E.field(E.identifier);
251
+ const User = E.defineModel(class {
268
252
  firstName = E.field(E.string);
269
253
  lastName = E.field(E.string);
270
254
 
@@ -279,27 +263,30 @@ class User extends E.Model<User> {
279
263
  greet(): string {
280
264
  return `Hello, ${this.fullName}!`;
281
265
  }
282
- }
266
+ });
283
267
  ```
284
268
 
285
269
  #### Computed Indexes
286
270
 
287
- Instead of naming fields, you can pass a **function** to `E.unique()` or `E.index()`. The function receives a model instance and returns an **array** of index key values. Each element creates a separate index entry, enabling multi-value indexes. Return `[]` to skip indexing for that instance (partial index).
271
+ Instead of naming fields, you can pass a function as an index specification. The function receives a model instance and returns an **array** of index key values. Each element creates a separate index entry, enabling multi-value indexes. Return `[]` to skip indexing for that instance (partial index).
288
272
 
289
273
  ```typescript
290
- @E.registerModel
291
- class Article extends E.Model<Article> {
292
- static pk = E.primary(Article, "id");
293
- static byFullName = E.unique(Article, (a: Article) => [`${a.firstName} ${a.lastName}`]);
294
- static byWord = E.index(Article, (a: Article) => a.title.toLowerCase().split(" "));
295
- static byDomain = E.index(Article, (a: Article) => a.email ? [a.email.split("@")[1]] : []);
296
-
274
+ const Article = E.defineModel(class {
297
275
  id = E.field(E.identifier);
298
276
  firstName = E.field(E.string);
299
277
  lastName = E.field(E.string);
300
278
  title = E.field(E.string);
301
279
  email = E.field(E.opt(E.string));
302
- }
280
+ }, {
281
+ pk: "id",
282
+ unique: {
283
+ byFullName: (a: any) => [`${a.firstName} ${a.lastName}`], // computed covering unique index
284
+ },
285
+ index: {
286
+ byDomain: (a: any) => a.email ? [a.email.split("@")[1]] : [], // computed partial index
287
+ byWord: (a: any) => a.title.toLowerCase().split(" "), // computed multi-index
288
+ },
289
+ });
303
290
 
304
291
  await E.transact(() => {
305
292
  new Article({ firstName: "Jane", lastName: "Doe", title: "Hello World", email: "jane@acme.com" });
@@ -315,34 +302,29 @@ await E.transact(() => {
315
302
  });
316
303
  ```
317
304
 
318
- Computed indexes also support `find()` range queries and `batchProcess()`, just like field-based indexes.
319
-
320
- ### Relationships (Links)
305
+ ### Relationships
321
306
 
322
- Use `E.link(Model)` for foreign keys:
307
+ Use `E.link(Model)` for foreign keys. Use a thunk (a function that just returns a value) for forward references when needed:
323
308
 
324
309
  ```typescript
325
- @E.registerModel
326
- class Author extends E.Model<Author> {
327
- static pk = E.primary(Author, "id");
310
+ const Author = E.defineModel(class {
328
311
  id = E.field(E.identifier);
329
312
  name = E.field(E.string);
330
- }
313
+ }, { pk: "id" });
331
314
 
332
- @E.registerModel
333
- class Book extends E.Model<Book> {
334
- static pk = E.primary(Book, "id");
315
+ const Book = E.defineModel(class {
335
316
  id = E.field(E.identifier);
336
317
  title = E.field(E.string);
337
318
  author = E.field(E.link(Author));
338
- }
319
+ }, { pk: "id" });
339
320
 
340
321
  await E.transact(() => {
341
322
  const author = new Author({name: "Tolkien"});
342
323
  const book = new Book({title: "The Hobbit", author});
343
324
 
344
325
  // Later: linked models are lazy-loaded on property access
345
- const b = Book.pk.get(book.id)!;
326
+ const b = Book.get(book.id)!;
327
+ console.log(b.author.id); // no need to load yet..
346
328
  console.log(b.author.name); // loads Author automatically (~1µs)
347
329
  });
348
330
  ```
@@ -351,7 +333,7 @@ await E.transact(() => {
351
333
 
352
334
  ```typescript
353
335
  await E.transact(() => {
354
- const user = User.pk.get(someId);
336
+ const user = User.get(someId);
355
337
  if (user) user.delete();
356
338
  });
357
339
  ```
@@ -369,10 +351,16 @@ await E.transact(() => {
369
351
  user.preventPersist(); // exclude from commit
370
352
  });
371
353
 
372
- // findAll iterates all instances
354
+ // find() iterates all instances (or use range options)
373
355
  await E.transact(() => {
374
- for (const user of User.findAll()) { ... }
375
- for (const user of User.findAll({reverse: true})) { ... }
356
+ for (const user of User.find()) { ... }
357
+ for (const user of User.find({reverse: true})) { ... }
358
+
359
+ // {fetch: 'first'} returns a single instance or undefined
360
+ const first = User.find({fetch: 'first'});
361
+
362
+ // {fetch: 'single'} returns a single instance, or throws if there are none or more than one
363
+ const only = User.find({fetch: 'single'});
376
364
  });
377
365
 
378
366
  // replaceInto: upsert by primary key
@@ -386,7 +374,7 @@ await E.transact(() => {
386
374
  For large datasets, `batchProcess` auto-commits in batches:
387
375
 
388
376
  ```typescript
389
- await Product.byCategory.batchProcess({is: "old"}, (product) => {
377
+ await Product.batchProcess({ limitRows: 1000 }, (product) => {
390
378
  product.category = "archived";
391
379
  });
392
380
  // Commits every ~1 second or 4096 rows (configurable via limitSeconds, limitRows)
@@ -394,12 +382,10 @@ await Product.byCategory.batchProcess({is: "old"}, (product) => {
394
382
 
395
383
  ### Lazy Schema Migrations
396
384
 
397
- When you change a model's schema, Edinburgh will lazily try to migrate old records on access. This allows you to deploy code changes without downtime or a separate migration step. Optionally, you may provide a `static migrate(record: Record<string, any>)` function on the model to transform old records during lazy migration. If there is a migration error (like a new field without a default value, or an incompatible type change), a run-time error is thrown when loading the affected model instance.
385
+ When you change a model's schema, Edinburgh lazily migrates old records on access. You can provide a `static migrate(record)` function to transform old rows:
398
386
 
399
387
  ```typescript
400
- @E.registerModel
401
- class User extends E.Model<User> {
402
- static pk = E.primary(User, "id");
388
+ const UserV2 = E.defineModel(class {
403
389
  id = E.field(E.identifier);
404
390
  name = E.field(E.string);
405
391
  role = E.field(E.string); // newly added field
@@ -442,9 +428,7 @@ console.log(result.secondaries); // { User: 1500 }
442
428
  Compute derived fields before data is written:
443
429
 
444
430
  ```typescript
445
- @E.registerModel
446
- class Article extends E.Model<Article> {
447
- static pk = E.primary(Article, "id");
431
+ const Article = E.defineModel(class {
448
432
  id = E.field(E.identifier);
449
433
  title = E.field(E.string);
450
434
  slug = E.field(E.string);
@@ -452,7 +436,7 @@ class Article extends E.Model<Article> {
452
436
  preCommit() {
453
437
  this.slug = this.title.toLowerCase().replace(/\s+/g, "-");
454
438
  }
455
- }
439
+ }, { pk: "id" });
456
440
  ```
457
441
 
458
442
  ### Change Tracking
@@ -532,7 +516,7 @@ The database table name (defaults to class name).
532
516
 
533
517
  #### Model.override · static property
534
518
 
535
- When true, registerModel replaces an existing model with the same tableName.
519
+ When true, defineModel replaces an existing model with the same tableName.
536
520
 
537
521
  **Type:** `boolean`
538
522
 
@@ -548,21 +532,31 @@ Optional migration function called when deserializing rows written with an older
548
532
  Receives a plain record with all fields (primary key fields + value fields) and should mutate it
549
533
  in-place to match the current schema.
550
534
 
551
- #### Model.initFields · static method
535
+ #### Model.get · static method
536
+
537
+ **Signature:** `(...args: any[]) => any`
538
+
539
+ **Parameters:**
540
+
541
+ - `args: any[]`
552
542
 
553
- Transform the model's `E.field` properties into the appropriate JavaScript properties. Normally this is done
554
- automatically when using `transact()`, but in case you need to access `Model.fields` directly before the first
555
- transaction, you can call this method manually.
543
+ #### Model.getLazy · static method
556
544
 
557
- **Signature:** `(reset?: boolean) => void`
545
+ **Signature:** `(...args: any[]) => any`
558
546
 
559
547
  **Parameters:**
560
548
 
561
- - `reset?: boolean`
549
+ - `args: any[]`
562
550
 
563
- #### [Model.findAll](Model_findAll.md) · static method
551
+ #### Model.find · static method
564
552
 
565
- Find all instances of this model in the database, ordered by primary key.
553
+ **Signature:** `(opts?: any) => any`
554
+
555
+ **Parameters:**
556
+
557
+ - `opts?: any`
558
+
559
+ #### [Model.batchProcess](Model_batchProcess.md) · static method
566
560
 
567
561
  #### [Model.replaceInto](Model_replaceInto.md) · static method
568
562
 
@@ -617,7 +611,7 @@ Check if this model instance is valid.
617
611
 
618
612
  **Signature:** `() => string`
619
613
 
620
- ### [registerModel](registerModel.md) · function
614
+ ### [defineModel](defineModel.md) · function
621
615
 
622
616
  Register a model class with the Edinburgh ORM system.
623
617
 
@@ -625,6 +619,13 @@ Register a model class with the Edinburgh ORM system.
625
619
 
626
620
  Create a field definition for a model property.
627
621
 
622
+ ### currentTxn · function
623
+
624
+ Returns the current transaction from AsyncLocalStorage.
625
+ Throws if called outside a transact() callback.
626
+
627
+ **Signature:** `() => Transaction`
628
+
628
629
  ### string · constant
629
630
 
630
631
  Type wrapper instance for the string type.
@@ -699,18 +700,6 @@ Create a literal type wrapper for a constant value.
699
700
 
700
701
  Create a link type wrapper for model relationships.
701
702
 
702
- ### [index](index.md) · function
703
-
704
- Create a secondary index on model fields, or a computed secondary index using a function.
705
-
706
- ### [primary](primary.md) · function
707
-
708
- Create a primary index on model fields.
709
-
710
- ### [unique](unique.md) · function
711
-
712
- Create a unique index on model fields, or a computed unique index using a function.
713
-
714
703
  ### [dump](dump.md) · function
715
704
 
716
705
  Dump database contents for debugging.
@@ -721,6 +710,12 @@ Base class for database indexes for efficient lookups on model fields.
721
710
 
722
711
  #### [baseIndex.find](BaseIndex_find.md) · method
723
712
 
713
+ #### [baseIndex.find](BaseIndex_find_2.md) · method
714
+
715
+ #### [baseIndex.find](BaseIndex_find_3.md) · method
716
+
717
+ #### [baseIndex.find](BaseIndex_find_4.md) · method
718
+
724
719
  #### [baseIndex.batchProcess](BaseIndex_batchProcess.md) · method
725
720
 
726
721
  [object Object],[object Object],[object Object]
@@ -729,27 +724,22 @@ Base class for database indexes for efficient lookups on model fields.
729
724
 
730
725
  **Signature:** `() => string`
731
726
 
732
- ### [UniqueIndex](UniqueIndex.md) · class
733
-
734
- Unique index that stores references to the primary key.
735
-
736
- #### [uniqueIndex.get](UniqueIndex_get.md) · method
727
+ ### [NonPrimaryIndex](NonPrimaryIndex.md) · abstract class
737
728
 
738
- Get a model instance by unique index key values.
729
+ Abstract base for all non-primary indexes (unique and secondary).
730
+ Provides shared key serialization, write/delete/update logic.
739
731
 
740
- ### [PrimaryIndex](PrimaryIndex.md) · class
732
+ ### [UniqueIndex](UniqueIndex.md) · class
741
733
 
742
- Primary index that stores the actual model data.
734
+ Unique index that stores references to the primary key.
743
735
 
744
- #### [primaryIndex.get](PrimaryIndex_get.md) · method
736
+ #### uniqueIndex.get · method
745
737
 
746
- Get a model instance by primary key values.
738
+ **Signature:** `(...args: ARGS) => InstanceType<M>`
747
739
 
748
- #### [primaryIndex.getLazy](PrimaryIndex_getLazy.md) · method
740
+ **Parameters:**
749
741
 
750
- Does the same as as `get()`, but will delay loading the instance from disk until the first
751
- property access. In case it turns out the instance doesn't exist, an error will be thrown
752
- at that time.
742
+ - `args: ARGS`
753
743
 
754
744
  ### [SecondaryIndex](SecondaryIndex.md) · class
755
745
 
@@ -4,6 +4,6 @@ Secondary index for non-unique lookups.
4
4
 
5
5
  **Type Parameters:**
6
6
 
7
- - `M extends typeof Model` - The model class this index belongs to.
8
- - `F extends readonly (keyof InstanceType<M> & string)[]` - The field names that make up this index.
7
+ - `M extends typeof Model`
8
+ - `F extends readonly (keyof InstanceType<M> & string)[]`
9
9
  - `ARGS extends readonly any[] = IndexArgTypes<M, F>`
@@ -4,6 +4,6 @@ Unique index that stores references to the primary key.
4
4
 
5
5
  **Type Parameters:**
6
6
 
7
- - `M extends typeof Model` - The model class this index belongs to.
8
- - `F extends readonly (keyof InstanceType<M> & string)[]` - The field names that make up this index.
7
+ - `M extends typeof Model`
8
+ - `F extends readonly (keyof InstanceType<M> & string)[]`
9
9
  - `ARGS extends readonly any[] = IndexArgTypes<M, F>`
@@ -0,0 +1,22 @@
1
+ ### defineModel · function
2
+
3
+ Register a model class with the Edinburgh ORM system.
4
+
5
+ Converts a plain class into a fully-featured model with database persistence,
6
+ typed fields, primary key access, and optional secondary and unique indexes.
7
+
8
+ **Signature:** `<T extends new () => any, const PK extends (keyof FieldsOf<T> & string) | readonly (keyof FieldsOf<T> & string)[], const UNIQUE extends Record<string, (keyof FieldsOf<T> & string) | readonly (keyof FieldsOf<T> & string)[] | ((instance: any) => any)>, const INDEX extends Record<string, (keyof FieldsOf<T> & string) | ...`
9
+
10
+ **Type Parameters:**
11
+
12
+ - `T extends new () => any`
13
+ - `PK extends (keyof FieldsOf<T> & string) | readonly (keyof FieldsOf<T> & string)[]`
14
+ - `UNIQUE extends Record<string, (keyof FieldsOf<T> & string) | readonly (keyof FieldsOf<T> & string)[] | ((instance: any) => any)>`
15
+ - `INDEX extends Record<string, (keyof FieldsOf<T> & string) | readonly (keyof FieldsOf<T> & string)[] | ((instance: any) => any)>`
16
+
17
+ **Parameters:**
18
+
19
+ - `cls: T` - - A plain class whose properties use E.field().
20
+ - `opts?: { pk?: PK, unique?: UNIQUE, index?: INDEX, tableName?: string, override?: boolean }` - - Registration options.
21
+
22
+ **Returns:** The enhanced model constructor.
package/skill/field.md CHANGED
@@ -22,8 +22,8 @@ This allows for both runtime introspection and compile-time type safety.
22
22
  **Examples:**
23
23
 
24
24
  ```typescript
25
- class User extends E.Model<User> {
25
+ const User = E.defineModel(class {
26
26
  name = E.field(E.string, {description: "User's full name"});
27
27
  age = E.field(E.opt(E.number), {description: "User's age", default: 25});
28
- }
28
+ });
29
29
  ```
package/skill/link.md CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  Create a link type wrapper for model relationships.
4
4
 
5
- **Signature:** `<const T extends typeof Model<any>>(TargetModel: T) => TypeWrapper<InstanceType<T>>`
5
+ **Signature:** `{ <const T extends new (...args: any[]) => Model<any>>(TargetModel: T): TypeWrapper<InstanceType<T>>; <const T extends new (...args: any[]) => Model<any>>(TargetModel: () => T): TypeWrapper<...>; }`
6
6
 
7
7
  **Type Parameters:**
8
8
 
9
- - `T extends typeof Model<any>` - The target model class.
9
+ - `T extends new (...args: any[]) => Model<any>` - The target model class.
10
10
 
11
11
  **Parameters:**
12
12
 
@@ -17,11 +17,13 @@ Create a link type wrapper for model relationships.
17
17
  **Examples:**
18
18
 
19
19
  ```typescript
20
- class User extends E.Model<User> {
21
- posts = E.field(E.array(E.link(Post, 'author')));
22
- }
23
-
24
- class Post extends E.Model<Post> {
25
- author = E.field(E.link(User));
26
- }
20
+ const Author = E.defineModel(class {
21
+ id = E.field(E.identifier);
22
+ posts = E.field(E.array(E.link(() => Book)));
23
+ }, { pk: "id" });
24
+
25
+ const Book = E.defineModel(class {
26
+ id = E.field(E.identifier);
27
+ author = E.field(E.link(Author));
28
+ }, { pk: "id" });
27
29
  ```
package/skill/transact.md CHANGED
@@ -32,7 +32,7 @@ times.
32
32
 
33
33
  ```typescript
34
34
  const paid = await E.transact(() => {
35
- const user = User.pk.get("john_doe");
35
+ const user = User.get("john_doe");
36
36
  if (user.credits > 0) {
37
37
  user.credits--;
38
38
  return true;
@@ -43,7 +43,7 @@ const paid = await E.transact(() => {
43
43
  ```typescript
44
44
  // Transaction with automatic retry on conflicts
45
45
  await E.transact(() => {
46
- const counter = Counter.pk.get("global") || new Counter({id: "global", value: 0});
46
+ const counter = Counter.get("global") || new Counter({id: "global", value: 0});
47
47
  counter.value++;
48
48
  });
49
49
  ```
package/src/datapack.ts CHANGED
@@ -187,7 +187,7 @@ export default class DataPack {
187
187
  this.ensureCapacity(data.length);
188
188
  this.buffer.set(data, this.writePos);
189
189
  this.writePos += data.length;
190
- } else if (Array.isArray(data)) {
190
+ } else if (Array.isArray(data) || (typeof data.length === 'number' && typeof data[Symbol.iterator] === 'function')) {
191
191
  // Type 4, subtype 5: array start
192
192
  this.buffer[this.writePos++] = (4 << 5) | 5;
193
193
  for (const item of data) {