@yydb/yydb 0.0.0 → 0.0.1

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,30 @@
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
+ TypeScript / Node **product surface** for YYDB. One dependency:
4
+
5
+ ```ts
6
+ import { Database } from "@yydb/yydb";
7
+
8
+ const db = await Database.open("app.yydb");
9
+ await db.ensureSchema(1, "table Setting { @@id: uuid, key: utf8, value: utf8 }");
10
+ await db.put("theme", "dark");
11
+ await db.close();
12
+ ```
13
+
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.
18
+
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) |
24
+
25
+ Local monorepo/dev: falls back to `target/release|debug/yydb` or `YYDB_BIN`.
26
+
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).
29
+
30
+ Security: engine binds **loopback only**. No ACL — do not expose the port.
@@ -0,0 +1,32 @@
1
+ export type { SchemaVersion } from "@yydb/yydb-client";
2
+ export interface OpenOptions {
3
+ /** Override engine binary (tests / special installs). */
4
+ binary?: string;
5
+ /** Prefer a specific loopback port; default = ephemeral free port. */
6
+ port?: number;
7
+ /** Extra args after `serve <path> --bind …`. */
8
+ serveArgs?: string[];
9
+ }
10
+ /**
11
+ * Node-facing YYDB handle. Looks like one library; owns a private loopback
12
+ * `yydb serve` child under the hood.
13
+ */
14
+ export declare class Database {
15
+ readonly path: string;
16
+ readonly endpoint: string;
17
+ private client;
18
+ private child;
19
+ private constructor();
20
+ /**
21
+ * Open (create) a `.yydb` file. Spawns the engine on `127.0.0.1` automatically.
22
+ */
23
+ static open(dbPath: string, options?: OpenOptions): Promise<Database>;
24
+ serverVersion(): Promise<string>;
25
+ info(): Promise<string>;
26
+ getSchema(): Promise<import("@yydb/yydb-client").SchemaVersion | null>;
27
+ ensureSchema(version: number, document: string): Promise<void>;
28
+ get(key: string): Promise<Uint8Array | null>;
29
+ put(key: string, value: Uint8Array | string): Promise<void>;
30
+ /** Stop the private engine process. */
31
+ close(): Promise<void>;
32
+ }
@@ -0,0 +1,139 @@
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";
7
+ async function freeLoopbackPort() {
8
+ return await new Promise((resolve, reject) => {
9
+ const server = net.createServer();
10
+ server.listen(0, "127.0.0.1", () => {
11
+ const address = server.address();
12
+ if (!address || typeof address === "string") {
13
+ server.close();
14
+ reject(new Error("could not allocate loopback port"));
15
+ return;
16
+ }
17
+ const { port } = address;
18
+ server.close((error) => {
19
+ if (error)
20
+ reject(error);
21
+ else
22
+ resolve(port);
23
+ });
24
+ });
25
+ server.on("error", reject);
26
+ });
27
+ }
28
+ async function waitForTcp(host, port, timeoutMs) {
29
+ const deadline = Date.now() + timeoutMs;
30
+ while (Date.now() < deadline) {
31
+ try {
32
+ await new Promise((resolve, reject) => {
33
+ const socket = net.connect({ host, port }, () => {
34
+ socket.end();
35
+ resolve();
36
+ });
37
+ socket.on("error", reject);
38
+ });
39
+ return;
40
+ }
41
+ catch {
42
+ await new Promise((r) => setTimeout(r, 40));
43
+ }
44
+ }
45
+ throw new Error(`yydb engine did not accept connections on ${host}:${port}`);
46
+ }
47
+ /**
48
+ * Node-facing YYDB handle. Looks like one library; owns a private loopback
49
+ * `yydb serve` child under the hood.
50
+ */
51
+ export class Database {
52
+ path;
53
+ endpoint;
54
+ client;
55
+ child;
56
+ constructor(dbPath, endpoint, client, child) {
57
+ this.path = dbPath;
58
+ this.endpoint = endpoint;
59
+ this.client = client;
60
+ this.child = child;
61
+ }
62
+ /**
63
+ * Open (create) a `.yydb` file. Spawns the engine on `127.0.0.1` automatically.
64
+ */
65
+ static async open(dbPath, options = {}) {
66
+ const resolved = path.resolve(dbPath);
67
+ fs.mkdirSync(path.dirname(resolved), { recursive: true });
68
+ const binary = options.binary ?? resolveYydbBinary();
69
+ const port = options.port ?? (await freeLoopbackPort());
70
+ 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",
74
+ windowsHide: true,
75
+ });
76
+ let stderr = "";
77
+ child.stderr?.setEncoding("utf8");
78
+ child.stderr?.on("data", (chunk) => {
79
+ stderr += chunk;
80
+ });
81
+ const exitPromise = new Promise((_, reject) => {
82
+ child.once("exit", (code, signal) => {
83
+ reject(new Error(`yydb engine exited early (code=${code}, signal=${signal}): ${stderr.trim()}`));
84
+ });
85
+ child.once("error", reject);
86
+ });
87
+ try {
88
+ await Promise.race([waitForTcp("127.0.0.1", port, 8000), exitPromise]);
89
+ const client = await connect(bind);
90
+ child.removeAllListeners("exit");
91
+ child.removeAllListeners("error");
92
+ child.on("exit", () => {
93
+ /* closed via Database.close or crash */
94
+ });
95
+ return new Database(resolved, bind, client, child);
96
+ }
97
+ catch (error) {
98
+ child.kill("SIGTERM");
99
+ throw error;
100
+ }
101
+ }
102
+ async serverVersion() {
103
+ return this.client.serverVersion();
104
+ }
105
+ async info() {
106
+ return this.client.info();
107
+ }
108
+ async getSchema() {
109
+ return this.client.getSchema();
110
+ }
111
+ async ensureSchema(version, document) {
112
+ await this.client.ensureSchema(version, document);
113
+ }
114
+ async get(key) {
115
+ return this.client.get(key);
116
+ }
117
+ async put(key, value) {
118
+ await this.client.put(key, value);
119
+ }
120
+ /** Stop the private engine process. */
121
+ async close() {
122
+ this.client.close();
123
+ const child = this.child;
124
+ this.child = null;
125
+ if (!child || child.exitCode !== null || child.killed) {
126
+ return;
127
+ }
128
+ await new Promise((resolve) => {
129
+ child.once("exit", () => resolve());
130
+ child.kill("SIGTERM");
131
+ setTimeout(() => {
132
+ if (child.exitCode === null && !child.killed) {
133
+ child.kill("SIGKILL");
134
+ }
135
+ resolve();
136
+ }, 2000);
137
+ });
138
+ }
139
+ }
@@ -0,0 +1,2 @@
1
+ export { Database, type OpenOptions } from "./database.js";
2
+ export { resolveYydbBinary, platformKey } from "./resolve-bin.js";
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { Database } from "./database.js";
2
+ export { resolveYydbBinary, platformKey } from "./resolve-bin.js";
@@ -0,0 +1,10 @@
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
+ /**
6
+ * Resolve the `yydb` engine binary without asking the app developer to
7
+ * configure downloads. Order: `YYDB_BIN` → optional platform package →
8
+ * cargo `target/{release,debug}` (monorepo).
9
+ */
10
+ export declare function resolveYydbBinary(): string;
@@ -0,0 +1,69 @@
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
+ const require = createRequire(import.meta.url);
6
+ /** Map Node platform/arch to optionalDependency package suffix. */
7
+ export function platformKey(platform = process.platform, arch = process.arch) {
8
+ if (platform === "win32" && arch === "x64")
9
+ return "win32-x64";
10
+ if (platform === "linux" && arch === "x64")
11
+ return "linux-x64";
12
+ if (platform === "darwin" && arch === "x64")
13
+ return "darwin-x64";
14
+ if (platform === "darwin" && arch === "arm64")
15
+ return "darwin-arm64";
16
+ return null;
17
+ }
18
+ function binaryName(platform = process.platform) {
19
+ return platform === "win32" ? "yydb.exe" : "yydb";
20
+ }
21
+ function existsExecutable(filePath) {
22
+ try {
23
+ fs.accessSync(filePath, fs.constants.F_OK);
24
+ return true;
25
+ }
26
+ catch {
27
+ return false;
28
+ }
29
+ }
30
+ /**
31
+ * Resolve the `yydb` engine binary without asking the app developer to
32
+ * configure downloads. Order: `YYDB_BIN` → optional platform package →
33
+ * cargo `target/{release,debug}` (monorepo).
34
+ */
35
+ export function resolveYydbBinary() {
36
+ const fromEnv = process.env.YYDB_BIN?.trim();
37
+ if (fromEnv) {
38
+ if (!existsExecutable(fromEnv)) {
39
+ throw new Error(`YYDB_BIN points to missing binary: ${fromEnv}`);
40
+ }
41
+ return path.resolve(fromEnv);
42
+ }
43
+ const key = platformKey();
44
+ if (key) {
45
+ const pkg = `@yydb/yydb-${key}`;
46
+ const name = binaryName();
47
+ try {
48
+ const pkgJson = require.resolve(`${pkg}/package.json`);
49
+ const candidate = path.join(path.dirname(pkgJson), name);
50
+ if (existsExecutable(candidate)) {
51
+ return candidate;
52
+ }
53
+ }
54
+ catch {
55
+ // optionalDependency not installed for this platform
56
+ }
57
+ }
58
+ const here = path.dirname(fileURLToPath(import.meta.url));
59
+ const repoRoot = path.resolve(here, "../../..");
60
+ for (const profile of ["release", "debug"]) {
61
+ const candidate = path.join(repoRoot, "target", profile, binaryName());
62
+ if (existsExecutable(candidate)) {
63
+ return candidate;
64
+ }
65
+ }
66
+ throw new Error("YYDB engine binary not found. Install the matching optionalDependency " +
67
+ `(@yydb/yydb-${key ?? "<platform>"}), set YYDB_BIN, or build ` +
68
+ "`cargo build -p yydb-tools` in this repo.");
69
+ }
package/package.json CHANGED
@@ -1,14 +1,58 @@
1
1
  {
2
2
  "name": "@yydb/yydb",
3
- "version": "0.0.0",
4
- "description": "Placeholder — YYDB Node product API",
3
+ "version": "0.0.1",
4
+ "description": "YYDB for TypeScript/Node — one package; engine binary managed for you",
5
5
  "license": "MIT",
6
- "private": false,
7
- "publishConfig": {
8
- "access": "public"
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsc -p tsconfig.json",
21
+ "test": "node --experimental-strip-types --test src/resolve-bin.test.ts"
9
22
  },
10
23
  "repository": {
11
24
  "type": "git",
12
- "url": "git+https://github.com/yy-database/yydb.git"
25
+ "url": "git+https://github.com/yy-database/yydb.rs.git",
26
+ "directory": "frontends/yydb"
27
+ },
28
+ "homepage": "https://github.com/yy-database/yydb.rs/tree/dev/frontends/yydb#readme",
29
+ "bugs": {
30
+ "url": "https://github.com/yy-database/yydb.rs/issues"
31
+ },
32
+ "keywords": [
33
+ "yydb",
34
+ "vos",
35
+ "database",
36
+ "embedded",
37
+ "typescript"
38
+ ],
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "dependencies": {
43
+ "@yydb/yydb-client": "0.0.1"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^20.10.0",
47
+ "typescript": "~5.3.0"
48
+ },
49
+ "engines": {
50
+ "node": ">=18"
51
+ },
52
+ "optionalDependencies": {
53
+ "@yydb/yydb-win32-x64": "0.0.1",
54
+ "@yydb/yydb-linux-x64": "0.0.1",
55
+ "@yydb/yydb-darwin-x64": "0.0.1",
56
+ "@yydb/yydb-darwin-arm64": "0.0.1"
13
57
  }
14
58
  }