@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.
Files changed (53) hide show
  1. package/README.md +86 -86
  2. package/dist/api/database.d.ts +127 -12
  3. package/dist/api/database.js +156 -14
  4. package/dist/cli/cli.js +28 -1
  5. package/dist/cli/info-cmd.js +30 -1
  6. package/dist/cli/verify-cmd.d.ts +11 -0
  7. package/dist/cli/verify-cmd.js +73 -0
  8. package/dist/database.d.ts +50 -0
  9. package/dist/database.js +181 -0
  10. package/dist/index.d.ts +1 -1
  11. package/dist/index.js +1 -1
  12. package/dist/micro.d.ts +63 -0
  13. package/dist/micro.js +160 -0
  14. package/dist/native/binding.d.ts +28 -0
  15. package/dist/native.d.ts +9 -0
  16. package/dist/native.js +32 -0
  17. package/dist/node/cli/cli.d.ts +1 -0
  18. package/dist/node/cli/cli.js +25 -0
  19. package/dist/node/cli/info-cmd.d.ts +3 -0
  20. package/dist/node/cli/info-cmd.js +10 -0
  21. package/dist/node/cli/init-cmd.d.ts +3 -0
  22. package/dist/node/cli/init-cmd.js +18 -0
  23. package/dist/node/cli/options.d.ts +4 -0
  24. package/dist/node/cli/options.js +14 -0
  25. package/dist/node/cli/serve-cmd.d.ts +3 -0
  26. package/dist/node/cli/serve-cmd.js +14 -0
  27. package/dist/node/cli/version-cmd.d.ts +3 -0
  28. package/dist/node/cli/version-cmd.js +8 -0
  29. package/dist/node/cli.d.ts +2 -0
  30. package/dist/node/cli.js +6 -0
  31. package/dist/node/database.d.ts +50 -0
  32. package/dist/node/database.js +173 -0
  33. package/dist/node/index.d.ts +7 -0
  34. package/dist/node/index.js +5 -0
  35. package/dist/node/native.d.ts +9 -0
  36. package/dist/node/native.js +32 -0
  37. package/dist/node/resolve-bin.d.ts +15 -0
  38. package/dist/node/resolve-bin.js +62 -0
  39. package/dist/resolve-bin.d.ts +15 -0
  40. package/dist/resolve-bin.js +62 -0
  41. package/dist/shared/errors.d.ts +90 -15
  42. package/dist/shared/errors.js +112 -26
  43. package/dist/shared/wasm-envelope.d.ts +41 -0
  44. package/dist/shared/wasm-envelope.js +56 -0
  45. package/dist/udf-types.d.ts +26 -0
  46. package/dist/udf-types.js +16 -0
  47. package/dist/wasm/binding.d.ts +1 -0
  48. package/dist/wasm/binding.js +1 -0
  49. package/dist/wasm/database.d.ts +54 -0
  50. package/dist/wasm/database.js +118 -0
  51. package/dist/wasm/index.d.ts +8 -0
  52. package/dist/wasm/index.js +5 -0
  53. 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
- [![npm](https://img.shields.io/npm/v/@yydb/yydb)](https://www.npmjs.com/package/@yydb/yydb)
13
- [![Node.js](https://img.shields.io/node/v/@yydb/yydb)](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
+ [![npm](https://img.shields.io/npm/v/@yydb/yydb)](https://www.npmjs.com/package/@yydb/yydb)
13
+ [![Node.js](https://img.shields.io/node/v/@yydb/yydb)](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,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
  /**
@@ -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 { dirname, join } from 'node:path';
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
  }
@@ -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').action((options) => cmdInfo(options));
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;