@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.
- package/dist/chunk-DD5XS45L.js +703 -0
- package/dist/chunk-J7OD7MXK.js +741 -0
- package/dist/chunk-JETRGCOS.js +703 -0
- package/dist/chunk-JIGKUFXC.js +741 -0
- package/dist/client.d.ts +14 -0
- package/dist/client.js +55 -0
- package/dist/collection.d.ts +16 -0
- package/dist/collection.js +36 -0
- package/dist/db.d.ts +13 -0
- package/dist/db.js +30 -0
- package/dist/index.d.ts +49 -3
- package/dist/index.js +5 -435
- package/dist/types.d.ts +18 -0
- package/dist/types.js +2 -0
- package/dist/utils/parseUri.d.ts +7 -0
- package/dist/utils/parseUri.js +20 -0
- package/dist/worker/dbWorker.d.ts +2 -0
- package/dist/worker/dbWorker.js +40 -0
- package/package.json +40 -38
- package/src/LioranManager.ts +140 -9
- package/src/core/collection.ts +278 -112
- package/src/core/database.ts +50 -9
- package/src/ipc/index.ts +56 -0
- package/src/ipc/queue.ts +78 -0
- package/src/types/index.ts +2 -1
- package/src/utils/schema.ts +14 -0
- package/src/worker/dbWorker.ts +45 -0
package/src/LioranManager.ts
CHANGED
|
@@ -3,20 +3,25 @@ import fs from "fs";
|
|
|
3
3
|
import { LioranDB } from "./core/database.js";
|
|
4
4
|
import { setEncryptionKey } from "./utils/encryption.js";
|
|
5
5
|
import { getDefaultRootPath } from "./utils/rootpath.js";
|
|
6
|
+
import { dbQueue } from "./ipc/queue.js";
|
|
6
7
|
|
|
7
8
|
export interface LioranManagerOptions {
|
|
8
9
|
rootPath?: string;
|
|
9
10
|
encryptionKey?: string | Buffer;
|
|
11
|
+
ipc?: boolean;
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
export class LioranManager {
|
|
13
15
|
rootPath: string;
|
|
14
16
|
openDBs: Map<string, LioranDB>;
|
|
17
|
+
private closed = false;
|
|
18
|
+
private ipc: boolean;
|
|
15
19
|
|
|
16
20
|
constructor(options: LioranManagerOptions = {}) {
|
|
17
|
-
const { rootPath, encryptionKey } = options;
|
|
21
|
+
const { rootPath, encryptionKey, ipc } = options;
|
|
18
22
|
|
|
19
23
|
this.rootPath = rootPath || getDefaultRootPath();
|
|
24
|
+
this.ipc = ipc ?? process.env.LIORANDB_IPC === "1";
|
|
20
25
|
|
|
21
26
|
if (!fs.existsSync(this.rootPath)) {
|
|
22
27
|
fs.mkdirSync(this.rootPath, { recursive: true });
|
|
@@ -27,13 +32,26 @@ export class LioranManager {
|
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
this.openDBs = new Map();
|
|
35
|
+
|
|
36
|
+
if (!this.ipc) {
|
|
37
|
+
this._registerShutdownHooks();
|
|
38
|
+
}
|
|
30
39
|
}
|
|
31
40
|
|
|
41
|
+
/* -------------------------------- CORE -------------------------------- */
|
|
42
|
+
|
|
32
43
|
async db(name: string): Promise<LioranDB> {
|
|
44
|
+
if (this.ipc) {
|
|
45
|
+
await dbQueue.exec("db", { db: name });
|
|
46
|
+
return new IPCDatabase(name) as any;
|
|
47
|
+
}
|
|
48
|
+
|
|
33
49
|
return this.openDatabase(name);
|
|
34
50
|
}
|
|
35
51
|
|
|
36
52
|
async createDatabase(name: string): Promise<LioranDB> {
|
|
53
|
+
this._assertOpen();
|
|
54
|
+
|
|
37
55
|
const dbPath = path.join(this.rootPath, name);
|
|
38
56
|
|
|
39
57
|
if (fs.existsSync(dbPath)) {
|
|
@@ -41,44 +59,99 @@ export class LioranManager {
|
|
|
41
59
|
}
|
|
42
60
|
|
|
43
61
|
await fs.promises.mkdir(dbPath, { recursive: true });
|
|
44
|
-
return this.
|
|
62
|
+
return this.db(name);
|
|
45
63
|
}
|
|
46
64
|
|
|
47
65
|
async openDatabase(name: string): Promise<LioranDB> {
|
|
66
|
+
this._assertOpen();
|
|
67
|
+
|
|
68
|
+
if (this.openDBs.has(name)) {
|
|
69
|
+
return this.openDBs.get(name)!;
|
|
70
|
+
}
|
|
71
|
+
|
|
48
72
|
const dbPath = path.join(this.rootPath, name);
|
|
49
73
|
|
|
50
74
|
if (!fs.existsSync(dbPath)) {
|
|
51
75
|
await fs.promises.mkdir(dbPath, { recursive: true });
|
|
52
76
|
}
|
|
53
77
|
|
|
54
|
-
if (this.openDBs.has(name)) {
|
|
55
|
-
return this.openDBs.get(name)!;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
78
|
const db = new LioranDB(dbPath, name, this);
|
|
59
79
|
this.openDBs.set(name, db);
|
|
60
80
|
return db;
|
|
61
81
|
}
|
|
62
82
|
|
|
83
|
+
/* -------------------------------- LIFECYCLE -------------------------------- */
|
|
84
|
+
|
|
63
85
|
async closeDatabase(name: string): Promise<void> {
|
|
86
|
+
if (this.ipc) return;
|
|
87
|
+
|
|
64
88
|
if (!this.openDBs.has(name)) return;
|
|
65
89
|
|
|
66
90
|
const db = this.openDBs.get(name)!;
|
|
91
|
+
await db.close();
|
|
92
|
+
this.openDBs.delete(name);
|
|
93
|
+
}
|
|
67
94
|
|
|
68
|
-
|
|
69
|
-
|
|
95
|
+
/**
|
|
96
|
+
* Gracefully shuts down everything.
|
|
97
|
+
* - Closes all databases
|
|
98
|
+
* - Terminates IPC worker if running
|
|
99
|
+
*/
|
|
100
|
+
async closeAll(): Promise<void> {
|
|
101
|
+
if (this.closed) return;
|
|
102
|
+
this.closed = true;
|
|
103
|
+
|
|
104
|
+
if (this.ipc) {
|
|
105
|
+
await dbQueue.shutdown();
|
|
106
|
+
return;
|
|
70
107
|
}
|
|
71
108
|
|
|
72
|
-
this.openDBs.
|
|
109
|
+
for (const db of this.openDBs.values()) {
|
|
110
|
+
try {
|
|
111
|
+
await db.close();
|
|
112
|
+
} catch {}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
this.openDBs.clear();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Alias for closeAll() (clean public API)
|
|
120
|
+
*/
|
|
121
|
+
async close(): Promise<void> {
|
|
122
|
+
return this.closeAll();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private _registerShutdownHooks() {
|
|
126
|
+
const shutdown = async () => {
|
|
127
|
+
await this.closeAll();
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
process.on("SIGINT", shutdown);
|
|
131
|
+
process.on("SIGTERM", shutdown);
|
|
132
|
+
process.on("exit", shutdown);
|
|
73
133
|
}
|
|
74
134
|
|
|
135
|
+
private _assertOpen() {
|
|
136
|
+
if (this.closed) {
|
|
137
|
+
throw new Error("LioranManager is closed");
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* -------------------------------- MANAGEMENT -------------------------------- */
|
|
142
|
+
|
|
75
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
|
+
|
|
76
148
|
const oldPath = path.join(this.rootPath, oldName);
|
|
77
149
|
const newPath = path.join(this.rootPath, newName);
|
|
78
150
|
|
|
79
151
|
if (!fs.existsSync(oldPath)) {
|
|
80
152
|
throw new Error(`Database "${oldName}" not found`);
|
|
81
153
|
}
|
|
154
|
+
|
|
82
155
|
if (fs.existsSync(newPath)) {
|
|
83
156
|
throw new Error(`Database "${newName}" already exists`);
|
|
84
157
|
}
|
|
@@ -93,6 +166,10 @@ export class LioranManager {
|
|
|
93
166
|
}
|
|
94
167
|
|
|
95
168
|
async dropDatabase(name: string): Promise<boolean> {
|
|
169
|
+
if (this.ipc) {
|
|
170
|
+
return (await dbQueue.exec("dropDatabase", { name })) as boolean;
|
|
171
|
+
}
|
|
172
|
+
|
|
96
173
|
const dbPath = path.join(this.rootPath, name);
|
|
97
174
|
|
|
98
175
|
if (!fs.existsSync(dbPath)) return false;
|
|
@@ -103,10 +180,64 @@ export class LioranManager {
|
|
|
103
180
|
}
|
|
104
181
|
|
|
105
182
|
async listDatabases(): Promise<string[]> {
|
|
183
|
+
if (this.ipc) {
|
|
184
|
+
return (await dbQueue.exec("listDatabases", {})) as string[];
|
|
185
|
+
}
|
|
186
|
+
|
|
106
187
|
const items = await fs.promises.readdir(this.rootPath, {
|
|
107
188
|
withFileTypes: true
|
|
108
189
|
});
|
|
109
190
|
|
|
110
191
|
return items.filter(i => i.isDirectory()).map(i => i.name);
|
|
111
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
|
+
}
|
|
205
|
+
|
|
206
|
+
/* -------------------------------- IPC PROXY DB -------------------------------- */
|
|
207
|
+
|
|
208
|
+
class IPCDatabase {
|
|
209
|
+
constructor(private name: string) {}
|
|
210
|
+
|
|
211
|
+
collection(name: string) {
|
|
212
|
+
return new IPCCollection(this.name, name);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
class IPCCollection {
|
|
217
|
+
constructor(
|
|
218
|
+
private db: string,
|
|
219
|
+
private col: string
|
|
220
|
+
) {}
|
|
221
|
+
|
|
222
|
+
private call(method: string, params: any[]) {
|
|
223
|
+
return dbQueue.exec("op", {
|
|
224
|
+
db: this.db,
|
|
225
|
+
col: this.col,
|
|
226
|
+
method,
|
|
227
|
+
params
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
insertOne = (doc: any) => this.call("insertOne", [doc]);
|
|
232
|
+
insertMany = (docs: any[]) => this.call("insertMany", [docs]);
|
|
233
|
+
find = (query?: any) => this.call("find", [query]);
|
|
234
|
+
findOne = (query?: any) => this.call("findOne", [query]);
|
|
235
|
+
updateOne = (filter: any, update: any, options?: any) =>
|
|
236
|
+
this.call("updateOne", [filter, update, options]);
|
|
237
|
+
updateMany = (filter: any, update: any) =>
|
|
238
|
+
this.call("updateMany", [filter, update]);
|
|
239
|
+
deleteOne = (filter: any) => this.call("deleteOne", [filter]);
|
|
240
|
+
deleteMany = (filter: any) => this.call("deleteMany", [filter]);
|
|
241
|
+
countDocuments = (filter?: any) =>
|
|
242
|
+
this.call("countDocuments", [filter]);
|
|
112
243
|
}
|
package/src/core/collection.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
1
3
|
import { ClassicLevel } from "classic-level";
|
|
2
4
|
import { matchDocument, applyUpdate } from "./query.js";
|
|
3
5
|
import { v4 as uuid } from "uuid";
|
|
4
6
|
import { encryptData, decryptData } from "../utils/encryption.js";
|
|
7
|
+
import type { ZodSchema } from "zod";
|
|
8
|
+
import { validateSchema } from "../utils/schema.js";
|
|
5
9
|
|
|
6
10
|
export interface UpdateOptions {
|
|
7
11
|
upsert?: boolean;
|
|
@@ -11,153 +15,315 @@ export class Collection<T = any> {
|
|
|
11
15
|
dir: string;
|
|
12
16
|
db: ClassicLevel<string, string>;
|
|
13
17
|
private queue: Promise<any>;
|
|
18
|
+
private walPath: string;
|
|
19
|
+
private schema?: ZodSchema<T>;
|
|
14
20
|
|
|
15
|
-
constructor(dir: string) {
|
|
21
|
+
constructor(dir: string, schema?: ZodSchema<T>) {
|
|
16
22
|
this.dir = dir;
|
|
17
23
|
this.db = new ClassicLevel(dir);
|
|
18
24
|
this.queue = Promise.resolve();
|
|
25
|
+
this.walPath = path.join(dir, "__wal.log");
|
|
26
|
+
this.schema = schema;
|
|
27
|
+
|
|
28
|
+
this.recoverFromWAL().catch(console.error);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
setSchema(schema: ZodSchema<T>) {
|
|
32
|
+
this.schema = schema;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private validate(doc: any): T {
|
|
36
|
+
return this.schema ? validateSchema(this.schema, doc) : doc;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* ---------------- WAL ---------------- */
|
|
40
|
+
|
|
41
|
+
private async writeWAL(entry: any) {
|
|
42
|
+
await fs.promises.appendFile(
|
|
43
|
+
this.walPath,
|
|
44
|
+
JSON.stringify(entry) + "\n"
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private async clearWAL() {
|
|
49
|
+
if (fs.existsSync(this.walPath)) {
|
|
50
|
+
await fs.promises.unlink(this.walPath);
|
|
51
|
+
}
|
|
19
52
|
}
|
|
20
53
|
|
|
54
|
+
private async recoverFromWAL() {
|
|
55
|
+
if (!fs.existsSync(this.walPath)) return;
|
|
56
|
+
|
|
57
|
+
const lines = (await fs.promises.readFile(this.walPath, "utf8"))
|
|
58
|
+
.split("\n")
|
|
59
|
+
.filter(Boolean);
|
|
60
|
+
|
|
61
|
+
for (const line of lines) {
|
|
62
|
+
try {
|
|
63
|
+
const { op, args } = JSON.parse(line);
|
|
64
|
+
await this._exec(op, args, false);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
console.error("WAL recovery failed:", err);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
await this.clearWAL();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* ---------------- Queue ---------------- */
|
|
74
|
+
|
|
21
75
|
private _enqueue<R>(task: () => Promise<R>): Promise<R> {
|
|
22
76
|
this.queue = this.queue.then(task).catch(console.error);
|
|
23
77
|
return this.queue;
|
|
24
78
|
}
|
|
25
79
|
|
|
80
|
+
/* ---------------- Core Executor ---------------- */
|
|
81
|
+
|
|
82
|
+
private async _exec(op: string, args: any[], log = true) {
|
|
83
|
+
if (log) await this.writeWAL({ op, args });
|
|
84
|
+
|
|
85
|
+
let result: any;
|
|
86
|
+
|
|
87
|
+
switch (op) {
|
|
88
|
+
case "insertOne":
|
|
89
|
+
result = await this._insertOne(args[0]);
|
|
90
|
+
break;
|
|
91
|
+
|
|
92
|
+
case "insertMany":
|
|
93
|
+
result = await this._insertMany(args[0]);
|
|
94
|
+
break;
|
|
95
|
+
|
|
96
|
+
case "find":
|
|
97
|
+
result = await this._find(args[0]);
|
|
98
|
+
break;
|
|
99
|
+
|
|
100
|
+
case "findOne":
|
|
101
|
+
result = await this._findOne(args[0]);
|
|
102
|
+
break;
|
|
103
|
+
|
|
104
|
+
case "updateOne":
|
|
105
|
+
result = await this._updateOne(args[0], args[1], args[2]);
|
|
106
|
+
break;
|
|
107
|
+
|
|
108
|
+
case "updateMany":
|
|
109
|
+
result = await this._updateMany(args[0], args[1]);
|
|
110
|
+
break;
|
|
111
|
+
|
|
112
|
+
case "deleteOne":
|
|
113
|
+
result = await this._deleteOne(args[0]);
|
|
114
|
+
break;
|
|
115
|
+
|
|
116
|
+
case "deleteMany":
|
|
117
|
+
result = await this._deleteMany(args[0]);
|
|
118
|
+
break;
|
|
119
|
+
|
|
120
|
+
case "countDocuments":
|
|
121
|
+
result = await this._countDocuments(args[0]);
|
|
122
|
+
break;
|
|
123
|
+
|
|
124
|
+
default:
|
|
125
|
+
throw new Error(`Unknown operation: ${op}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (log) await this.clearWAL();
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/* ---------------- Public API ---------------- */
|
|
133
|
+
|
|
26
134
|
async close(): Promise<void> {
|
|
27
135
|
try {
|
|
28
136
|
await this.db.close();
|
|
29
137
|
} catch {}
|
|
30
138
|
}
|
|
31
139
|
|
|
32
|
-
|
|
33
|
-
return this._enqueue(
|
|
34
|
-
|
|
35
|
-
const final = { _id, ...doc } as T;
|
|
36
|
-
await this.db.put(String(_id), encryptData(final));
|
|
37
|
-
return final;
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async insertMany(docs: (T & { _id?: string })[] = []): Promise<T[]> {
|
|
42
|
-
return this._enqueue(async () => {
|
|
43
|
-
const ops: Array<{ type: "put"; key: string; value: string }> = [];
|
|
44
|
-
const out: T[] = [];
|
|
45
|
-
|
|
46
|
-
for (const d of docs) {
|
|
47
|
-
const _id = d._id ?? uuid();
|
|
48
|
-
const final = { _id, ...d } as T;
|
|
49
|
-
ops.push({
|
|
50
|
-
type: "put",
|
|
51
|
-
key: String(_id),
|
|
52
|
-
value: encryptData(final)
|
|
53
|
-
});
|
|
54
|
-
out.push(final);
|
|
55
|
-
}
|
|
140
|
+
insertOne(doc: T & { _id?: string }): Promise<T> {
|
|
141
|
+
return this._enqueue(() => this._exec("insertOne", [doc]));
|
|
142
|
+
}
|
|
56
143
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
});
|
|
144
|
+
insertMany(docs: (T & { _id?: string })[] = []): Promise<T[]> {
|
|
145
|
+
return this._enqueue(() => this._exec("insertMany", [docs]));
|
|
60
146
|
}
|
|
61
147
|
|
|
62
|
-
|
|
63
|
-
return this._enqueue(
|
|
64
|
-
const out: T[] = [];
|
|
65
|
-
for await (const [, enc] of this.db.iterator()) {
|
|
66
|
-
const value = decryptData(enc);
|
|
67
|
-
if (matchDocument(value, query)) out.push(value);
|
|
68
|
-
}
|
|
69
|
-
return out;
|
|
70
|
-
});
|
|
148
|
+
find(query: any = {}): Promise<T[]> {
|
|
149
|
+
return this._enqueue(() => this._exec("find", [query]));
|
|
71
150
|
}
|
|
72
151
|
|
|
73
|
-
|
|
74
|
-
return this._enqueue(
|
|
75
|
-
for await (const [, enc] of this.db.iterator()) {
|
|
76
|
-
const value = decryptData(enc);
|
|
77
|
-
if (matchDocument(value, query)) return value;
|
|
78
|
-
}
|
|
79
|
-
return null;
|
|
80
|
-
});
|
|
152
|
+
findOne(query: any = {}): Promise<T | null> {
|
|
153
|
+
return this._enqueue(() => this._exec("findOne", [query]));
|
|
81
154
|
}
|
|
82
155
|
|
|
83
|
-
|
|
156
|
+
updateOne(
|
|
84
157
|
filter: any = {},
|
|
85
158
|
update: any = {},
|
|
86
159
|
options: UpdateOptions = { upsert: false }
|
|
87
160
|
): Promise<T | null> {
|
|
88
|
-
return this._enqueue(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const updated = applyUpdate(value, update);
|
|
93
|
-
updated._id = value._id;
|
|
94
|
-
await this.db.put(key, encryptData(updated));
|
|
95
|
-
return updated;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
161
|
+
return this._enqueue(() =>
|
|
162
|
+
this._exec("updateOne", [filter, update, options])
|
|
163
|
+
);
|
|
164
|
+
}
|
|
98
165
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
166
|
+
updateMany(filter: any = {}, update: any = {}): Promise<T[]> {
|
|
167
|
+
return this._enqueue(() =>
|
|
168
|
+
this._exec("updateMany", [filter, update])
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
deleteOne(filter: any = {}): Promise<boolean> {
|
|
173
|
+
return this._enqueue(() => this._exec("deleteOne", [filter]));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
deleteMany(filter: any = {}): Promise<number> {
|
|
177
|
+
return this._enqueue(() => this._exec("deleteMany", [filter]));
|
|
178
|
+
}
|
|
105
179
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
180
|
+
countDocuments(filter: any = {}): Promise<number> {
|
|
181
|
+
return this._enqueue(() =>
|
|
182
|
+
this._exec("countDocuments", [filter])
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/* ---------------- Internal Ops ---------------- */
|
|
187
|
+
|
|
188
|
+
private async _insertOne(doc: T & { _id?: string }): Promise<T> {
|
|
189
|
+
const _id = doc._id ?? uuid();
|
|
190
|
+
const final = this.validate({ _id, ...doc });
|
|
191
|
+
|
|
192
|
+
await this.db.put(String(_id), encryptData(final));
|
|
193
|
+
return final;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private async _insertMany(
|
|
197
|
+
docs: (T & { _id?: string })[]
|
|
198
|
+
): Promise<T[]> {
|
|
199
|
+
const ops: Array<{ type: "put"; key: string; value: string }> = [];
|
|
200
|
+
const out: T[] = [];
|
|
201
|
+
|
|
202
|
+
for (const d of docs) {
|
|
203
|
+
const _id = d._id ?? uuid();
|
|
204
|
+
const final = this.validate({ _id, ...d });
|
|
205
|
+
|
|
206
|
+
ops.push({
|
|
207
|
+
type: "put",
|
|
208
|
+
key: String(_id),
|
|
209
|
+
value: encryptData(final)
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
out.push(final);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
await this.db.batch(ops);
|
|
216
|
+
return out;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private async _updateOne(
|
|
220
|
+
filter: any,
|
|
221
|
+
update: any,
|
|
222
|
+
options: UpdateOptions
|
|
223
|
+
): Promise<T | null> {
|
|
224
|
+
for await (const [key, enc] of this.db.iterator()) {
|
|
225
|
+
const value = decryptData(enc);
|
|
226
|
+
|
|
227
|
+
if (matchDocument(value, filter)) {
|
|
228
|
+
const updated = applyUpdate(value, update);
|
|
229
|
+
updated._id = value._id;
|
|
230
|
+
|
|
231
|
+
const validated = this.validate(updated);
|
|
232
|
+
await this.db.put(key, encryptData(validated));
|
|
233
|
+
|
|
234
|
+
return validated;
|
|
121
235
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (options?.upsert) {
|
|
239
|
+
const doc = applyUpdate(filter, update);
|
|
240
|
+
doc._id ??= uuid();
|
|
241
|
+
|
|
242
|
+
const validated = this.validate(doc);
|
|
243
|
+
await this.db.put(String(doc._id), encryptData(validated));
|
|
244
|
+
|
|
245
|
+
return validated;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private async _updateMany(filter: any, update: any): Promise<T[]> {
|
|
252
|
+
const updated: T[] = [];
|
|
253
|
+
|
|
254
|
+
for await (const [key, enc] of this.db.iterator()) {
|
|
255
|
+
const value = decryptData(enc);
|
|
256
|
+
|
|
257
|
+
if (matchDocument(value, filter)) {
|
|
258
|
+
const doc = applyUpdate(value, update);
|
|
259
|
+
doc._id = value._id;
|
|
260
|
+
|
|
261
|
+
const validated = this.validate(doc);
|
|
262
|
+
await this.db.put(key, encryptData(validated));
|
|
263
|
+
|
|
264
|
+
updated.push(validated);
|
|
134
265
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return updated;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private async _find(query: any): Promise<T[]> {
|
|
272
|
+
const out: T[] = [];
|
|
273
|
+
|
|
274
|
+
for await (const [, enc] of this.db.iterator()) {
|
|
275
|
+
const value = decryptData(enc);
|
|
276
|
+
if (matchDocument(value, query)) out.push(value);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return out;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private async _findOne(query: any): Promise<T | null> {
|
|
283
|
+
for await (const [, enc] of this.db.iterator()) {
|
|
284
|
+
const value = decryptData(enc);
|
|
285
|
+
if (matchDocument(value, query)) return value;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private async _deleteOne(filter: any): Promise<boolean> {
|
|
292
|
+
for await (const [key, enc] of this.db.iterator()) {
|
|
293
|
+
const value = decryptData(enc);
|
|
294
|
+
|
|
295
|
+
if (matchDocument(value, filter)) {
|
|
296
|
+
await this.db.del(key);
|
|
297
|
+
return true;
|
|
148
298
|
}
|
|
149
|
-
|
|
150
|
-
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return false;
|
|
151
302
|
}
|
|
152
303
|
|
|
153
|
-
async
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
304
|
+
private async _deleteMany(filter: any): Promise<number> {
|
|
305
|
+
let count = 0;
|
|
306
|
+
|
|
307
|
+
for await (const [key, enc] of this.db.iterator()) {
|
|
308
|
+
const value = decryptData(enc);
|
|
309
|
+
|
|
310
|
+
if (matchDocument(value, filter)) {
|
|
311
|
+
await this.db.del(key);
|
|
312
|
+
count++;
|
|
159
313
|
}
|
|
160
|
-
|
|
161
|
-
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return count;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
private async _countDocuments(filter: any): Promise<number> {
|
|
320
|
+
let c = 0;
|
|
321
|
+
|
|
322
|
+
for await (const [, enc] of this.db.iterator()) {
|
|
323
|
+
const value = decryptData(enc);
|
|
324
|
+
if (matchDocument(value, filter)) c++;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return c;
|
|
162
328
|
}
|
|
163
329
|
}
|