@delali/sirannon-db 0.1.5 → 0.1.7

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 (56) hide show
  1. package/README.md +135 -18
  2. package/dist/backup-scheduler/index.d.ts +18 -3
  3. package/dist/backup-scheduler/index.mjs +2 -2
  4. package/dist/{change-tracker-CFTQ9TSn.d.ts → change-tracker-CbmaMO-N.d.ts} +12 -2
  5. package/dist/chunk-4IGMIJQK.mjs +318 -0
  6. package/dist/chunk-BNUTBHHH.mjs +22 -0
  7. package/dist/chunk-CJLYFDP5.mjs +26 -0
  8. package/dist/chunk-CW6S3WL5.mjs +222 -0
  9. package/dist/chunk-DJLX6CAE.mjs +20 -0
  10. package/dist/chunk-DVWQD3GF.mjs +49 -0
  11. package/dist/chunk-GEZUUIKV.mjs +268 -0
  12. package/dist/{chunk-UVMVN3OT.mjs → chunk-H5AB6NIR.mjs} +1 -1
  13. package/dist/{chunk-UTO3ZAFS.mjs → chunk-HHRMRFFR.mjs} +148 -28
  14. package/dist/chunk-TKGHYWQ6.mjs +35 -0
  15. package/dist/chunk-VLTICJOD.mjs +470 -0
  16. package/dist/{chunk-O7BHI3CF.mjs → chunk-YPYVQJ4C.mjs} +15 -1
  17. package/dist/client/index.d.ts +107 -15
  18. package/dist/client/index.mjs +348 -37
  19. package/dist/core/index.d.ts +70 -13
  20. package/dist/core/index.mjs +635 -192
  21. package/dist/core/writer-worker.d.ts +2 -0
  22. package/dist/core/writer-worker.mjs +107 -0
  23. package/dist/{database-BVY1GqE7.d.ts → database-DuGp0Rtr.d.ts} +51 -19
  24. package/dist/driver/better-sqlite3.d.ts +1 -1
  25. package/dist/driver/better-sqlite3.mjs +44 -6
  26. package/dist/driver/bun.mjs +35 -6
  27. package/dist/driver/expo.mjs +2 -2
  28. package/dist/driver/node.d.ts +1 -1
  29. package/dist/driver/node.mjs +48 -5
  30. package/dist/driver/wa-sqlite.d.ts +109 -0
  31. package/dist/driver/wa-sqlite.mjs +30 -2
  32. package/dist/{errors-C00ed08Q.d.ts → errors-5Nf5ZAEC.d.ts} +19 -1
  33. package/dist/file-migrations/index.d.ts +2 -3
  34. package/dist/file-migrations/index.mjs +1 -1
  35. package/dist/replication/coordinator/etcd.mjs +2 -2
  36. package/dist/replication/index.d.ts +8 -8
  37. package/dist/replication/index.mjs +85 -79
  38. package/dist/server/index.d.ts +111 -28
  39. package/dist/server/index.mjs +1016 -384
  40. package/dist/{sirannon-Cd-lK6T0.d.ts → sirannon-4SspRvP5.d.ts} +3 -3
  41. package/dist/transport/grpc.d.ts +4 -4
  42. package/dist/transport/grpc.mjs +24 -6
  43. package/dist/transport/memory.d.ts +1 -0
  44. package/dist/transport/memory.mjs +4 -1
  45. package/dist/{types-B2byqt0B.d.ts → types-BsjobKbl.d.ts} +3 -2
  46. package/dist/{types-BeozgNPr.d.ts → types-D4p4UyDK.d.ts} +1 -1
  47. package/dist/types-D_hQW1hr.d.ts +494 -0
  48. package/package.json +3 -23
  49. package/dist/chunk-3MCMONVP.mjs +0 -115
  50. package/dist/chunk-74UN4DIE.mjs +0 -14
  51. package/dist/chunk-FB2U2Q3Y.mjs +0 -21
  52. package/dist/chunk-GS7T5YMI.mjs +0 -51
  53. package/dist/chunk-PXKAKK2V.mjs +0 -124
  54. package/dist/index-CLdNrcPz.d.ts +0 -16
  55. package/dist/types-BFSsG77t.d.ts +0 -29
  56. package/dist/types-D-74JiXb.d.ts +0 -265
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@delali/sirannon-db",
3
3
  "type": "module",
4
- "version": "0.1.5",
4
+ "version": "0.1.7",
5
5
  "description": "A production-grade library that turns SQLite databases into a networked data layer with real-time subscriptions.",
6
6
  "author": "Delali (https://sondelali.com)",
