@yydb/yydb 0.0.0 → 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.
package/README.md CHANGED
@@ -1,4 +1,90 @@
1
- # @yydb/yydb
1
+ # `@yydb/yydb`
2
2
 
3
- Placeholder 0.0.0 for npm Trusted Publisher bootstrap.
4
- Real releases replace this from CI (`release-npm.yml`).
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
25
+
26
+ ```ts
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
+ );
39
+
40
+ await db.put("theme", "dark");
41
+ const value = await db.get("theme");
42
+ console.log(value && new TextDecoder().decode(value));
43
+
44
+ await db.close();
45
+ ```
46
+
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.
79
+
80
+ ## Security
81
+
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.
84
+
85
+ ## Links
86
+
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
+ }
@@ -0,0 +1,50 @@
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';
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 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>;
48
+ /** Stop the private engine process. */
49
+ close(): Promise<void>;
50
+ }
@@ -0,0 +1,181 @@
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
+ }
16
+ async function freeLoopbackPort() {
17
+ return await new Promise((resolve, reject) => {
18
+ const server = net.createServer();
19
+ server.listen(0, '127.0.0.1', () => {
20
+ const address = server.address();
21
+ if (!address || typeof address === 'string') {
22
+ server.close();
23
+ reject(new Error('could not allocate loopback port'));
24
+ return;
25
+ }
26
+ const { port } = address;
27
+ server.close((error) => {
28
+ if (error)
29
+ reject(error);
30
+ else
31
+ resolve(port);
32
+ });
33
+ });
34
+ server.on('error', reject);
35
+ });
36
+ }
37
+ async function waitForTcp(host, port, timeoutMs) {
38
+ const deadline = Date.now() + timeoutMs;
39
+ while (Date.now() < deadline) {
40
+ try {
41
+ await new Promise((resolve, reject) => {
42
+ const socket = net.connect({ host, port }, () => {
43
+ socket.end();
44
+ resolve();
45
+ });
46
+ socket.on('error', reject);
47
+ });
48
+ return;
49
+ }
50
+ catch {
51
+ await new Promise((r) => setTimeout(r, 40));
52
+ }
53
+ }
54
+ throw new Error(`yydb engine did not accept connections on ${host}:${port}`);
55
+ }
56
+ /**
57
+ * Node-facing YYDB handle. Looks like one library; owns a private loopback
58
+ * `yydb serve` child under the hood.
59
+ */
60
+ export class Database {
61
+ path;
62
+ endpoint;
63
+ client;
64
+ child;
65
+ microRegistry;
66
+ constructor(dbPath, endpoint, client, child) {
67
+ this.path = dbPath;
68
+ this.endpoint = endpoint;
69
+ this.client = client;
70
+ this.child = child;
71
+ this.microRegistry = new MicroSessionRegistry();
72
+ this.client.setMicroHostHandler((payload) => this.microRegistry.invokeFromWire(payload));
73
+ }
74
+ /**
75
+ * Open (create) a `.yydb` file. Spawns the engine on `127.0.0.1` automatically.
76
+ */
77
+ static async open(dbPath, options = {}) {
78
+ const resolved = path.resolve(dbPath);
79
+ fs.mkdirSync(path.dirname(resolved), { recursive: true });
80
+ const cli = options.cli ?? options.binary ?? resolveYydbCli();
81
+ const port = options.port ?? (await freeLoopbackPort());
82
+ const bind = `127.0.0.1:${port}`;
83
+ const args = [cli, 'serve', resolved, '--bind', bind, ...(options.serveArgs ?? [])];
84
+ const child = spawn(process.execPath, args, {
85
+ stdio: 'pipe',
86
+ windowsHide: true,
87
+ });
88
+ let stderr = '';
89
+ child.stderr?.setEncoding('utf8');
90
+ child.stderr?.on('data', (chunk) => {
91
+ stderr += chunk;
92
+ });
93
+ const exitPromise = new Promise((_, reject) => {
94
+ child.once('exit', (code, signal) => {
95
+ reject(new Error(`yydb engine exited early (code=${code}, signal=${signal}): ${stderr.trim()}`));
96
+ });
97
+ child.once('error', reject);
98
+ });
99
+ try {
100
+ await Promise.race([waitForTcp('127.0.0.1', port, 8000), exitPromise]);
101
+ const client = await connect(bind);
102
+ child.removeAllListeners('exit');
103
+ child.removeAllListeners('error');
104
+ child.on('exit', () => {
105
+ /* closed via Database.close or crash */
106
+ });
107
+ return new Database(resolved, bind, client, child);
108
+ }
109
+ catch (error) {
110
+ child.kill('SIGTERM');
111
+ throw error;
112
+ }
113
+ }
114
+ async serverVersion() {
115
+ return this.client.serverVersion();
116
+ }
117
+ async info() {
118
+ return this.client.info();
119
+ }
120
+ async getSchema() {
121
+ return this.client.getSchema();
122
+ }
123
+ async ensureSchema(version, document) {
124
+ await this.client.ensureSchema(version, document);
125
+ }
126
+ async get(key) {
127
+ return this.client.get(key);
128
+ }
129
+ async put(key, value) {
130
+ await this.client.put(key, value);
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
+ }
162
+ /** Stop the private engine process. */
163
+ async close() {
164
+ this.client.close();
165
+ const child = this.child;
166
+ this.child = null;
167
+ if (!child || child.exitCode !== null || child.killed) {
168
+ return;
169
+ }
170
+ await new Promise((resolve) => {
171
+ child.once('exit', () => resolve());
172
+ child.kill('SIGTERM');
173
+ setTimeout(() => {
174
+ if (child.exitCode === null && !child.killed) {
175
+ child.kill('SIGKILL');
176
+ }
177
+ resolve();
178
+ }, 2000);
179
+ });
180
+ }
181
+ }
@@ -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;
@@ -0,0 +1,149 @@
1
+ export class VosParseError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = 'VosParseError';
5
+ }
6
+ }
7
+ /** Interim VOS subset parser for `yydb generate` (MVP). */
8
+ export function parseVosSubset(source, schemaVersion) {
9
+ if (source.includes('\0')) {
10
+ throw new VosParseError('VOS document must not contain NUL bytes');
11
+ }
12
+ let normalized = source.startsWith('\uFEFF') ? source.slice(1) : source;
13
+ normalized = normalized.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
14
+ const lexer = new Lexer(normalized);
15
+ const tables = [];
16
+ while (!lexer.eof()) {
17
+ lexer.skipWsAndComments();
18
+ if (lexer.eof()) {
19
+ break;
20
+ }
21
+ tables.push(parseTable(lexer));
22
+ }
23
+ if (tables.length === 0) {
24
+ throw new VosParseError('expected at least one `table` declaration');
25
+ }
26
+ return {
27
+ vosSource: normalized,
28
+ schemaVersion,
29
+ tables,
30
+ };
31
+ }
32
+ function parseTable(lexer) {
33
+ lexer.expectIdent('table');
34
+ const name = lexer.expectAnyIdent();
35
+ lexer.expectPunct('{');
36
+ const fields = [];
37
+ while (true) {
38
+ lexer.skipWsAndComments();
39
+ if (lexer.eatPunct('}')) {
40
+ break;
41
+ }
42
+ if (lexer.eatPunct('@')) {
43
+ lexer.expectPunct('@');
44
+ const attr = lexer.expectAnyIdent();
45
+ if (attr !== 'id') {
46
+ throw new VosParseError(`unsupported table attribute \`@@${attr}\` (MVP supports \`@@id\` only)`);
47
+ }
48
+ lexer.expectPunct(':');
49
+ const ty = parseScalar(lexer);
50
+ fields.push({ name: 'id', ty, isId: true });
51
+ }
52
+ else {
53
+ const fieldName = lexer.expectAnyIdent();
54
+ lexer.expectPunct(':');
55
+ const ty = parseScalar(lexer);
56
+ fields.push({ name: fieldName, ty, isId: false });
57
+ }
58
+ lexer.skipWsAndComments();
59
+ lexer.eatPunct(',');
60
+ }
61
+ if (!fields.some((field) => field.isId)) {
62
+ throw new VosParseError(`table \`${name}\` requires \`@@id: <type>\``);
63
+ }
64
+ return { name, fields };
65
+ }
66
+ function parseScalar(lexer) {
67
+ const name = lexer.expectAnyIdent();
68
+ switch (name) {
69
+ case 'uuid':
70
+ return 'uuid';
71
+ case 'utf8':
72
+ return 'utf8';
73
+ case 'bool':
74
+ return 'bool';
75
+ case 'i64':
76
+ return 'i64';
77
+ case 'f64':
78
+ return 'f64';
79
+ default:
80
+ throw new VosParseError(`unsupported scalar type \`${name}\` (MVP: uuid, utf8, bool, i64, f64)`);
81
+ }
82
+ }
83
+ class Lexer {
84
+ src;
85
+ i = 0;
86
+ constructor(src) {
87
+ this.src = src;
88
+ }
89
+ eof() {
90
+ return this.i >= this.src.length;
91
+ }
92
+ peek() {
93
+ return this.src[this.i];
94
+ }
95
+ bump() {
96
+ const ch = this.peek();
97
+ if (ch === undefined) {
98
+ return undefined;
99
+ }
100
+ this.i += 1;
101
+ return ch;
102
+ }
103
+ skipWsAndComments() {
104
+ while (true) {
105
+ while (this.peek() !== undefined && /\s/.test(this.peek())) {
106
+ this.bump();
107
+ }
108
+ if (this.src.startsWith('//', this.i)) {
109
+ while (this.peek() !== undefined && this.peek() !== '\n') {
110
+ this.bump();
111
+ }
112
+ continue;
113
+ }
114
+ break;
115
+ }
116
+ }
117
+ eatPunct(expected) {
118
+ this.skipWsAndComments();
119
+ if (this.peek() === expected) {
120
+ this.bump();
121
+ return true;
122
+ }
123
+ return false;
124
+ }
125
+ expectPunct(expected) {
126
+ if (!this.eatPunct(expected)) {
127
+ throw new VosParseError(`expected \`${expected}\` at byte ${this.i}`);
128
+ }
129
+ }
130
+ expectIdent(expected) {
131
+ const got = this.expectAnyIdent();
132
+ if (got !== expected) {
133
+ throw new VosParseError(`expected \`${expected}\`, found \`${got}\``);
134
+ }
135
+ }
136
+ expectAnyIdent() {
137
+ this.skipWsAndComments();
138
+ const start = this.i;
139
+ const first = this.peek();
140
+ if (first === undefined || !/[A-Za-z_]/.test(first)) {
141
+ throw new VosParseError(`expected identifier at byte ${this.i}`);
142
+ }
143
+ this.bump();
144
+ while (this.peek() !== undefined && /[A-Za-z0-9_]/.test(this.peek())) {
145
+ this.bump();
146
+ }
147
+ return this.src.slice(start, this.i);
148
+ }
149
+ }
@@ -0,0 +1,4 @@
1
+ export { Database, type OpenOptions } from './database.js';
2
+ export { defineMicro, type DefinedMicro, type MicroHandle, type TsMicroDefinitionInput, MicroSessionRegistry } from './micro.js';
3
+ export { bool, i64, nullType, text, type InferUdfArgs, type InferUdfReturn, type MicroEffect, type MicroInvocationMode, type UdfScalarKind, type UdfTypeDescriptor, } from './udf-types.js';
4
+ export { nativeBindingName, platformKey, resolveYydbBinary, resolveYydbCli } from './resolve-bin.js';
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { Database } from './database.js';
2
+ export { defineMicro, MicroSessionRegistry } from './micro.js';
3
+ export { bool, i64, nullType, text, } from './udf-types.js';
4
+ export { nativeBindingName, platformKey, resolveYydbBinary, resolveYydbCli } from './resolve-bin.js';
@@ -0,0 +1,63 @@
1
+ import type { InferUdfArgs, InferUdfReturn, MicroEffect, MicroInvocationMode, UdfTypeDescriptor } from './udf-types.js';
2
+ /** Input accepted by [`Database.defineMicro`](./database.ts). */
3
+ export interface TsMicroDefinitionInput<TArgs extends readonly UdfTypeDescriptor[], TReturn extends UdfTypeDescriptor> {
4
+ readonly name: string;
5
+ readonly version: number;
6
+ readonly args: TArgs;
7
+ readonly returns: TReturn;
8
+ readonly effect: MicroEffect;
9
+ readonly deterministic: boolean;
10
+ readonly mode?: MicroInvocationMode;
11
+ readonly fn: (...args: InferUdfArgs<TArgs>) => InferUdfReturn<TReturn>;
12
+ }
13
+ /** Opaque definition produced by `defineMicro` before session registration. */
14
+ export interface DefinedMicro<TArgs extends readonly UdfTypeDescriptor[] = readonly UdfTypeDescriptor[], TReturn extends UdfTypeDescriptor = UdfTypeDescriptor> {
15
+ readonly name: string;
16
+ readonly version: number;
17
+ readonly args: TArgs;
18
+ readonly returns: TReturn;
19
+ readonly effect: MicroEffect;
20
+ readonly deterministic: boolean;
21
+ readonly mode: MicroInvocationMode;
22
+ readonly placement: 'host';
23
+ readonly fn: (...args: InferUdfArgs<TArgs>) => InferUdfReturn<TReturn>;
24
+ }
25
+ /** Handle returned after `registerMicro` installs a micro into the session. */
26
+ export interface MicroHandle {
27
+ readonly name: string;
28
+ readonly version: number;
29
+ readonly functionId: string;
30
+ readonly hostId: number;
31
+ /** Monotonic host implementation version for opaque handle checks. */
32
+ readonly implementationVersion: number;
33
+ }
34
+ type AnyDefinedMicro = DefinedMicro<readonly UdfTypeDescriptor[], UdfTypeDescriptor>;
35
+ /**
36
+ * Process-local registry for JS functions behind opaque host handles.
37
+ * Rust keeps `host_id + function_id + version`, not the closure.
38
+ */
39
+ export declare class MicroSessionRegistry {
40
+ readonly hostId: number;
41
+ private nextFunctionId;
42
+ private readonly byKey;
43
+ private readonly byFunctionId;
44
+ constructor(hostId?: number);
45
+ /** Installs a defined micro into the current session registry. */
46
+ register<TArgs extends readonly UdfTypeDescriptor[], TReturn extends UdfTypeDescriptor>(definition: DefinedMicro<TArgs, TReturn>): MicroHandle;
47
+ /** Returns a registered micro by opaque host handle. */
48
+ resolve(handle: MicroHandle): AnyDefinedMicro | undefined;
49
+ /** Invokes a registered micro from a wire `MicroHostInvoke` payload. */
50
+ invokeFromWire(payload: {
51
+ hostId: number;
52
+ handleVersion: number;
53
+ functionId: string;
54
+ args: readonly unknown[];
55
+ }): string | number | boolean | null;
56
+ /** Invokes a registered scalar micro (host adapter entry point). */
57
+ invokeScalar(handle: MicroHandle, args: unknown[]): unknown;
58
+ /** Lists logical names currently registered in this session. */
59
+ list(): string[];
60
+ }
61
+ /** Builds a session-local micro definition without registering it. */
62
+ export declare function defineMicro<TArgs extends readonly UdfTypeDescriptor[], TReturn extends UdfTypeDescriptor>(input: TsMicroDefinitionInput<TArgs, TReturn>): DefinedMicro<TArgs, TReturn>;
63
+ export {};
package/dist/micro.js ADDED
@@ -0,0 +1,160 @@
1
+ let nextHostId = 1;
2
+ /**
3
+ * Process-local registry for JS functions behind opaque host handles.
4
+ * Rust keeps `host_id + function_id + version`, not the closure.
5
+ */
6
+ export class MicroSessionRegistry {
7
+ hostId;
8
+ nextFunctionId = 1;
9
+ byKey = new Map();
10
+ byFunctionId = new Map();
11
+ constructor(hostId = nextHostId++) {
12
+ this.hostId = hostId;
13
+ }
14
+ /** Installs a defined micro into the current session registry. */
15
+ register(definition) {
16
+ const key = logicalKey(definition.name, definition.version);
17
+ if (this.byKey.has(key)) {
18
+ throw new Error(`micro already registered: ${definition.name}@${definition.version}`);
19
+ }
20
+ const functionId = String(this.nextFunctionId++);
21
+ const handle = {
22
+ name: definition.name,
23
+ version: definition.version,
24
+ functionId,
25
+ hostId: this.hostId,
26
+ implementationVersion: 1,
27
+ };
28
+ const entry = { handle, definition: definition };
29
+ this.byKey.set(key, entry);
30
+ this.byFunctionId.set(functionId, entry);
31
+ return handle;
32
+ }
33
+ /** Returns a registered micro by opaque host handle. */
34
+ resolve(handle) {
35
+ if (handle.hostId !== this.hostId) {
36
+ return undefined;
37
+ }
38
+ return this.byFunctionId.get(handle.functionId)?.definition;
39
+ }
40
+ /** Invokes a registered micro from a wire `MicroHostInvoke` payload. */
41
+ invokeFromWire(payload) {
42
+ const handle = {
43
+ hostId: payload.hostId,
44
+ functionId: payload.functionId,
45
+ implementationVersion: payload.handleVersion,
46
+ name: '',
47
+ version: 0,
48
+ };
49
+ return this.invokeScalar(handle, [...payload.args]);
50
+ }
51
+ /** Invokes a registered scalar micro (host adapter entry point). */
52
+ invokeScalar(handle, args) {
53
+ const definition = this.resolve(handle);
54
+ if (!definition) {
55
+ throw new Error(`micro not found: ${handle.functionId}`);
56
+ }
57
+ if (definition.mode !== 'scalar') {
58
+ throw new Error(`micro ${definition.name} is not scalar mode`);
59
+ }
60
+ if (args.length !== definition.args.length) {
61
+ throw new Error(`micro ${definition.name} arity mismatch: expected ${definition.args.length}, got ${args.length}`);
62
+ }
63
+ validateArgTypes(definition, args);
64
+ const result = definition.fn(...args);
65
+ validateReturnType(definition.returns, result);
66
+ return result;
67
+ }
68
+ /** Lists logical names currently registered in this session. */
69
+ list() {
70
+ return [...this.byKey.values()].map((entry) => entry.definition.name).sort();
71
+ }
72
+ }
73
+ /** Builds a session-local micro definition without registering it. */
74
+ export function defineMicro(input) {
75
+ if (!input.name.trim()) {
76
+ throw new Error('micro name must not be empty');
77
+ }
78
+ if (!Number.isInteger(input.version) || input.version <= 0) {
79
+ throw new Error('micro version must be a positive integer');
80
+ }
81
+ if (input.effect !== 'pure') {
82
+ throw new Error('TS micro effect must be pure in Phase 1');
83
+ }
84
+ if (!input.deterministic) {
85
+ throw new Error('TS micro must be deterministic in Phase 1');
86
+ }
87
+ if (typeof input.fn !== 'function') {
88
+ throw new Error('micro fn must be a function');
89
+ }
90
+ return {
91
+ name: input.name,
92
+ version: input.version,
93
+ args: input.args,
94
+ returns: input.returns,
95
+ effect: input.effect,
96
+ deterministic: input.deterministic,
97
+ mode: input.mode ?? 'scalar',
98
+ placement: 'host',
99
+ fn: input.fn,
100
+ };
101
+ }
102
+ function logicalKey(name, version) {
103
+ return `${name}@${version}`;
104
+ }
105
+ function validateArgTypes(definition, args) {
106
+ for (let index = 0; index < definition.args.length; index += 1) {
107
+ const kind = definition.args[index]?.kind;
108
+ const value = args[index];
109
+ switch (kind) {
110
+ case 'i64':
111
+ if (typeof value !== 'number' || !Number.isInteger(value)) {
112
+ throw new Error(`micro ${definition.name} arg ${index} expects i64`);
113
+ }
114
+ break;
115
+ case 'bool':
116
+ if (typeof value !== 'boolean') {
117
+ throw new Error(`micro ${definition.name} arg ${index} expects bool`);
118
+ }
119
+ break;
120
+ case 'text':
121
+ if (typeof value !== 'string') {
122
+ throw new Error(`micro ${definition.name} arg ${index} expects text`);
123
+ }
124
+ break;
125
+ case 'null':
126
+ if (value !== null) {
127
+ throw new Error(`micro ${definition.name} arg ${index} expects null`);
128
+ }
129
+ break;
130
+ default:
131
+ throw new Error(`unsupported micro arg kind: ${kind}`);
132
+ }
133
+ }
134
+ }
135
+ function validateReturnType(descriptor, value) {
136
+ switch (descriptor.kind) {
137
+ case 'i64':
138
+ if (typeof value !== 'number' || !Number.isInteger(value)) {
139
+ throw new Error('micro return expects i64');
140
+ }
141
+ break;
142
+ case 'bool':
143
+ if (typeof value !== 'boolean') {
144
+ throw new Error('micro return expects bool');
145
+ }
146
+ break;
147
+ case 'text':
148
+ if (typeof value !== 'string') {
149
+ throw new Error('micro return expects text');
150
+ }
151
+ break;
152
+ case 'null':
153
+ if (value !== null) {
154
+ throw new Error('micro return expects null');
155
+ }
156
+ break;
157
+ default:
158
+ throw new Error(`unsupported micro return kind: ${descriptor.kind}`);
159
+ }
160
+ }
@@ -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;
package/dist/native.js ADDED
@@ -0,0 +1,66 @@
1
+ import { createRequire } from 'node:module';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { nativeBindingName, platformKey } from './resolve-bin.js';
6
+ function existsFile(filePath) {
7
+ try {
8
+ fs.accessSync(filePath, fs.constants.F_OK);
9
+ return true;
10
+ }
11
+ catch {
12
+ return false;
13
+ }
14
+ }
15
+ function loadFromPath(bindingPath) {
16
+ const require = createRequire(import.meta.url);
17
+ return require(bindingPath);
18
+ }
19
+ function monorepoBindingPath(key) {
20
+ const here = path.dirname(fileURLToPath(import.meta.url));
21
+ const repoRoot = path.resolve(here, '../../..');
22
+ const profile = process.env.YYDB_NATIVE_PROFILE === 'debug' ? 'debug' : 'release';
23
+ const base = path.join(repoRoot, 'target', profile);
24
+ const candidates = process.platform === 'win32'
25
+ ? [path.join(base, 'yydb_napi.dll'), path.join(base, 'yydb_napi.node')]
26
+ : [path.join(base, 'libyydb_napi.so'), path.join(base, 'libyydb_napi.dylib'), path.join(base, 'yydb_napi.node')];
27
+ for (const candidate of candidates) {
28
+ if (existsFile(candidate)) {
29
+ return candidate;
30
+ }
31
+ }
32
+ const staged = path.join(repoRoot, 'projects', 'packages', `yydb-${key}`, nativeBindingName(key));
33
+ return existsFile(staged) ? staged : null;
34
+ }
35
+ /** Load the platform native binding used by the `yydb` CLI. */
36
+ export function loadNative() {
37
+ const fromEnv = process.env.YYDB_NATIVE?.trim();
38
+ if (fromEnv) {
39
+ if (!existsFile(fromEnv)) {
40
+ throw new Error(`YYDB_NATIVE points to missing binding: ${fromEnv}`);
41
+ }
42
+ return loadFromPath(path.resolve(fromEnv));
43
+ }
44
+ const key = platformKey();
45
+ if (key) {
46
+ const pkg = `@yydb/yydb-${key}`;
47
+ const require = createRequire(import.meta.url);
48
+ try {
49
+ const pkgJson = require.resolve(`${pkg}/package.json`);
50
+ const candidate = path.join(path.dirname(pkgJson), nativeBindingName(key));
51
+ if (existsFile(candidate)) {
52
+ return loadFromPath(candidate);
53
+ }
54
+ }
55
+ catch {
56
+ // optionalDependency not installed for this platform
57
+ }
58
+ const local = monorepoBindingPath(key);
59
+ if (local) {
60
+ return loadFromPath(local);
61
+ }
62
+ }
63
+ throw new Error('YYDB native binding not found. Install the matching optionalDependency ' +
64
+ `(@yydb/yydb-${key ?? '<platform>'}), set YYDB_NATIVE, or build ` +
65
+ '`cargo build -p yydb-napi --release` in this repo.');
66
+ }
@@ -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. */
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,53 @@
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. */
17
+ export function nativeBindingName(key) {
18
+ return `yydb.${key}.node`;
19
+ }
20
+ function existsFile(filePath) {
21
+ try {
22
+ fs.accessSync(filePath, fs.constants.F_OK);
23
+ return true;
24
+ }
25
+ catch {
26
+ return false;
27
+ }
28
+ }
29
+ /**
30
+ * Resolve the TypeScript `yydb` CLI entry script.
31
+ * Order: `YYDB_CLI` → this package `dist/cli.js`.
32
+ */
33
+ export function resolveYydbCli() {
34
+ const fromEnv = process.env.YYDB_CLI?.trim();
35
+ if (fromEnv) {
36
+ if (!existsFile(fromEnv)) {
37
+ throw new Error(`YYDB_CLI points to missing script: ${fromEnv}`);
38
+ }
39
+ return path.resolve(fromEnv);
40
+ }
41
+ const here = path.dirname(fileURLToPath(import.meta.url));
42
+ const candidate = path.join(here, 'cli.js');
43
+ if (!existsFile(candidate)) {
44
+ throw new Error('YYDB CLI script not found. Build `@yydb/yydb` (`pnpm --filter @yydb/yydb build`) or set YYDB_CLI.');
45
+ }
46
+ return candidate;
47
+ }
48
+ /**
49
+ * @deprecated Use {@link resolveYydbCli}. Kept for callers that still import this name.
50
+ */
51
+ export function resolveYydbBinary() {
52
+ return resolveYydbCli();
53
+ }
@@ -0,0 +1,26 @@
1
+ /** Phase-1 scalar UDF wire kinds aligned with `yydb-udf`. */
2
+ export type UdfScalarKind = 'i64' | 'bool' | 'text' | 'null';
3
+ /** Runtime type descriptor used in `defineMicro` contracts. */
4
+ export interface UdfTypeDescriptor<T = unknown> {
5
+ readonly kind: UdfScalarKind;
6
+ /** Phantom type for author-side inference only. */
7
+ readonly _type?: T;
8
+ }
9
+ /** `i64` argument or return descriptor. */
10
+ export declare function i64(): UdfTypeDescriptor<number>;
11
+ /** `bool` argument or return descriptor. */
12
+ export declare function bool(): UdfTypeDescriptor<boolean>;
13
+ /** `text` argument or return descriptor. */
14
+ export declare function text(): UdfTypeDescriptor<string>;
15
+ /** `null` return descriptor. */
16
+ export declare function nullType(): UdfTypeDescriptor<null>;
17
+ /** Maps descriptor tuple types to positional argument types. */
18
+ export type InferUdfArgs<T extends readonly UdfTypeDescriptor[]> = {
19
+ [K in keyof T]: T[K] extends UdfTypeDescriptor<infer V> ? V : never;
20
+ };
21
+ /** Maps a return descriptor to its value type. */
22
+ export type InferUdfReturn<T extends UdfTypeDescriptor> = T extends UdfTypeDescriptor<infer V> ? V : never;
23
+ /** Session micro effect surface (Phase 1: pure only). */
24
+ export type MicroEffect = 'pure';
25
+ /** Invocation mode stored with the definition for future batch host calls. */
26
+ export type MicroInvocationMode = 'scalar' | 'batch';
@@ -0,0 +1,16 @@
1
+ /** `i64` argument or return descriptor. */
2
+ export function i64() {
3
+ return { kind: 'i64' };
4
+ }
5
+ /** `bool` argument or return descriptor. */
6
+ export function bool() {
7
+ return { kind: 'bool' };
8
+ }
9
+ /** `text` argument or return descriptor. */
10
+ export function text() {
11
+ return { kind: 'text' };
12
+ }
13
+ /** `null` return descriptor. */
14
+ export function nullType() {
15
+ return { kind: 'null' };
16
+ }
package/package.json CHANGED
@@ -1,14 +1,70 @@
1
1
  {
2
2
  "name": "@yydb/yydb",
3
- "version": "0.0.0",
4
- "description": "Placeholder — YYDB Node product API",
5
- "license": "MIT",
6
- "private": false,
7
- "publishConfig": {
8
- "access": "public"
3
+ "version": "0.0.2",
4
+ "description": "YYDB all-in-one for Node: embedded engine, CLI, and local serve",
5
+ "license": "MPL-2.0",
6
+ "type": "module",
7
+ "bin": {
8
+ "yydb": "./dist/cli.js"
9
+ },
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js"
16
+ },
17
+ "./cli": {
18
+ "types": "./dist/cli.d.ts",
19
+ "import": "./dist/cli.js"
20
+ },
21
+ "./generator": {
22
+ "types": "./dist/generator/index.d.ts",
23
+ "import": "./dist/generator/index.js"
24
+ }
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "README.md"
29
+ ],
30
+ "scripts": {
31
+ "build": "tsc -p tsconfig.json",
32
+ "test": "pnpm run build && node --experimental-strip-types --test src/**/*.test.ts"
9
33
  },
