@liorandb/core 1.0.11 → 1.0.13

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,34 @@
1
+ import { Collection } from "./collection.js";
2
+
3
+ let TX_SEQ = 0;
4
+
5
+ export class TransactionContext {
6
+ private txId = ++TX_SEQ;
7
+ private ops: (() => Promise<any>)[] = [];
8
+
9
+ constructor(private db: any) {}
10
+
11
+ collection(name: string) {
12
+ const col = this.db.collection(name);
13
+
14
+ return new Proxy(col, {
15
+ get: (target, prop: any) => {
16
+ if (typeof (target as any)[prop] !== "function") return target[prop];
17
+
18
+ return (...args: any[]) => {
19
+ this.ops.push(() => (target as any)[prop](...args));
20
+ };
21
+ }
22
+ });
23
+ }
24
+
25
+ async run<T>(fn: (tx: TransactionContext) => Promise<T>): Promise<T> {
26
+ const result = await fn(this);
27
+
28
+ for (const op of this.ops) {
29
+ await op();
30
+ }
31
+
32
+ return result;
33
+ }
34
+ }
@@ -0,0 +1,85 @@
1
+ import net from "net";
2
+ import { getIPCSocketPath } from "./socketPath.js";
3
+
4
+ function delay(ms: number) {
5
+ return new Promise(r => setTimeout(r, ms));
6
+ }
7
+
8
+ async function connectWithRetry(path: string): Promise<net.Socket> {
9
+ let attempt = 0;
10
+
11
+ while (true) {
12
+ try {
13
+ return await new Promise((resolve, reject) => {
14
+ const socket = net.connect(path, () => resolve(socket));
15
+ socket.once("error", reject);
16
+ });
17
+ } catch (err: any) {
18
+ if (err.code === "ENOENT" || err.code === "ECONNREFUSED") {
19
+ if (attempt++ > 80) {
20
+ throw new Error("IPC server not reachable");
21
+ }
22
+ await delay(50);
23
+ continue;
24
+ }
25
+ throw err;
26
+ }
27
+ }
28
+ }
29
+
30
+ export class IPCClient {
31
+ private socket!: net.Socket;
32
+ private buffer = "";
33
+ private seq = 0;
34
+ private pending = new Map<number, (v: any) => void>();
35
+ private ready: Promise<void>;
36
+
37
+ constructor(rootPath: string) {
38
+ const socketPath = getIPCSocketPath(rootPath);
39
+ this.ready = this.init(socketPath);
40
+ }
41
+
42
+ private async init(socketPath: string) {
43
+ this.socket = await connectWithRetry(socketPath);
44
+
45
+ this.socket.on("data", data => {
46
+ this.buffer += data.toString();
47
+
48
+ while (this.buffer.includes("\n")) {
49
+ const idx = this.buffer.indexOf("\n");
50
+ const raw = this.buffer.slice(0, idx);
51
+ this.buffer = this.buffer.slice(idx + 1);
52
+
53
+ const msg = JSON.parse(raw);
54
+ const cb = this.pending.get(msg.id);
55
+
56
+ if (cb) {
57
+ this.pending.delete(msg.id);
58
+ cb(msg);
59
+ }
60
+ }
61
+ });
62
+
63
+ this.socket.on("error", err => {
64
+ console.error("IPC socket error:", err);
65
+ });
66
+ }
67
+
68
+ async exec(action: string, args: any) {
69
+ await this.ready; // 🔥 HARD BARRIER — guarantees socket exists
70
+
71
+ return new Promise((resolve, reject) => {
72
+ const id = ++this.seq;
73
+
74
+ this.pending.set(id, msg => {
75
+ msg.ok ? resolve(msg.result) : reject(new Error(msg.error));
76
+ });
77
+
78
+ this.socket.write(JSON.stringify({ id, action, args }) + "\n");
79
+ });
80
+ }
81
+
82
+ close() {
83
+ try { this.socket.end(); } catch {}
84
+ }
85
+ }
package/src/ipc/queue.ts CHANGED
@@ -1,137 +1,22 @@
1
- import { fork, ChildProcess } from "child_process";
2
- import path from "path";
3
- import { fileURLToPath } from "url";
4
-
5
- const __filename = fileURLToPath(import.meta.url);
6
- const __dirname = path.dirname(__filename);
7
-
8
- function resolveWorkerPath() {
9
- return path.resolve(__dirname, "../dist/worker/dbWorker.js");
10
- }
1
+ import { IPCClient } from "./client.js";
2
+ import { getDefaultRootPath } from "../utils/rootpath.js";
11
3
 
