@liorandb/core 1.0.8 → 1.0.10

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.
@@ -2,6 +2,7 @@ import path from "path";
2
2
  import fs from "fs";
3
3
  import { Collection } from "./collection.js";
4
4
  import type { LioranManager } from "../LioranManager.js";
5
+ import type { ZodSchema } from "zod";
5
6
 
6
7
  export class LioranDB {
7
8
  basePath: string;
@@ -20,9 +21,13 @@ export class LioranDB {
20
21
  }
21
22
  }
22
23
 
23
- collection<T = any>(name: string): Collection<T> {
24
+ /* -------------------------------- COLLECTION -------------------------------- */
25
+
26
+ collection<T = any>(name: string, schema?: ZodSchema<T>): Collection<T> {
24
27
  if (this.collections.has(name)) {
25
- return this.collections.get(name)!;
28
+ const col = this.collections.get(name)!;
29
+ if (schema) col.setSchema(schema);
30
+ return col as Collection<T>;
26
31
  }
27
32
 
28
33
  const colPath = path.join(this.basePath, name);
@@ -31,12 +36,16 @@ export class LioranDB {
31
36
  fs.mkdirSync(colPath, { recursive: true });
32
37
  }
33
38
 
34
- const col = new Collection<T>(colPath);
39
+ const col = new Collection<T>(colPath, schema);
35
40
  this.collections.set(name, col);
41
+
36
42
  return col;
37
43
  }
38
44
 
39
- async createCollection(name: string): Promise<boolean> {
45
+ async createCollection<T = any>(
46
+ name: string,
47
+ schema?: ZodSchema<T>
48
+ ): Promise<Collection<T>> {
40
49
  const colPath = path.join(this.basePath, name);
41
50
 
42
51
  if (fs.existsSync(colPath)) {
@@ -44,8 +53,11 @@ export class LioranDB {
44
53
  }
45
54
 
46
55
  await fs.promises.mkdir(colPath, { recursive: true });
47
- this.collections.set(name, new Collection(colPath));
48
- return true;
56
+
57
+ const col = new Collection<T>(colPath, schema);
58
+ this.collections.set(name, col);
59
+
60
+ return col;
49
61
  }
50
62
 
51
63
  async deleteCollection(name: string): Promise<boolean> {
@@ -68,8 +80,13 @@ export class LioranDB {
68
80
  const oldPath = path.join(this.basePath, oldName);
69
81
  const newPath = path.join(this.basePath, newName);
70
82
 
71
- if (!fs.existsSync(oldPath)) throw new Error("Collection does not exist");
72
- if (fs.existsSync(newPath)) throw new Error("New collection name exists");
83
+ if (!fs.existsSync(oldPath)) {
84
+ throw new Error("Collection does not exist");
85
+ }
86
+
87
+ if (fs.existsSync(newPath)) {
88
+ throw new Error("New collection name already exists");
89
+ }
73
90
 
74
91
  if (this.collections.has(oldName)) {
75
92
  await this.collections.get(oldName)!.close();
@@ -77,7 +94,10 @@ export class LioranDB {
77
94
  }
78
95
 
79
96
  await fs.promises.rename(oldPath, newPath);
80
- this.collections.set(newName, new Collection(newPath));
97
+
98
+ const col = new Collection(newPath);
99
+ this.collections.set(newName, col);
100
+
81
101
  return true;
82
102
  }
83
103
 
@@ -92,4 +112,25 @@ export class LioranDB {
92
112
 
93
113
  return dirs.filter(d => d.isDirectory()).map(d => d.name);
94
114
  }
115
+
116
+ /* -------------------------------- LIFECYCLE -------------------------------- */
117
+
118
+ async close(): Promise<void> {
119
+ for (const col of this.collections.values()) {
120
+ try {
121
+ await col.close();
122
+ } catch {}
123
+ }
124
+ this.collections.clear();
125
+ }
126
+
127
+ /* -------------------------------- DEBUG -------------------------------- */
128
+
129
+ getStats() {
130
+ return {
131
+ dbName: this.dbName,
132
+ basePath: this.basePath,
133
+ collections: [...this.collections.keys()]
134
+ };
135
+ }
95
136
  }
@@ -0,0 +1,56 @@
1
+ import { dbQueue } from "./queue.js";
2
+
3
+ type AnyFn = (...args: any[]) => Promise<any>;
4
+
5
+ /* -------------------------------- COLLECTION PROXY -------------------------------- */
6
+
7
+ class CollectionProxy {
8
+ constructor(
9
+ private dbName: string,
10
+ private collectionName: string
11
+ ) {}
12
+
13
+ private call(method: string, params: any[]): Promise<any> {
14
+ return dbQueue.exec("op", {
15
+ db: this.dbName,
16
+ col: this.collectionName,
17
+ method,
18
+ params
19
+ });
20
+ }
21
+
22
+ insertOne = (doc: any) => this.call("insertOne", [doc]);
23
+ insertMany = (docs: any[]) => this.call("insertMany", [docs]);
24
+ find = (query?: any) => this.call("find", [query]);
25
+ findOne = (query?: any) => this.call("findOne", [query]);
26
+ updateOne = (filter: any, update: any, options?: any) =>
27
+ this.call("updateOne", [filter, update, options]);
28
+ updateMany = (filter: any, update: any) =>
29
+ this.call("updateMany", [filter, update]);
30
+ deleteOne = (filter: any) => this.call("deleteOne", [filter]);
31
+ deleteMany = (filter: any) => this.call("deleteMany", [filter]);
32
+ countDocuments = (filter?: any) =>
33
+ this.call("countDocuments", [filter]);
34
+ }
35
+
36
+ /* -------------------------------- DATABASE PROXY -------------------------------- */
37
+
38
+ class DBProxy {
39
+ constructor(private dbName: string) {}
40
+
41
+ collection(name: string) {
42
+ return new CollectionProxy(this.dbName, name);
43
+ }
44
+ }
45
+
46
+ /* -------------------------------- MANAGER PROXY -------------------------------- */
47
+
48
+ class LioranManagerIPC {
49
+ async db(name: string) {
50
+ await dbQueue.exec("db", { db: name });
51
+ return new DBProxy(name);
52
+ }
53
+ }
54
+
55
+ export const manager = new LioranManagerIPC();
56
+ export type { CollectionProxy, DBProxy };
@@ -0,0 +1,78 @@
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
+ // dist/ipc/queue.js → dist/worker/dbWorker.js
10
+ return path.resolve(__dirname, "../dist/worker/dbWorker.js");
11
+ }
12
+
13
+ export class DBQueue {
14
+ private worker: ChildProcess;
15
+ private seq = 0;
16
+ private pending = new Map<number, (r: any) => void>();
17
+ private isShutdown = false;
18
+
19
+ constructor() {
20
+ const workerPath = resolveWorkerPath();
21
+
22
+ this.worker = fork(workerPath, [], {
23
+ stdio: ["inherit", "inherit", "inherit", "ipc"],
24
+ });
25
+
26
+ this.worker.on("message", (msg: any) => {
27
+ const cb = this.pending.get(msg.id);
28
+ if (cb) {
29
+ this.pending.delete(msg.id);
30
+ cb(msg);
31
+ }
32
+ });
33
+
34
+ this.worker.on("exit", () => {
35
+ this.isShutdown = true;
36
+ });
37
+
38
+ // Auto cleanup
39
+ process.on("exit", () => this.shutdown());
40
+ process.on("SIGINT", () => this.shutdown());
41
+ process.on("SIGTERM", () => this.shutdown());
42
+ }
43
+
44
+ exec(action: string, args: any) {
45
+ if (this.isShutdown) {
46
+ return Promise.reject(new Error("DBQueue is shutdown"));
47
+ }
48
+
49
+ return new Promise((resolve, reject) => {
50
+ const id = ++this.seq;
51
+
52
+ this.pending.set(id, (msg) => {
53
+ if (msg.ok) resolve(msg.result);
54
+ else reject(new Error(msg.error));
55
+ });
56
+
57
+ this.worker.send({ id, action, args });
58
+ });
59
+ }
60
+
61
+ async shutdown() {
62
+ if (this.isShutdown) return;
63
+
64
+ this.isShutdown = true;
65
+
66
+ try {
67
+ this.worker.send({ action: "shutdown" });
68
+ } catch {}
69
+
70
+ setTimeout(() => {
71
+ try {
72
+ this.worker.kill("SIGTERM");
73
+ } catch {}
74
+ }, 200);
75
+ }
76
+ }
77
+
78
+ export const dbQueue = new DBQueue();
@@ -1,6 +1,7 @@
1
1
  export interface LioranManagerOptions {
2
2
  rootPath?: string
3
- encryptionKey?: string | Buffer
3
+ encryptionKey?: string | Buffer,
4
+ ipc?: boolean
4
5
  }
5
6
 
6
7
  export interface UpdateOptions {
@@ -0,0 +1,14 @@
1
+ import { ZodSchema } from "zod";
2
+
3
+ export function validateSchema<T>(schema: ZodSchema<T>, data: any): T {
4
+ const result = schema.safeParse(data);
5
+
6
+ if (!result.success) {
7
+ throw new Error(
8
+ "Schema validation failed:\n" +
9
+ JSON.stringify(result.error.format(), null, 2)
10
+ );
11
+ }
12
+
13
+ return result.data;
14
+ }
@@ -0,0 +1,45 @@
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
+ });