@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.
- package/dist/chunk-ST6KMJQJ.js +759 -0
- package/dist/index.d.ts +46 -49
- package/dist/index.js +790 -5
- package/dist/worker/dbWorker.js +11 -13
- package/package.json +2 -2
- package/src/LioranManager.ts +62 -114
- package/src/core/collection.ts +74 -244
- package/src/core/database.ts +103 -86
- package/src/core/transaction.ts +34 -0
- package/src/ipc/client.ts +85 -0
- package/src/ipc/queue.ts +11 -126
- package/src/ipc/server.ts +84 -0
- package/src/ipc/socketPath.ts +10 -0
- package/src/worker/dbWorker.ts +0 -45
package/dist/worker/dbWorker.js
CHANGED
|
@@ -1,37 +1,35 @@
|
|
|
1
1
|
import {
|
|
2
2
|
LioranManager
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-ST6KMJQJ.js";
|
|
4
4
|
|
|
5
5
|
// src/worker/dbWorker.ts
|
|
6
|
-
var manager = new LioranManager();
|
|
6
|
+
var manager = new LioranManager({ ipc: false });
|
|
7
7
|
process.on("message", async (msg) => {
|
|
8
8
|
const { id, action, args } = msg;
|
|
9
9
|
try {
|
|
10
10
|
let result;
|
|
11
11
|
switch (action) {
|
|
12
|
-
case "shutdown":
|
|
12
|
+
case "shutdown":
|
|
13
13
|
await manager.closeAll();
|
|
14
14
|
result = true;
|
|
15
15
|
break;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const db = await manager.db(args.db);
|
|
16
|
+
case "db":
|
|
17
|
+
await manager.db(args.db);
|
|
19
18
|
result = true;
|
|
20
19
|
break;
|
|
21
|
-
}
|
|
22
|
-
case "collection": {
|
|
23
|
-
const db = await manager.db(args.db);
|
|
24
|
-
result = db.collection(args.collection);
|
|
25
|
-
break;
|
|
26
|
-
}
|
|
27
20
|
case "op": {
|
|
28
21
|
const { db, col, method, params } = args;
|
|
29
22
|
const collection = (await manager.db(db)).collection(col);
|
|
30
23
|
result = await collection[method](...params);
|
|
31
24
|
break;
|
|
32
25
|
}
|
|
26
|
+
case "tx": {
|
|
27
|
+
const db = await manager.db(args.db);
|
|
28
|
+
result = await db.transaction(args.fn);
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
33
31
|
default:
|
|
34
|
-
throw new Error("Unknown action");
|
|
32
|
+
throw new Error("Unknown IPC action");
|
|
35
33
|
}
|
|
36
34
|
process.send?.({ id, ok: true, result });
|
|
37
35
|
} catch (err) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liorandb/core",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.13",
|
|
4
|
+
"description": "LioranDB Core Module – Lightweight, local-first, peer-to-peer database management for Node.js.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
package/src/LioranManager.ts
CHANGED
|
@@ -1,27 +1,34 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import fs from "fs";
|
|
3
|
+
import process from "process";
|
|
3
4
|
import { LioranDB } from "./core/database.js";
|
|
4
5
|
import { setEncryptionKey } from "./utils/encryption.js";
|
|
5
6
|
import { getDefaultRootPath } from "./utils/rootpath.js";
|
|
6
7
|
import { dbQueue } from "./ipc/queue.js";
|
|
8
|
+
import { IPCServer } from "./ipc/server.js";
|
|
9
|
+
|
|
10
|
+
enum ProcessMode {
|
|
11
|
+
PRIMARY = "primary",
|
|
12
|
+
CLIENT = "client"
|
|
13
|
+
}
|
|
7
14
|
|
|
8
15
|
export interface LioranManagerOptions {
|
|
9
16
|
rootPath?: string;
|
|
10
17
|
encryptionKey?: string | Buffer;
|
|
11
|
-
ipc?: boolean;
|
|
12
18
|
}
|
|
13
19
|
|
|
14
20
|
export class LioranManager {
|
|
15
21
|
rootPath: string;
|
|
16
22
|
openDBs: Map<string, LioranDB>;
|
|
17
23
|
private closed = false;
|
|
18
|
-
private
|
|
24
|
+
private mode: ProcessMode;
|
|
25
|
+
private lockFd?: number;
|
|
26
|
+
private ipcServer?: IPCServer;
|
|
19
27
|
|
|
20
28
|
constructor(options: LioranManagerOptions = {}) {
|
|
21
|
-
const { rootPath, encryptionKey
|
|
29
|
+
const { rootPath, encryptionKey } = options;
|
|
22
30
|
|
|
23
31
|
this.rootPath = rootPath || getDefaultRootPath();
|
|
24
|
-
this.ipc = ipc ?? process.env.LIORANDB_IPC === "1";
|
|
25
32
|
|
|
26
33
|
if (!fs.existsSync(this.rootPath)) {
|
|
27
34
|
fs.mkdirSync(this.rootPath, { recursive: true });
|
|
@@ -33,33 +40,56 @@ export class LioranManager {
|
|
|
33
40
|
|
|
34
41
|
this.openDBs = new Map();
|
|
35
42
|
|
|
36
|
-
|
|
43
|
+
this.mode = this.tryAcquireLock()
|
|
44
|
+
? ProcessMode.PRIMARY
|
|
45
|
+
: ProcessMode.CLIENT;
|
|
46
|
+
|
|
47
|
+
if (this.mode === ProcessMode.PRIMARY) {
|
|
48
|
+
this.ipcServer = new IPCServer(this, this.rootPath);
|
|
49
|
+
this.ipcServer.start();
|
|
37
50
|
this._registerShutdownHooks();
|
|
38
51
|
}
|
|
39
52
|
}
|
|
40
53
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return
|
|
54
|
+
private isProcessAlive(pid: number): boolean {
|
|
55
|
+
try {
|
|
56
|
+
process.kill(pid, 0);
|
|
57
|
+
return true;
|
|
58
|
+
} catch {
|
|
59
|
+
return false;
|
|
47
60
|
}
|
|
48
|
-
|
|
49
|
-
return this.openDatabase(name);
|
|
50
61
|
}
|
|
51
62
|
|
|
52
|
-
|
|
53
|
-
this.
|
|
63
|
+
private tryAcquireLock(): boolean {
|
|
64
|
+
const lockPath = path.join(this.rootPath, ".lioran.lock");
|
|
54
65
|
|
|
55
|
-
|
|
66
|
+
try {
|
|
67
|
+
this.lockFd = fs.openSync(lockPath, "wx");
|
|
68
|
+
fs.writeSync(this.lockFd, String(process.pid));
|
|
69
|
+
return true;
|
|
70
|
+
} catch {
|
|
71
|
+
// Possible stale lock → validate PID
|
|
72
|
+
try {
|
|
73
|
+
const pid = Number(fs.readFileSync(lockPath, "utf8"));
|
|
74
|
+
if (!this.isProcessAlive(pid)) {
|
|
75
|
+
fs.unlinkSync(lockPath);
|
|
76
|
+
this.lockFd = fs.openSync(lockPath, "wx");
|
|
77
|
+
fs.writeSync(this.lockFd, String(process.pid));
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
} catch {}
|
|
56
81
|
|
|
57
|
-
|
|
58
|
-
throw new Error(`Database "${name}" already exists`);
|
|
82
|
+
return false;
|
|
59
83
|
}
|
|
84
|
+
}
|
|
60
85
|
|
|
61
|
-
|
|
62
|
-
|
|
86
|
+
async db(name: string): Promise<LioranDB> {
|
|
87
|
+
if (this.mode === ProcessMode.CLIENT) {
|
|
88
|
+
await dbQueue.exec("db", { db: name });
|
|
89
|
+
return new IPCDatabase(name) as any;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return this.openDatabase(name);
|
|
63
93
|
}
|
|
64
94
|
|
|
65
95
|
async openDatabase(name: string): Promise<LioranDB> {
|
|
@@ -70,54 +100,36 @@ export class LioranManager {
|
|
|
70
100
|
}
|
|
71
101
|
|
|
72
102
|
const dbPath = path.join(this.rootPath, name);
|
|
73
|
-
|
|
74
|
-
if (!fs.existsSync(dbPath)) {
|
|
75
|
-
await fs.promises.mkdir(dbPath, { recursive: true });
|
|
76
|
-
}
|
|
103
|
+
await fs.promises.mkdir(dbPath, { recursive: true });
|
|
77
104
|
|
|
78
105
|
const db = new LioranDB(dbPath, name, this);
|
|
79
106
|
this.openDBs.set(name, db);
|
|
80
107
|
return db;
|
|
81
108
|
}
|
|
82
109
|
|
|
83
|
-
/* -------------------------------- LIFECYCLE -------------------------------- */
|
|
84
|
-
|
|
85
|
-
async closeDatabase(name: string): Promise<void> {
|
|
86
|
-
if (this.ipc) return;
|
|
87
|
-
|
|
88
|
-
if (!this.openDBs.has(name)) return;
|
|
89
|
-
|
|
90
|
-
const db = this.openDBs.get(name)!;
|
|
91
|
-
await db.close();
|
|
92
|
-
this.openDBs.delete(name);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Gracefully shuts down everything.
|
|
97
|
-
* - Closes all databases
|
|
98
|
-
* - Terminates IPC worker if running
|
|
99
|
-
*/
|
|
100
110
|
async closeAll(): Promise<void> {
|
|
101
111
|
if (this.closed) return;
|
|
102
112
|
this.closed = true;
|
|
103
113
|
|
|
104
|
-
if (this.
|
|
114
|
+
if (this.mode === ProcessMode.CLIENT) {
|
|
105
115
|
await dbQueue.shutdown();
|
|
106
116
|
return;
|
|
107
117
|
}
|
|
108
118
|
|
|
109
119
|
for (const db of this.openDBs.values()) {
|
|
110
|
-
try {
|
|
111
|
-
await db.close();
|
|
112
|
-
} catch {}
|
|
120
|
+
try { await db.close(); } catch {}
|
|
113
121
|
}
|
|
114
122
|
|
|
115
123
|
this.openDBs.clear();
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
if (this.lockFd) fs.closeSync(this.lockFd);
|
|
127
|
+
fs.unlinkSync(path.join(this.rootPath, ".lioran.lock"));
|
|
128
|
+
} catch {}
|
|
129
|
+
|
|
130
|
+
await this.ipcServer?.close();
|
|
116
131
|
}
|
|
117
132
|
|
|
118
|
-
/**
|
|
119
|
-
* Alias for closeAll() (clean public API)
|
|
120
|
-
*/
|
|
121
133
|
async close(): Promise<void> {
|
|
122
134
|
return this.closeAll();
|
|
123
135
|
}
|
|
@@ -137,73 +149,9 @@ export class LioranManager {
|
|
|
137
149
|
throw new Error("LioranManager is closed");
|
|
138
150
|
}
|
|
139
151
|
}
|
|
140
|
-
|
|
141
|
-
/* -------------------------------- MANAGEMENT -------------------------------- */
|
|
142
|
-
|
|
143
|
-
async renameDatabase(oldName: string, newName: string): Promise<boolean> {
|
|
144
|
-
if (this.ipc) {
|
|
145
|
-
return (await dbQueue.exec("renameDatabase", { oldName, newName })) as boolean;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const oldPath = path.join(this.rootPath, oldName);
|
|
149
|
-
const newPath = path.join(this.rootPath, newName);
|
|
150
|
-
|
|
151
|
-
if (!fs.existsSync(oldPath)) {
|
|
152
|
-
throw new Error(`Database "${oldName}" not found`);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
if (fs.existsSync(newPath)) {
|
|
156
|
-
throw new Error(`Database "${newName}" already exists`);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
await this.closeDatabase(oldName);
|
|
160
|
-
await fs.promises.rename(oldPath, newPath);
|
|
161
|
-
return true;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
async deleteDatabase(name: string): Promise<boolean> {
|
|
165
|
-
return this.dropDatabase(name);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
async dropDatabase(name: string): Promise<boolean> {
|
|
169
|
-
if (this.ipc) {
|
|
170
|
-
return (await dbQueue.exec("dropDatabase", { name })) as boolean;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const dbPath = path.join(this.rootPath, name);
|
|
174
|
-
|
|
175
|
-
if (!fs.existsSync(dbPath)) return false;
|
|
176
|
-
|
|
177
|
-
await this.closeDatabase(name);
|
|
178
|
-
await fs.promises.rm(dbPath, { recursive: true, force: true });
|
|
179
|
-
return true;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
async listDatabases(): Promise<string[]> {
|
|
183
|
-
if (this.ipc) {
|
|
184
|
-
return (await dbQueue.exec("listDatabases", {})) as string[];
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const items = await fs.promises.readdir(this.rootPath, {
|
|
188
|
-
withFileTypes: true
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
return items.filter(i => i.isDirectory()).map(i => i.name);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/* -------------------------------- DEBUG -------------------------------- */
|
|
195
|
-
|
|
196
|
-
getStats() {
|
|
197
|
-
return {
|
|
198
|
-
rootPath: this.rootPath,
|
|
199
|
-
openDatabases: this.ipc ? ["<ipc>"] : [...this.openDBs.keys()],
|
|
200
|
-
ipc: this.ipc,
|
|
201
|
-
closed: this.closed
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
152
|
}
|
|
205
153
|
|
|
206
|
-
/*
|
|
154
|
+
/* ---------------- IPC PROXY DB ---------------- */
|
|
207
155
|
|
|
208
156
|
class IPCDatabase {
|
|
209
157
|
constructor(private name: string) {}
|
|
@@ -240,4 +188,4 @@ class IPCCollection {
|
|
|
240
188
|
deleteMany = (filter: any) => this.call("deleteMany", [filter]);
|
|
241
189
|
countDocuments = (filter?: any) =>
|
|
242
190
|
this.call("countDocuments", [filter]);
|
|
243
|
-
}
|
|
191
|
+
}
|