@yydb/yydb 0.0.1 → 0.0.2

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 (40) hide show
  1. package/README.md +77 -17
  2. package/dist/cli/cli.d.ts +1 -0
  3. package/dist/cli/cli.js +27 -0
  4. package/dist/cli/generate-cmd.d.ts +3 -0
  5. package/dist/cli/generate-cmd.js +25 -0
  6. package/dist/cli/info-cmd.d.ts +3 -0
  7. package/dist/cli/info-cmd.js +10 -0
  8. package/dist/cli/init-cmd.d.ts +3 -0
  9. package/dist/cli/init-cmd.js +18 -0
  10. package/dist/cli/options.d.ts +4 -0
  11. package/dist/cli/options.js +14 -0
  12. package/dist/cli/serve-cmd.d.ts +3 -0
  13. package/dist/cli/serve-cmd.js +14 -0
  14. package/dist/cli/version-cmd.d.ts +3 -0
  15. package/dist/cli/version-cmd.js +8 -0
  16. package/dist/cli.d.ts +2 -0
  17. package/dist/cli.js +6 -0
  18. package/dist/database.d.ts +20 -2
  19. package/dist/database.js +70 -28
  20. package/dist/generator/emit-typescript.d.ts +3 -0
  21. package/dist/generator/emit-typescript.js +26 -0
  22. package/dist/generator/fixtures/setting.generated.d.ts +13 -0
  23. package/dist/generator/fixtures/setting.generated.js +3 -0
  24. package/dist/generator/index.d.ts +6 -0
  25. package/dist/generator/index.js +10 -0
  26. package/dist/generator/ir.d.ts +20 -0
  27. package/dist/generator/ir.js +12 -0
  28. package/dist/generator/parse-vos.d.ts +6 -0
  29. package/dist/generator/parse-vos.js +149 -0
  30. package/dist/index.d.ts +4 -2
  31. package/dist/index.js +4 -2
  32. package/dist/micro.d.ts +63 -0
  33. package/dist/micro.js +160 -0
  34. package/dist/native.d.ts +9 -0
  35. package/dist/native.js +66 -0
  36. package/dist/resolve-bin.d.ts +9 -4
  37. package/dist/resolve-bin.js +31 -47
  38. package/dist/udf-types.d.ts +26 -0
  39. package/dist/udf-types.js +16 -0
  40. package/package.json +25 -13
package/README.md CHANGED
@@ -1,30 +1,90 @@
1
1
  # `@yydb/yydb`
2
2
 
