edinburgh 0.4.6 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +403 -461
- package/build/src/datapack.d.ts +9 -9
- package/build/src/datapack.js +10 -10
- package/build/src/datapack.js.map +1 -1
- package/build/src/edinburgh.d.ts +21 -10
- package/build/src/edinburgh.js +33 -55
- package/build/src/edinburgh.js.map +1 -1
- package/build/src/indexes.d.ts +99 -288
- package/build/src/indexes.js +253 -636
- package/build/src/indexes.js.map +1 -1
- package/build/src/migrate.js +17 -39
- package/build/src/migrate.js.map +1 -1
- package/build/src/models.d.ts +177 -113
- package/build/src/models.js +487 -259
- package/build/src/models.js.map +1 -1
- package/build/src/types.d.ts +41 -51
- package/build/src/types.js +39 -52
- package/build/src/types.js.map +1 -1
- package/build/src/utils.d.ts +4 -4
- package/build/src/utils.js +4 -4
- package/package.json +1 -3
- package/skill/AnyModelClass.md +7 -0
- package/skill/FindOptions.md +37 -0
- package/skill/Lifecycle Hooks.md +24 -0
- package/skill/{Model_delete.md → Lifecycle Hooks_delete.md } +2 -2
- package/skill/{Model_getPrimaryKeyHash.md → Lifecycle Hooks_getPrimaryKeyHash.md } +1 -1
- package/skill/{Model_isValid.md → Lifecycle Hooks_isValid.md } +1 -1
- package/skill/Lifecycle Hooks_migrate.md +26 -0
- package/skill/{Model_preCommit.md → Lifecycle Hooks_preCommit.md } +3 -5
- package/skill/{Model_preventPersist.md → Lifecycle Hooks_preventPersist.md } +2 -2
- package/skill/{Model_validate.md → Lifecycle Hooks_validate.md } +2 -2
- package/skill/ModelBase.md +7 -0
- package/skill/ModelClass.md +8 -0
- package/skill/SKILL.md +253 -215
- package/skill/Schema Evolution.md +19 -0
- package/skill/TypeWrapper_containsNull.md +11 -0
- package/skill/TypeWrapper_deserialize.md +9 -0
- package/skill/TypeWrapper_getError.md +11 -0
- package/skill/TypeWrapper_serialize.md +10 -0
- package/skill/TypeWrapper_serializeType.md +9 -0
- package/skill/array.md +2 -2
- package/skill/defineModel.md +23 -0
- package/skill/deleteEverything.md +8 -0
- package/skill/field.md +4 -4
- package/skill/link.md +12 -10
- package/skill/literal.md +1 -1
- package/skill/opt.md +1 -1
- package/skill/or.md +1 -1
- package/skill/record.md +1 -1
- package/skill/set.md +2 -2
- package/skill/setOnSaveCallback.md +2 -2
- package/skill/transact.md +3 -3
- package/src/datapack.ts +10 -10
- package/src/edinburgh.ts +46 -58
- package/src/indexes.ts +338 -802
- package/src/migrate.ts +15 -37
- package/src/models.ts +617 -314
- package/src/types.ts +61 -54
- package/src/utils.ts +4 -4
- package/skill/BaseIndex.md +0 -16
- package/skill/BaseIndex_batchProcess.md +0 -10
- package/skill/BaseIndex_find.md +0 -7
- package/skill/Model.md +0 -22
- package/skill/Model_findAll.md +0 -12
- package/skill/Model_migrate.md +0 -34
- package/skill/Model_replaceInto.md +0 -16
- package/skill/PrimaryIndex.md +0 -8
- package/skill/PrimaryIndex_get.md +0 -17
- package/skill/PrimaryIndex_getLazy.md +0 -13
- package/skill/SecondaryIndex.md +0 -9
- package/skill/UniqueIndex.md +0 -9
- package/skill/UniqueIndex_get.md +0 -17
- package/skill/dump.md +0 -8
- package/skill/index.md +0 -32
- package/skill/primary.md +0 -26
- package/skill/registerModel.md +0 -26
- package/skill/unique.md +0 -32
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
### Schema Evolution
|
|
2
|
+
|
|
3
|
+
Edinburgh tracks the schema version of each model automatically. When you add, remove, or
|
|
4
|
+
change the types of fields, or add/remove indexes, Edinburgh detects the new schema version.
|
|
5
|
+
|
|
6
|
+
**Lazy migration:** Changes to non-key field values are migrated lazily, when a row with an
|
|
7
|
+
old schema version is read from disk, it is deserialized using the old schema and optionally
|
|
8
|
+
transformed by the static `migrate()` function. This happens transparently on every read
|
|
9
|
+
and requires no downtime or batch processing.
|
|
10
|
+
|
|
11
|
+
**Batch migration (via `npx migrate-edinburgh` or `runMigration()`):** Certain schema changes
|
|
12
|
+
require an explicit migration run:
|
|
13
|
+
- Adding or removing secondary/unique indexes
|
|
14
|
+
- Changing the fields or types of an existing index
|
|
15
|
+
- A `migrate()` function that changes values used in secondary index fields
|
|
16
|
+
|
|
17
|
+
The batch migration tool populates new indexes, deletes orphaned ones, and updates index
|
|
18
|
+
entries whose values were changed by `migrate()`. It does *not* rewrite primary data rows
|
|
19
|
+
(lazy migration handles that).
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#### typeWrapper.serialize · abstract method
|
|
2
|
+
|
|
3
|
+
Serialize a value from an object property to a Pack.
|
|
4
|
+
|
|
5
|
+
**Signature:** `(value: T, pack: DataPack) => void`
|
|
6
|
+
|
|
7
|
+
**Parameters:**
|
|
8
|
+
|
|
9
|
+
- `value: T` - The value to serialize.
|
|
10
|
+
- `pack: DataPack` - The Pack instance to write to.
|
package/skill/array.md
CHANGED
|
@@ -10,8 +10,8 @@ Create an array type wrapper with optional length constraints.
|
|
|
10
10
|
|
|
11
11
|
**Parameters:**
|
|
12
12
|
|
|
13
|
-
- `inner: TypeWrapper<T>` -
|
|
14
|
-
- `opts: {min?: number, max?: number}` (optional) -
|
|
13
|
+
- `inner: TypeWrapper<T>` - Type wrapper for array elements.
|
|
14
|
+
- `opts: {min?: number, max?: number}` (optional) - Optional constraints (min/max length).
|
|
15
15
|
|
|
16
16
|
**Returns:** An array type instance.
|
|
17
17
|
|
|
@@ -0,0 +1,23 @@
|
|
|
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
|
+
- `tableName: string` - The database table name for this model.
|
|
20
|
+
- `cls: T` - A plain class whose properties use E.field().
|
|
21
|
+
- `opts?: { pk?: PK, unique?: UNIQUE, index?: INDEX, override?: boolean }` - Registration options.
|
|
22
|
+
|
|
23
|
+
**Returns:** The enhanced model constructor.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
### deleteEverything · function
|
|
2
|
+
|
|
3
|
+
Delete every key/value entry in the database and reinitialize all registered models.
|
|
4
|
+
|
|
5
|
+
This clears rows, index metadata, and schema-version records. It is mainly useful
|
|
6
|
+
for tests, local resets, or tooling that needs a completely empty database.
|
|
7
|
+
|
|
8
|
+
**Signature:** `() => Promise<void>`
|
package/skill/field.md
CHANGED
|
@@ -14,16 +14,16 @@ This allows for both runtime introspection and compile-time type safety.
|
|
|
14
14
|
|
|
15
15
|
**Parameters:**
|
|
16
16
|
|
|
17
|
-
- `type: TypeWrapper<T>` -
|
|
18
|
-
- `options: Partial<FieldConfig<T>>` (optional) -
|
|
17
|
+
- `type: TypeWrapper<T>` - The type wrapper for this field.
|
|
18
|
+
- `options: Partial<FieldConfig<T>>` (optional) - Additional field configuration options.
|
|
19
19
|
|
|
20
20
|
**Returns:** The field value (typed as T, but actually returns FieldConfig<T>).
|
|
21
21
|
|
|
22
22
|
**Examples:**
|
|
23
23
|
|
|
24
24
|
```typescript
|
|
25
|
-
|
|
25
|
+
const User = E.defineModel("User", 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,26 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
Create a link type wrapper for model relationships.
|
|
4
4
|
|
|
5
|
-
**Signature:**
|
|
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
|
|
9
|
+
- `T extends new (...args: any[]) => Model<any>` - The target model class.
|
|
10
10
|
|
|
11
11
|
**Parameters:**
|
|
12
12
|
|
|
13
|
-
- `TargetModel: T` -
|
|
13
|
+
- `TargetModel: T` - The model class this link points to.
|
|
14
14
|
|
|
15
15
|
**Returns:** A link type instance.
|
|
16
16
|
|
|
17
17
|
**Examples:**
|
|
18
18
|
|
|
19
19
|
```typescript
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
const Author = E.defineModel("Author", 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("Book", class {
|
|
26
|
+
id = E.field(E.identifier);
|
|
27
|
+
author = E.field(E.link(Author));
|
|
28
|
+
}, { pk: "id" });
|
|
27
29
|
```
|
package/skill/literal.md
CHANGED
package/skill/opt.md
CHANGED
package/skill/or.md
CHANGED
package/skill/record.md
CHANGED
|
@@ -10,7 +10,7 @@ Create a Record type wrapper for key-value objects with string or number keys.
|
|
|
10
10
|
|
|
11
11
|
**Parameters:**
|
|
12
12
|
|
|
13
|
-
- `inner: TypeWrapper<T>` -
|
|
13
|
+
- `inner: TypeWrapper<T>` - Type wrapper for record values.
|
|
14
14
|
|
|
15
15
|
**Returns:** A record type instance.
|
|
16
16
|
|
package/skill/set.md
CHANGED
|
@@ -10,8 +10,8 @@ Create a Set type wrapper with optional length constraints.
|
|
|
10
10
|
|
|
11
11
|
**Parameters:**
|
|
12
12
|
|
|
13
|
-
- `inner: TypeWrapper<T>` -
|
|
14
|
-
- `opts: {min?: number, max?: number}` (optional) -
|
|
13
|
+
- `inner: TypeWrapper<T>` - Type wrapper for set elements.
|
|
14
|
+
- `opts: {min?: number, max?: number}` (optional) - Optional constraints (min/max length).
|
|
15
15
|
|
|
16
16
|
**Returns:** A set type instance.
|
|
17
17
|
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
Set a callback function to be called after a model is saved and committed.
|
|
4
4
|
|
|
5
|
-
**Signature:** `(callback: (commitId: number, items: Map<
|
|
5
|
+
**Signature:** `(callback: (commitId: number, items: Map<ModelBase, Change>) => void) => void`
|
|
6
6
|
|
|
7
7
|
**Parameters:**
|
|
8
8
|
|
|
9
|
-
- `callback: ((commitId: number, items: Map<Model<
|
|
9
|
+
- `callback: ((commitId: number, items: Map<Model<unknown>, Change>) => void) | undefined` - The callback function to set. It gets called after each successful
|
|
10
10
|
`transact()` commit that has changes, with the following arguments:
|
|
11
11
|
- A sequential number. Higher numbers have been committed after lower numbers.
|
|
12
12
|
- A map of model instances to their changes. The change can be "created", "deleted", or an object containing the old values.
|
package/skill/transact.md
CHANGED
|
@@ -18,7 +18,7 @@ times.
|
|
|
18
18
|
|
|
19
19
|
**Parameters:**
|
|
20
20
|
|
|
21
|
-
- `fn: () => T` -
|
|
21
|
+
- `fn: () => T` - The function to execute within the transaction context. Receives a Transaction instance.
|
|
22
22
|
|
|
23
23
|
**Returns:** A promise that resolves with the function's return value.
|
|
24
24
|
|
|
@@ -32,7 +32,7 @@ times.
|
|
|
32
32
|
|
|
33
33
|
```typescript
|
|
34
34
|
const paid = await E.transact(() => {
|
|
35
|
-
const user = User.
|
|
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.
|
|
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
|
@@ -46,7 +46,7 @@ export default class DataPack {
|
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
48
|
* Create a new DataPack instance.
|
|
49
|
-
* @param data
|
|
49
|
+
* @param data Optional initial data as Uint8Array or buffer size as number.
|
|
50
50
|
*/
|
|
51
51
|
constructor(data: Uint8Array | number = 1900) {
|
|
52
52
|
if (data instanceof Uint8Array) {
|
|
@@ -59,10 +59,10 @@ export default class DataPack {
|
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
61
|
* Helper function to write a multi-byte integer with length prefix
|
|
62
|
-
* @param value
|
|
63
|
-
* @param headerType
|
|
64
|
-
* @param invertBytes
|
|
65
|
-
* @param invertByteCount
|
|
62
|
+
* @param value The value to write
|
|
63
|
+
* @param headerType The type bits (0-7) for the header
|
|
64
|
+
* @param invertBytes Whether to invert bytes (for negative numbers)
|
|
65
|
+
* @param invertByteCount Whether to invert the byte count (for type 0)
|
|
66
66
|
*/
|
|
67
67
|
private writeMultiByteNumber(value: number, headerType: number, invertBytes: boolean = false, invertByteCount: boolean = false): void {
|
|
68
68
|
let byteCount = 0;
|
|
@@ -87,8 +87,8 @@ export default class DataPack {
|
|
|
87
87
|
|
|
88
88
|
/**
|
|
89
89
|
* Helper function to read a multi-byte integer with length prefix
|
|
90
|
-
* @param byteCount
|
|
91
|
-
* @param invertBytes
|
|
90
|
+
* @param byteCount Number of bytes to read
|
|
91
|
+
* @param invertBytes Whether to invert bytes (for negative numbers)
|
|
92
92
|
* @returns The read value
|
|
93
93
|
*/
|
|
94
94
|
private readMultiByteNumber(byteCount: number, invertBytes: boolean = false): number {
|
|
@@ -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) {
|
|
@@ -383,7 +383,7 @@ export default class DataPack {
|
|
|
383
383
|
|
|
384
384
|
/**
|
|
385
385
|
* Ensure the buffer has capacity for additional bytes.
|
|
386
|
-
* @param bytesNeeded
|
|
386
|
+
* @param bytesNeeded Number of additional bytes needed.
|
|
387
387
|
*/
|
|
388
388
|
private ensureCapacity(bytesNeeded: number): void {
|
|
389
389
|
const needed = this.writePos + bytesNeeded;
|
|
@@ -511,7 +511,7 @@ export default class DataPack {
|
|
|
511
511
|
/**
|
|
512
512
|
* Like writeString but writes without a length prefix and with a null terminator, for ordered storage.
|
|
513
513
|
* Can be read with {@link read} or {@link readString} just like any other string.
|
|
514
|
-
* @param str
|
|
514
|
+
* @param str The string to write. May not contain null characters.
|
|
515
515
|
*/
|
|
516
516
|
writeOrderedString(str: string): DataPack {
|
|
517
517
|
const utf8Bytes = new TextEncoder().encode(str);
|
package/src/edinburgh.ts
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import * as lowlevel from "olmdb/lowlevel";
|
|
2
2
|
import { init as olmdbInit, DatabaseError } from "olmdb/lowlevel";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
let initNeeded = true;
|
|
6
|
-
export function scheduleInit() { initNeeded = true; }
|
|
3
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
|
+
import { pendingModelInits } from "./models.js";
|
|
7
5
|
|
|
8
6
|
|
|
9
7
|
// Re-export public API from models
|
|
10
8
|
export {
|
|
11
9
|
Model,
|
|
12
|
-
|
|
10
|
+
type ModelClass,
|
|
11
|
+
type AnyModelClass,
|
|
12
|
+
type ModelBase,
|
|
13
|
+
defineModel,
|
|
14
|
+
deleteEverything,
|
|
13
15
|
field,
|
|
14
16
|
} from "./models.js";
|
|
15
17
|
|
|
16
|
-
import type {
|
|
18
|
+
import type { Change, Model } from "./models.js";
|
|
17
19
|
|
|
18
20
|
// Re-export public API from types (only factory functions and instances)
|
|
19
21
|
export {
|
|
@@ -37,20 +39,36 @@ export {
|
|
|
37
39
|
|
|
38
40
|
// Re-export public API from indexes
|
|
39
41
|
export {
|
|
40
|
-
index,
|
|
41
|
-
primary,
|
|
42
|
-
unique,
|
|
43
42
|
dump,
|
|
44
43
|
} from "./indexes.js";
|
|
45
44
|
|
|
46
|
-
export {
|
|
45
|
+
export type { FindOptions, IndexRangeIterator } from './indexes.js';
|
|
47
46
|
|
|
48
47
|
export { type Change } from './models.js';
|
|
49
|
-
export type {
|
|
48
|
+
export type { FieldConfig } from './models.js';
|
|
49
|
+
export { TypeWrapper } from './types.js';
|
|
50
50
|
export { DatabaseError } from "olmdb/lowlevel";
|
|
51
51
|
export { runMigration } from './migrate.js';
|
|
52
52
|
export type { MigrationOptions, MigrationResult } from './migrate.js';
|
|
53
53
|
|
|
54
|
+
export interface Transaction {
|
|
55
|
+
id: number;
|
|
56
|
+
instances: Map<number, Model<unknown>>; // pkHash => instance
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const txnStorage = new AsyncLocalStorage<Transaction>();
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Returns the current transaction from AsyncLocalStorage.
|
|
63
|
+
* Throws if called outside a transact() callback.
|
|
64
|
+
* @internal
|
|
65
|
+
*/
|
|
66
|
+
export function currentTxn(): Transaction {
|
|
67
|
+
const txn = txnStorage.getStore();
|
|
68
|
+
if (!txn) throw new DatabaseError("No active transaction. Operations must be performed within a transact() callback.", 'NO_TRANSACTION');
|
|
69
|
+
return txn;
|
|
70
|
+
}
|
|
71
|
+
|
|
54
72
|
let olmdbReady = false;
|
|
55
73
|
let maxRetryCount = 6;
|
|
56
74
|
|
|
@@ -90,7 +108,7 @@ const STALE_INSTANCE_DESCRIPTOR = {
|
|
|
90
108
|
* times.
|
|
91
109
|
*
|
|
92
110
|
* @template T - The return type of the transaction function.
|
|
93
|
-
* @param fn
|
|
111
|
+
* @param fn The function to execute within the transaction context. Receives a Transaction instance.
|
|
94
112
|
* @returns A promise that resolves with the function's return value.
|
|
95
113
|
* @throws {DatabaseError} With code "RACING_TRANSACTION" if the transaction fails after retries due to conflicts.
|
|
96
114
|
* @throws {DatabaseError} With code "TXN_LIMIT" if maximum number of transactions is reached.
|
|
@@ -99,7 +117,7 @@ const STALE_INSTANCE_DESCRIPTOR = {
|
|
|
99
117
|
* @example
|
|
100
118
|
* ```typescript
|
|
101
119
|
* const paid = await E.transact(() => {
|
|
102
|
-
* const user = User.
|
|
120
|
+
* const user = User.get("john_doe");
|
|
103
121
|
* if (user.credits > 0) {
|
|
104
122
|
* user.credits--;
|
|
105
123
|
* return true;
|
|
@@ -110,24 +128,23 @@ const STALE_INSTANCE_DESCRIPTOR = {
|
|
|
110
128
|
* ```typescript
|
|
111
129
|
* // Transaction with automatic retry on conflicts
|
|
112
130
|
* await E.transact(() => {
|
|
113
|
-
* const counter = Counter.
|
|
131
|
+
* const counter = Counter.get("global") || new Counter({id: "global", value: 0});
|
|
114
132
|
* counter.value++;
|
|
115
133
|
* });
|
|
116
134
|
* ```
|
|
117
135
|
*/
|
|
118
136
|
export async function transact<T>(fn: () => T): Promise<T> {
|
|
119
|
-
while (
|
|
120
|
-
// Make sure only one async task is doing the inits, the rest should wait for it
|
|
137
|
+
while (pendingModelInits.size || pendingInit) {
|
|
121
138
|
if (pendingInit) {
|
|
122
139
|
await pendingInit;
|
|
123
140
|
} else {
|
|
124
141
|
pendingInit = (async () => {
|
|
125
142
|
if (!olmdbReady) olmdbInit('.edinburgh');
|
|
126
143
|
olmdbReady = true;
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
await model.
|
|
144
|
+
const models = [...pendingModelInits];
|
|
145
|
+
pendingModelInits.clear();
|
|
146
|
+
for (const model of models) {
|
|
147
|
+
await model._initialize();
|
|
131
148
|
}
|
|
132
149
|
})();
|
|
133
150
|
await pendingInit;
|
|
@@ -138,7 +155,7 @@ export async function transact<T>(fn: () => T): Promise<T> {
|
|
|
138
155
|
// try {
|
|
139
156
|
for (let retryCount = 0; retryCount < maxRetryCount; retryCount++) {
|
|
140
157
|
const txnId = lowlevel.startTransaction();
|
|
141
|
-
const txn: Transaction = { id: txnId, instances: new
|
|
158
|
+
const txn: Transaction = { id: txnId, instances: new Map() };
|
|
142
159
|
const onSaveItems: Map<Model<unknown>, Change> | undefined = onSaveCallback ? new Map() : undefined;
|
|
143
160
|
|
|
144
161
|
let result: T | undefined;
|
|
@@ -147,16 +164,16 @@ export async function transact<T>(fn: () => T): Promise<T> {
|
|
|
147
164
|
result = await fn();
|
|
148
165
|
|
|
149
166
|
// Call preCommit() on all instances before writing.
|
|
150
|
-
// Note:
|
|
167
|
+
// Note: Map iteration visits newly added items, so preCommit() creating
|
|
151
168
|
// new instances is handled correctly.
|
|
152
|
-
for (const instance of txn.instances) {
|
|
153
|
-
instance.preCommit?.();
|
|
169
|
+
for (const instance of txn.instances.values()) {
|
|
170
|
+
if (instance._oldValues !== false) instance.preCommit?.();
|
|
154
171
|
}
|
|
155
172
|
|
|
156
173
|
// Save all modified instances before committing
|
|
157
174
|
// This needs to happen inside txnStorage.run, because resolving default values
|
|
158
175
|
// for identifiers requires database access.
|
|
159
|
-
for (const instance of txn.instances) {
|
|
176
|
+
for (const instance of txn.instances.values()) {
|
|
160
177
|
const change = instance._write(txn);
|
|
161
178
|
if (onSaveItems && change) {
|
|
162
179
|
onSaveItems.set(instance, change);
|
|
@@ -168,14 +185,14 @@ export async function transact<T>(fn: () => T): Promise<T> {
|
|
|
168
185
|
throw e;
|
|
169
186
|
} finally {
|
|
170
187
|
// Make the instances read-only to make it clear that their transaction has ended.
|
|
171
|
-
for (const instance of txn.instances) {
|
|
188
|
+
for (const instance of txn.instances.values()) {
|
|
172
189
|
delete instance._oldValues;
|
|
173
190
|
Object.defineProperty(instance, "_txn", STALE_INSTANCE_DESCRIPTOR);
|
|
174
191
|
Object.freeze(instance);
|
|
175
192
|
}
|
|
176
193
|
// Destroy the transaction object, to make sure things crash if they are used after
|
|
177
194
|
// this point, and to help the GC reclaim memory.
|
|
178
|
-
txn.id = txn.instances =
|
|
195
|
+
txn.id = txn.instances = undefined as any;
|
|
179
196
|
}
|
|
180
197
|
|
|
181
198
|
const commitResult = lowlevel.commitTransaction(txnId);
|
|
@@ -210,7 +227,7 @@ export function setMaxRetryCount(count: number) {
|
|
|
210
227
|
maxRetryCount = count;
|
|
211
228
|
}
|
|
212
229
|
|
|
213
|
-
let onSaveCallback: ((commitId: number, items: Map<Model<
|
|
230
|
+
let onSaveCallback: ((commitId: number, items: Map<Model<unknown>, Change>) => void) | undefined;
|
|
214
231
|
|
|
215
232
|
/**
|
|
216
233
|
* Set a callback function to be called after a model is saved and committed.
|
|
@@ -220,35 +237,6 @@ let onSaveCallback: ((commitId: number, items: Map<Model<any>, Change>) => void)
|
|
|
220
237
|
* - A sequential number. Higher numbers have been committed after lower numbers.
|
|
221
238
|
* - A map of model instances to their changes. The change can be "created", "deleted", or an object containing the old values.
|
|
222
239
|
*/
|
|
223
|
-
export function setOnSaveCallback(callback: ((commitId: number, items: Map<Model<
|
|
240
|
+
export function setOnSaveCallback(callback: ((commitId: number, items: Map<Model<unknown>, Change>) => void) | undefined) {
|
|
224
241
|
onSaveCallback = callback;
|
|
225
242
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
export async function deleteEverything(): Promise<void> {
|
|
229
|
-
let done = false;
|
|
230
|
-
while (!done) {
|
|
231
|
-
await transact(() => {
|
|
232
|
-
const txn = currentTxn();
|
|
233
|
-
const iteratorId = lowlevel.createIterator(txn.id, undefined, undefined, false);
|
|
234
|
-
const deadline = Date.now() + 150;
|
|
235
|
-
let count = 0;
|
|
236
|
-
try {
|
|
237
|
-
while (true) {
|
|
238
|
-
const raw = lowlevel.readIterator(iteratorId);
|
|
239
|
-
if (!raw) { done = true; break; }
|
|
240
|
-
lowlevel.del(txn.id, raw.key);
|
|
241
|
-
if (++count >= 4096 || Date.now() >= deadline) break;
|
|
242
|
-
}
|
|
243
|
-
} finally {
|
|
244
|
-
lowlevel.closeIterator(iteratorId);
|
|
245
|
-
}
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
// Re-init indexes since metadata was deleted
|
|
249
|
-
for (const model of Object.values(modelRegistry)) {
|
|
250
|
-
if (!model.fields) continue;
|
|
251
|
-
model.initFields(true);
|
|
252
|
-
await model._loadCreateIndexes();
|
|
253
|
-
}
|
|
254
|
-
}
|