@yydb/yydb 0.0.11 → 0.0.15
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 +86 -86
- package/dist/api/database.d.ts +127 -12
- package/dist/api/database.js +156 -14
- package/dist/cli/cli.js +28 -1
- package/dist/cli/info-cmd.js +30 -1
- package/dist/cli/verify-cmd.d.ts +11 -0
- package/dist/cli/verify-cmd.js +73 -0
- package/dist/database.d.ts +50 -0
- package/dist/database.js +181 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/micro.d.ts +63 -0
- package/dist/micro.js +160 -0
- package/dist/native/binding.d.ts +28 -0
- package/dist/native.d.ts +9 -0
- package/dist/native.js +32 -0
- package/dist/node/cli/cli.d.ts +1 -0
- package/dist/node/cli/cli.js +25 -0
- package/dist/node/cli/info-cmd.d.ts +3 -0
- package/dist/node/cli/info-cmd.js +10 -0
- package/dist/node/cli/init-cmd.d.ts +3 -0
- package/dist/node/cli/init-cmd.js +18 -0
- package/dist/node/cli/options.d.ts +4 -0
- package/dist/node/cli/options.js +14 -0
- package/dist/node/cli/serve-cmd.d.ts +3 -0
- package/dist/node/cli/serve-cmd.js +14 -0
- package/dist/node/cli/version-cmd.d.ts +3 -0
- package/dist/node/cli/version-cmd.js +8 -0
- package/dist/node/cli.d.ts +2 -0
- package/dist/node/cli.js +6 -0
- package/dist/node/database.d.ts +50 -0
- package/dist/node/database.js +173 -0
- package/dist/node/index.d.ts +7 -0
- package/dist/node/index.js +5 -0
- package/dist/node/native.d.ts +9 -0
- package/dist/node/native.js +32 -0
- package/dist/node/resolve-bin.d.ts +15 -0
- package/dist/node/resolve-bin.js +62 -0
- package/dist/resolve-bin.d.ts +15 -0
- package/dist/resolve-bin.js +62 -0
- package/dist/shared/errors.d.ts +90 -15
- package/dist/shared/errors.js +112 -26
- package/dist/shared/wasm-envelope.d.ts +41 -0
- package/dist/shared/wasm-envelope.js +56 -0
- package/dist/udf-types.d.ts +26 -0
- package/dist/udf-types.js +16 -0
- package/dist/wasm/binding.d.ts +1 -0
- package/dist/wasm/binding.js +1 -0
- package/dist/wasm/database.d.ts +54 -0
- package/dist/wasm/database.js +118 -0
- package/dist/wasm/index.d.ts +8 -0
- package/dist/wasm/index.js +5 -0
- package/package.json +40 -40
package/README.md
CHANGED
|
@@ -1,86 +1,86 @@
|
|
|
1
|
-
# `@yydb/yydb`
|
|
2
|
-
|
|
3
|
-
Node embedded engine and CLI. `Database.open` starts a private loopback `yydb serve` process and talks over the YY
|
|
4
|
-
wire protocol; platform `.node` bindings install through optional dependencies.
|
|
5
|
-
|
|
6
|
-
Browsers are served by the pure TypeScript [`@yydb/light-db`](../light-db/readme.md) package. This package no longer
|
|
7
|
-
ships a WebAssembly entry.
|
|
8
|
-
|
|
9
|
-
For a wire client only (renderer or an app that already runs `yydb serve`), use
|
|
10
|
-
[`@yydb/yydb-client`](https://www.npmjs.com/package/@yydb/yydb-client).
|
|
11
|
-
|
|
12
|
-
[](https://www.npmjs.com/package/@yydb/yydb)
|
|
13
|
-
[](https://www.npmjs.com/package/@yydb/yydb)
|
|
14
|
-
|
|
15
|
-
## Exports
|
|
16
|
-
|
|
17
|
-
| Import | Runtime | Role |
|
|
18
|
-
|--------|---------|------|
|
|
19
|
-
| `@yydb/yydb` | Node | `Database.open`, micro helpers, VOS re-exports |
|
|
20
|
-
| `@yydb/yydb/cli` | Node | CLI (`version`, `init`, `info`, `serve`) |
|
|
21
|
-
|
|
22
|
-
Node.js 18+.
|
|
23
|
-
|
|
24
|
-
Schema truth lives in the `.yydb` file via `ensureSchema` and VOS documents.
|
|
25
|
-
|
|
26
|
-
## Example
|
|
27
|
-
|
|
28
|
-
```ts
|
|
29
|
-
import { Database } from '@yydb/yydb';
|
|
30
|
-
|
|
31
|
-
const db = await Database.open('./data/app.yydb');
|
|
32
|
-
|
|
33
|
-
await db.ensureSchema(
|
|
34
|
-
1,
|
|
35
|
-
`table Setting {
|
|
36
|
-
@@id: uuid,
|
|
37
|
-
key: utf8,
|
|
38
|
-
value: utf8,
|
|
39
|
-
}`,
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
await db.put('theme', 'dark');
|
|
43
|
-
console.log(await db.get('theme'));
|
|
44
|
-
await db.close();
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
The file is created when missing. Schema version and VOS document persist with the data.
|
|
48
|
-
|
|
49
|
-
## API
|
|
50
|
-
|
|
51
|
-
```ts
|
|
52
|
-
const db = await Database.open(path, options?);
|
|
53
|
-
|
|
54
|
-
await db.ensureSchema(version, vosDocument);
|
|
55
|
-
await db.getSchema();
|
|
56
|
-
await db.put(key, stringOrBytes);
|
|
57
|
-
await db.get(key);
|
|
58
|
-
await db.info();
|
|
59
|
-
await db.serverVersion();
|
|
60
|
-
await db.registerMicro(microDefinition);
|
|
61
|
-
await db.callScalar('double', 1, [21]);
|
|
62
|
-
await db.close();
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
`OpenOptions`: `cli` (explicit CLI script path), `port` (preferred loopback port), `serveArgs` (extra `yydb serve`
|
|
66
|
-
arguments). `binary` is a deprecated alias for `cli`.
|
|
67
|
-
|
|
68
|
-
The native binding exposes additive operations (`schemaVersion`, `schemaDocument`, `recordCount`, `journalMode`,
|
|
69
|
-
`checkpoint`, `walFrameCount`, `get`, `put`); the CLI composes commands from them.
|
|
70
|
-
|
|
71
|
-
## Related packages
|
|
72
|
-
|
|
73
|
-
| Need | Package |
|
|
74
|
-
|------|---------|
|
|
75
|
-
| Node embedded engine and CLI (this package) | **`@yydb/yydb`** |
|
|
76
|
-
| Browser, pure TypeScript | **`@yydb/light-db`** |
|
|
77
|
-
| Wire client only | [`@yydb/yydb-client`](https://www.npmjs.com/package/@yydb/yydb-client) |
|
|
78
|
-
| In-process Rust embed | [`yydb`](https://crates.io/crates/yydb) |
|
|
79
|
-
| Agent skills (wire protocol) | [`@yydb/yydb-skills`](https://www.npmjs.com/package/@yydb/yydb-skills) |
|
|
80
|
-
|
|
81
|
-
Electron: main process can use `@yydb/yydb`; renderer code can use `@yydb/yydb-client` against the main-process
|
|
82
|
-
endpoint.
|
|
83
|
-
|
|
84
|
-
The managed engine listens on `127.0.0.1` with no authentication — keep the endpoint private.
|
|
85
|
-
|
|
86
|
-
[YYDB overview](https://github.com/yy-database/yydb.rs) · [User guides](https://github.com/yy-database/yydb.rs/tree/dev/projects/packages/homepage/documentation/zh-hans)
|
|
1
|
+
# `@yydb/yydb`
|
|
2
|
+
|
|
3
|
+
Node embedded engine and CLI. `Database.open` starts a private loopback `yydb serve` process and talks over the YY
|
|
4
|
+
wire protocol; platform `.node` bindings install through optional dependencies.
|
|
5
|
+
|
|
6
|
+
Browsers are served by the pure TypeScript [`@yydb/light-db`](../light-db/readme.md) package. This package no longer
|
|
7
|
+
ships a WebAssembly entry.
|
|
8
|
+
|
|
9
|
+
For a wire client only (renderer or an app that already runs `yydb serve`), use
|
|
10
|
+
[`@yydb/yydb-client`](https://www.npmjs.com/package/@yydb/yydb-client).
|
|
11
|
+
|
|
12
|
+
[](https://www.npmjs.com/package/@yydb/yydb)
|
|
13
|
+
[](https://www.npmjs.com/package/@yydb/yydb)
|
|
14
|
+
|
|
15
|
+
## Exports
|
|
16
|
+
|
|
17
|
+
| Import | Runtime | Role |
|
|
18
|
+
|--------|---------|------|
|
|
19
|
+
| `@yydb/yydb` | Node | `Database.open`, micro helpers, VOS re-exports |
|
|
20
|
+
| `@yydb/yydb/cli` | Node | CLI (`version`, `init`, `info`, `serve`) |
|
|
21
|
+
|
|
22
|
+
Node.js 18+.
|
|
23
|
+
|
|
24
|
+
Schema truth lives in the `.yydb` file via `ensureSchema` and VOS documents.
|
|
25
|
+
|
|
26
|
+
## Example
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { Database } from '@yydb/yydb';
|
|
30
|
+
|
|
31
|
+
const db = await Database.open('./data/app.yydb');
|
|
32
|
+
|
|
33
|
+
await db.ensureSchema(
|
|
34
|
+
1,
|
|
35
|
+
`table Setting {
|
|
36
|
+
@@id: uuid,
|
|
37
|
+
key: utf8,
|
|
38
|
+
value: utf8,
|
|
39
|
+
}`,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
await db.put('theme', 'dark');
|
|
43
|
+
console.log(await db.get('theme'));
|
|
44
|
+
await db.close();
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The file is created when missing. Schema version and VOS document persist with the data.
|
|
48
|
+
|
|
49
|
+
## API
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
const db = await Database.open(path, options?);
|
|
53
|
+
|
|
54
|
+
await db.ensureSchema(version, vosDocument);
|
|
55
|
+
await db.getSchema();
|
|
56
|
+
await db.put(key, stringOrBytes);
|
|
57
|
+
await db.get(key);
|
|
58
|
+
await db.info();
|
|
59
|
+
await db.serverVersion();
|
|
60
|
+
await db.registerMicro(microDefinition);
|
|
61
|
+
await db.callScalar('double', 1, [21]);
|
|
62
|
+
await db.close();
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`OpenOptions`: `cli` (explicit CLI script path), `port` (preferred loopback port), `serveArgs` (extra `yydb serve`
|
|
66
|
+
arguments). `binary` is a deprecated alias for `cli`.
|
|
67
|
+
|
|
68
|
+
The native binding exposes additive operations (`schemaVersion`, `schemaDocument`, `recordCount`, `journalMode`,
|
|
69
|
+
`checkpoint`, `walFrameCount`, `get`, `put`); the CLI composes commands from them.
|
|
70
|
+
|
|
71
|
+
## Related packages
|
|
72
|
+
|
|
73
|
+
| Need | Package |
|
|
74
|
+
|------|---------|
|
|
75
|
+
| Node embedded engine and CLI (this package) | **`@yydb/yydb`** |
|
|
76
|
+
| Browser, pure TypeScript | **`@yydb/light-db`** |
|
|
77
|
+
| Wire client only | [`@yydb/yydb-client`](https://www.npmjs.com/package/@yydb/yydb-client) |
|
|
78
|
+
| In-process Rust embed | [`yydb`](https://crates.io/crates/yydb) |
|
|
79
|
+
| Agent skills (wire protocol) | [`@yydb/yydb-skills`](https://www.npmjs.com/package/@yydb/yydb-skills) |
|
|
80
|
+
|
|
81
|
+
Electron: main process can use `@yydb/yydb`; renderer code can use `@yydb/yydb-client` against the main-process
|
|
82
|
+
endpoint.
|
|
83
|
+
|
|
84
|
+
The managed engine listens on `127.0.0.1` with no authentication — keep the endpoint private.
|
|
85
|
+
|
|
86
|
+
[YYDB overview](https://github.com/yy-database/yydb.rs) · [User guides](https://github.com/yy-database/yydb.rs/tree/dev/projects/packages/homepage/documentation/zh-hans)
|
package/dist/api/database.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type EmittableItem } from '@voml/vos';
|
|
2
2
|
import { defineMicro as buildMicro, type DefinedMicro, type MicroHandle } from '../shared/micro.js';
|
|
3
|
+
import { loadNative } from '../native/binding.js';
|
|
3
4
|
import type { UdfScalarDescriptor } from '../shared/udf-types.js';
|
|
4
5
|
/** Published schema metadata. */
|
|
5
6
|
export interface SchemaVersion {
|
|
@@ -11,6 +12,86 @@ export interface OpenOptions {
|
|
|
11
12
|
/** Create parent directories when missing (default: true). */
|
|
12
13
|
mkdir?: boolean;
|
|
13
14
|
}
|
|
15
|
+
/** One row from a {@link Database.scan}. */
|
|
16
|
+
export interface ScanRow {
|
|
17
|
+
key: string;
|
|
18
|
+
value: Uint8Array;
|
|
19
|
+
}
|
|
20
|
+
/** Options for {@link Database.scan}. */
|
|
21
|
+
export interface ScanOptions {
|
|
22
|
+
/** Include this prefix in every returned key; default `""` returns everything. */
|
|
23
|
+
prefix?: string;
|
|
24
|
+
/** Exclusive continuation cursor from a previous scan's last row; default `null` starts fresh. */
|
|
25
|
+
after?: string | null;
|
|
26
|
+
/** Max rows returned per call; default `1000`. */
|
|
27
|
+
limit?: number;
|
|
28
|
+
}
|
|
29
|
+
/** One change in a {@link Database.batch}. Use `null` to delete. */
|
|
30
|
+
export interface BatchChange {
|
|
31
|
+
key: string;
|
|
32
|
+
value: Uint8Array | string | null;
|
|
33
|
+
}
|
|
34
|
+
/** Options for {@link Database.migrateSchema}. */
|
|
35
|
+
export interface MigrateOptions {
|
|
36
|
+
/** Rename entries — `fromType`/`toType` updates a type name; `type`/`fromField`/`toField` updates one field. */
|
|
37
|
+
renames?: ReadonlyArray<{
|
|
38
|
+
fromType: string;
|
|
39
|
+
toType: string;
|
|
40
|
+
} | {
|
|
41
|
+
type: string;
|
|
42
|
+
fromField: string;
|
|
43
|
+
toField: string;
|
|
44
|
+
}>;
|
|
45
|
+
}
|
|
46
|
+
/** One consistency finding from {@link Database.doctor}. */
|
|
47
|
+
export interface DoctorIssue {
|
|
48
|
+
severity: 'warn' | 'error';
|
|
49
|
+
code: string;
|
|
50
|
+
message: string;
|
|
51
|
+
key_hint?: string | null;
|
|
52
|
+
}
|
|
53
|
+
/** Per-namespace usage snapshot from {@link Database.doctor}. */
|
|
54
|
+
export interface DoctorNamespaceStats {
|
|
55
|
+
prefix: string;
|
|
56
|
+
record_count: number;
|
|
57
|
+
bytes_used: number;
|
|
58
|
+
object_bytes: number;
|
|
59
|
+
quota_max_bytes?: number | null;
|
|
60
|
+
ttl_expired_pending: number;
|
|
61
|
+
}
|
|
62
|
+
/** Read-only consistency report. */
|
|
63
|
+
export interface DoctorReport {
|
|
64
|
+
file_path?: string | null;
|
|
65
|
+
last_commit_sequence: number;
|
|
66
|
+
last_checkpoint_sequence: number;
|
|
67
|
+
checkpoint_lag_records: number;
|
|
68
|
+
wal_bytes_uncheckpointed: number;
|
|
69
|
+
orphan_object_count: number;
|
|
70
|
+
namespaces: DoctorNamespaceStats[];
|
|
71
|
+
issues: DoctorIssue[];
|
|
72
|
+
}
|
|
73
|
+
/** Summary of a v0 `.yydx` compaction pass. */
|
|
74
|
+
export interface CompactReport {
|
|
75
|
+
checkpointed: boolean;
|
|
76
|
+
orphans_reclaimed: number;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Transaction-local handle passed to {@link Database.transaction} callbacks.
|
|
80
|
+
* All CRUD methods forward to the underlying native handle while the
|
|
81
|
+
* transaction is in flight. Commit/rollback are owned by {@link Database}.
|
|
82
|
+
*/
|
|
83
|
+
export declare class TransactionHandle {
|
|
84
|
+
private readonly native;
|
|
85
|
+
constructor(native: ReturnType<ReturnType<typeof loadNative>['Database']['open']>);
|
|
86
|
+
get(key: string): Promise<Uint8Array | null>;
|
|
87
|
+
put(key: string, value: Uint8Array | string): Promise<void>;
|
|
88
|
+
delete(key: string): Promise<void>;
|
|
89
|
+
scan(options?: ScanOptions): Promise<ScanRow[]>;
|
|
90
|
+
batch(changes: readonly BatchChange[]): Promise<void>;
|
|
91
|
+
compareExchange(key: string, expected: Uint8Array | string | null, replacement: Uint8Array | string | null): Promise<boolean>;
|
|
92
|
+
query<T = Record<string, unknown>>(source: string): Promise<T[]>;
|
|
93
|
+
execute(source: string): Promise<void>;
|
|
94
|
+
}
|
|
14
95
|
/**
|
|
15
96
|
* Node-facing YYDB local database handle.
|
|
16
97
|
*
|
|
@@ -39,26 +120,60 @@ export declare class Database {
|
|
|
39
120
|
* Accepts either a raw `.vos` source string, one `@voml/vos` descriptor
|
|
40
121
|
* (`v.table()`, `v.class()`) or a list of descriptors. When descriptors
|
|
41
122
|
* are supplied, they are rendered to source via `emitSource` first.
|
|
42
|
-
*
|
|
43
|
-
* ```ts
|
|
44
|
-
* // Raw source (unchanged behaviour):
|
|
45
|
-
* await db.ensureSchema('namespace demo { table User { @@id: uuid } }');
|
|
46
|
-
*
|
|
47
|
-
* // Zod-like builder — the Vos schema-ecosystem sweet spot:
|
|
48
|
-
* const User = v.table('User', { id: v.uuid().primary(), name: v.utf8() });
|
|
49
|
-
* await db.ensureSchema(User);
|
|
50
|
-
*
|
|
51
|
-
* // Multiple declarations in one shot:
|
|
52
|
-
* await db.ensureSchema(TableA, TableB, ClassC);
|
|
53
|
-
* ```
|
|
54
123
|
*/
|
|
55
124
|
ensureSchema(...input: [string] | [string, ...EmittableItem[]] | EmittableItem[]): Promise<void>;
|
|
56
125
|
/** Read one record, or `null` when the key is absent. */
|
|
57
126
|
get(key: string): Promise<Uint8Array | null>;
|
|
58
127
|
/** Insert or replace one record. */
|
|
59
128
|
put(key: string, value: Uint8Array | string): Promise<void>;
|
|
129
|
+
/** Delete one record by key. Silently succeeds when the key is absent. */
|
|
130
|
+
delete(key: string): Promise<void>;
|
|
131
|
+
/** List key/value pairs in lexical order. `prefix` defaults to `""` (all keys). */
|
|
132
|
+
scan(options?: ScanOptions): Promise<ScanRow[]>;
|
|
133
|
+
/** Atomically write or delete many keys in one call. */
|
|
134
|
+
batch(changes: readonly BatchChange[]): Promise<void>;
|
|
135
|
+
/**
|
|
136
|
+
* Compare-and-swap: replace or delete `key` only when its current value
|
|
137
|
+
* exactly equals `expected`. Both `expected` and `replacement` may be
|
|
138
|
+
* `null` (meaning absent / delete). Returns `true` when the swap applied.
|
|
139
|
+
*/
|
|
140
|
+
compareExchange(key: string, expected: Uint8Array | string | null, replacement: Uint8Array | string | null): Promise<boolean>;
|
|
60
141
|
/** Fold the WAL into the main file and truncate the sidecar. */
|
|
61
142
|
checkpoint(): Promise<void>;
|
|
143
|
+
/** Checkpoint then copy a self-contained `.yydb` main file to `dest`. */
|
|
144
|
+
backup(dest: string): Promise<void>;
|
|
145
|
+
/** Copy main + WAL + SHM without checkpoint; opening `dest` replays the WAL tail. */
|
|
146
|
+
backupWithWal(dest: string): Promise<void>;
|
|
147
|
+
/** Run read-only consistency probes against the database. */
|
|
148
|
+
doctor(): Promise<DoctorReport>;
|
|
149
|
+
/** Fold WAL + reclaim orphan blobs (`.yydx` layouts only). */
|
|
150
|
+
compactYydx(): Promise<CompactReport>;
|
|
151
|
+
/**
|
|
152
|
+
* Run `fn` inside an explicit transaction. The transaction commits on
|
|
153
|
+
* normal return and rolls back on throw. Nested calls are not supported.
|
|
154
|
+
*
|
|
155
|
+
* ```ts
|
|
156
|
+
* await db.transaction(async (tx) => {
|
|
157
|
+
* await tx.put('a', '1');
|
|
158
|
+
* await tx.put('b', '2');
|
|
159
|
+
* });
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
transaction<T>(fn: (tx: TransactionHandle) => Promise<T>): Promise<T>;
|
|
163
|
+
/** Execute a VOS read pipeline and return typed rows. */
|
|
164
|
+
query<T = Record<string, unknown>>(source: string): Promise<T[]>;
|
|
165
|
+
/** Execute a VOS write program. */
|
|
166
|
+
execute(source: string): Promise<void>;
|
|
167
|
+
/**
|
|
168
|
+
* Publish a new schema version, migrating existing records when their
|
|
169
|
+
* shape no longer matches.
|
|
170
|
+
*
|
|
171
|
+
* Accepts raw `.vos` source or `@voml/vos` descriptors (same as
|
|
172
|
+
* {@link Database.ensureSchema}). Use `renames` to tell the engine which
|
|
173
|
+
* old type/field names map to the new ones — records that would otherwise
|
|
174
|
+
* look structurally unrelated get preserved.
|
|
175
|
+
*/
|
|
176
|
+
migrateSchema(documentOrSource: string | EmittableItem, ...rest: (EmittableItem | MigrateOptions)[]): Promise<void>;
|
|
62
177
|
/** Build a typed session-local micro definition (without registering). */
|
|
63
178
|
defineMicro<TArgs extends readonly UdfScalarDescriptor[], TReturn extends UdfScalarDescriptor>(definition: Parameters<typeof buildMicro<TArgs, TReturn>>[0]): DefinedMicro<TArgs, TReturn>;
|
|
64
179
|
/**
|
package/dist/api/database.js
CHANGED
|
@@ -3,6 +3,44 @@ import path from 'node:path';
|
|
|
3
3
|
import { emitSource, } from '@voml/vos';
|
|
4
4
|
import { defineMicro as buildMicro, microFingerprint, udfWireKind, MicroSessionRegistry, } from '../shared/micro.js';
|
|
5
5
|
import { loadNative } from '../native/binding.js';
|
|
6
|
+
import { YydbError } from '../shared/errors.js';
|
|
7
|
+
/**
|
|
8
|
+
* Transaction-local handle passed to {@link Database.transaction} callbacks.
|
|
9
|
+
* All CRUD methods forward to the underlying native handle while the
|
|
10
|
+
* transaction is in flight. Commit/rollback are owned by {@link Database}.
|
|
11
|
+
*/
|
|
12
|
+
export class TransactionHandle {
|
|
13
|
+
native;
|
|
14
|
+
constructor(native) {
|
|
15
|
+
this.native = native;
|
|
16
|
+
}
|
|
17
|
+
async get(key) { return this.native.get(key); }
|
|
18
|
+
async put(key, value) {
|
|
19
|
+
const bytes = typeof value === 'string' ? new TextEncoder().encode(value) : value;
|
|
20
|
+
this.native.put(key, bytes);
|
|
21
|
+
}
|
|
22
|
+
async delete(key) { this.native.delete(key); }
|
|
23
|
+
async scan(options = {}) {
|
|
24
|
+
const rowsJson = this.native.scanPrefix(options.prefix ?? '', options.after ?? null, options.limit ?? 1000);
|
|
25
|
+
const rows = JSON.parse(rowsJson);
|
|
26
|
+
return rows.map((r) => ({ key: r.key, value: new Uint8Array(r.value) }));
|
|
27
|
+
}
|
|
28
|
+
async batch(changes) {
|
|
29
|
+
const body = changes.map((c) => ({
|
|
30
|
+
key: c.key,
|
|
31
|
+
value: c.value === null ? null : typeof c.value === 'string' ? Array.from(new TextEncoder().encode(c.value)) : Array.from(c.value),
|
|
32
|
+
}));
|
|
33
|
+
this.native.writeBatch(JSON.stringify(body));
|
|
34
|
+
}
|
|
35
|
+
async compareExchange(key, expected, replacement) {
|
|
36
|
+
const toBytes = (v) => v === null ? null : typeof v === 'string' ? new TextEncoder().encode(v) : v;
|
|
37
|
+
return this.native.compareExchange(key, toBytes(expected), toBytes(replacement));
|
|
38
|
+
}
|
|
39
|
+
async query(source) {
|
|
40
|
+
return JSON.parse(this.native.queryVos(source));
|
|
41
|
+
}
|
|
42
|
+
async execute(source) { this.native.executeVosWrite(source); }
|
|
43
|
+
}
|
|
6
44
|
/**
|
|
7
45
|
* Node-facing YYDB local database handle.
|
|
8
46
|
*
|
|
@@ -57,27 +95,13 @@ export class Database {
|
|
|
57
95
|
* Accepts either a raw `.vos` source string, one `@voml/vos` descriptor
|
|
58
96
|
* (`v.table()`, `v.class()`) or a list of descriptors. When descriptors
|
|
59
97
|
* are supplied, they are rendered to source via `emitSource` first.
|
|
60
|
-
*
|
|
61
|
-
* ```ts
|
|
62
|
-
* // Raw source (unchanged behaviour):
|
|
63
|
-
* await db.ensureSchema('namespace demo { table User { @@id: uuid } }');
|
|
64
|
-
*
|
|
65
|
-
* // Zod-like builder — the Vos schema-ecosystem sweet spot:
|
|
66
|
-
* const User = v.table('User', { id: v.uuid().primary(), name: v.utf8() });
|
|
67
|
-
* await db.ensureSchema(User);
|
|
68
|
-
*
|
|
69
|
-
* // Multiple declarations in one shot:
|
|
70
|
-
* await db.ensureSchema(TableA, TableB, ClassC);
|
|
71
|
-
* ```
|
|
72
98
|
*/
|
|
73
99
|
async ensureSchema(...input) {
|
|
74
100
|
let document;
|
|
75
101
|
if (typeof input[0] === 'string') {
|
|
76
|
-
// Raw source string, optionally followed by descriptors we just ignore.
|
|
77
102
|
document = input[0];
|
|
78
103
|
}
|
|
79
104
|
else {
|
|
80
|
-
// One or more Vos descriptor items → render to source.
|
|
81
105
|
document = emitSource(input);
|
|
82
106
|
}
|
|
83
107
|
this.native.ensureSchema(document);
|
|
@@ -91,10 +115,128 @@ export class Database {
|
|
|
91
115
|
const bytes = typeof value === 'string' ? new TextEncoder().encode(value) : value;
|
|
92
116
|
this.native.put(key, bytes);
|
|
93
117
|
}
|
|
118
|
+
/** Delete one record by key. Silently succeeds when the key is absent. */
|
|
119
|
+
async delete(key) {
|
|
120
|
+
this.native.delete(key);
|
|
121
|
+
}
|
|
122
|
+
/** List key/value pairs in lexical order. `prefix` defaults to `""` (all keys). */
|
|
123
|
+
async scan(options = {}) {
|
|
124
|
+
const rowsJson = this.native.scanPrefix(options.prefix ?? '', options.after ?? null, options.limit ?? 1000);
|
|
125
|
+
const rows = JSON.parse(rowsJson);
|
|
126
|
+
return rows.map((r) => ({ key: r.key, value: new Uint8Array(r.value) }));
|
|
127
|
+
}
|
|
128
|
+
/** Atomically write or delete many keys in one call. */
|
|
129
|
+
async batch(changes) {
|
|
130
|
+
const body = changes.map((c) => ({
|
|
131
|
+
key: c.key,
|
|
132
|
+
value: c.value === null
|
|
133
|
+
? null
|
|
134
|
+
: typeof c.value === 'string'
|
|
135
|
+
? Array.from(new TextEncoder().encode(c.value))
|
|
136
|
+
: Array.from(c.value),
|
|
137
|
+
}));
|
|
138
|
+
this.native.writeBatch(JSON.stringify(body));
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Compare-and-swap: replace or delete `key` only when its current value
|
|
142
|
+
* exactly equals `expected`. Both `expected` and `replacement` may be
|
|
143
|
+
* `null` (meaning absent / delete). Returns `true` when the swap applied.
|
|
144
|
+
*/
|
|
145
|
+
async compareExchange(key, expected, replacement) {
|
|
146
|
+
const toBytes = (v) => v === null ? null : typeof v === 'string' ? new TextEncoder().encode(v) : v;
|
|
147
|
+
return this.native.compareExchange(key, toBytes(expected), toBytes(replacement));
|
|
148
|
+
}
|
|
94
149
|
/** Fold the WAL into the main file and truncate the sidecar. */
|
|
95
150
|
async checkpoint() {
|
|
96
151
|
this.native.checkpoint();
|
|
97
152
|
}
|
|
153
|
+
/** Checkpoint then copy a self-contained `.yydb` main file to `dest`. */
|
|
154
|
+
async backup(dest) {
|
|
155
|
+
this.native.backup(dest);
|
|
156
|
+
}
|
|
157
|
+
/** Copy main + WAL + SHM without checkpoint; opening `dest` replays the WAL tail. */
|
|
158
|
+
async backupWithWal(dest) {
|
|
159
|
+
this.native.backupWithWal(dest);
|
|
160
|
+
}
|
|
161
|
+
/** Run read-only consistency probes against the database. */
|
|
162
|
+
async doctor() {
|
|
163
|
+
return JSON.parse(this.native.doctor());
|
|
164
|
+
}
|
|
165
|
+
/** Fold WAL + reclaim orphan blobs (`.yydx` layouts only). */
|
|
166
|
+
async compactYydx() {
|
|
167
|
+
return JSON.parse(this.native.compactYydx());
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Run `fn` inside an explicit transaction. The transaction commits on
|
|
171
|
+
* normal return and rolls back on throw. Nested calls are not supported.
|
|
172
|
+
*
|
|
173
|
+
* ```ts
|
|
174
|
+
* await db.transaction(async (tx) => {
|
|
175
|
+
* await tx.put('a', '1');
|
|
176
|
+
* await tx.put('b', '2');
|
|
177
|
+
* });
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
async transaction(fn) {
|
|
181
|
+
this.native.begin();
|
|
182
|
+
const tx = new TransactionHandle(this.native);
|
|
183
|
+
try {
|
|
184
|
+
const result = await fn(tx);
|
|
185
|
+
this.native.commit();
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
try {
|
|
190
|
+
this.native.rollback();
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
// rollback already failed, don't mask the original error
|
|
194
|
+
}
|
|
195
|
+
throw YydbError.fromDiagnosticJson(err instanceof Error ? err.message : String(err)) ?? err;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/** Execute a VOS read pipeline and return typed rows. */
|
|
199
|
+
async query(source) {
|
|
200
|
+
const rowsJson = this.native.queryVos(source);
|
|
201
|
+
return JSON.parse(rowsJson);
|
|
202
|
+
}
|
|
203
|
+
/** Execute a VOS write program. */
|
|
204
|
+
async execute(source) {
|
|
205
|
+
this.native.executeVosWrite(source);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Publish a new schema version, migrating existing records when their
|
|
209
|
+
* shape no longer matches.
|
|
210
|
+
*
|
|
211
|
+
* Accepts raw `.vos` source or `@voml/vos` descriptors (same as
|
|
212
|
+
* {@link Database.ensureSchema}). Use `renames` to tell the engine which
|
|
213
|
+
* old type/field names map to the new ones — records that would otherwise
|
|
214
|
+
* look structurally unrelated get preserved.
|
|
215
|
+
*/
|
|
216
|
+
async migrateSchema(documentOrSource, ...rest) {
|
|
217
|
+
let document;
|
|
218
|
+
let options = {};
|
|
219
|
+
const all = [documentOrSource, ...rest];
|
|
220
|
+
const last = all[all.length - 1];
|
|
221
|
+
if (last &&
|
|
222
|
+
typeof last === 'object' &&
|
|
223
|
+
!('table' in last) &&
|
|
224
|
+
!('class' in last) &&
|
|
225
|
+
!('emit' in last) &&
|
|
226
|
+
('renames' in last || Object.keys(last).length === 0)) {
|
|
227
|
+
options = all.pop() ?? {};
|
|
228
|
+
}
|
|
229
|
+
if (typeof all[0] === 'string') {
|
|
230
|
+
document = all[0];
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
document = emitSource(all);
|
|
234
|
+
}
|
|
235
|
+
const renamesJson = options.renames?.length
|
|
236
|
+
? JSON.stringify(options.renames)
|
|
237
|
+
: null;
|
|
238
|
+
this.native.migrateSchema(document, renamesJson);
|
|
239
|
+
}
|
|
98
240
|
/** Build a typed session-local micro definition (without registering). */
|
|
99
241
|
defineMicro(definition) {
|
|
100
242
|
return buildMicro(definition);
|
package/dist/cli/cli.js
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { dirname, join, resolve } from 'node:path';
|
|
2
3
|
import { fileURLToPath } from 'node:url';
|
|
3
4
|
import { createCli } from '@vmz/commander';
|
|
5
|
+
import { formatDiagnostic } from '@vmz/diagnostic';
|
|
6
|
+
import { YydbError } from '../shared/errors.js';
|
|
4
7
|
import { registerInfoCommand } from './info-cmd.js';
|
|
5
8
|
import { registerInitCommand } from './init-cmd.js';
|
|
6
9
|
import { registerServeCommand } from './serve-cmd.js';
|
|
10
|
+
import { registerVerifyCommand } from './verify-cmd.js';
|
|
7
11
|
import { registerVersionCommand } from './version-cmd.js';
|
|
12
|
+
const CLI_CATALOG = JSON.parse(fs.readFileSync(resolve(import.meta.dirname, '../../locales/en-US/cli.json'), 'utf8'));
|
|
8
13
|
function buildYydbCli() {
|
|
9
14
|
const localesRoot = join(dirname(fileURLToPath(import.meta.url)), '../../locales');
|
|
10
15
|
const cli = createCli('yydb').locales(localesRoot).intro('cli.intro');
|
|
11
16
|
registerVersionCommand(cli);
|
|
12
17
|
registerInitCommand(cli);
|
|
13
18
|
registerInfoCommand(cli);
|
|
19
|
+
registerVerifyCommand(cli);
|
|
14
20
|
registerServeCommand(cli);
|
|
15
21
|
return cli;
|
|
16
22
|
}
|
|
@@ -19,6 +25,27 @@ export async function runCli(argv) {
|
|
|
19
25
|
return await buildYydbCli().parse(argv);
|
|
20
26
|
}
|
|
21
27
|
catch (error) {
|
|
28
|
+
const parsed = error instanceof Error
|
|
29
|
+
? YydbError.fromDiagnosticJson(error.message)
|
|
30
|
+
: null;
|
|
31
|
+
if (parsed) {
|
|
32
|
+
// Structured engine error from N-API / wire / WASM envelope.
|
|
33
|
+
const severity = parsed.retryable ? 'warning' : 'error';
|
|
34
|
+
const text = formatDiagnostic({
|
|
35
|
+
path: '',
|
|
36
|
+
severity,
|
|
37
|
+
code: parsed.code,
|
|
38
|
+
args: parsed.params,
|
|
39
|
+
message: error instanceof Error ? error.message : String(error),
|
|
40
|
+
}, {
|
|
41
|
+
locale: 'en-US',
|
|
42
|
+
catalog: CLI_CATALOG,
|
|
43
|
+
style: 'compact',
|
|
44
|
+
});
|
|
45
|
+
console.error(text);
|
|
46
|
+
return parsed.retryable ? 13 : 1;
|
|
47
|
+
}
|
|
48
|
+
// Plain text fallback (commander parse failures, unknown errors).
|
|
22
49
|
console.error(`error: ${error instanceof Error ? error.message : String(error)}`);
|
|
23
50
|
return 1;
|
|
24
51
|
}
|
package/dist/cli/info-cmd.js
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { formatDiagnostic } from '@vmz/diagnostic';
|
|
1
4
|
import { loadNative } from '../native/binding.js';
|
|
2
5
|
import { requiredPositional } from './options.js';
|
|
6
|
+
const CLI_CATALOG = JSON.parse(fs.readFileSync(path.resolve(import.meta.dirname, '../../locales/en-US/cli.json'), 'utf8'));
|
|
3
7
|
export function registerInfoCommand(cli) {
|
|
4
|
-
cli.command('info', 'cli.cmd.info')
|
|
8
|
+
cli.command('info', 'cli.cmd.info')
|
|
9
|
+
.option('--doctor', 'cli.opt.doctor')
|
|
10
|
+
.action((options) => cmdInfo(options));
|
|
5
11
|
}
|
|
6
12
|
export function cmdInfo(options) {
|
|
7
13
|
const dbPath = requiredPositional(options, 'info');
|
|
14
|
+
const wantDoctor = Boolean(options.doctor);
|
|
8
15
|
const database = loadNative().Database.open(dbPath);
|
|
9
16
|
const lines = [`path=${database.path() ?? '<memory>'}`];
|
|
10
17
|
const schemaVersion = database.schemaVersion();
|
|
@@ -25,6 +32,28 @@ export function cmdInfo(options) {
|
|
|
25
32
|
lines.push(`shm=${shm}`);
|
|
26
33
|
lines.push(`wal_frames=${database.walFrameCount()}`);
|
|
27
34
|
console.log(lines.join('\n'));
|
|
35
|
+
if (wantDoctor) {
|
|
36
|
+
const report = JSON.parse(database.doctor());
|
|
37
|
+
if (report.issues.length === 0) {
|
|
38
|
+
console.log(`doctor=clean orphan_objects=${report.orphan_object_count}`);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
for (const issue of report.issues) {
|
|
42
|
+
const severity = issue.severity === 'error' ? 'error' : 'warning';
|
|
43
|
+
console.error(formatDiagnostic({
|
|
44
|
+
path: dbPath,
|
|
45
|
+
severity,
|
|
46
|
+
code: issue.code,
|
|
47
|
+
message: issue.message,
|
|
48
|
+
}, {
|
|
49
|
+
locale: 'en-US',
|
|
50
|
+
catalog: CLI_CATALOG,
|
|
51
|
+
style: 'compact',
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
console.error(`doctor=issues(${report.issues.length}) orphans=${report.orphan_object_count}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
28
57
|
return 0;
|
|
29
58
|
}
|
|
30
59
|
function summarizeDocument(document) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Cli, ParsedOptions } from '@vmz/commander';
|
|
2
|
+
export declare function registerVerifyCommand(cli: Cli): void;
|
|
3
|
+
/**
|
|
4
|
+
* Run read-only consistency probes against a YYDB database file.
|
|
5
|
+
*
|
|
6
|
+
* Exit codes:
|
|
7
|
+
* 0 — clean (no issues)
|
|
8
|
+
* 1 — doctor found at least one error (database may be corrupt)
|
|
9
|
+
* 13 — only warnings present (readable but should be inspected)
|
|
10
|
+
*/
|
|
11
|
+
export declare function cmdVerify(options: ParsedOptions): number;
|