3
- TypeScript / Node **product surface** for YYDB. One dependency:
3
+ **All-in-one YYDB for Node.js: CLI, embedded engine, and local `serve`.**
4
+
5
+ Open a `.yydb` file with one dependency — `@yydb/yydb` spawns a private loopback server and talks to it over the YY
6
+ wire protocol. For a **lightweight client only** (browser, renderer, or an app that already runs `yydb serve`), use
7
+ [`@yydb/yydb-client`](../yydb-client).
8
+
9
+ [![npm](https://img.shields.io/npm/v/@yydb/yydb)](https://www.npmjs.com/package/@yydb/yydb)
10
+ [![Node.js](https://img.shields.io/node/v/@yydb/yydb)](https://www.npmjs.com/package/@yydb/yydb)
11
+ [![Check Rust](https://github.com/yy-database/yydb/actions/workflows/check-rust.yml/badge.svg?branch=dev)](https://github.com/yy-database/yydb/actions/workflows/check-rust.yml)
12
+ [![Check TypeScript](https://github.com/yy-database/yydb/actions/workflows/check-typescript.yml/badge.svg?branch=dev)](https://github.com/yy-database/yydb/actions/workflows/check-typescript.yml)
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ npm install @yydb/yydb
18
+ ```
19
+
20
+ Node.js 18 or newer is supported. The matching `yydb-napi` binding is installed as an optional platform dependency and
21
+ is
22
+ started on a private loopback endpoint when `Database.open()` runs.
23
+
24
+ ## Example
4
25
 
5
26
  ```ts
6
- import { Database } from "@yydb/yydb";
27
+ import {Database} from "@yydb/yydb";
28
+
29
+ const db = await Database.open("./data/app.yydb");
30
+
31
+ await db.ensureSchema(
32
+ 1,
33
+ `table Setting {
34
+ @@id: uuid,
35
+ key: utf8,
36
+ value: utf8,
37
+ }`,
38
+ );
7
39
 
8
- const db = await Database.open("app.yydb");
9
- await db.ensureSchema(1, "table Setting { @@id: uuid, key: utf8, value: utf8 }");
10
40
  await db.put("theme", "dark");
41
+ const value = await db.get("theme");
42
+ console.log(value && new TextDecoder().decode(value));
43
+
11
44
  await db.close();
12
45
  ```
13
46
 
14
- You do **not** download `yydb.exe`, configure PATH, or call `serve` yourself.
15
- This package starts a loopback engine process behind the API (same Rust kernel
16
- as `backends/yydb`). Platform binaries ship as optionalDependencies
17
- (`@yydb/yydb-<platform>-<arch>`); npm installs only your OS’s package.
47
+ The database file is created if it does not exist. The VOS schema and its version are stored with the data, so reopening
48
+ the file does not depend on a separate schema registry.
49
+
50
+ ## API
51
+
52
+ ```ts
53
+ const db = await Database.open(path, options ?);
54
+
55
+ await db.ensureSchema(version, vosDocument);
56
+ await db.getSchema();
57
+ await db.put(key, stringOrBytes);
58
+ await db.get(key);
59
+ await db.info();
60
+ await db.serverVersion();
61
+ await db.close();
62
+ ```
63
+
64
+ `OpenOptions` supports `cli` (an explicit CLI script path), `port` (a preferred loopback port), and `serveArgs` (extra
65
+ arguments forwarded to `yydb serve`). `binary` is a deprecated alias for `cli`.
66
+
67
+ ## Use a different package when
68
+
69
+ | Need | Package |
70
+ |----------------------------------------|-------------------------------------------|
71
+ | Node all-in-one (CLI + engine + serve) | **`@yydb/yydb`** (this package) |
72
+ | Lightweight wire client only | [`@yydb/yydb-client`](../yydb-client) |
73
+ | In-process Rust embed | [`yydb`](https://crates.io/crates/yydb) |
74
+ | Rust wire server crate (used by N-API) | [`yydb-server`](../../crates/yydb-server) |
75
+ | Rust remote client | [`yydb-client`](../../crates/yydb-client) |
76
+
77
+ For Electron, the main process can use `@yydb/yydb`; renderer code can use
78
+ `@yydb/yydb-client` against the main process endpoint.
18
79
 
19
- | Package | Role |
20
- | --- | --- |
21
- | `@yydb/yydb` | High-level `Database.open` (this package) |
22
- | `@yydb/yydb-client` | Low-level YY wire (also used by WebUI) |
23
- | `@yydb/yydb-<os>-<arch>` | Prebuilt `yydb` binary (publish pipeline) |
80
+ ## Security
24
81
 
25
- Local monorepo/dev: falls back to `target/release|debug/yydb` or `YYDB_BIN`.
82
+ The managed engine listens on `127.0.0.1` and has no authentication layer. Keep the endpoint private and use the
83
+ operating system's file permissions for the `.yydb` file.
26
84
 
27
- Browser: still use `@yydb/yydb-client` against a host that already runs
28
- the engine (e.g. Electron main used `@yydb/yydb`, renderer talks WS).
85
+ ## Links
29
86
 
30
- Security: engine binds **loopback only**. No ACL — do not expose the port.
87
+ - [YYDB overview](https://github.com/yy-database/yydb.rs)
88
+ - [User guides](https://github.com/yy-database/yydb.rs/tree/dev/projects/packages/homepage/documentation/zh-hans)
89
+ - [API documentation](https://docs.rs/yydb)
90
+ - [License](https://github.com/yy-database/yydb.rs/blob/dev/License.md)
@@ -0,0 +1 @@
1
+ export declare function runCli(argv: string[]): Promise<number>;
@@ -0,0 +1,27 @@
1
+ import { dirname, join } from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { createCli } from '@vmz/commander';
4
+ import { registerGenerateCommand } from './generate-cmd.js';
5
+ import { registerInfoCommand } from './info-cmd.js';
6
+ import { registerInitCommand } from './init-cmd.js';
7
+ import { registerServeCommand } from './serve-cmd.js';
8
+ import { registerVersionCommand } from './version-cmd.js';
9
+ function buildYydbCli() {
10
+ const localesRoot = join(dirname(fileURLToPath(import.meta.url)), '../../locales');
11
+ const cli = createCli('yydb').locales(localesRoot).intro('cli.intro');
12
+ registerVersionCommand(cli);
13
+ registerInitCommand(cli);
14
+ registerInfoCommand(cli);
15
+ registerServeCommand(cli);
16
+ registerGenerateCommand(cli);
17
+ return cli;
18
+ }
19
+ export async function runCli(argv) {
20
+ try {
21
+ return await buildYydbCli().parse(argv);
22
+ }
23
+ catch (error) {
24
+ console.error(`error: ${error instanceof Error ? error.message : String(error)}`);
25
+ return 1;
26
+ }
27
+ }
@@ -0,0 +1,3 @@
1
+ import type { Cli, ParsedOptions } from '@vmz/commander';
2
+ export declare function registerGenerateCommand(cli: Cli): void;
3
+ export declare function cmdGenerate(options: ParsedOptions): number;
@@ -0,0 +1,25 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { generateTypeScript } from '../generator/index.js';
4
+ import { requiredPositional, str } from './options.js';
5
+ export function registerGenerateCommand(cli) {
6
+ cli.command('generate', 'cli.cmd.generate')
7
+ .option('-o, --output <path>', 'cli.opt.output')
8
+ .option('--schema-version <n>', 'cli.opt.schema-version')
9
+ .action((options) => cmdGenerate(options));
10
+ }
11
+ export function cmdGenerate(options) {
12
+ const input = requiredPositional(options, 'generate');
13
+ const output = str(options, 'output');
14
+ if (!output) {
15
+ throw new Error('generate requires --output <path>');
16
+ }
17
+ const schemaVersion = Number.parseInt(str(options, 'schema-version') ?? '1', 10);
18
+ const source = fs.readFileSync(input, 'utf8');
19
+ const ts = generateTypeScript(source, schemaVersion);
20
+ const outPath = path.resolve(output);
21
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
22
+ fs.writeFileSync(outPath, ts);
23
+ console.log(`wrote ${outPath}`);
24
+ return 0;
25
+ }
@@ -0,0 +1,3 @@
1
+ import type { Cli, ParsedOptions } from '@vmz/commander';
2
+ export declare function registerInfoCommand(cli: Cli): void;
3
+ export declare function cmdInfo(options: ParsedOptions): number;
@@ -0,0 +1,10 @@
1
+ import { loadNative } from '../native.js';
2
+ import { requiredPositional } from './options.js';
3
+ export function registerInfoCommand(cli) {
4
+ cli.command('info', 'cli.cmd.info').action((options) => cmdInfo(options));
5
+ }
6
+ export function cmdInfo(options) {
7
+ const dbPath = requiredPositional(options, 'info');
8
+ console.log(loadNative().infoText(dbPath));
9
+ return 0;
10
+ }
@@ -0,0 +1,3 @@
1
+ import type { Cli, ParsedOptions } from '@vmz/commander';
2
+ export declare function registerInitCommand(cli: Cli): void;
3
+ export declare function cmdInit(options: ParsedOptions): number;
@@ -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
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -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
+ }
@@ -1,6 +1,10 @@
1
- export type { SchemaVersion } from "@yydb/yydb-client";
1
+ import { defineMicro as buildMicro, type DefinedMicro, type MicroHandle } from './micro.js';
2
+ import type { UdfTypeDescriptor } from './udf-types.js';
3
+ export type { SchemaVersion } from '@yydb/yydb-client';
2
4
  export interface OpenOptions {
3
- /** Override engine binary (tests / special installs). */
5
+ /** Override CLI script path (tests / special installs). */
6
+ cli?: string;
7
+ /** @deprecated Use `cli`. */
4
8
  binary?: string;
5
9
  /** Prefer a specific loopback port; default = ephemeral free port. */
6
10
  port?: number;
@@ -16,6 +20,7 @@ export declare class Database {
16
20
  readonly endpoint: string;
17
21
  private client;
18
22
  private child;
23
+ private readonly microRegistry;
19
24
  private constructor();
20
25
  /**
21
26
  * Open (create) a `.yydb` file. Spawns the engine on `127.0.0.1` automatically.
@@ -27,6 +32,19 @@ export declare class Database {
27
32
  ensureSchema(version: number, document: string): Promise<void>;
28
33
  get(key: string): Promise<Uint8Array | null>;
29
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 UdfTypeDescriptor[], TReturn extends UdfTypeDescriptor>(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 UdfTypeDescriptor[], TReturn extends UdfTypeDescriptor>(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>;
30
48
  /** Stop the private engine process. */
31
49
  close(): Promise<void>;
32
50
  }
package/dist/database.js CHANGED
@@ -1,17 +1,26 @@
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 { resolveYydbBinary } from "./resolve-bin.js";
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, MicroSessionRegistry } from './micro.js';
7
+ import { resolveYydbCli } from './resolve-bin.js';
8
+ function microFingerprint(name, version) {
9
+ const bytes = new Uint8Array(32);
10
+ const seed = `${name}@${version}`;
11
+ for (let index = 0; index < bytes.byteLength; index += 1) {
12
+ bytes[index] = seed.charCodeAt(index % seed.length) & 0xff;
13
+ }
14
+ return bytes;
15
+ }
7
16
  async function freeLoopbackPort() {
8
17
  return await new Promise((resolve, reject) => {
9
18
  const server = net.createServer();
10
- server.listen(0, "127.0.0.1", () => {
19
+ server.listen(0, '127.0.0.1', () => {
11
20
  const address = server.address();
12
- if (!address || typeof address === "string") {
21
+ if (!address || typeof address === 'string') {
13
22
  server.close();
14
- reject(new Error("could not allocate loopback port"));
23
+ reject(new Error('could not allocate loopback port'));
15
24
  return;
16
25
  }
17
26
  const { port } = address;
@@ -22,7 +31,7 @@ async function freeLoopbackPort() {
22
31
  resolve(port);
23
32
  });
24
33
  });
25
- server.on("error", reject);
34
+ server.on('error', reject);
26
35
  });
27
36
  }
28
37
  async function waitForTcp(host, port, timeoutMs) {
@@ -34,7 +43,7 @@ async function waitForTcp(host, port, timeoutMs) {
34
43
  socket.end();
35
44
  resolve();
36
45
  });
37
- socket.on("error", reject);
46
+ socket.on('error', reject);
38
47
  });
39
48
  return;
40
49
  }
@@ -53,11 +62,14 @@ export class Database {
53
62
  endpoint;
54
63
  client;
55
64
  child;
65
+ microRegistry;
56
66
  constructor(dbPath, endpoint, client, child) {
57
67
  this.path = dbPath;
58
68
  this.endpoint = endpoint;
59
69
  this.client = client;
60
70
  this.child = child;
71
+ this.microRegistry = new MicroSessionRegistry();
72
+ this.client.setMicroHostHandler((payload) => this.microRegistry.invokeFromWire(payload));
61
73
  }
62
74
  /**
63
75
  * Open (create) a `.yydb` file. Spawns the engine on `127.0.0.1` automatically.
@@ -65,37 +77,37 @@ export class Database {
65
77
  static async open(dbPath, options = {}) {
66
78
  const resolved = path.resolve(dbPath);
67
79
  fs.mkdirSync(path.dirname(resolved), { recursive: true });
68
- const binary = options.binary ?? resolveYydbBinary();
80
+ const cli = options.cli ?? options.binary ?? resolveYydbCli();
69
81
  const port = options.port ?? (await freeLoopbackPort());
70
82
  const bind = `127.0.0.1:${port}`;
71
- const args = ["serve", resolved, "--bind", bind, ...(options.serveArgs ?? [])];
72
- const child = spawn(binary, args, {
73
- stdio: "pipe",
83
+ const args = [cli, 'serve', resolved, '--bind', bind, ...(options.serveArgs ?? [])];
84
+ const child = spawn(process.execPath, args, {
85
+ stdio: 'pipe',
74
86
  windowsHide: true,
75
87
  });
76
- let stderr = "";
77
- child.stderr?.setEncoding("utf8");
78
- child.stderr?.on("data", (chunk) => {
88
+ let stderr = '';
89
+ child.stderr?.setEncoding('utf8');
90
+ child.stderr?.on('data', (chunk) => {
79
91
  stderr += chunk;
80
92
  });
81
93
  const exitPromise = new Promise((_, reject) => {
82
- child.once("exit", (code, signal) => {
94
+ child.once('exit', (code, signal) => {
83
95
  reject(new Error(`yydb engine exited early (code=${code}, signal=${signal}): ${stderr.trim()}`));
84
96
  });
85
- child.once("error", reject);
97
+ child.once('error', reject);
86
98
  });
87
99
  try {
88
- await Promise.race([waitForTcp("127.0.0.1", port, 8000), exitPromise]);
100
+ await Promise.race([waitForTcp('127.0.0.1', port, 8000), exitPromise]);
89
101
  const client = await connect(bind);
90
- child.removeAllListeners("exit");
91
- child.removeAllListeners("error");
92
- child.on("exit", () => {
102
+ child.removeAllListeners('exit');
103
+ child.removeAllListeners('error');
104
+ child.on('exit', () => {
93
105
  /* closed via Database.close or crash */
94
106
  });
95
107
  return new Database(resolved, bind, client, child);
96
108
  }
97
109
  catch (error) {
98
- child.kill("SIGTERM");
110
+ child.kill('SIGTERM');
99
111
  throw error;
100
112
  }
101
113
  }
@@ -117,6 +129,36 @@ export class Database {
117
129
  async put(key, value) {
118
130
  await this.client.put(key, value);
119
131
  }
132
+ /**
133
+ * Build a typed session-local micro definition. Does not register it with
134
+ * the engine until [`registerMicro`](./database.ts) is called.
135
+ */
136
+ defineMicro(definition) {
137
+ return buildMicro(definition);
138
+ }
139
+ /**
140
+ * Install a [`defineMicro`](./database.ts) definition into the current
141
+ * session registry and sync metadata to the private engine. Host micros are
142
+ * session-local and never written to `.yydb`.
143
+ */
144
+ async registerMicro(definition) {
145
+ const handle = this.microRegistry.register(definition);
146
+ await this.client.registerMicro({
147
+ hostId: handle.hostId,
148
+ handleVersion: handle.implementationVersion,
149
+ udfVersion: definition.version,
150
+ name: definition.name,
151
+ functionId: handle.functionId,
152
+ args: definition.args.map((arg) => arg.kind),
153
+ returns: definition.returns.kind,
154
+ fingerprint: microFingerprint(definition.name, definition.version),
155
+ });
156
+ return handle;
157
+ }
158
+ /** Invoke a registered scalar UDF on the private engine. */
159
+ async callScalar(name, version, args) {
160
+ return this.client.callScalar(name, version, args);
161
+ }
120
162
  /** Stop the private engine process. */
121
163
  async close() {
122
164
  this.client.close();
@@ -126,11 +168,11 @@ export class Database {
126
168
  return;
127
169
  }
128
170
  await new Promise((resolve) => {
129
- child.once("exit", () => resolve());
130
- child.kill("SIGTERM");
171
+ child.once('exit', () => resolve());
172
+ child.kill('SIGTERM');
131
173
  setTimeout(() => {
132
174
  if (child.exitCode === null && !child.killed) {
133
- child.kill("SIGKILL");
175
+ child.kill('SIGKILL');
134
176
  }
135
177
  resolve();
136
178
  }, 2000);
@@ -0,0 +1,3 @@
1
+ import { type TsSchemaIr } from './ir.js';
2
+ /** Generate `schema.generated.ts` source from IR. */
3
+ export declare function emitTypeScript(ir: TsSchemaIr): string;
@@ -0,0 +1,26 @@
1
+ import { scalarToTs } from './ir.js';
2
+ const BANNER = '// @generated by `yydb generate`, do not edit!';
3
+ /** Generate `schema.generated.ts` source from IR. */
4
+ export function emitTypeScript(ir) {
5
+ const lines = [BANNER];
6
+ lines.push(`export const APP_SCHEMA_VERSION = ${ir.schemaVersion};`);
7
+ lines.push(`export const APP_SCHEMA = ${JSON.stringify(ir.vosSource)};`);
8
+ for (const table of ir.tables) {
9
+ lines.push(emitInterfaceRow(table));
10
+ }
11
+ lines.push(emitInterfaceAppDb(ir));
12
+ lines.push(`export type AppDbTableName = keyof AppDb["tables"];`);
13
+ return `${lines.join('\n')}\n`;
14
+ }
15
+ function emitInterfaceRow(table) {
16
+ const lines = [`export interface ${table.name}Row {`];
17
+ for (const field of table.fields) {
18
+ lines.push(` ${field.name}: ${scalarToTs(field.ty)};`);
19
+ }
20
+ lines.push('}');
21
+ return lines.join('\n');
22
+ }
23
+ function emitInterfaceAppDb(ir) {
24
+ const tableLines = ir.tables.map((table) => ` ${table.name}: ${table.name}Row;`);
25
+ return ['export interface AppDb {', ' tables: {', ...tableLines, ' };', '}'].join('\n');
26
+ }
@@ -0,0 +1,13 @@
1
+ export declare const APP_SCHEMA_VERSION = 1;
2
+ export declare const APP_SCHEMA = "table Setting {\n @@id: uuid,\n key: utf8,\n value: utf8,\n}\n";
3
+ export interface SettingRow {
4
+ id: string;
5
+ key: string;
6
+ value: string;
7
+ }
8
+ export interface AppDb {
9
+ tables: {
10
+ Setting: SettingRow;
11
+ };
12
+ }
13
+ export type AppDbTableName = keyof AppDb['tables'];
@@ -0,0 +1,3 @@
1
+ // @generated by `yydb generate`, do not edit!
2
+ export const APP_SCHEMA_VERSION = 1;
3
+ export const APP_SCHEMA = 'table Setting {\n @@id: uuid,\n key: utf8,\n value: utf8,\n}\n';
@@ -0,0 +1,6 @@
1
+ export type { SchemaField, SchemaTable, ScalarType, TsSchemaIr } from './ir.js';
2
+ export { emitTypeScript } from './emit-typescript.js';
3
+ export { parseVosSubset, VosParseError } from './parse-vos.js';
4
+ /** Generate TypeScript from a VOS document. */
5
+ export declare function generateTypeScript(vosSource: string, schemaVersion: number): string;
6
+ export { generateTypeScript as generateTypescript };
@@ -0,0 +1,10 @@
1
+ import { emitTypeScript } from './emit-typescript.js';
2
+ import { parseVosSubset } from './parse-vos.js';
3
+ export { emitTypeScript } from './emit-typescript.js';
4
+ export { parseVosSubset, VosParseError } from './parse-vos.js';
5
+ /** Generate TypeScript from a VOS document. */
6
+ export function generateTypeScript(vosSource, schemaVersion) {
7
+ const ir = parseVosSubset(vosSource, schemaVersion);
8
+ return emitTypeScript(ir);
9
+ }
10
+ export { generateTypeScript as generateTypescript };
@@ -0,0 +1,20 @@
1
+ /** Scalar types supported by the MVP emitter (expand with `vos-parser`). */
2
+ export type ScalarType = 'uuid' | 'utf8' | 'bool' | 'i64' | 'f64';
3
+ export interface SchemaField {
4
+ readonly name: string;
5
+ readonly ty: ScalarType;
6
+ /** True when this field is declared via `@@id`. */
7
+ readonly isId: boolean;
8
+ }
9
+ export interface SchemaTable {
10
+ readonly name: string;
11
+ readonly fields: readonly SchemaField[];
12
+ }
13
+ /** One schema document ready for TypeScript emission. */
14
+ export interface TsSchemaIr {
15
+ /** Original VOS source (database truth embedded into generated module). */
16
+ readonly vosSource: string;
17
+ readonly schemaVersion: number;
18
+ readonly tables: readonly SchemaTable[];
19
+ }
20
+ export declare function scalarToTs(ty: ScalarType): string;
@@ -0,0 +1,12 @@
1
+ export function scalarToTs(ty) {
2
+ switch (ty) {
3
+ case 'uuid':
4
+ case 'utf8':
5
+ return 'string';
6
+ case 'bool':
7
+ return 'boolean';
8
+ case 'i64':
9
+ case 'f64':
10
+ return 'number';
11
+ }
12
+ }
@@ -0,0 +1,6 @@
1
+ import type { TsSchemaIr } from './ir.js';
2
+ export declare class VosParseError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ /** Interim VOS subset parser for `yydb generate` (MVP). */
6
+ export declare function parseVosSubset(source: string, schemaVersion: number): TsSchemaIr;