edinburgh 0.4.2 → 0.4.5
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 +251 -170
- package/build/src/datapack.d.ts +17 -1
- package/build/src/datapack.js +44 -5
- package/build/src/datapack.js.map +1 -1
- package/build/src/edinburgh.d.ts +1 -1
- package/build/src/edinburgh.js +1 -1
- package/build/src/edinburgh.js.map +1 -1
- package/build/src/indexes.d.ts +34 -17
- package/build/src/indexes.js +126 -55
- package/build/src/indexes.js.map +1 -1
- package/build/src/migrate-cli.d.ts +1 -16
- package/build/src/migrate-cli.js +56 -42
- package/build/src/migrate-cli.js.map +1 -1
- package/build/src/migrate.d.ts +15 -11
- package/build/src/migrate.js +62 -32
- package/build/src/migrate.js.map +1 -1
- package/build/src/models.d.ts +1 -1
- package/build/src/models.js +20 -7
- package/build/src/models.js.map +1 -1
- package/build/src/types.d.ts +13 -1
- package/build/src/types.js +89 -1
- package/build/src/types.js.map +1 -1
- package/build/src/utils.d.ts +2 -0
- package/build/src/utils.js +12 -0
- package/build/src/utils.js.map +1 -1
- package/package.json +7 -4
- package/skill/BaseIndex.md +16 -0
- package/skill/BaseIndex_batchProcess.md +10 -0
- package/skill/BaseIndex_find.md +7 -0
- package/skill/DatabaseError.md +9 -0
- package/skill/Model.md +22 -0
- package/skill/Model_delete.md +14 -0
- package/skill/Model_findAll.md +12 -0
- package/skill/Model_getPrimaryKeyHash.md +5 -0
- package/skill/Model_isValid.md +14 -0
- package/skill/Model_migrate.md +34 -0
- package/skill/Model_preCommit.md +28 -0
- package/skill/Model_preventPersist.md +15 -0
- package/skill/Model_replaceInto.md +16 -0
- package/skill/Model_validate.md +21 -0
- package/skill/PrimaryIndex.md +8 -0
- package/skill/PrimaryIndex_get.md +17 -0
- package/skill/PrimaryIndex_getLazy.md +13 -0
- package/skill/SKILL.md +158 -664
- package/skill/SecondaryIndex.md +9 -0
- package/skill/UniqueIndex.md +9 -0
- package/skill/UniqueIndex_get.md +17 -0
- package/skill/array.md +23 -0
- package/skill/dump.md +8 -0
- package/skill/field.md +29 -0
- package/skill/index.md +32 -0
- package/skill/init.md +17 -0
- package/skill/link.md +27 -0
- package/skill/literal.md +22 -0
- package/skill/opt.md +22 -0
- package/skill/or.md +22 -0
- package/skill/primary.md +26 -0
- package/skill/record.md +21 -0
- package/skill/registerModel.md +26 -0
- package/skill/runMigration.md +10 -0
- package/skill/set.md +23 -0
- package/skill/setMaxRetryCount.md +10 -0
- package/skill/setOnSaveCallback.md +12 -0
- package/skill/transact.md +49 -0
- package/skill/unique.md +32 -0
- package/src/datapack.ts +49 -7
- package/src/edinburgh.ts +2 -0
- package/src/indexes.ts +143 -71
- package/src/migrate-cli.ts +44 -46
- package/src/migrate.ts +71 -39
- package/src/models.ts +19 -7
- package/src/types.ts +97 -1
- package/src/utils.ts +12 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
### SecondaryIndex · class
|
|
2
|
+
|
|
3
|
+
Secondary index for non-unique lookups.
|
|
4
|
+
|
|
5
|
+
**Type Parameters:**
|
|
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.
|
|
9
|
+
- `ARGS extends readonly any[] = IndexArgTypes<M, F>`
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
### UniqueIndex · class
|
|
2
|
+
|
|
3
|
+
Unique index that stores references to the primary key.
|
|
4
|
+
|
|
5
|
+
**Type Parameters:**
|
|
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.
|
|
9
|
+
- `ARGS extends readonly any[] = IndexArgTypes<M, F>`
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#### uniqueIndex.get · method
|
|
2
|
+
|
|
3
|
+
Get a model instance by unique index key values.
|
|
4
|
+
|
|
5
|
+
**Signature:** `(...args: ARGS) => InstanceType<M>`
|
|
6
|
+
|
|
7
|
+
**Parameters:**
|
|
8
|
+
|
|
9
|
+
- `args: ARGS` - - The unique index key values.
|
|
10
|
+
|
|
11
|
+
**Returns:** The model instance if found, undefined otherwise.
|
|
12
|
+
|
|
13
|
+
**Examples:**
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
const userByEmail = User.byEmail.get("john@example.com");
|
|
17
|
+
```
|
package/skill/array.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
### array · function
|
|
2
|
+
|
|
3
|
+
Create an array type wrapper with optional length constraints.
|
|
4
|
+
|
|
5
|
+
**Signature:** `<const T>(inner: TypeWrapper<T>, opts?: { min?: number; max?: number; }) => TypeWrapper<T[]>`
|
|
6
|
+
|
|
7
|
+
**Type Parameters:**
|
|
8
|
+
|
|
9
|
+
- `T` - The element type.
|
|
10
|
+
|
|
11
|
+
**Parameters:**
|
|
12
|
+
|
|
13
|
+
- `inner: TypeWrapper<T>` - - Type wrapper for array elements.
|
|
14
|
+
- `opts: {min?: number, max?: number}` (optional) - - Optional constraints (min/max length).
|
|
15
|
+
|
|
16
|
+
**Returns:** An array type instance.
|
|
17
|
+
|
|
18
|
+
**Examples:**
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
const stringArray = E.array(E.string);
|
|
22
|
+
const boundedArray = E.array(E.number, {min: 1, max: 10});
|
|
23
|
+
```
|
package/skill/dump.md
ADDED
package/skill/field.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
### field · function
|
|
2
|
+
|
|
3
|
+
Create a field definition for a model property.
|
|
4
|
+
|
|
5
|
+
This function uses TypeScript magic to return the field configuration object
|
|
6
|
+
while appearing to return the actual field value type to the type system.
|
|
7
|
+
This allows for both runtime introspection and compile-time type safety.
|
|
8
|
+
|
|
9
|
+
**Signature:** `<T>(type: TypeWrapper<T>, options?: Partial<FieldConfig<T>>) => T`
|
|
10
|
+
|
|
11
|
+
**Type Parameters:**
|
|
12
|
+
|
|
13
|
+
- `T` - The field type.
|
|
14
|
+
|
|
15
|
+
**Parameters:**
|
|
16
|
+
|
|
17
|
+
- `type: TypeWrapper<T>` - - The type wrapper for this field.
|
|
18
|
+
- `options: Partial<FieldConfig<T>>` (optional) - - Additional field configuration options.
|
|
19
|
+
|
|
20
|
+
**Returns:** The field value (typed as T, but actually returns FieldConfig<T>).
|
|
21
|
+
|
|
22
|
+
**Examples:**
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
class User extends E.Model<User> {
|
|
26
|
+
name = E.field(E.string, {description: "User's full name"});
|
|
27
|
+
age = E.field(E.opt(E.number), {description: "User's age", default: 25});
|
|
28
|
+
}
|
|
29
|
+
```
|
package/skill/index.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
### index · function
|
|
2
|
+
|
|
3
|
+
Create a secondary index on model fields, or a computed secondary index using a function.
|
|
4
|
+
|
|
5
|
+
For field-based indexes, pass a field name or array of field names.
|
|
6
|
+
For computed indexes, pass a function that takes a model instance and returns an array of
|
|
7
|
+
index keys. Return `[]` to skip indexing for that instance. Each array element creates a
|
|
8
|
+
separate index entry, enabling multi-value indexes (e.g., indexing by each word in a name).
|
|
9
|
+
|
|
10
|
+
**Signature:** `{ <M extends typeof Model, V>(MyModel: M, fn: (instance: InstanceType<M>) => V[]): SecondaryIndex<M, [], [V]>; <M extends typeof Model, const F extends (keyof InstanceType<M> & string)>(MyModel: M, field: F): SecondaryIndex<...>; <M extends typeof Model, const FS extends readonly (keyof InstanceType<M> & string)[]>(...`
|
|
11
|
+
|
|
12
|
+
**Type Parameters:**
|
|
13
|
+
|
|
14
|
+
- `M extends typeof Model` - The model class.
|
|
15
|
+
- `V` - The computed index value type (for function-based indexes).
|
|
16
|
+
|
|
17
|
+
**Parameters:**
|
|
18
|
+
|
|
19
|
+
- `MyModel: M` - - The model class to create the index for.
|
|
20
|
+
- `fn: (instance: InstanceType<M>) => V[]`
|
|
21
|
+
|
|
22
|
+
**Returns:** A new SecondaryIndex instance.
|
|
23
|
+
|
|
24
|
+
**Examples:**
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
class User extends E.Model<User> {
|
|
28
|
+
static byAge = E.index(User, "age");
|
|
29
|
+
static byTagsDate = E.index(User, ["tags", "createdAt"]);
|
|
30
|
+
static byWord = E.index(User, (u: User) => u.name.split(" "));
|
|
31
|
+
}
|
|
32
|
+
```
|
package/skill/init.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
### init · function
|
|
2
|
+
|
|
3
|
+
Initialize the database with the specified directory path.
|
|
4
|
+
This function may be called multiple times with the same parameters. If it is not called before the first transact(),
|
|
5
|
+
the database will be automatically initialized with the default directory.
|
|
6
|
+
|
|
7
|
+
**Signature:** `(dbDir: string) => void`
|
|
8
|
+
|
|
9
|
+
**Parameters:**
|
|
10
|
+
|
|
11
|
+
- `dbDir: string`
|
|
12
|
+
|
|
13
|
+
**Examples:**
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
init("./my-database");
|
|
17
|
+
```
|
package/skill/link.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
### link · function
|
|
2
|
+
|
|
3
|
+
Create a link type wrapper for model relationships.
|
|
4
|
+
|
|
5
|
+
**Signature:** `<const T extends typeof Model<any>>(TargetModel: T) => TypeWrapper<InstanceType<T>>`
|
|
6
|
+
|
|
7
|
+
**Type Parameters:**
|
|
8
|
+
|
|
9
|
+
- `T extends typeof Model<any>` - The target model class.
|
|
10
|
+
|
|
11
|
+
**Parameters:**
|
|
12
|
+
|
|
13
|
+
- `TargetModel: T` - - The model class this link points to.
|
|
14
|
+
|
|
15
|
+
**Returns:** A link type instance.
|
|
16
|
+
|
|
17
|
+
**Examples:**
|
|
18
|
+
|
|
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
|
+
}
|
|
27
|
+
```
|
package/skill/literal.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
### literal · function
|
|
2
|
+
|
|
3
|
+
Create a literal type wrapper for a constant value.
|
|
4
|
+
|
|
5
|
+
**Signature:** `<const T>(value: T) => TypeWrapper<T>`
|
|
6
|
+
|
|
7
|
+
**Type Parameters:**
|
|
8
|
+
|
|
9
|
+
- `T` - The literal type.
|
|
10
|
+
|
|
11
|
+
**Parameters:**
|
|
12
|
+
|
|
13
|
+
- `value: T` - - The literal value.
|
|
14
|
+
|
|
15
|
+
**Returns:** A literal type instance.
|
|
16
|
+
|
|
17
|
+
**Examples:**
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
const statusType = E.literal("active");
|
|
21
|
+
const countType = E.literal(42);
|
|
22
|
+
```
|
package/skill/opt.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
### opt · function
|
|
2
|
+
|
|
3
|
+
Create an optional type wrapper (allows undefined).
|
|
4
|
+
|
|
5
|
+
**Signature:** `<const T extends TypeWrapper<unknown> | BasicType>(inner: T) => TypeWrapper<T extends TypeWrapper<infer U> ? U : T>`
|
|
6
|
+
|
|
7
|
+
**Type Parameters:**
|
|
8
|
+
|
|
9
|
+
- `T extends TypeWrapper<unknown>|BasicType` - Type wrapper or basic type to make optional.
|
|
10
|
+
|
|
11
|
+
**Parameters:**
|
|
12
|
+
|
|
13
|
+
- `inner: T` - - The inner type to make optional.
|
|
14
|
+
|
|
15
|
+
**Returns:** A union type that accepts the inner type or undefined.
|
|
16
|
+
|
|
17
|
+
**Examples:**
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
const optionalString = E.opt(E.string);
|
|
21
|
+
const optionalNumber = E.opt(E.number);
|
|
22
|
+
```
|
package/skill/or.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
### or · function
|
|
2
|
+
|
|
3
|
+
Create a union type wrapper from multiple type choices.
|
|
4
|
+
|
|
5
|
+
**Signature:** `<const T extends (TypeWrapper<unknown> | BasicType)[]>(...choices: T) => TypeWrapper<UnwrapTypes<T>>`
|
|
6
|
+
|
|
7
|
+
**Type Parameters:**
|
|
8
|
+
|
|
9
|
+
- `T extends (TypeWrapper<unknown>|BasicType)[]` - Array of type wrapper or basic types.
|
|
10
|
+
|
|
11
|
+
**Parameters:**
|
|
12
|
+
|
|
13
|
+
- `choices: T` - - The type choices for the union.
|
|
14
|
+
|
|
15
|
+
**Returns:** A union type instance.
|
|
16
|
+
|
|
17
|
+
**Examples:**
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
const stringOrNumber = E.or(E.string, E.number);
|
|
21
|
+
const status = E.or("active", "inactive", "pending");
|
|
22
|
+
```
|
package/skill/primary.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
### primary · function
|
|
2
|
+
|
|
3
|
+
Create a primary index on model fields.
|
|
4
|
+
|
|
5
|
+
**Signature:** `{ <M extends typeof Model, const F extends (keyof InstanceType<M> & string)>(MyModel: M, field: F): PrimaryIndex<M, [F]>; <M extends typeof Model, const FS extends readonly (keyof InstanceType<M> & string)[]>(MyModel: M, fields: FS): PrimaryIndex<...>; }`
|
|
6
|
+
|
|
7
|
+
**Type Parameters:**
|
|
8
|
+
|
|
9
|
+
- `M extends typeof Model` - The model class.
|
|
10
|
+
- `F extends (keyof InstanceType<M> & string)` - The field name (for single field index).
|
|
11
|
+
|
|
12
|
+
**Parameters:**
|
|
13
|
+
|
|
14
|
+
- `MyModel: M` - - The model class to create the index for.
|
|
15
|
+
- `field: F` - - Single field name for simple indexes.
|
|
16
|
+
|
|
17
|
+
**Returns:** A new PrimaryIndex instance.
|
|
18
|
+
|
|
19
|
+
**Examples:**
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
class User extends E.Model<User> {
|
|
23
|
+
static pk = E.primary(User, ["id"]);
|
|
24
|
+
static pkSingle = E.primary(User, "id");
|
|
25
|
+
}
|
|
26
|
+
```
|
package/skill/record.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
### record · function
|
|
2
|
+
|
|
3
|
+
Create a Record type wrapper for key-value objects with string or number keys.
|
|
4
|
+
|
|
5
|
+
**Signature:** `<const T>(inner: TypeWrapper<T>) => TypeWrapper<Record<string | number, T>>`
|
|
6
|
+
|
|
7
|
+
**Type Parameters:**
|
|
8
|
+
|
|
9
|
+
- `T` - The value type.
|
|
10
|
+
|
|
11
|
+
**Parameters:**
|
|
12
|
+
|
|
13
|
+
- `inner: TypeWrapper<T>` - - Type wrapper for record values.
|
|
14
|
+
|
|
15
|
+
**Returns:** A record type instance.
|
|
16
|
+
|
|
17
|
+
**Examples:**
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
const scores = E.record(E.number); // Record<string | number, number>
|
|
21
|
+
```
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
### registerModel · function
|
|
2
|
+
|
|
3
|
+
Register a model class with the Edinburgh ORM system.
|
|
4
|
+
|
|
5
|
+
**Signature:** `<T extends typeof Model<unknown>>(MyModel: T) => T`
|
|
6
|
+
|
|
7
|
+
**Type Parameters:**
|
|
8
|
+
|
|
9
|
+
- `T extends typeof Model<unknown>` - The model class type.
|
|
10
|
+
|
|
11
|
+
**Parameters:**
|
|
12
|
+
|
|
13
|
+
- `MyModel: T` - - The model class to register.
|
|
14
|
+
|
|
15
|
+
**Returns:** The enhanced model class with ORM capabilities.
|
|
16
|
+
|
|
17
|
+
**Examples:**
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
@E.registerModel
|
|
21
|
+
class User extends E.Model<User> {
|
|
22
|
+
static pk = E.index(User, ["id"], "primary");
|
|
23
|
+
id = E.field(E.identifier);
|
|
24
|
+
name = E.field(E.string);
|
|
25
|
+
}
|
|
26
|
+
```
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
### runMigration · function
|
|
2
|
+
|
|
3
|
+
Run database migration: populate secondary indexes for old-version rows,
|
|
4
|
+
convert old primary indices, rewrite row data, and clean up orphaned indices.
|
|
5
|
+
|
|
6
|
+
**Signature:** `(options?: MigrationOptions) => Promise<MigrationResult>`
|
|
7
|
+
|
|
8
|
+
**Parameters:**
|
|
9
|
+
|
|
10
|
+
- `options: MigrationOptions` (optional)
|
package/skill/set.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
### set · function
|
|
2
|
+
|
|
3
|
+
Create a Set type wrapper with optional length constraints.
|
|
4
|
+
|
|
5
|
+
**Signature:** `<const T>(inner: TypeWrapper<T>, opts?: { min?: number; max?: number; }) => TypeWrapper<Set<T>>`
|
|
6
|
+
|
|
7
|
+
**Type Parameters:**
|
|
8
|
+
|
|
9
|
+
- `T` - The element type.
|
|
10
|
+
|
|
11
|
+
**Parameters:**
|
|
12
|
+
|
|
13
|
+
- `inner: TypeWrapper<T>` - - Type wrapper for set elements.
|
|
14
|
+
- `opts: {min?: number, max?: number}` (optional) - - Optional constraints (min/max length).
|
|
15
|
+
|
|
16
|
+
**Returns:** A set type instance.
|
|
17
|
+
|
|
18
|
+
**Examples:**
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
const stringSet = E.set(E.string);
|
|
22
|
+
const boundedSet = E.set(E.number, {min: 1, max: 10});
|
|
23
|
+
```
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
### setMaxRetryCount · function
|
|
2
|
+
|
|
3
|
+
Set the maximum number of retries for a transaction in case of conflicts.
|
|
4
|
+
The default value is 6. Setting it to 0 will disable retries and cause transactions to fail immediately on conflict.
|
|
5
|
+
|
|
6
|
+
**Signature:** `(count: number) => void`
|
|
7
|
+
|
|
8
|
+
**Parameters:**
|
|
9
|
+
|
|
10
|
+
- `count: number` - The maximum number of retries for a transaction.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
### setOnSaveCallback · function
|
|
2
|
+
|
|
3
|
+
Set a callback function to be called after a model is saved and committed.
|
|
4
|
+
|
|
5
|
+
**Signature:** `(callback: (commitId: number, items: Map<Model<any>, Change>) => void) => void`
|
|
6
|
+
|
|
7
|
+
**Parameters:**
|
|
8
|
+
|
|
9
|
+
- `callback: ((commitId: number, items: Map<Model<any>, Change>) => void) | undefined` - The callback function to set. It gets called after each successful
|
|
10
|
+
`transact()` commit that has changes, with the following arguments:
|
|
11
|
+
- A sequential number. Higher numbers have been committed after lower numbers.
|
|
12
|
+
- A map of model instances to their changes. The change can be "created", "deleted", or an object containing the old values.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
### transact · function
|
|
2
|
+
|
|
3
|
+
Executes a function within a database transaction context.
|
|
4
|
+
|
|
5
|
+
Loading models (also through links in other models) and changing models can only be done from
|
|
6
|
+
within a transaction.
|
|
7
|
+
|
|
8
|
+
Transactions have a consistent view of the database, and changes made within a transaction are
|
|
9
|
+
isolated from other transactions until they are committed. In case a commit clashes with changes
|
|
10
|
+
made by another transaction, the transaction function will automatically be re-executed up to 6
|
|
11
|
+
times.
|
|
12
|
+
|
|
13
|
+
**Signature:** `<T>(fn: () => T) => Promise<T>`
|
|
14
|
+
|
|
15
|
+
**Type Parameters:**
|
|
16
|
+
|
|
17
|
+
- `T` - The return type of the transaction function.
|
|
18
|
+
|
|
19
|
+
**Parameters:**
|
|
20
|
+
|
|
21
|
+
- `fn: () => T` - - The function to execute within the transaction context. Receives a Transaction instance.
|
|
22
|
+
|
|
23
|
+
**Returns:** A promise that resolves with the function's return value.
|
|
24
|
+
|
|
25
|
+
**Throws:**
|
|
26
|
+
|
|
27
|
+
- With code "RACING_TRANSACTION" if the transaction fails after retries due to conflicts.
|
|
28
|
+
- With code "TXN_LIMIT" if maximum number of transactions is reached.
|
|
29
|
+
- With code "LMDB-{code}" for LMDB-specific errors.
|
|
30
|
+
|
|
31
|
+
**Examples:**
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
const paid = await E.transact(() => {
|
|
35
|
+
const user = User.pk.get("john_doe");
|
|
36
|
+
if (user.credits > 0) {
|
|
37
|
+
user.credits--;
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
```typescript
|
|
44
|
+
// Transaction with automatic retry on conflicts
|
|
45
|
+
await E.transact(() => {
|
|
46
|
+
const counter = Counter.pk.get("global") || new Counter({id: "global", value: 0});
|
|
47
|
+
counter.value++;
|
|
48
|
+
});
|
|
49
|
+
```
|
package/skill/unique.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
### unique · function
|
|
2
|
+
|
|
3
|
+
Create a unique index on model fields, or a computed unique index using a function.
|
|
4
|
+
|
|
5
|
+
For field-based indexes, pass a field name or array of field names.
|
|
6
|
+
For computed indexes, pass a function that takes a model instance and returns an array of
|
|
7
|
+
index keys. Return `[]` to skip indexing for that instance. Each array element creates a
|
|
8
|
+
separate index entry, enabling multi-value indexes (e.g., indexing by each word in a name).
|
|
9
|
+
|
|
10
|
+
**Signature:** `{ <M extends typeof Model, V>(MyModel: M, fn: (instance: InstanceType<M>) => V[]): UniqueIndex<M, [], [V]>; <M extends typeof Model, const F extends (keyof InstanceType<M> & string)>(MyModel: M, field: F): UniqueIndex<...>; <M extends typeof Model, const FS extends readonly (keyof InstanceType<M> & string)[]>(MyMode...`
|
|
11
|
+
|
|
12
|
+
**Type Parameters:**
|
|
13
|
+
|
|
14
|
+
- `M extends typeof Model` - The model class.
|
|
15
|
+
- `V` - The computed index value type (for function-based indexes).
|
|
16
|
+
|
|
17
|
+
**Parameters:**
|
|
18
|
+
|
|
19
|
+
- `MyModel: M` - - The model class to create the index for.
|
|
20
|
+
- `fn: (instance: InstanceType<M>) => V[]`
|
|
21
|
+
|
|
22
|
+
**Returns:** A new UniqueIndex instance.
|
|
23
|
+
|
|
24
|
+
**Examples:**
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
class User extends E.Model<User> {
|
|
28
|
+
static byEmail = E.unique(User, "email");
|
|
29
|
+
static byNameAge = E.unique(User, ["name", "age"]);
|
|
30
|
+
static byFullName = E.unique(User, (u: User) => [`${u.firstName} ${u.lastName}`]);
|
|
31
|
+
}
|
|
32
|
+
```
|
package/src/datapack.ts
CHANGED
|
@@ -14,6 +14,8 @@ const COLORS = USE_COLORS ? ['\x1b[32m', '\x1b[33m', '\x1b[34m', '\x1b[35m'] : [
|
|
|
14
14
|
const RESET_COLOR = USE_COLORS ? '\x1b[0m' : '';
|
|
15
15
|
const ERROR_COLOR = USE_COLORS ? '\x1b[31m' : ''; // red
|
|
16
16
|
|
|
17
|
+
const COLLECTION_BOUNDARIES = {'array': 5, 'object': 6, 'map': 7, 'set': 8, 'end': 9};
|
|
18
|
+
|
|
17
19
|
let toStringTermCount = 0;
|
|
18
20
|
let useExtendedLogging = typeof process !== 'undefined' ? !!process.env?.DATAPACK_EXTENDED_LOGGING : false;
|
|
19
21
|
|
|
@@ -32,6 +34,8 @@ export default class DataPack {
|
|
|
32
34
|
|
|
33
35
|
public readPos: number = 0;
|
|
34
36
|
public writePos: number = 0;
|
|
37
|
+
|
|
38
|
+
static EOD = EOD;
|
|
35
39
|
|
|
36
40
|
/**
|
|
37
41
|
* Backward compatibility: Access to the internal buffer
|
|
@@ -578,24 +582,62 @@ export default class DataPack {
|
|
|
578
582
|
* });
|
|
579
583
|
*/
|
|
580
584
|
writeCollection(type: 'array' | 'set', bodyFunc: (addField: (value: any) => void) => void): DataPack;
|
|
581
|
-
writeCollection(type: 'object', bodyFunc: (addField: (field: number|string
|
|
585
|
+
writeCollection(type: 'object', bodyFunc: (addField: (field: number|string, value: any) => void) => void): DataPack;
|
|
582
586
|
writeCollection(type: 'map', bodyFunc: (addField: (field: any, value: any) => void) => void): DataPack;
|
|
583
587
|
|
|
584
|
-
writeCollection(type:
|
|
585
|
-
|
|
586
|
-
if (!subType) throw new Error(`Invalid collection type: ${type}`);
|
|
587
|
-
this.buffer[this.writePos++] = (4 << 5) | subType; // Collection start
|
|
588
|
+
writeCollection(type: 'array' | 'set' | 'object' | 'map', bodyFunc: (addField: (a: any, b?: any) => void) => void): DataPack {
|
|
589
|
+
this.writeCollectionBoundary(type);
|
|
588
590
|
|
|
589
591
|
bodyFunc((type === 'array' || type === 'set') ? (value: any) => {
|
|
590
592
|
this.write(value);
|
|
591
|
-
} : (name, value) => {
|
|
593
|
+
} : (type === 'object') ? (name, value) => {
|
|
594
|
+
this.writeObjectKey(name);
|
|
595
|
+
this.write(value);
|
|
596
|
+
} : (name, value) => { // map
|
|
592
597
|
this.write(name);
|
|
593
598
|
this.write(value);
|
|
594
599
|
});
|
|
595
|
-
|
|
600
|
+
|
|
601
|
+
return this.writeCollectionBoundary('end');
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Write a collection boundary marker (start or end) for arrays, sets, objects, or maps.
|
|
606
|
+
* This is a low-level method for advanced use cases. Use `writeCollection` (or just `write`)
|
|
607
|
+
* if possible.
|
|
608
|
+
*/
|
|
609
|
+
writeCollectionBoundary(marker: 'array' | 'set' | 'object' | 'map' | 'end'): DataPack {
|
|
610
|
+
let subType = COLLECTION_BOUNDARIES[marker];
|
|
611
|
+
if (!subType) throw new Error(`Invalid collection type: ${marker}`);
|
|
612
|
+
this.buffer[this.writePos++] = (4 << 5) | subType;
|
|
596
613
|
return this;
|
|
597
614
|
}
|
|
598
615
|
|
|
616
|
+
/**
|
|
617
|
+
* Writes either a string or a number, converting strings to numbers if they represent javascript-safe integers.
|
|
618
|
+
* This is useful for writing object keys, which are always strings but may be more compactly represented as numbers.
|
|
619
|
+
*/
|
|
620
|
+
writeObjectKey(key: number | string): void {
|
|
621
|
+
if (typeof key === 'string') {
|
|
622
|
+
const num = parseInt(key, 10);
|
|
623
|
+
if (num.toString() == key) key = num;
|
|
624
|
+
|
|
625
|
+
} else if (typeof key !== 'number') {
|
|
626
|
+
key = '' + key;
|
|
627
|
+
}
|
|
628
|
+
this.write(key);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Read and consume a collection boundary marker, validating it matches the expected type.
|
|
633
|
+
*/
|
|
634
|
+
readCollectionBoundary(expected: 'array' | 'set' | 'object' | 'map' | 'end'): void {
|
|
635
|
+
if (this.readPos >= this.writePos) this.notEnoughData('collection boundary');
|
|
636
|
+
const byte = this.buffer[this.readPos++];
|
|
637
|
+
const expectedByte = (4 << 5) | COLLECTION_BOUNDARIES[expected];
|
|
638
|
+
if (byte !== expectedByte) throw new Error(`Expected ${expected} boundary but got byte ${byte}`);
|
|
639
|
+
}
|
|
640
|
+
|
|
599
641
|
/**
|
|
600
642
|
* Increment the last byte of the buffer. If it was already 255 set it to 0 and
|
|
601
643
|
* increment the previous byte, and so on. If all bytes were 255, return undefined.
|