12
4
  export class DBQueue {
13
- private worker!: ChildProcess;
14
- private seq = 0;
15
- private pending = new Map<number, (r: any) => void>();
16
- private isShutdown = false;
17
- private restarting = false;
18
- private workerAlive = false;
19
-
20
- constructor() {
21
- this.spawnWorker();
5
+ private client: IPCClient;
22
6
 
23
- process.once("exit", () => this.shutdown());
24
- process.once("SIGINT", () => this.shutdown());
25
- process.once("SIGTERM", () => this.shutdown());
7
+ constructor(rootPath = getDefaultRootPath()) {
8
+ this.client = new IPCClient(rootPath);
26
9
  }
27
10
 
28
- /* ---------------- Worker Control ---------------- */
29
-
30
- private spawnWorker() {
31
- const workerPath = resolveWorkerPath();
32
-
33
- this.worker = fork(workerPath, [], {
34
- stdio: ["inherit", "inherit", "inherit", "ipc"],
35
- });
36
-
37
- this.workerAlive = true;
38
-
39
- this.worker.on("message", (msg: any) => {
40
- const cb = this.pending.get(msg.id);
41
- if (cb) {
42
- this.pending.delete(msg.id);
43
- cb(msg);
44
- }
45
- });
46
-
47
- this.worker.once("exit", (code, signal) => {
48
- this.workerAlive = false;
49
-
50
- if (this.isShutdown) return;
51
-
52
- console.error("DB Worker crashed, restarting...", { code, signal });
53
-
54
- this.restartWorker();
55
- });
11
+ exec(action: string, args: any) {
12
+ return this.client.exec(action, args);
56
13
  }
57
14
 
58
- private restartWorker() {
59
- if (this.restarting || this.isShutdown) return;
60
-
61
- this.restarting = true;
62
-
63
- setTimeout(() => {
64
- if (this.isShutdown) return;
65
-
66
- for (const [, cb] of this.pending) {
67
- cb({ ok: false, error: "IPC worker crashed" });
68
- }
69
- this.pending.clear();
70
- this.seq = 0;
71
-
72
- this.spawnWorker();
73
- this.restarting = false;
74
- }, 500);
75
- }
76
-
77
- /* ---------------- IPC Exec ---------------- */
78
-
79
- exec(action: string, args: any, timeout = 15000) {
80
- if (this.isShutdown) {
81
- return Promise.reject(new Error("DBQueue is shutdown"));
82
- }
83
-
84
- if (!this.workerAlive) {
85
- return Promise.reject(new Error("IPC worker not running"));
86
- }
87
-
88
- return new Promise((resolve, reject) => {
89
- const id = ++this.seq;
90
-
91
- const timer = setTimeout(() => {
92
- this.pending.delete(id);
93
- reject(new Error(`IPC timeout: ${action}`));
94
- }, timeout);
95
-
96
- this.pending.set(id, (msg) => {
97
- clearTimeout(timer);
98
- this.pending.delete(id);
99
- msg.ok ? resolve(msg.result) : reject(new Error(msg.error));
100
- });
101
-
102
- try {
103
- this.worker.send({ id, action, args });
104
- } catch (err) {
105
- clearTimeout(timer);
106
- this.pending.delete(id);
107
- reject(err);
108
- }
109
- });
110
- }
111
-
112
- /* ---------------- Clean Shutdown ---------------- */
113
-
114
15
  async shutdown() {
115
- if (this.isShutdown) return;
116
- this.isShutdown = true;
117
-
118
- if (this.workerAlive) {
119
- try {
120
- this.worker.send({ action: "shutdown" });
121
- } catch {}
122
- }
123
-
124
- await new Promise(resolve => {
125
- if (!this.workerAlive) return resolve(null);
126
- this.worker.once("exit", resolve);
127
- setTimeout(resolve, 250);
128
- });
129
-
130
- for (const [, cb] of this.pending) {
131
- cb({ ok: false, error: "IPC shutdown" });
132
- }
133
-
134
- this.pending.clear();
16
+ try {
17
+ await this.exec("shutdown", {});
18
+ } catch {}
19
+ this.client.close();
135
20
  }
136
21
  }
137
22
 
@@ -0,0 +1,84 @@
1
+ import net from "net";
2
+ import fs from "fs";
3
+ import { LioranManager } from "../LioranManager.js";
4
+ import { getIPCSocketPath } from "./socketPath.js";
5
+
6
+ export class IPCServer {
7
+ private server!: net.Server;
8
+ private manager: LioranManager;
9
+ private socketPath: string;
10
+
11
+ constructor(manager: LioranManager, rootPath: string) {
12
+ this.manager = manager;
13
+ this.socketPath = getIPCSocketPath(rootPath);
14
+ }
15
+
16
+ start() {
17
+ if (!this.socketPath.startsWith("\\\\.\\")) {
18
+ if (fs.existsSync(this.socketPath)) fs.unlinkSync(this.socketPath);
19
+ }
20
+
21
+ this.server = net.createServer(socket => {
22
+ let buffer = "";
23
+
24
+ socket.on("data", async data => {
25
+ buffer += data.toString();
26
+
27
+ while (buffer.includes("\n")) {
28
+ const idx = buffer.indexOf("\n");
29
+ const raw = buffer.slice(0, idx);
30
+ buffer = buffer.slice(idx + 1);
31
+
32
+ const msg = JSON.parse(raw);
33
+ this.handleMessage(socket, msg).catch(console.error);
34
+ }
35
+ });
36
+ });
37
+
38
+ this.server.listen(this.socketPath, () => {
39
+ console.log("[IPC] Server listening:", this.socketPath);
40
+ });
41
+ }
42
+
43
+ private async handleMessage(socket: net.Socket, msg: any) {
44
+ const { id, action, args } = msg;
45
+
46
+ try {
47
+ let result;
48
+
49
+ switch (action) {
50
+ case "db":
51
+ await this.manager.db(args.db);
52
+ result = true;
53
+ break;
54
+
55
+ case "op": {
56
+ const { db, col, method, params } = args;
57
+ const collection = (await this.manager.db(db)).collection(col);
58
+ result = await (collection as any)[method](...params);
59
+ break;
60
+ }
61
+
62
+ case "shutdown":
63
+ await this.manager.closeAll();
64
+ result = true;
65
+ break;
66
+
67
+ default:
68
+ throw new Error("Unknown IPC action");
69
+ }
70
+
71
+ socket.write(JSON.stringify({ id, ok: true, result }) + "\n");
72
+ } catch (err: any) {
73
+ socket.write(JSON.stringify({ id, ok: false, error: err.message }) + "\n");
74
+ }
75
+ }
76
+
77
+ async close() {
78
+ if (this.server) this.server.close();
79
+
80
+ if (!this.socketPath.startsWith("\\\\.\\")) {
81
+ try { fs.unlinkSync(this.socketPath); } catch {}
82
+ }
83
+ }
84
+ }
@@ -0,0 +1,10 @@
1
+ import os from "os";
2
+ import path from "path";
3
+
4
+ export function getIPCSocketPath(rootPath: string) {
5
+ if (os.platform() === "win32") {
6
+ return `\\\\.\\pipe\\liorandb_${rootPath.replace(/[:\\\/]/g, "_")}`;
7
+ }
8
+
9
+ return path.join(rootPath, ".lioran.sock");
10
+ }
@@ -1,45 +0,0 @@
1
- import { LioranManager } from "../LioranManager.js";
2
-
3
- const manager = new LioranManager();
4
-
5
- process.on("message", async (msg: any) => {
6
- const { id, action, args } = msg;
7
-
8
- try {
9
- let result;
10
-
11
- switch (action) {
12
- case "shutdown": {
13
- await manager.closeAll();
14
- result = true;
15
- break;
16
- }
17
-
18
- case "db": {
19
- const db = await manager.db(args.db);
20
- result = true;
21
- break;
22
- }
23
-
24
- case "collection": {
25
- const db = await manager.db(args.db);
26
- result = db.collection(args.collection);
27
- break;
28
- }
29
-
30
- case "op": {
31
- const { db, col, method, params } = args;
32
- const collection = (await manager.db(db)).collection(col);
33
- result = await (collection as any)[method](...params);
34
- break;
35
- }
36
-
37
- default:
38
- throw new Error("Unknown action");
39
- }
40
-
41
- process.send?.({ id, ok: true, result });
42
- } catch (err: any) {
43
- process.send?.({ id, ok: false, error: err.message });
44
- }
45
- });