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 +450 -218
- package/build/src/datapack.d.ts +138 -0
- package/build/src/datapack.js +684 -0
- package/build/src/datapack.js.map +1 -0
- package/build/src/edinburgh.d.ts +41 -11
- package/build/src/edinburgh.js +163 -43
- package/build/src/edinburgh.js.map +1 -1
- package/build/src/indexes.d.ts +100 -111
- package/build/src/indexes.js +679 -369
- package/build/src/indexes.js.map +1 -1
- package/build/src/migrate-cli.d.ts +20 -0
- package/build/src/migrate-cli.js +122 -0
- package/build/src/migrate-cli.js.map +1 -0
- package/build/src/migrate.d.ts +33 -0
- package/build/src/migrate.js +225 -0
- package/build/src/migrate.js.map +1 -0
- package/build/src/models.d.ts +147 -46
- package/build/src/models.js +322 -268
- package/build/src/models.js.map +1 -1
- package/build/src/types.d.ts +209 -260
- package/build/src/types.js +423 -324
- package/build/src/types.js.map +1 -1
- package/build/src/utils.d.ts +9 -9
- package/build/src/utils.js +32 -9
- package/build/src/utils.js.map +1 -1
- package/package.json +14 -11
- package/src/datapack.ts +726 -0
- package/src/edinburgh.ts +174 -43
- package/src/indexes.ts +722 -380
- package/src/migrate-cli.ts +138 -0
- package/src/migrate.ts +267 -0
- package/src/models.ts +415 -285
- package/src/types.ts +510 -391
- package/src/utils.ts +40 -12
- package/build/src/bytes.d.ts +0 -155
- package/build/src/bytes.js +0 -455
- package/build/src/bytes.js.map +0 -1
- package/src/bytes.ts +0 -500
package/build/src/models.d.ts
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import { TypeWrapper } from "./types.js";
|
|
3
|
-
|
|
3
|
+
export declare const txnStorage: AsyncLocalStorage<Transaction>;
|
|
4
|
+
/**
|
|
5
|
+
* Returns the current transaction from AsyncLocalStorage.
|
|
6
|
+
* Throws if called outside a transact() callback.
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export declare function currentTxn(): Transaction;
|
|
10
|
+
export interface Transaction {
|
|
11
|
+
id: number;
|
|
12
|
+
instances: Set<Model<unknown>>;
|
|
13
|
+
instancesByPk: Map<number, Model<unknown>>;
|
|
14
|
+
}
|
|
15
|
+
import { BaseIndex as BaseIndex, PrimaryIndex, IndexRangeIterator } from "./indexes.js";
|
|
4
16
|
/**
|
|
5
17
|
* Configuration interface for model fields.
|
|
6
18
|
* @template T - The field type.
|
|
@@ -35,21 +47,10 @@ export interface FieldConfig<T> {
|
|
|
35
47
|
*/
|
|
36
48
|
export declare function field<T>(type: TypeWrapper<T>, options?: Partial<FieldConfig<T>>): T;
|
|
37
49
|
export declare const modelRegistry: Record<string, typeof Model>;
|
|
38
|
-
export
|
|
39
|
-
type OnSaveType = (model: InstanceType<typeof Model>, newKey: Uint8Array | undefined, oldKey: Uint8Array | undefined) => void;
|
|
40
|
-
/**
|
|
41
|
-
* Set a callback function to be called after a model is saved and committed.
|
|
42
|
-
*
|
|
43
|
-
* @param callback 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).
|
|
44
|
-
*/
|
|
45
|
-
export declare function setOnSaveCallback(callback: OnSaveType | undefined): void;
|
|
50
|
+
export type Change = Record<any, any> | "created" | "deleted";
|
|
46
51
|
/**
|
|
47
52
|
* Register a model class with the Edinburgh ORM system.
|
|
48
53
|
*
|
|
49
|
-
* This decorator function transforms the model class to use a proxy-based constructor
|
|
50
|
-
* that enables change tracking and automatic field initialization. It also extracts
|
|
51
|
-
* field metadata and sets up default values on the prototype.
|
|
52
|
-
*
|
|
53
54
|
* @template T - The model class type.
|
|
54
55
|
* @param MyModel - The model class to register.
|
|
55
56
|
* @returns The enhanced model class with ORM capabilities.
|
|
@@ -66,9 +67,6 @@ export declare function setOnSaveCallback(callback: OnSaveType | undefined): voi
|
|
|
66
67
|
*/
|
|
67
68
|
export declare function registerModel<T extends typeof Model<unknown>>(MyModel: T): T;
|
|
68
69
|
export declare function getMockModel<T extends typeof Model<unknown>>(OrgModel: T): T;
|
|
69
|
-
/** @internal Symbol used to attach modified instances to running transaction */
|
|
70
|
-
export declare const MODIFIED_INSTANCES_SYMBOL: unique symbol;
|
|
71
|
-
/** @internal Symbol used to access the underlying model from a proxy */
|
|
72
70
|
/**
|
|
73
71
|
* Model interface that ensures proper typing for the constructor property.
|
|
74
72
|
* @template SUB - The concrete model subclass.
|
|
@@ -83,62 +81,145 @@ export interface Model<SUB> {
|
|
|
83
81
|
* change tracking, and relationship management. All model classes should extend
|
|
84
82
|
* this base class and be decorated with `@registerModel`.
|
|
85
83
|
*
|
|
84
|
+
* ### Schema Evolution
|
|
85
|
+
*
|
|
86
|
+
* Edinburgh tracks the schema version of each model automatically. When you add, remove, or
|
|
87
|
+
* change the types of fields, or add/remove indexes, Edinburgh detects the new schema version.
|
|
88
|
+
*
|
|
89
|
+
* **Lazy migration:** Changes to non-key field values are migrated lazily, when a row with an
|
|
90
|
+
* old schema version is read from disk, it is deserialized using the old schema and optionally
|
|
91
|
+
* transformed by the static `migrate()` function. This happens transparently on every read
|
|
92
|
+
* and requires no downtime or batch processing.
|
|
93
|
+
*
|
|
94
|
+
* **Batch migration (via `npx migrate-edinburgh` or `runMigration()`):** Certain schema changes
|
|
95
|
+
* require an explicit migration run:
|
|
96
|
+
* - Adding or removing secondary/unique indexes
|
|
97
|
+
* - Changing the fields or types of an existing index
|
|
98
|
+
* - A `migrate()` function that changes values used in secondary index fields
|
|
99
|
+
*
|
|
100
|
+
* The batch migration tool populates new indexes, deletes orphaned ones, and updates index
|
|
101
|
+
* entries whose values were changed by `migrate()`. It does *not* rewrite primary data rows
|
|
102
|
+
* (lazy migration handles that).
|
|
103
|
+
*
|
|
104
|
+
* ### Lifecycle Hooks
|
|
105
|
+
*
|
|
106
|
+
* - **`static migrate(record)`**: Called when deserializing rows written with an older schema
|
|
107
|
+
* version. Receives a plain record object; mutate it in-place to match the current schema.
|
|
108
|
+
* See {@link Model.migrate}.
|
|
109
|
+
*
|
|
110
|
+
* - **`preCommit()`**: Called on each modified instance right before the transaction commits.
|
|
111
|
+
* Useful for computing derived fields, enforcing cross-field invariants, or creating related
|
|
112
|
+
* instances. See {@link Model.preCommit}.
|
|
113
|
+
*
|
|
86
114
|
* @template SUB - The concrete model subclass (for proper typing).
|
|
87
115
|
*
|
|
88
116
|
* @example
|
|
89
117
|
* ```typescript
|
|
90
118
|
* @E.registerModel
|
|
91
119
|
* class User extends E.Model<User> {
|
|
92
|
-
* static pk = E.
|
|
120
|
+
* static pk = E.primary(User, "id");
|
|
93
121
|
*
|
|
94
122
|
* id = E.field(E.identifier);
|
|
95
123
|
* name = E.field(E.string);
|
|
96
124
|
* email = E.field(E.string);
|
|
97
125
|
*
|
|
98
|
-
* static byEmail = E.
|
|
126
|
+
* static byEmail = E.unique(User, "email");
|
|
99
127
|
* }
|
|
100
128
|
* ```
|
|
101
129
|
*/
|
|
102
130
|
export declare abstract class Model<SUB> {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
static _indexes?: BaseIndex<any, any>[];
|
|
131
|
+
static _primary: PrimaryIndex<any, any>;
|
|
132
|
+
/** @internal All non-primary indexes for this model. */
|
|
133
|
+
static _secondaries?: BaseIndex<any, readonly (keyof any & string)[]>[];
|
|
107
134
|
/** The database table name (defaults to class name). */
|
|
108
135
|
static tableName: string;
|
|
136
|
+
/** When true, registerModel replaces an existing model with the same tableName. */
|
|
137
|
+
static override?: boolean;
|
|
109
138
|
/** Field configuration metadata. */
|
|
110
|
-
static fields: Record<string, FieldConfig<unknown>>;
|
|
111
|
-
/**
|
|
112
|
-
|
|
139
|
+
static fields: Record<string | symbol | number, FieldConfig<unknown>>;
|
|
140
|
+
/**
|
|
141
|
+
* Optional migration function called when deserializing rows written with an older schema version.
|
|
142
|
+
* Receives a plain record with all fields (primary key fields + value fields) and should mutate it
|
|
143
|
+
* in-place to match the current schema.
|
|
144
|
+
*
|
|
145
|
+
* This is called both during lazy loading (when a row is read from disk) and during batch
|
|
146
|
+
* migration (via `runMigration()` / `npx migrate-edinburgh`). The function's source code is hashed
|
|
147
|
+
* to detect changes. Modifying `migrate()` triggers a new schema version.
|
|
148
|
+
*
|
|
149
|
+
* If `migrate()` changes values of fields used in secondary or unique indexes, those indexes
|
|
150
|
+
* will only be updated when `runMigration()` is run (not during lazy loading).
|
|
151
|
+
*
|
|
152
|
+
* @param record - A plain object with all field values from the old schema version.
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```typescript
|
|
156
|
+
* @E.registerModel
|
|
157
|
+
* class User extends E.Model<User> {
|
|
158
|
+
* static pk = E.primary(User, "id");
|
|
159
|
+
* id = E.field(E.identifier);
|
|
160
|
+
* name = E.field(E.string);
|
|
161
|
+
* role = E.field(E.string); // new field
|
|
162
|
+
*
|
|
163
|
+
* static migrate(record: Record<string, any>) {
|
|
164
|
+
* record.role ??= "user"; // default for rows that predate the 'role' field
|
|
165
|
+
* }
|
|
166
|
+
* }
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
static migrate?(record: Record<string, any>): void;
|
|
113
170
|
/**
|
|
114
|
-
* @internal
|
|
115
|
-
* - undefined:
|
|
116
|
-
* -
|
|
117
|
-
* -
|
|
118
|
-
* - 3: persistence disabled
|
|
119
|
-
* - array: loaded from disk, modified (and in modifiedInstances), array values are original index buffers
|
|
171
|
+
* @internal
|
|
172
|
+
* - _oldValues===undefined: New instance, not yet saved.
|
|
173
|
+
* - _oldValues===null: Instance is to be deleted.
|
|
174
|
+
* - _oldValues is an object: Loaded (possibly only partial, still lazy) from disk, _oldValues contains (partial) old values
|
|
120
175
|
*/
|
|
121
|
-
|
|
176
|
+
_oldValues: Record<string, any> | undefined | null;
|
|
177
|
+
_primaryKey: Uint8Array | undefined;
|
|
178
|
+
_primaryKeyHash: number | undefined;
|
|
179
|
+
_txn: Transaction;
|
|
122
180
|
constructor(initial?: Partial<Omit<SUB, "constructor">>);
|
|
123
|
-
_save(): void;
|
|
124
181
|
/**
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
182
|
+
* Optional hook called on each modified instance right before the transaction commits.
|
|
183
|
+
* Runs before data is written to disk, so changes made here are included in the commit.
|
|
184
|
+
*
|
|
185
|
+
* Common use cases:
|
|
186
|
+
* - Computing derived or denormalized fields
|
|
187
|
+
* - Enforcing cross-field validation rules
|
|
188
|
+
* - Creating or updating related model instances (newly created instances will also
|
|
189
|
+
* have their `preCommit()` called)
|
|
128
190
|
*
|
|
129
191
|
* @example
|
|
130
192
|
* ```typescript
|
|
131
|
-
*
|
|
132
|
-
*
|
|
193
|
+
* @E.registerModel
|
|
194
|
+
* class Post extends E.Model<Post> {
|
|
195
|
+
* static pk = E.primary(Post, "id");
|
|
196
|
+
* id = E.field(E.identifier);
|
|
197
|
+
* title = E.field(E.string);
|
|
198
|
+
* slug = E.field(E.string);
|
|
199
|
+
*
|
|
200
|
+
* preCommit() {
|
|
201
|
+
* this.slug = this.title.toLowerCase().replace(/\s+/g, "-");
|
|
202
|
+
* }
|
|
203
|
+
* }
|
|
133
204
|
* ```
|
|
134
205
|
*/
|
|
135
|
-
|
|
206
|
+
preCommit?(): void;
|
|
207
|
+
static _delayedInit(cleared?: boolean): Promise<void>;
|
|
208
|
+
_setLoadedField(fieldName: string, value: any): void;
|
|
209
|
+
/**
|
|
210
|
+
* @returns The primary key for this instance.
|
|
211
|
+
*/
|
|
212
|
+
getPrimaryKey(): Uint8Array;
|
|
213
|
+
_setPrimaryKey(key: Uint8Array, hash?: number): void;
|
|
214
|
+
/**
|
|
215
|
+
* @returns A 53-bit positive integer non-cryptographic hash of the primary key, or undefined if not yet saved.
|
|
216
|
+
*/
|
|
217
|
+
getPrimaryKeyHash(): number;
|
|
218
|
+
isLazyField(field: keyof this): boolean;
|
|
219
|
+
_write(txn: Transaction): undefined | Change;
|
|
136
220
|
/**
|
|
137
221
|
* Prevent this instance from being persisted to the database.
|
|
138
222
|
*
|
|
139
|
-
* Removes the instance from the modified instances set and disables
|
|
140
|
-
* automatic persistence at transaction commit.
|
|
141
|
-
*
|
|
142
223
|
* @returns This model instance for chaining.
|
|
143
224
|
*
|
|
144
225
|
* @example
|
|
@@ -149,6 +230,26 @@ export declare abstract class Model<SUB> {
|
|
|
149
230
|
* ```
|
|
150
231
|
*/
|
|
151
232
|
preventPersist(): this;
|
|
233
|
+
/**
|
|
234
|
+
* Find all instances of this model in the database, ordered by primary key.
|
|
235
|
+
* @param opts - Optional parameters.
|
|
236
|
+
* @param opts.reverse - If true, iterate in reverse order.
|
|
237
|
+
* @returns An iterator.
|
|
238
|
+
*/
|
|
239
|
+
static findAll<T extends typeof Model<unknown>>(this: T, opts?: {
|
|
240
|
+
reverse?: boolean;
|
|
241
|
+
}): IndexRangeIterator<T>;
|
|
242
|
+
/**
|
|
243
|
+
* Load an existing instance by primary key and update it, or create a new one.
|
|
244
|
+
*
|
|
245
|
+
* The provided object must contain all primary key fields. If a matching row exists,
|
|
246
|
+
* the remaining properties from `obj` are set on the loaded instance. Otherwise a
|
|
247
|
+
* new instance is created with `obj` as its initial properties.
|
|
248
|
+
*
|
|
249
|
+
* @param obj - Partial model data that **must** include every primary key field.
|
|
250
|
+
* @returns The loaded-and-updated or newly created instance.
|
|
251
|
+
*/
|
|
252
|
+
static replaceInto<T extends typeof Model<any>>(this: T, obj: Partial<Omit<InstanceType<T>, "constructor">>): InstanceType<T>;
|
|
152
253
|
/**
|
|
153
254
|
* Delete this model instance from the database.
|
|
154
255
|
*
|
|
@@ -175,7 +276,7 @@ export declare abstract class Model<SUB> {
|
|
|
175
276
|
* }
|
|
176
277
|
* ```
|
|
177
278
|
*/
|
|
178
|
-
validate(raise?: boolean):
|
|
279
|
+
validate(raise?: boolean): Error[];
|
|
179
280
|
/**
|
|
180
281
|
* Check if this model instance is valid.
|
|
181
282
|
* @returns true if all validations pass.
|
|
@@ -187,6 +288,6 @@ export declare abstract class Model<SUB> {
|
|
|
187
288
|
* ```
|
|
188
289
|
*/
|
|
189
290
|
isValid(): boolean;
|
|
291
|
+
getState(): "deleted" | "created" | "loaded" | "lazy";
|
|
292
|
+
toString(): string;
|
|
190
293
|
}
|
|
191
|
-
export declare const modificationTracker: ProxyHandler<any>;
|
|
192
|
-
export {};
|