@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
@@ -0,0 +1,18 @@
1
+ import { loadNative } from '../native.js';
2
+ import { requiredPositional, str } from './options.js';
3
+ export function registerInitCommand(cli) {
4
+ cli.command('init', 'cli.cmd.init')
5
+ .option('--schema-version <n>', 'cli.opt.schema-version')
6
+ .option('--schema-file <path>', 'cli.opt.schema-file')
7
+ .action((options) => cmdInit(options));
8
+ }
9
+ export function cmdInit(options) {
10
+ const dbPath = requiredPositional(options, 'init');
11
+ const schemaVersion = Number.parseInt(str(options, 'schema-version') ?? '1', 10);
12
+ const schemaFile = str(options, 'schema-file');
13
+ const native = loadNative();
14
+ const schemaDocument = schemaFile ? native.readSchemaFile(schemaFile) : null;
15
+ native.initDb(dbPath, schemaVersion, schemaDocument);
16
+ console.log(`initialized ${dbPath}`);
17
+ return 0;
18
+ }
@@ -0,0 +1,4 @@
1
+ import type { ParsedOptions } from '@vmz/commander';
2
+ export declare function str(options: ParsedOptions, key: string): string | undefined;
3
+ export declare function flag(options: ParsedOptions, key: string): boolean;
4
+ export declare function requiredPositional(options: ParsedOptions, label: string): string;
@@ -0,0 +1,14 @@
1
+ export function str(options, key) {
2
+ const value = options[key];
3
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
4
+ }
5
+ export function flag(options, key) {
6
+ return options[key] === true;
7
+ }
8
+ export function requiredPositional(options, label) {
9
+ const value = options._[0];
10
+ if (!value) {
11
+ throw new Error(`${label} requires a path argument`);
12
+ }
13
+ return value;
14
+ }
@@ -0,0 +1,3 @@
1
+ import type { Cli, ParsedOptions } from '@vmz/commander';
2
+ export declare function registerServeCommand(cli: Cli): void;
3
+ export declare function cmdServe(options: ParsedOptions): number;
@@ -0,0 +1,14 @@
1
+ import { loadNative } from '../native.js';
2
+ import { flag, requiredPositional, str } from './options.js';
3
+ export function registerServeCommand(cli) {
4
+ cli.command('serve', 'cli.cmd.serve')
5
+ .option('--bind <host:port>', 'cli.opt.bind')
6
+ .option('--insecure-bind', 'cli.opt.insecure-bind')
7
+ .action((options) => cmdServe(options));
8
+ }
9
+ export function cmdServe(options) {
10
+ const dbPath = requiredPositional(options, 'serve');
11
+ const bind = str(options, 'bind') ?? '127.0.0.1:7700';
12
+ loadNative().serve(dbPath, bind, flag(options, 'insecure-bind'));
13
+ return 0;
14
+ }
@@ -0,0 +1,3 @@
1
+ import type { Cli } from '@vmz/commander';
2
+ export declare function registerVersionCommand(cli: Cli): void;
3
+ export declare function cmdVersion(): number;
@@ -0,0 +1,8 @@
1
+ import { loadNative } from '../native.js';
2
+ export function registerVersionCommand(cli) {
3
+ cli.command('version', 'cli.cmd.version').action(() => cmdVersion());
4
+ }
5
+ export function cmdVersion() {
6
+ console.log(loadNative().version());
7
+ return 0;
8
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ import { runCli } from './cli/cli.js';
3
+ const code = await runCli(process.argv);
4
+ if (code !== 0) {
5
+ process.exitCode = code;
6
+ }
@@ -0,0 +1,50 @@
1
+ import { defineMicro as buildMicro, type DefinedMicro, type MicroHandle } from '../shared/micro.js';
2
+ import type { UdfScalarDescriptor } from '../shared/udf-types.js';
3
+ export type { SchemaVersion } from '@yydb/yydb-client';
4
+ export interface OpenOptions {
5
+ /** Override CLI script path (tests / special installs). */
6
+ cli?: string;
7
+ /** @deprecated Use `cli`. */
8
+ binary?: string;
9
+ /** Prefer a specific loopback port; default = ephemeral free port. */
10
+ port?: number;
11
+ /** Extra args after `serve <path> --bind …`. */
12
+ serveArgs?: string[];
13
+ }
14
+ /**
15
+ * Node-facing YYDB handle. Looks like one library; owns a private loopback
16
+ * `yydb serve` child under the hood.
17
+ */
18
+ export declare class Database {
19
+ readonly path: string;
20
+ readonly endpoint: string;
21
+ private client;
22
+ private child;
23
+ private readonly microRegistry;
24
+ private constructor();
25
+ /**
26
+ * Open (create) a `.yydb` file. Spawns the engine on `127.0.0.1` automatically.
27
+ */
28
+ static open(dbPath: string, options?: OpenOptions): Promise<Database>;
29
+ serverVersion(): Promise<string>;
30
+ info(): Promise<string>;
31
+ getSchema(): Promise<import("@yydb/yydb-client").SchemaVersion | null>;
32
+ ensureSchema(version: number, document: string): Promise<void>;
33
+ get(key: string): Promise<Uint8Array | null>;
34
+ put(key: string, value: Uint8Array | string): Promise<void>;
35
+ /**
36
+ * Build a typed session-local micro definition. Does not register it with
37
+ * the engine until [`registerMicro`](./database.ts) is called.
38
+ */
39
+ defineMicro<TArgs extends readonly UdfScalarDescriptor[], TReturn extends UdfScalarDescriptor>(definition: Parameters<typeof buildMicro<TArgs, TReturn>>[0]): DefinedMicro<TArgs, TReturn>;
40
+ /**
41
+ * Install a [`defineMicro`](./database.ts) definition into the current
42
+ * session registry and sync metadata to the private engine. Host micros are
43
+ * session-local and never written to `.yydb`.
44
+ */
45
+ registerMicro<TArgs extends readonly UdfScalarDescriptor[], TReturn extends UdfScalarDescriptor>(definition: DefinedMicro<TArgs, TReturn>): Promise<MicroHandle>;
46
+ /** Invoke a registered scalar UDF on the private engine. */
47
+ callScalar(name: string, version: number, args: readonly (string | number | boolean | null)[]): Promise<string | number | boolean | null>;
48
+ /** Stop the private engine process. */
49
+ close(): Promise<void>;
50
+ }
@@ -0,0 +1,173 @@
1
+ import { spawn } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import net from 'node:net';
4
+ import path from 'node:path';
5
+ import { connect } from '@yydb/yydb-client/node';
6
+ import { defineMicro as buildMicro, microFingerprint, udfWireKind, MicroSessionRegistry } from '../shared/micro.js';
7
+ import { resolveYydbCli } from './resolve-bin.js';
8
+ async function freeLoopbackPort() {
9
+ return await new Promise((resolve, reject) => {
10
+ const server = net.createServer();
11
+ server.listen(0, '127.0.0.1', () => {
12
+ const address = server.address();
13
+ if (!address || typeof address === 'string') {
14
+ server.close();
15
+ reject(new Error('could not allocate loopback port'));
16
+ return;
17
+ }
18
+ const { port } = address;
19
+ server.close((error) => {
20
+ if (error)
21
+ reject(error);
22
+ else
23
+ resolve(port);
24
+ });
25
+ });
26
+ server.on('error', reject);
27
+ });
28
+ }
29
+ async function waitForTcp(host, port, timeoutMs) {
30
+ const deadline = Date.now() + timeoutMs;
31
+ while (Date.now() < deadline) {
32
+ try {
33
+ await new Promise((resolve, reject) => {
34
+ const socket = net.connect({ host, port }, () => {
35
+ socket.end();
36
+ resolve();
37
+ });
38
+ socket.on('error', reject);
39
+ });
40
+ return;
41
+ }
42
+ catch {
43
+ await new Promise((r) => setTimeout(r, 40));
44
+ }
45
+ }
46
+ throw new Error(`yydb engine did not accept connections on ${host}:${port}`);
47
+ }
48
+ /**
49
+ * Node-facing YYDB handle. Looks like one library; owns a private loopback
50
+ * `yydb serve` child under the hood.
51
+ */
52
+ export class Database {
53
+ path;
54
+ endpoint;
55
+ client;
56
+ child;
57
+ microRegistry;
58
+ constructor(dbPath, endpoint, client, child) {
59
+ this.path = dbPath;
60
+ this.endpoint = endpoint;
61
+ this.client = client;
62
+ this.child = child;
63
+ this.microRegistry = new MicroSessionRegistry();
64
+ this.client.setMicroHostHandler((payload) => this.microRegistry.invokeFromWire(payload));
65
+ }
66
+ /**
67
+ * Open (create) a `.yydb` file. Spawns the engine on `127.0.0.1` automatically.
68
+ */
69
+ static async open(dbPath, options = {}) {
70
+ const resolved = path.resolve(dbPath);
71
+ fs.mkdirSync(path.dirname(resolved), { recursive: true });
72
+ const cli = options.cli ?? options.binary ?? resolveYydbCli();
73
+ const port = options.port ?? (await freeLoopbackPort());
74
+ const bind = `127.0.0.1:${port}`;
75
+ const args = [cli, 'serve', resolved, '--bind', bind, ...(options.serveArgs ?? [])];
76
+ const child = spawn(process.execPath, args, {
77
+ stdio: 'pipe',
78
+ windowsHide: true,
79
+ });
80
+ let stderr = '';
81
+ child.stderr?.setEncoding('utf8');
82
+ child.stderr?.on('data', (chunk) => {
83
+ stderr += chunk;
84
+ });
85
+ const exitPromise = new Promise((_, reject) => {
86
+ child.once('exit', (code, signal) => {
87
+ reject(new Error(`yydb engine exited early (code=${code}, signal=${signal}): ${stderr.trim()}`));
88
+ });
89
+ child.once('error', reject);
90
+ });
91
+ try {
92
+ await Promise.race([waitForTcp('127.0.0.1', port, 8000), exitPromise]);
93
+ const client = await connect(bind);
94
+ child.removeAllListeners('exit');
95
+ child.removeAllListeners('error');
96
+ child.on('exit', () => {
97
+ /* closed via Database.close or crash */
98
+ });
99
+ return new Database(resolved, bind, client, child);
100
+ }
101
+ catch (error) {
102
+ child.kill('SIGTERM');
103
+ throw error;
104
+ }
105
+ }
106
+ async serverVersion() {
107
+ return this.client.serverVersion();
108
+ }
109
+ async info() {
110
+ return this.client.info();
111
+ }
112
+ async getSchema() {
113
+ return this.client.getSchema();
114
+ }
115
+ async ensureSchema(version, document) {
116
+ await this.client.ensureSchema(version, document);
117
+ }
118
+ async get(key) {
119
+ return this.client.get(key);
120
+ }
121
+ async put(key, value) {
122
+ await this.client.put(key, value);
123
+ }
124
+ /**
125
+ * Build a typed session-local micro definition. Does not register it with
126
+ * the engine until [`registerMicro`](./database.ts) is called.
127
+ */
128
+ defineMicro(definition) {
129
+ return buildMicro(definition);
130
+ }
131
+ /**
132
+ * Install a [`defineMicro`](./database.ts) definition into the current
133
+ * session registry and sync metadata to the private engine. Host micros are
134
+ * session-local and never written to `.yydb`.
135
+ */
136
+ async registerMicro(definition) {
137
+ const handle = this.microRegistry.register(definition);
138
+ await this.client.registerMicro({
139
+ hostId: handle.hostId,
140
+ handleVersion: handle.implementationVersion,
141
+ udfVersion: definition.version,
142
+ name: definition.name,
143
+ functionId: handle.functionId,
144
+ args: definition.args.map(udfWireKind),
145
+ returns: udfWireKind(definition.returns),
146
+ fingerprint: microFingerprint(definition.name, definition.version),
147
+ });
148
+ return handle;
149
+ }
150
+ /** Invoke a registered scalar UDF on the private engine. */
151
+ async callScalar(name, version, args) {
152
+ return this.client.callScalar(name, version, args);
153
+ }
154
+ /** Stop the private engine process. */
155
+ async close() {
156
+ this.client.close();
157
+ const child = this.child;
158
+ this.child = null;
159
+ if (!child || child.exitCode !== null || child.killed) {
160
+ return;
161
+ }
162
+ await new Promise((resolve) => {
163
+ child.once('exit', () => resolve());
164
+ child.kill('SIGTERM');
165
+ setTimeout(() => {
166
+ if (child.exitCode === null && !child.killed) {
167
+ child.kill('SIGKILL');
168
+ }
169
+ resolve();
170
+ }, 2000);
171
+ });
172
+ }
173
+ }
@@ -0,0 +1,7 @@
1
+ export { Database, type OpenOptions } from './database.js';
2
+ export { YydbError, YydbErrorCodes, isRetryableYydbCode, isYydbError } from '@yydb/yydb-client';
3
+ export type { YydbErrorCode, YydbErrorOptions } from '@yydb/yydb-client';
4
+ export { defineMicro, type DefinedMicro, type MicroHandle, type TsMicroDefinitionInput, MicroSessionRegistry } from '../shared/micro.js';
5
+ export { type InferUdfArgs, type InferUdfReturn, type MicroEffect, type MicroInvocationMode, type UdfScalarDescriptor, type UdfScalarKind, } from '../shared/udf-types.js';
6
+ export { nativeBindingName, platformKey, resolveYydbBinary, resolveYydbCli } from './resolve-bin.js';
7
+ export * from '../shared/vos.js';
@@ -0,0 +1,5 @@
1
+ export { Database } from './database.js';
2
+ export { YydbError, YydbErrorCodes, isRetryableYydbCode, isYydbError } from '@yydb/yydb-client';
3
+ export { defineMicro, MicroSessionRegistry } from '../shared/micro.js';
4
+ export { nativeBindingName, platformKey, resolveYydbBinary, resolveYydbCli } from './resolve-bin.js';
5
+ export * from '../shared/vos.js';
@@ -0,0 +1,9 @@
1
+ export interface YydbNative {
2
+ version(): string;
3
+ serve(dbPath: string, bind: string, insecureBind?: boolean): void;
4
+ initDb(dbPath: string, schemaVersion: number, schemaDocument?: string | null): void;
5
+ infoText(dbPath: string): string;
6
+ readSchemaFile(schemaPath: string): string;
7
+ }
8
+ /** Load the platform native binding used by the `yydb` CLI. */
9
+ export declare function loadNative(): YydbNative;
@@ -0,0 +1,32 @@
1
+ import { createRequire } from 'node:module';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { platformKey } from './resolve-bin.js';
5
+ function loadModule(specifier) {
6
+ const require = createRequire(import.meta.url);
7
+ const loaded = require(specifier);
8
+ return (loaded.default ?? loaded);
9
+ }
10
+ /** Load the platform native binding used by the `yydb` CLI. */
11
+ export function loadNative() {
12
+ const fromEnv = process.env.YYDB_NATIVE?.trim();
13
+ if (fromEnv) {
14
+ const bindingPath = path.resolve(fromEnv);
15
+ if (!fs.existsSync(bindingPath)) {
16
+ throw new Error(`YYDB_NATIVE points to missing binding: ${fromEnv}`);
17
+ }
18
+ return loadModule(bindingPath);
19
+ }
20
+ const key = platformKey();
21
+ if (!key) {
22
+ throw new Error(`YYDB native binding not available for ${process.platform}-${process.arch}`);
23
+ }
24
+ const pkg = `@yydb/yydb-${key}`;
25
+ try {
26
+ return loadModule(pkg);
27
+ }
28
+ catch (error) {
29
+ const message = error instanceof Error ? error.message : String(error);
30
+ throw new Error(`YYDB native binding not found. Reinstall @yydb/yydb with optional dependencies, set YYDB_NATIVE, or stage ${pkg}. ${message}`);
31
+ }
32
+ }
@@ -0,0 +1,15 @@
1
+ /// <reference types="node" />
2
+ export type PlatformKey = 'win32-x64' | 'linux-x64' | 'darwin-x64' | 'darwin-arm64';
3
+ /** Map Node platform/arch to optionalDependency package suffix. */
4
+ export declare function platformKey(platform?: NodeJS.Platform, arch?: NodeJS.Architecture): PlatformKey | null;
5
+ /** Native binding filename inside each `@yydb/yydb-<platform>` package `lib/`. */
6
+ export declare function nativeBindingName(key: PlatformKey): string;
7
+ /**
8
+ * Resolve the TypeScript `yydb` CLI entry script.
9
+ * Order: `YYDB_CLI` → this package `dist/cli.js`.
10
+ */
11
+ export declare function resolveYydbCli(): string;
12
+ /**
13
+ * @deprecated Use {@link resolveYydbCli}. Kept for callers that still import this name.
14
+ */
15
+ export declare function resolveYydbBinary(): string;
@@ -0,0 +1,62 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ /** Map Node platform/arch to optionalDependency package suffix. */
5
+ export function platformKey(platform = process.platform, arch = process.arch) {
6
+ if (platform === 'win32' && arch === 'x64')
7
+ return 'win32-x64';
8
+ if (platform === 'linux' && arch === 'x64')
9
+ return 'linux-x64';
10
+ if (platform === 'darwin' && arch === 'x64')
11
+ return 'darwin-x64';
12
+ if (platform === 'darwin' && arch === 'arm64')
13
+ return 'darwin-arm64';
14
+ return null;
15
+ }
16
+ /** Native binding filename inside each `@yydb/yydb-<platform>` package `lib/`. */
17
+ export function nativeBindingName(key) {
18
+ switch (key) {
19
+ case 'win32-x64':
20
+ return 'yydb-win32-x64-msvc.node';
21
+ case 'linux-x64':
22
+ return 'yydb-linux-x64-gnu.node';
23
+ case 'darwin-x64':
24
+ return 'yydb-darwin-x64.node';
25
+ case 'darwin-arm64':
26
+ return 'yydb-darwin-arm64.node';
27
+ }
28
+ }
29
+ function existsFile(filePath) {
30
+ try {
31
+ fs.accessSync(filePath, fs.constants.F_OK);
32
+ return true;
33
+ }
34
+ catch {
35
+ return false;
36
+ }
37
+ }
38
+ /**
39
+ * Resolve the TypeScript `yydb` CLI entry script.
40
+ * Order: `YYDB_CLI` → this package `dist/cli.js`.
41
+ */
42
+ export function resolveYydbCli() {
43
+ const fromEnv = process.env.YYDB_CLI?.trim();
44
+ if (fromEnv) {
45
+ if (!existsFile(fromEnv)) {
46
+ throw new Error(`YYDB_CLI points to missing script: ${fromEnv}`);
47
+ }
48
+ return path.resolve(fromEnv);
49
+ }
50
+ const here = path.dirname(fileURLToPath(import.meta.url));
51
+ const candidate = path.join(here, 'cli.js');
52
+ if (!existsFile(candidate)) {
53
+ throw new Error('YYDB CLI script not found. Build `@yydb/yydb` (`pnpm --filter @yydb/yydb build`) or set YYDB_CLI.');
54
+ }
55
+ return candidate;
56
+ }
57
+ /**
58
+ * @deprecated Use {@link resolveYydbCli}. Kept for callers that still import this name.
59
+ */
60
+ export function resolveYydbBinary() {
61
+ return resolveYydbCli();
62
+ }
@@ -0,0 +1,15 @@
1
+ /// <reference types="node" />
2
+ export type PlatformKey = 'win32-x64' | 'linux-x64' | 'darwin-x64' | 'darwin-arm64';
3
+ /** Map Node platform/arch to optionalDependency package suffix. */
4
+ export declare function platformKey(platform?: NodeJS.Platform, arch?: NodeJS.Architecture): PlatformKey | null;
5
+ /** Native binding filename inside each `@yydb/yydb-<platform>` package `lib/`. */
6
+ export declare function nativeBindingName(key: PlatformKey): string;
7
+ /**
8
+ * Resolve the TypeScript `yydb` CLI entry script.
9
+ * Order: `YYDB_CLI` → this package `dist/cli.js`.
10
+ */
11
+ export declare function resolveYydbCli(): string;
12
+ /**
13
+ * @deprecated Use {@link resolveYydbCli}. Kept for callers that still import this name.
14
+ */
15
+ export declare function resolveYydbBinary(): string;
@@ -0,0 +1,62 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ /** Map Node platform/arch to optionalDependency package suffix. */
5
+ export function platformKey(platform = process.platform, arch = process.arch) {
6
+ if (platform === 'win32' && arch === 'x64')
7
+ return 'win32-x64';
8
+ if (platform === 'linux' && arch === 'x64')
9
+ return 'linux-x64';
10
+ if (platform === 'darwin' && arch === 'x64')
11
+ return 'darwin-x64';
12
+ if (platform === 'darwin' && arch === 'arm64')
13
+ return 'darwin-arm64';
14
+ return null;
15
+ }
16
+ /** Native binding filename inside each `@yydb/yydb-<platform>` package `lib/`. */
17
+ export function nativeBindingName(key) {
18
+ switch (key) {
19
+ case 'win32-x64':
20
+ return 'yydb-win32-x64-msvc.node';
21
+ case 'linux-x64':
22
+ return 'yydb-linux-x64-gnu.node';
23
+ case 'darwin-x64':
24
+ return 'yydb-darwin-x64.node';
25
+ case 'darwin-arm64':
26
+ return 'yydb-darwin-arm64.node';
27
+ }
28
+ }
29
+ function existsFile(filePath) {
30
+ try {
31
+ fs.accessSync(filePath, fs.constants.F_OK);
32
+ return true;
33
+ }
34
+ catch {
35
+ return false;
36
+ }
37
+ }
38
+ /**
39
+ * Resolve the TypeScript `yydb` CLI entry script.
40
+ * Order: `YYDB_CLI` → this package `dist/cli.js`.
41
+ */
42
+ export function resolveYydbCli() {
43
+ const fromEnv = process.env.YYDB_CLI?.trim();
44
+ if (fromEnv) {
45
+ if (!existsFile(fromEnv)) {
46
+ throw new Error(`YYDB_CLI points to missing script: ${fromEnv}`);
47
+ }
48
+ return path.resolve(fromEnv);
49
+ }
50
+ const here = path.dirname(fileURLToPath(import.meta.url));
51
+ const candidate = path.join(here, 'cli.js');
52
+ if (!existsFile(candidate)) {
53
+ throw new Error('YYDB CLI script not found. Build `@yydb/yydb` (`pnpm --filter @yydb/yydb build`) or set YYDB_CLI.');
54
+ }
55
+ return candidate;
56
+ }
57
+ /**
58
+ * @deprecated Use {@link resolveYydbCli}. Kept for callers that still import this name.
59
+ */
60
+ export function resolveYydbBinary() {
61
+ return resolveYydbCli();
62
+ }
@@ -2,12 +2,57 @@
2
2
  * Host-facing YYDB error surface.
3
3
  *
4
4
  * Every public failure carries a stable `code`: engine codes come from the
5
- * Rust core (`yydb.schema.invalid`, …) over N-API or the wire, client codes
6
- * are minted locally with the `yydb.client.` prefix. Branch on `code` —
7
- * `message` is display text only.
5
+ * Rust core (`yydb.schema.invalid`, …) over N-API or the wire; client codes
6
+ * are minted locally with the `yydb.client.` prefix. **Branch on `code`** —
7
+ * `message` is display text only and must never be used for retry decisions.
8
+ *
9
+ * ## Where codes live
10
+ *
11
+ * Engine codes live in Rust (`yydb_types::Error` variants → DiagnosticEnvelope).
12
+ * The TS side no longer keeps a hardcoded engine-code table — when Rust gains a
13
+ * new variant, hosts pick up the new code string via {@link fromDiagnosticJson}
14
+ * without any TS change. Client codes (`yydb.client.*`) are minted only on the
15
+ * host side and remain available as constants below.
16
+ */
17
+ /**
18
+ * Client-side error codes that never originate from Rust.
19
+ *
20
+ * Engine codes (`yydb.schema.*`, `yydb.udf.*`, `yydb.io`, …) are emitted by
21
+ * Rust and should be matched via string literals or from the diagnostic JSON
22
+ * envelope — they are intentionally not duplicated here.
23
+ */
24
+ export declare const YydbClientErrorCodes: {
25
+ /** serve() endpoint is empty or invalid. */
26
+ readonly clientInvalidEndpoint: "yydb.client.invalid_endpoint";
27
+ /** connect() socket / transport layer failure. */
28
+ readonly clientTransportFailure: "yydb.client.transport_failure";
29
+ /** Server replied with a request id we did not send. */
30
+ readonly clientRequestIdMismatch: "yydb.client.request_id_mismatch";
31
+ /** Server replied with a msg_type we did not request. */
32
+ readonly clientUnexpectedResponse: "yydb.client.unexpected_response";
33
+ /** Could not parse an Error frame body from the wire. */
34
+ readonly clientDecodeFailure: "yydb.client.decode_failure";
35
+ /** The YYDB session is already closed. */
36
+ readonly sessionClosed: "yydb.session.closed";
37
+ };
38
+ /**
39
+ * @deprecated Engine codes moved to Rust — use string literals or match on
40
+ * {@link YydbError.code} returned from {@link fromDiagnosticJson}. This table
41
+ * exists only for transition; new engine error variants will **not** be added here.
8
42
  */
9
- /** Stable codes known to the TypeScript hosts. */
10
43
  export declare const YydbErrorCodes: {
44
+ /** serve() endpoint is empty or invalid. */
45
+ readonly clientInvalidEndpoint: "yydb.client.invalid_endpoint";
46
+ /** connect() socket / transport layer failure. */
47
+ readonly clientTransportFailure: "yydb.client.transport_failure";
48
+ /** Server replied with a request id we did not send. */
49
+ readonly clientRequestIdMismatch: "yydb.client.request_id_mismatch";
50
+ /** Server replied with a msg_type we did not request. */
51
+ readonly clientUnexpectedResponse: "yydb.client.unexpected_response";
52
+ /** Could not parse an Error frame body from the wire. */
53
+ readonly clientDecodeFailure: "yydb.client.decode_failure";
54
+ /** The YYDB session is already closed. */
55
+ readonly sessionClosed: "yydb.session.closed";
11
56
  readonly io: "yydb.io";
12
57
  readonly corrupt: "yydb.corrupt";
13
58
  readonly schemaConflict: "yydb.schema.conflict";
@@ -25,33 +70,63 @@ export declare const YydbErrorCodes: {
25
70
  readonly fencingMismatch: "yydb.fencing.mismatch";
26
71
  readonly leaseExpired: "yydb.lease.expired";
27
72
  readonly quotaExceeded: "yydb.quota.exceeded";
28
- readonly sessionClosed: "yydb.session.closed";
29
- readonly clientInvalidEndpoint: "yydb.client.invalid_endpoint";
30
- readonly clientTransportFailure: "yydb.client.transport_failure";
31
- readonly clientRequestIdMismatch: "yydb.client.request_id_mismatch";
32
- readonly clientUnexpectedResponse: "yydb.client.unexpected_response";
33
- readonly clientDecodeFailure: "yydb.client.decode_failure";
34
73
  };
35
- /** A known code, or any future code the engine introduces. */
36
- export type YydbErrorCode = (typeof YydbErrorCodes)[keyof typeof YydbErrorCodes] | (string & {});
37
- /** Whether `code` may be retried with the same arguments. */
38
- export declare function isRetryableYydbCode(code: string): boolean;
74
+ /** Any stable YYDB error code string — engine, client, or future variant. */
75
+ export type YydbErrorCode = string;
39
76
  /** Options carried by a {@link YydbError}. */
40
77
  export interface YydbErrorOptions {
41
78
  /** Operation that failed (`schemaGet`, `kvPut`, `query`, …). */
42
79
  operation?: string;
80
+ /**
81
+ * Whether the caller may retry with the same arguments.
82
+ *
83
+ * When omitted, constructor defaults to `false` (conservative). Callers
84
+ * that mint their own errors (especially client-local retryable ones like
85
+ * `yydb.client.transport_failure`) **must** set this explicitly. Engine
86
+ * errors coming through {@link fromDiagnosticJson} always carry the Rust-
87
+ * authoritative `retryable` value and override the default.
88
+ */
89
+ retryable?: boolean;
90
+ /** Structured parameters for catalog templating (when present). */
91
+ params?: Readonly<Record<string, string>>;
43
92
  /** Underlying failure when this error wraps one. */
44
93
  cause?: unknown;
45
94
  }
46
95
  /** YYDB failure with a stable machine-readable {@link YydbError.code}. */
47
96
  export declare class YydbError extends Error {
48
- /** Stable code — engine code or `yydb.client.*`. */
97
+ /** Stable code — engine code (from Rust) or `yydb.client.*`. */
49
98
  readonly code: string;
50
99
  /** Operation that failed. */
51
100
  readonly operation: string;
52
101
  /** Whether the same call may be retried. */
53
102
  readonly retryable: boolean;
103
+ /** Structured parameters for catalog templating (when present). */
104
+ readonly params?: Readonly<Record<string, string>>;
54
105
  constructor(code: YydbErrorCode, message: string, options?: YydbErrorOptions);
106
+ /**
107
+ * Parse the diagnostic JSON that N-API and WASM surfaces embed in their
108
+ * `Error.message`. Every boundary surface (N-API reason field, WASM JSON
109
+ * envelope, wire protocol) carries the same `{ code, operation, retryable,
110
+ * message, params? }` contract produced by `yydb::Error::to_json`.
111
+ *
112
+ * Returns the parsed {@link YydbError} if `value` is a diagnostic object,
113
+ * or `null` when the input is plain text. Hosts typically use this as a
114
+ * fallback in catch blocks:
115
+ *
116
+ * ```ts
117
+ * try {
118
+ * await db.put('key', value);
119
+ * } catch (err) {
120
+ * const parsed = YydbError.fromDiagnosticJson(
121
+ * err instanceof Error ? err.message : String(err),
122
+ * );
123
+ * if (parsed) {
124
+ * // branch on parsed.code / retryable / operation
125
+ * }
126
+ * }
127
+ * ```
128
+ */
129
+ static fromDiagnosticJson(value: unknown): YydbError | null;
55
130
  }
56
131
  /** Narrowing helper so callers can branch without `instanceof` imports. */
57
132
  export declare function isYydbError(value: unknown): value is YydbError;