7
7
  "license": "Apache-2.0",
@@ -95,7 +95,6 @@
95
95
  "@bufbuild/protobuf": ">=2.0.0",
96
96
  "@grpc/grpc-js": ">=1.10.0",
97
97
  "better-sqlite3": ">=12.0.0",
98
- "croner": ">=10.0.0",
99
98
  "expo-sqlite": ">=14.0.0",
100
99
  "etcd3": ">=1.1.2",
101
100
  "grpc-health-check": ">=2.0.0",
@@ -112,9 +111,6 @@
112
111
  "better-sqlite3": {
113
112
  "optional": true
114
113
  },
115
- "croner": {
116
- "optional": true
117
- },
118
114
  "expo-sqlite": {
119
115
  "optional": true
120
116
  },
@@ -136,17 +132,12 @@
136
132
  "@grpc/grpc-js": "1.14.3",
137
133
  "@types/better-sqlite3": "7.6.13",
138
134
  "@types/node": "25.3.3",
139
- "@types/pg": "8.20.0",
140
135
  "@vitest/coverage-v8": "4.0.18",
141
136
  "better-sqlite3": "12.6.2",
142
- "croner": "10.0.1",
143
137
  "etcd3": "1.1.2",
144
138
  "grpc-health-check": "2.1.0",
145
139
  "grpc-tools": "1.13.1",
146
- "pg": "8.20.0",
147
140
  "selfsigned": "5.5.0",
148
- "simple-statistics": "7.8.7",
149
- "tinybench": "6.0.0",
150
141
  "ts-proto": "2.11.6",
151
142
  "tsup": "8.5.1",
152
143
  "tsx": "4.21.0",
@@ -156,27 +147,16 @@
156
147
  },
157
148
  "scripts": {
158
149
  "build": "rm -rf dist && tsup",
150
+ "check:bundle": "node scripts/assert-browser-bundle.mjs",
159
151
  "test": "vitest run",
160
152
  "test:coverage": "vitest run --coverage",
161
153
  "test:e2e": "vitest run --config vitest.e2e.config.ts",
162
154
  "test:failover": "vitest run --config vitest.failover.config.ts",
163
155
  "test:soak": "vitest run --config vitest.soak.config.ts",
164
156
  "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json",
165
- "lint": "biome check .",
157
+ "lint": "biome check --error-on-warnings .",
166
158
  "lint:fix": "biome check --write .",
167
159
  "format": "biome format --write .",
168
- "bench": "node --expose-gc --import tsx benchmarks/run-all.ts",
169
- "bench:micro": "node --expose-gc --import tsx benchmarks/micro/point-select.ts",
170
- "bench:ycsb": "node --expose-gc --import tsx benchmarks/ycsb/workload-a.ts",
171
- "bench:oltp": "node --expose-gc --import tsx benchmarks/oltp/tpc-c-lite.ts",
172
- "bench:scaling": "node --expose-gc --import tsx benchmarks/scaling/concurrency.ts",
173
- "bench:pool": "node --expose-gc --import tsx benchmarks/scaling/pool-sweep.ts",
174
- "bench:cdc": "node --expose-gc --import tsx benchmarks/sirannon/cdc-latency.ts",
175
- "bench:docker": "node --import tsx benchmarks/run-docker.ts",
176
- "bench:docker:e2e": "node --import tsx benchmarks/run-e2e.ts",
177
- "bench:docker:engine": "node --expose-gc --import tsx benchmarks/run-engine.ts",
178
- "bench:statistical": "node --expose-gc --import tsx benchmarks/run-statistical.ts",
179
- "bench:charts": "python3 benchmarks/scripts/generate-charts.py benchmarks/results/",
180
160
  "proto:gen": "grpc_tools_node_protoc --plugin=protoc-gen-ts_proto=./node_modules/.bin/protoc-gen-ts_proto --ts_proto_out=src/transport/grpc/generated --ts_proto_opt=forceLong=bigint --ts_proto_opt=esModuleInterop=true --ts_proto_opt=outputServices=grpc-js --ts_proto_opt=env=node -I src/transport/grpc/proto src/transport/grpc/proto/replication.proto"
181
161
  }
182
162
  }