10
34
  "repository": {
11
35
  "type": "git",
12
- "url": "git+https://github.com/yy-database/yydb.git"
36
+ "url": "git+https://github.com/yy-database/yydb.git",
37
+ "directory": "projects/packages/yydb"
38
+ },
39
+ "homepage": "https://github.com/yy-database/yydb/tree/dev/projects/packages/yydb#readme",
40
+ "bugs": {
41
+ "url": "https://github.com/yy-database/yydb/issues"
42
+ },
43
+ "keywords": [
44
+ "yydb",
45
+ "vos",
46
+ "database",
47
+ "embedded",
48
+ "typescript"
49
+ ],
50
+ "publishConfig": {
51
+ "access": "public"
52
+ },
53
+ "dependencies": {
54
+ "@vmz/commander": "^0.2.0",
55
+ "@yydb/yydb-client": "0.0.2"
56
+ },
57
+ "devDependencies": {
58
+ "@types/node": "^20.10.0",
59
+ "typescript": "~5.3.0"
60
+ },
61
+ "engines": {
62
+ "node": ">=18"
63
+ },
64
+ "optionalDependencies": {
65
+ "@yydb/yydb-win32-x64": "0.0.2",
66
+ "@yydb/yydb-linux-x64": "0.0.2",
67
+ "@yydb/yydb-darwin-x64": "0.0.2",
68
+ "@yydb/yydb-darwin-arm64": "0.0.2"
13
69
  }
14
70
  }