@yydb/sqlite 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.
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from "node:fs";
3
+
4
+ import { SqliteDatabase, SqliteSnapshot } from "../src/node.ts";
5
+
6
+ const FORMAT_VERSION = "3.45.0";
7
+
8
+ function runDotCommand(database, command) {
9
+ if (command === ".tables") {
10
+ const tables = database
11
+ .schema()
12
+ .filter((object) => (object.kind === "table" || object.kind === "view") && !object.name.startsWith("sqlite_"))
13
+ .map((object) => object.name)
14
+ .sort();
15
+ for (const name of tables) console.log(name);
16
+ return;
17
+ }
18
+ if (command === ".schema") {
19
+ const statements = database
20
+ .schema()
21
+ .filter((object) => object.sql && !object.name.startsWith("sqlite_"))
22
+ .map((object) => object.sql?.trimEnd() ?? "");
23
+ for (const source of statements) {
24
+ console.log(source.endsWith(";") ? source : `${source};`);
25
+ }
26
+ return;
27
+ }
28
+ if (command === ".ping") {
29
+ const status = database.ping();
30
+ console.log(`ok ${status}`);
31
+ return;
32
+ }
33
+ if (command === ".encoding") {
34
+ console.log(database.encoding());
35
+ return;
36
+ }
37
+ if (command === ".version") {
38
+ console.log(database.sqliteVersion());
39
+ return;
40
+ }
41
+ const dump = command.match(/^\.dump\s+(\S+)\s*$/);
42
+ if (dump) {
43
+ const table = dump[1];
44
+ const rows = database.table(table);
45
+ if (rows.length === 0) {
46
+ return;
47
+ }
48
+ const width = new Array(rows[0].columns.length).fill(0);
49
+ for (const row of rows) {
50
+ for (let i = 0; i < row.columns.length; i++) {
51
+ const text = formatValue(row.columns[i]);
52
+ width[i] = Math.max(width[i], text.length);
53
+ }
54
+ }
55
+ const line = (cells) => cells.map((c, i) => c.padEnd(width[i])).join(" | ");
56
+ for (const row of rows) {
57
+ console.log(
58
+ line([
59
+ String(row.rowid),
60
+ ...row.columns.map(formatValue),
61
+ ]),
62
+ );
63
+ }
64
+ return;
65
+ }
66
+ throw new Error(`unsupported dot command '${command}'`);
67
+ }
68
+
69
+ function formatValue(value) {
70
+ if (value.kind === "null") return "NULL";
71
+ if (value.kind === "integer") return String(value.valueInteger ?? "");
72
+ if (value.kind === "real") {
73
+ const n = value.valueReal;
74
+ return n === undefined ? "" : (Number.isInteger(n) ? `${n}.0` : String(n));
75
+ }
76
+ if (value.kind === "text") return value.valueText ?? "";
77
+ if (value.kind === "blob") {
78
+ const buf = value.valueBlob;
79
+ if (!buf) return "";
80
+ let hex = "";
81
+ for (let i = 0; i < Math.min(buf.length, 64); i++) {
82
+ hex += buf[i].toString(16).padStart(2, "0");
83
+ }
84
+ return `X'${hex}${buf.length > 64 ? "…" : ""}'`;
85
+ }
86
+ return "";
87
+ }
88
+
89
+ function main(args) {
90
+ if (args.length === 1 && ["--version", "-version"].includes(args[0])) {
91
+ console.log(`sqlite3 ${FORMAT_VERSION} (YYDS pure-Rust format-3 reader)`);
92
+ return;
93
+ }
94
+ if (args.length === 1 && ["--help", "-help"].includes(args[0])) {
95
+ console.log(
96
+ "Usage: sqlite3 DATABASE COMMAND\n" +
97
+ "Supported dot commands: .tables, .schema, .ping, .encoding, .version, .dump TABLE\n" +
98
+ "This CLI inspects on-disk main-file bytes only; SQL execution is not available.",
99
+ );
100
+ return;
101
+ }
102
+ if (args.length !== 2 || args[0].startsWith("-") || args[0] === "") {
103
+ throw new Error("expected DATABASE and one supported dot command");
104
+ }
105
+ const [path, input] = args;
106
+ const command = input.trim();
107
+ if (!command.startsWith(".")) {
108
+ throw new Error("SQL execution is not available; use a dot command");
109
+ }
110
+ const database = SqliteDatabase.open(path);
111
+ runDotCommand(database, command);
112
+ }
113
+
114
+ try {
115
+ main(process.argv.slice(2));
116
+ } catch (error) {
117
+ console.error(`@yydb/sqlite: ${error.message}`);
118
+ process.exitCode = 1;
119
+ }
package/package.json CHANGED
@@ -1,46 +1,60 @@
1
1
  {
2
2
  "name": "@yydb/sqlite",
3
- "version": "0.0.1",
4
- "description": "SQLite driver for SQL Studio / sql-studio-orm — 0.0.1 Developer Preview",
5
- "type": "module",
3
+ "version": "0.0.2",
4
+ "description": "Bounded read-only SQLite format-3 snapshot reader, independent of YYDS storage.",
6
5
  "license": "MPL-2.0",
6
+ "type": "module",
7
7
  "sideEffects": false,
8
- "main": "./src/index.ts",
9
- "types": "./src/index.ts",
10
8
  "exports": {
11
9
  ".": {
12
10
  "types": "./src/index.ts",
13
11
  "import": "./src/index.ts",
14
12
  "default": "./src/index.ts"
15
13
  },
16
- "./wasm": {
17
- "types": "./src/wasm.ts",
18
- "import": "./src/wasm.ts",
19
- "default": "./src/wasm.ts"
20
- },
21
14
  "./node": {
22
15
  "types": "./src/node.ts",
23
16
  "import": "./src/node.ts",
24
- "node": "./src/node.ts",
25
17
  "default": "./src/node.ts"
26
18
  }
27
19
  },
20
+ "bin": {
21
+ "sqlite3": "./bin/sqlite3.mjs"
22
+ },
28
23
  "files": [
24
+ "bin",
29
25
  "src",
30
- "README.md"
26
+ "readme.md"
31
27
  ],
32
28
  "scripts": {
33
- "typecheck": "tsc --noEmit -p tsconfig.json"
29
+ "typecheck": "tsc --noEmit -p tsconfig.json",
30
+ "test": "node --experimental-strip-types --test \"./tests/**/*.test.ts\""
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/yy-database/yyds.git",
35
+ "directory": "projects/packages/sqlite"
36
+ },
37
+ "homepage": "https://github.com/yy-database/yyds/tree/dev/projects/packages/sqlite#readme",
38
+ "bugs": {
39
+ "url": "https://github.com/yy-database/yyds/issues"
40
+ },
41
+ "keywords": [
42
+ "yyds",
43
+ "sqlite",
44
+ "disguise",
45
+ "binary-compatible"
46
+ ],
47
+ "publishConfig": {
48
+ "access": "public"
34
49
  },
35
50
  "dependencies": {
36
- "@yydb/sql-studio-orm": "0.0.1",
37
- "@yydb/sql-studio-protocol": "0.0.1"
51
+ "@yydb/yyds": "0.0.2"
38
52
  },
39
53
  "devDependencies": {
54
+ "@types/node": "^22.15.0",
40
55
  "typescript": "^5.9.2"
41
56
  },
42
- "publishConfig": {
43
- "access": "public",
44
- "tag": "dev"
57
+ "engines": {
58
+ "node": ">=18"
45
59
  }
46
60
  }
package/readme.md ADDED
@@ -0,0 +1,31 @@
1
+ # @yydb/sqlite
2
+
3
+ Read-only inspection of SQLite format-3 main files. Copies bytes into memory, lists schema objects, and reads raw table records. Does not execute SQL, open a live database, or recover WAL sidecars.
4
+
5
+ SQLite files are not YYDS storage. They are not routed through `.yyds` catalogs or `.yykv` shards.
6
+
7
+ ## Exports
8
+
9
+ | Subpath | Use |
10
+ |---------|-----|
11
+ | `@yydb/sqlite` | Types only |
12
+ | `@yydb/sqlite/node` | `SqliteSnapshot` (requires the `@yydb/yyds` native addon) |
13
+
14
+ The `sqlite3` command supports `.tables` and `.schema` on a file path. It does not run SQL statements.
15
+
16
+ ## Example
17
+
18
+ ```ts
19
+ import { readFileSync } from "node:fs";
20
+ import { SqliteSnapshot } from "@yydb/sqlite/node";
21
+
22
+ const snapshot = new SqliteSnapshot(readFileSync("app.sqlite"));
23
+ const tables = snapshot.schema().filter((o) => o.kind === "table");
24
+ const rows = snapshot.table("samples", { maxRows: 100 });
25
+ ```
26
+
27
+ Each row is `{ rowid: bigint, payload: Buffer }` — storage bytes, not SQL-projected values. Schema `sql` fields are opaque CREATE text. The default snapshot size cap is 256 MiB; each scan accepts `maxPages`, `maxRows`, `maxPayloadBytes`, and `maxTotalPayloadBytes`.
28
+
29
+ ## Related packages
30
+
31
+ - [`@yydb/yyds`](https://github.com/yy-database/yyds/tree/dev/projects/packages/yyds) — hosts the native `SqliteSnapshot` binding
@@ -0,0 +1,3 @@
1
+ /** Why this package does not route through YY-optimized storage. */
2
+ export const WHY_NOT_YY_OPTIMIZED =
3
+ "SQLite requires binary-compatible on-disk bytes. YYDS stores VOS in .yyds and .yykv instead.";
package/src/index.ts CHANGED
@@ -1,69 +1,12 @@
1
- /**
2
- * `@yydb/sqlite` — SQLite SqlDriver + Studio registration.
3
- * Use `./wasm` or `./node` for concrete backends.
4
- */
5
-
6
- import type {
7
- CompiledQuery,
8
- ExecuteOptions,
9
- QueryChunk,
10
- QueryResult,
11
- SqlConnection,
12
- SqlDriver,
13
- SqlTransaction,
14
- StreamOptions,
15
- TransactionOptions,
16
- } from "@yydb/sql-studio-orm";
17
- import type { StudioDriverRegistration } from "@yydb/sql-studio-protocol";
18
-
19
- export const DRIVER_ID = "sqlite" as const;
20
-
21
- export type SqliteDriverOptions = {
22
- path: string;
23
- };
24
-
25
- export type SqliteStudioOptions = SqliteDriverOptions & {
26
- id: string;
27
- };
28
-
29
- class NotImplementedConnection implements SqlConnection {
30
- async execute<R>(_query: CompiledQuery, _options?: ExecuteOptions): Promise<QueryResult<R>> {
31
- throw new Error("@yydb/sqlite: execute not implemented yet");
32
- }
33
-
34
- stream<R>(_query: CompiledQuery, _options?: StreamOptions): AsyncIterable<QueryChunk<R>> {
35
- throw new Error("@yydb/sqlite: stream not implemented yet");
36
- }
37
-
38
- async begin(_options?: TransactionOptions): Promise<SqlTransaction> {
39
- throw new Error("@yydb/sqlite: transactions not implemented yet");
40
- }
41
- }
42
-
43
- class SqliteDriver implements SqlDriver {
44
- readonly dialect = "sqlite" as const;
45
- readonly path: string;
46
-
47
- constructor(options: SqliteDriverOptions) {
48
- this.path = options.path;
49
- }
50
-
51
- async acquire(): Promise<SqlConnection> {
52
- return new NotImplementedConnection();
53
- }
54
-
55
- async destroy(): Promise<void> {}
56
- }
57
-
58
- export function sqlite(options: SqliteDriverOptions): SqlDriver;
59
- export function sqlite(options: SqliteStudioOptions): StudioDriverRegistration;
60
- export function sqlite(options: SqliteDriverOptions | SqliteStudioOptions): SqlDriver | StudioDriverRegistration {
61
- if ("id" in options && options.id) {
62
- return {
63
- id: options.id,
64
- kind: "sqlite",
65
- driver: new SqliteDriver(options),
66
- };
67
- }
68
- return new SqliteDriver(options);
69
- }
1
+ export type {
2
+ SqliteDatabaseBinding,
3
+ SqliteDecodedRow,
4
+ SqliteReadLimits,
5
+ SqliteRecordValue,
6
+ SqliteSchemaObject,
7
+ SqliteSnapshotBinding,
8
+ SqliteTableRow,
9
+ SqliteTextEncoding,
10
+ } from "./node.ts";
11
+
12
+ export { SqliteSnapshot, SqliteDatabase, decodeRecord } from "./node.ts";
package/src/node.ts CHANGED
@@ -1,7 +1,195 @@
1
+ import { loadYydsSqliteNative } from "@yydb/yyds/node";
2
+ import type {
3
+ SqliteDatabaseBinding,
4
+ SqliteDecodedRow,
5
+ SqliteReadLimits,
6
+ SqliteRecordValue,
7
+ SqliteSchemaObject,
8
+ SqliteSnapshotBinding,
9
+ SqliteTableRow,
10
+ } from "@yydb/yyds/node";
11
+
12
+ export type {
13
+ SqliteDatabaseBinding,
14
+ SqliteDecodedRow,
15
+ SqliteReadLimits,
16
+ SqliteRecordValue,
17
+ SqliteSchemaObject,
18
+ SqliteSnapshotBinding,
19
+ SqliteTableRow,
20
+ } from "@yydb/yyds/node";
21
+
22
+ /** Text encodings recognised by the SQLite header. */
23
+ export type SqliteTextEncoding = "utf8" | "utf16le" | "utf16be";
24
+
25
+ /** Read-only, copied main-file bytes. WAL/journal recovery is not performed. */
26
+ export class SqliteSnapshot {
27
+ readonly #native: SqliteSnapshotBinding;
28
+
29
+ constructor(bytes: Uint8Array, maxSnapshotBytes = 256 * 1024 * 1024) {
30
+ unsignedLimit(maxSnapshotBytes, "maxSnapshotBytes");
31
+ if (!(bytes instanceof Uint8Array)) {
32
+ throw new TypeError("SQLite snapshot must be a Uint8Array or Buffer");
33
+ }
34
+ if (bytes.byteLength > maxSnapshotBytes) {
35
+ throw new RangeError("sqlite snapshot byte limit exceeded");
36
+ }
37
+ const native = loadYydsSqliteNative();
38
+ this.#native = new native.SqliteSnapshot(Buffer.from(bytes), maxSnapshotBytes);
39
+ }
40
+
41
+ schema(limits?: SqliteReadLimits): SqliteSchemaObject[] {
42
+ return this.#native.schema(validateLimits(limits));
43
+ }
44
+
45
+ table(name: string, limits?: SqliteReadLimits): SqliteTableRow[] {
46
+ if (typeof name !== "string") {
47
+ throw new TypeError("SQLite table name must be a string");
48
+ }
49
+ return this.#native.table(name, validateLimits(limits));
50
+ }
51
+
52
+ /** Reads rows with decoded storage-class values. */
53
+ tableDecoded(name: string, limits?: SqliteReadLimits): SqliteDecodedRow[] {
54
+ if (typeof name !== "string") {
55
+ throw new TypeError("SQLite table name must be a string");
56
+ }
57
+ return this.#native.tableDecoded(name, validateLimits(limits));
58
+ }
59
+
60
+ /** SQLite library version stamped in the snapshot header. */
61
+ sqliteVersion(): string {
62
+ return this.#native.sqliteVersion();
63
+ }
64
+
65
+ /** Returns the header-claimed text encoding. */
66
+ encoding(): SqliteTextEncoding {
67
+ const name = this.#native.encoding();
68
+ if (name === "utf16le" || name === "utf16be" || name === "utf8") return name;
69
+ throw new Error(`Unexpected SQLite encoding label: ${name}`);
70
+ }
71
+
72
+ /** Health probe re-running the full main-file validation. Returns 1 on success. */
73
+ ping(): number {
74
+ return this.#native.ping();
75
+ }
76
+
77
+ /** Raw main-file bytes, including the page-size header. */
78
+ pages(): Buffer {
79
+ return this.#native.pages();
80
+ }
81
+ }
82
+
1
83
  /**
2
- * Node / Bun / Deno file SQLite surface.
84
+ * File-backed or blank in-memory SQLite snapshot handle.
85
+ *
86
+ * No WAL or journal recovery is performed and no SQL engine is present.
87
+ * Use this when the path and header meta matter beyond raw main-file bytes.
3
88
  */
89
+ export class SqliteDatabase {
90
+ readonly #native: SqliteDatabaseBinding;
91
+
92
+ private constructor(native: SqliteDatabaseBinding) {
93
+ this.#native = native;
94
+ }
95
+
96
+ /** Opens a blank in-memory SQLite format-3 snapshot. */
97
+ static openInMemory(): SqliteDatabase {
98
+ const native = loadYydsSqliteNative();
99
+ return new SqliteDatabase(native.SqliteDatabase.openInMemory());
100
+ }
101
+
102
+ /** Opens or creates a file-backed SQLite snapshot (main-file only). */
103
+ static open(path: string): SqliteDatabase {
104
+ if (typeof path !== "string" || path.length === 0) {
105
+ throw new TypeError("SQLite path must be a non-empty string");
106
+ }
107
+ const native = loadYydsSqliteNative();
108
+ return new SqliteDatabase(native.SqliteDatabase.open(path));
109
+ }
110
+
111
+ /** File path when file-backed, `null` for in-memory. */
112
+ path(): string | null {
113
+ return this.#native.path();
114
+ }
115
+
116
+ /** Health probe re-running the full main-file validation. Returns 1 on success. */
117
+ ping(): number {
118
+ return this.#native.ping();
119
+ }
120
+
121
+ /** SQLite library version stamped in the snapshot header. */
122
+ sqliteVersion(): string {
123
+ return this.#native.sqliteVersion();
124
+ }
125
+
126
+ /** Returns the header-claimed text encoding. */
127
+ encoding(): SqliteTextEncoding {
128
+ const name = this.#native.encoding();
129
+ if (name === "utf16le" || name === "utf16be" || name === "utf8") return name;
130
+ throw new Error(`Unexpected SQLite encoding label: ${name}`);
131
+ }
132
+
133
+ schema(limits?: SqliteReadLimits): SqliteSchemaObject[] {
134
+ return this.#native.schema(validateLimits(limits));
135
+ }
136
+
137
+ /** Reads rows with decoded storage-class values from an ordinary rowid table. */
138
+ table(name: string, limits?: SqliteReadLimits): SqliteDecodedRow[] {
139
+ if (typeof name !== "string") {
140
+ throw new TypeError("SQLite table name must be a string");
141
+ }
142
+ return this.#native.table(name, validateLimits(limits));
143
+ }
144
+
145
+ /** Reads raw rows (undecoded payload) from an ordinary rowid table. */
146
+ tableRaw(name: string, limits?: SqliteReadLimits): SqliteTableRow[] {
147
+ if (typeof name !== "string") {
148
+ throw new TypeError("SQLite table name must be a string");
149
+ }
150
+ return this.#native.tableRaw(name, validateLimits(limits));
151
+ }
152
+
153
+ /** Raw main-file bytes, including the page-size header. */
154
+ pages(): Buffer {
155
+ return this.#native.pages();
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Decodes one SQLite record payload into typed storage-class values.
161
+ *
162
+ * Defaults to `utf8`. Feed the database header encoding (see `encoding()`)
163
+ * when the snapshot advertises utf16le or utf16be.
164
+ */
165
+ export function decodeRecord(
166
+ payload: Uint8Array,
167
+ encoding?: SqliteTextEncoding,
168
+ ): SqliteRecordValue[] {
169
+ if (!(payload instanceof Uint8Array)) {
170
+ throw new TypeError("payload must be a Uint8Array or Buffer");
171
+ }
172
+ const native = loadYydsSqliteNative();
173
+ const encArg: string | undefined =
174
+ encoding === undefined ? undefined : encoding;
175
+ return native.decodeRecord(Buffer.from(payload), encArg);
176
+ }
177
+
178
+ function unsignedLimit(value: number, name: string): void {
179
+ if (!Number.isInteger(value) || value < 0 || value > 0xffff_ffff) {
180
+ throw new RangeError(`${name} must be an unsigned 32-bit integer`);
181
+ }
182
+ }
4
183
 
5
- export function openSqliteFile(_path: string): Promise<never> {
6
- return Promise.reject(new Error("@yydb/sqlite/node: file backend not implemented yet"));
184
+ function validateLimits(limits?: SqliteReadLimits): SqliteReadLimits | undefined {
185
+ if (limits === undefined) return undefined;
186
+ if (limits === null || typeof limits !== "object" || Array.isArray(limits)) {
187
+ throw new TypeError("SQLite scan limits must be an object");
188
+ }
189
+ const keys = new Set(["maxPages", "maxRows", "maxPayloadBytes", "maxTotalPayloadBytes"]);
190
+ for (const [key, value] of Object.entries(limits)) {
191
+ if (!keys.has(key)) throw new TypeError(`Unknown SQLite scan limit: ${key}`);
192
+ if (value !== undefined) unsignedLimit(value, key);
193
+ }
194
+ return limits;
7
195
  }
package/README.md DELETED
@@ -1,6 +0,0 @@
1
- # 🪶 `@yydb/sqlite`
2
-
3
- SQLite for **SQL Studio**. Conditional exports isolate WASM vs file backends:
4
-
5
- - `@yydb/sqlite/wasm` — browser
6
- - `@yydb/sqlite/node` — server file database
package/src/wasm.ts DELETED
@@ -1,7 +0,0 @@
1
- /**
2
- * Browser / WASM SQLite surface (sql.js or native WASM later).
3
- */
4
-
5
- export function openSqliteWasm(_options: { wasmUrl?: string }): Promise<never> {
6
- return Promise.reject(new Error("@yydb/sqlite/wasm: not implemented yet"));
7
- }