@@ -1,115 +0,0 @@
1
- import { QueryError } from './chunk-O7BHI3CF.mjs';
2
-
3
- // src/core/query-executor.ts
4
- var STATEMENT_CACHE_CAPACITY = 128;
5
- var statementCaches = /* @__PURE__ */ new WeakMap();
6
- async function getStatement(conn, sql) {
7
- let cache = statementCaches.get(conn);
8
- if (!cache) {
9
- cache = /* @__PURE__ */ new Map();
10
- statementCaches.set(conn, cache);
11
- }
12
- const cached = cache.get(sql);
13
- if (cached) {
14
- cache.delete(sql);
15
- cache.set(sql, cached);
16
- return cached;
17
- }
18
- const pending = conn.prepare(sql);
19
- cache.set(sql, pending);
20
- if (cache.size > STATEMENT_CACHE_CAPACITY) {
21
- const oldest = cache.keys().next().value;
22
- if (oldest !== void 0) {
23
- cache.delete(oldest);
24
- }
25
- }
26
- try {
27
- return await pending;
28
- } catch (err) {
29
- cache.delete(sql);
30
- throw err;
31
- }
32
- }
33
- function bindParams(params) {
34
- if (params === void 0) return [];
35
- if (Array.isArray(params)) return params;
36
- return [params];
37
- }
38
- async function query(conn, sql, params) {
39
- try {
40
- const stmt = await getStatement(conn, sql);
41
- return await stmt.all(...bindParams(params));
42
- } catch (err) {
43
- throw new QueryError(err instanceof Error ? err.message : String(err), sql);
44
- }
45
- }
46
- async function queryOne(conn, sql, params) {
47
- try {
48
- const stmt = await getStatement(conn, sql);
49
- return await stmt.get(...bindParams(params));
50
- } catch (err) {
51
- throw new QueryError(err instanceof Error ? err.message : String(err), sql);
52
- }
53
- }
54
- async function execute(conn, sql, params) {
55
- try {
56
- const stmt = await getStatement(conn, sql);
57
- const result = await stmt.run(...bindParams(params));
58
- return {
59
- changes: result.changes,
60
- lastInsertRowId: result.lastInsertRowId
61
- };
62
- } catch (err) {
63
- throw new QueryError(err instanceof Error ? err.message : String(err), sql);
64
- }
65
- }
66
- async function executeBatch(conn, sql, paramsBatch) {
67
- try {
68
- const stmt = await getStatement(conn, sql);
69
- const results = [];
70
- for (const params of paramsBatch) {
71
- const result = await stmt.run(...bindParams(params));
72
- results.push({
73
- changes: result.changes,
74
- lastInsertRowId: result.lastInsertRowId
75
- });
76
- }
77
- return results;
78
- } catch (err) {
79
- throw new QueryError(err instanceof Error ? err.message : String(err), sql);
80
- }
81
- }
82
-
83
- // src/core/transaction.ts
84
- var Transaction = class _Transaction {
85
- constructor(conn) {
86
- this.conn = conn;
87
- }
88
- _lastInsertRowId = 0;
89
- async query(sql, params) {
90
- return query(this.conn, sql, params);
91
- }
92
- async execute(sql, params) {
93
- const result = await execute(this.conn, sql, params);
94
- this._lastInsertRowId = result.lastInsertRowId;
95
- return result;
96
- }
97
- async executeBatch(sql, paramsBatch) {
98
- const results = await executeBatch(this.conn, sql, paramsBatch);
99
- if (results.length > 0) {
100
- this._lastInsertRowId = results[results.length - 1].lastInsertRowId;
101
- }
102
- return results;
103
- }
104
- get lastInsertRowId() {
105
- return this._lastInsertRowId;
106
- }
107
- static async run(conn, fn) {
108
- return conn.transaction(async (txConn) => {
109
- const tx = new _Transaction(txConn);
110
- return fn(tx);
111
- });
112
- }
113
- };
114
-
115
- export { Transaction, execute, executeBatch, query, queryOne };
@@ -1,14 +0,0 @@
1
- import { SirannonError } from './chunk-O7BHI3CF.mjs';
2
-
3
- // src/core/driver/define.ts
4
- function defineDriver(config) {
5
- if (!config.capabilities || typeof config.open !== "function") {
6
- throw new SirannonError("Driver must define capabilities and open()", "INVALID_DRIVER");
7
- }
8
- return Object.freeze({
9
- capabilities: Object.freeze({ ...config.capabilities }),
10
- open: config.open
11
- });
12
- }
13
-
14
- export { defineDriver };
@@ -1,21 +0,0 @@
1
- // src/core/errors.ts
2
- var SirannonError = class extends Error {
3
- constructor(message, code) {
4
- super(message);
5
- this.code = code;
6
- this.name = "SirannonError";
7
- }
8
- };
9
-
10
- // src/core/driver/define.ts
11
- function defineDriver(config) {
12
- if (!config.capabilities || typeof config.open !== "function") {
13
- throw new SirannonError("Driver must define capabilities and open()", "INVALID_DRIVER");
14
- }
15
- return Object.freeze({
16
- capabilities: Object.freeze({ ...config.capabilities }),
17
- open: config.open
18
- });
19
- }
20
-
21
- export { defineDriver };
@@ -1,51 +0,0 @@
1
- // src/core/cdc/encoding.ts
2
- var BLOB_TAG = "__sirannon_blob";
3
- var INT_TAG = "__sirannon_int";
4
- var SAFE_INT_BOUND_TEXT = "9007199254740991";
5
- var HEX_BYTES_RE = /^[0-9a-fA-F]*$/;
6
- function decodeTaggedValues(value) {
7
- if (value === null || typeof value !== "object") return value;
8
- if (isBinaryValue(value)) return value;
9
- if (Array.isArray(value)) {
10
- return value.map(decodeTaggedValues);
11
- }
12
- const obj = value;
13
- const keys = Object.keys(obj);
14
- if (keys.length === 1) {
15
- const tag = keys[0];
16
- const tagValue = obj[tag];
17
- if (tag === BLOB_TAG && typeof tagValue === "string") {
18
- return decodeHexBytes(tagValue);
19
- }
20
- if (tag === INT_TAG && typeof tagValue === "string") {
21
- return BigInt(tagValue);
22
- }
23
- }
24
- const out = {};
25
- for (const k of keys) {
26
- out[k] = decodeTaggedValues(obj[k]);
27
- }
28
- return out;
29
- }
30
- function isBinaryValue(value) {
31
- return value instanceof Uint8Array || typeof Buffer !== "undefined" && Buffer.isBuffer(value);
32
- }
33
- function decodeHexBytes(hex) {
34
- if (hex.length % 2 !== 0 || !HEX_BYTES_RE.test(hex)) {
35
- throw new Error("Invalid CDC BLOB hex payload");
36
- }
37
- if (typeof Buffer !== "undefined") {
38
- return Buffer.from(hex, "hex");
39
- }
40
- const bytes = new Uint8Array(hex.length / 2);
41
- for (let i = 0; i < hex.length; i += 2) {
42
- const byte = Number.parseInt(hex.slice(i, i + 2), 16);
43
- if (Number.isNaN(byte)) {
44
- throw new Error("Invalid CDC BLOB hex payload");
45
- }
46
- bytes[i / 2] = byte;
47
- }
48
- return bytes;
49
- }
50
-
51
- export { SAFE_INT_BOUND_TEXT, decodeTaggedValues };
@@ -1,124 +0,0 @@
1
- import { BackupError } from './chunk-O7BHI3CF.mjs';
2
- import { existsSync, mkdirSync, rmSync, readdirSync, lstatSync } from 'fs';
3
- import { resolve, dirname, join } from 'path';
4
- import { Cron } from 'croner';
5
-
6
- var BACKUP_FILE_PREFIX = "backup";
7
- function hasControlCharacters(s) {
8
- for (let i = 0; i < s.length; i++) {
9
- const code = s.charCodeAt(i);
10
- if (code <= 31) return true;
11
- }
12
- return false;
13
- }
14
- var BackupManager = class {
15
- async backup(conn, destPath) {
16
- if (hasControlCharacters(destPath)) {
17
- throw new BackupError("Backup path contains invalid characters");
18
- }
19
- const segments = destPath.split(/[/\\]/);
20
- if (segments.includes("..")) {
21
- throw new BackupError("Backup path must not contain directory traversal segments");
22
- }
23
- const resolved = resolve(destPath);
24
- const dir = dirname(resolved);
25
- if (!existsSync(dir)) {
26
- try {
27
- mkdirSync(dir, { recursive: true });
28
- } catch (err) {
29
- throw new BackupError(
30
- `Failed to create backup directory '${dir}': ${err instanceof Error ? err.message : String(err)}`
31
- );
32
- }
33
- }
34
- if (existsSync(resolved)) {
35
- throw new BackupError(`Backup destination '${destPath}' already exists`);
36
- }
37
- const escaped = resolved.replace(/'/g, "''");
38
- try {
39
- await conn.exec(`VACUUM INTO '${escaped}'`);
40
- } catch (err) {
41
- try {
42
- rmSync(resolved, { force: true });
43
- } catch {
44
- }
45
- throw new BackupError(`Backup to '${destPath}' failed: ${err instanceof Error ? err.message : String(err)}`);
46
- }
47
- }
48
- generateFilename() {
49
- const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
50
- return `${BACKUP_FILE_PREFIX}-${ts}.db`;
51
- }
52
- rotate(dir, maxFiles) {
53
- if (maxFiles <= 0) return;
54
- const resolved = resolve(dir);
55
- if (!existsSync(resolved)) return;
56
- let entries;
57
- try {
58
- entries = readdirSync(resolved).filter((f) => f.startsWith(`${BACKUP_FILE_PREFIX}-`) && f.endsWith(".db")).map((f) => {
59
- const filePath = join(resolved, f);
60
- return { path: filePath, mtimeMs: lstatSync(filePath).mtimeMs };
61
- }).sort((a, b) => b.mtimeMs - a.mtimeMs);
62
- } catch (err) {
63
- throw new BackupError(
64
- `Failed to list backup files in '${dir}': ${err instanceof Error ? err.message : String(err)}`
65
- );
66
- }
67
- for (const entry of entries.slice(maxFiles)) {
68
- try {
69
- rmSync(entry.path, { force: true });
70
- } catch {
71
- }
72
- }
73
- }
74
- };
75
- var DEFAULT_MAX_FILES = 5;
76
- var BackupScheduler = class {
77
- manager;
78
- constructor(manager) {
79
- this.manager = manager ?? new BackupManager();
80
- }
81
- schedule(conn, options) {
82
- const { cron: cronExpr, destDir, maxFiles = DEFAULT_MAX_FILES, onError } = options;
83
- const resolvedDir = resolve(destDir);
84
- if (!existsSync(resolvedDir)) {
85
- try {
86
- mkdirSync(resolvedDir, { recursive: true });
87
- } catch (err) {
88
- throw new BackupError(
89
- `Failed to create backup directory '${destDir}': ${err instanceof Error ? err.message : String(err)}`
90
- );
91
- }
92
- }
93
- let job;
94
- try {
95
- job = new Cron(
96
- cronExpr,
97
- {
98
- unref: true,
99
- catch: (err) => {
100
- if (onError) {
101
- const error = err instanceof Error ? err : new BackupError(typeof err === "string" ? err : "Scheduled backup failed");
102
- onError(error);
103
- }
104
- }
105
- },
106
- async () => {
107
- const filename = this.manager.generateFilename();
108
- const destPath = join(resolvedDir, filename);
109
- await this.manager.backup(conn, destPath);
110
- this.manager.rotate(resolvedDir, maxFiles);
111
- }
112
- );
113
- } catch (err) {
114
- throw new BackupError(
115
- `Invalid cron expression '${cronExpr}': ${err instanceof Error ? err.message : String(err)}`
116
- );
117
- }
118
- return () => {
119
- job.stop();
120
- };
121
- }
122
- };
123
-
124
- export { BackupManager, BackupScheduler };
@@ -1,16 +0,0 @@
1
- import { a as SQLiteConnection } from './types-BFSsG77t.js';
2
- import { m as BackupScheduleOptions } from './types-D-74JiXb.js';
3
-
4
- declare class BackupManager {
5
- backup(conn: SQLiteConnection, destPath: string): Promise<void>;
6
- generateFilename(): string;
7
- rotate(dir: string, maxFiles: number): void;
8
- }
9
-
10
- declare class BackupScheduler {
11
- private readonly manager;
12
- constructor(manager?: BackupManager);
13
- schedule(conn: SQLiteConnection, options: BackupScheduleOptions): () => void;
14
- }
15
-
16
- export { BackupManager as B, BackupScheduler as a };
@@ -1,29 +0,0 @@
1
- interface RunResult {
2
- changes: number;
3
- lastInsertRowId: number | bigint;
4
- }
5
- interface SQLiteStatement {
6
- all<T = unknown>(...params: unknown[]): Promise<T[]>;
7
- get<T = unknown>(...params: unknown[]): Promise<T | undefined>;
8
- run(...params: unknown[]): Promise<RunResult>;
9
- }
10
- interface SQLiteConnection {
11
- exec(sql: string): Promise<void>;
12
- prepare(sql: string): Promise<SQLiteStatement>;
13
- transaction<T>(fn: (conn: SQLiteConnection) => Promise<T>): Promise<T>;
14
- close(): Promise<void>;
15
- }
16
- interface OpenOptions {
17
- readonly?: boolean;
18
- walMode?: boolean;
19
- }
20
- interface DriverCapabilities {
21
- multipleConnections: boolean;
22
- extensions: boolean;
23
- }
24
- interface SQLiteDriver {
25
- readonly capabilities: DriverCapabilities;
26
- open(path: string, options?: OpenOptions): Promise<SQLiteConnection>;
27
- }
28
-
29
- export type { DriverCapabilities as D, OpenOptions as O, RunResult as R, SQLiteDriver as S, SQLiteConnection as a, SQLiteStatement as b };