@liorandb/core 1.1.0 → 1.1.1
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-2FSI7HX7.js +1689 -0
- package/dist/index.d.ts +21 -11
- package/dist/index.js +5 -1828
- package/dist/queue-YILKSUEI.js +179 -0
- package/package.json +1 -1
- package/src/LioranManager.ts +99 -44
- package/src/core/collection.ts +39 -8
- package/src/core/database.ts +58 -33
- package/src/core/wal.ts +42 -13
- package/src/ipc/index.ts +41 -19
- package/src/ipc/pool.ts +136 -0
- package/src/ipc/queue.ts +85 -31
- package/src/ipc/worker.ts +72 -0
- package/src/ipc/client.ts +0 -85
- package/src/ipc/server.ts +0 -147
- package/src/ipc/socketPath.ts +0 -10
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LioranManager,
|
|
3
|
+
getDefaultRootPath
|
|
4
|
+
} from "./chunk-2FSI7HX7.js";
|
|
5
|
+
|
|
6
|
+
// src/ipc/pool.ts
|
|
7
|
+
import { Worker } from "worker_threads";
|
|
8
|
+
import os from "os";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import { fileURLToPath } from "url";
|
|
11
|
+
var IPCWorkerPool = class {
|
|
12
|
+
workers = [];
|
|
13
|
+
workerCount;
|
|
14
|
+
shuttingDown = false;
|
|
15
|
+
rrIndex = 0;
|
|
16
|
+
constructor() {
|
|
17
|
+
this.workerCount = Math.max(2, os.cpus().length);
|
|
18
|
+
}
|
|
19
|
+
/* -------------------------------------------------- */
|
|
20
|
+
/* START POOL */
|
|
21
|
+
/* -------------------------------------------------- */
|
|
22
|
+
start() {
|
|
23
|
+
for (let i = 0; i < this.workerCount; i++) {
|
|
24
|
+
this.spawnWorker();
|
|
25
|
+
}
|
|
26
|
+
console.log(
|
|
27
|
+
`[WorkerPool] Started ${this.workerCount} worker threads`
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
/* -------------------------------------------------- */
|
|
31
|
+
/* SPAWN WORKER */
|
|
32
|
+
/* -------------------------------------------------- */
|
|
33
|
+
spawnWorker() {
|
|
34
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
35
|
+
const __dirname = path.dirname(__filename);
|
|
36
|
+
const workerFile = path.join(__dirname, "worker.js");
|
|
37
|
+
const worker = new Worker(workerFile);
|
|
38
|
+
worker.on("exit", (code) => {
|
|
39
|
+
if (this.shuttingDown) return;
|
|
40
|
+
console.error(
|
|
41
|
+
`[WorkerPool] Worker exited (code=${code}). Restarting...`
|
|
42
|
+
);
|
|
43
|
+
this.workers = this.workers.filter((w) => w !== worker);
|
|
44
|
+
setTimeout(() => {
|
|
45
|
+
this.spawnWorker();
|
|
46
|
+
}, 500);
|
|
47
|
+
});
|
|
48
|
+
worker.on("error", (err) => {
|
|
49
|
+
console.error("[WorkerPool] Worker error:", err);
|
|
50
|
+
});
|
|
51
|
+
this.workers.push(worker);
|
|
52
|
+
}
|
|
53
|
+
/* -------------------------------------------------- */
|
|
54
|
+
/* EXECUTE TASK */
|
|
55
|
+
/* -------------------------------------------------- */
|
|
56
|
+
exec(task) {
|
|
57
|
+
if (this.workers.length === 0) {
|
|
58
|
+
throw new Error("No workers available");
|
|
59
|
+
}
|
|
60
|
+
const worker = this.workers[this.rrIndex];
|
|
61
|
+
this.rrIndex = (this.rrIndex + 1) % this.workers.length;
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
const id = Date.now() + Math.random();
|
|
64
|
+
const messageHandler = (msg) => {
|
|
65
|
+
if (msg.id !== id) return;
|
|
66
|
+
worker.off("message", messageHandler);
|
|
67
|
+
if (msg.ok) resolve(msg.result);
|
|
68
|
+
else reject(new Error(msg.error));
|
|
69
|
+
};
|
|
70
|
+
worker.on("message", messageHandler);
|
|
71
|
+
worker.postMessage({
|
|
72
|
+
id,
|
|
73
|
+
task
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/* -------------------------------------------------- */
|
|
78
|
+
/* SHUTDOWN */
|
|
79
|
+
/* -------------------------------------------------- */
|
|
80
|
+
async shutdown() {
|
|
81
|
+
this.shuttingDown = true;
|
|
82
|
+
console.log("[WorkerPool] Shutting down worker threads...");
|
|
83
|
+
for (const worker of this.workers) {
|
|
84
|
+
try {
|
|
85
|
+
await worker.terminate();
|
|
86
|
+
} catch (err) {
|
|
87
|
+
console.error("[WorkerPool] Worker terminate error:", err);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
this.workers = [];
|
|
91
|
+
}
|
|
92
|
+
/* -------------------------------------------------- */
|
|
93
|
+
/* INFO */
|
|
94
|
+
/* -------------------------------------------------- */
|
|
95
|
+
get size() {
|
|
96
|
+
return this.workerCount;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// src/ipc/queue.ts
|
|
101
|
+
var DBQueue = class {
|
|
102
|
+
constructor(rootPath = getDefaultRootPath()) {
|
|
103
|
+
this.rootPath = rootPath;
|
|
104
|
+
this.manager = new LioranManager({ rootPath });
|
|
105
|
+
this.pool = new IPCWorkerPool();
|
|
106
|
+
this.pool.start();
|
|
107
|
+
}
|
|
108
|
+
manager;
|
|
109
|
+
pool;
|
|
110
|
+
destroyed = false;
|
|
111
|
+
/* -------------------------------- EXEC -------------------------------- */
|
|
112
|
+
async exec(action, args) {
|
|
113
|
+
if (this.destroyed) {
|
|
114
|
+
throw new Error("DBQueue already shutdown");
|
|
115
|
+
}
|
|
116
|
+
switch (action) {
|
|
117
|
+
/* ---------------- DB ---------------- */
|
|
118
|
+
case "db":
|
|
119
|
+
await this.manager.db(args.db);
|
|
120
|
+
return true;
|
|
121
|
+
/* ---------------- CRUD OPS ---------------- */
|
|
122
|
+
case "op": {
|
|
123
|
+
const { db, col, method, params } = args;
|
|
124
|
+
const collection = (await this.manager.db(db)).collection(col);
|
|
125
|
+
return await collection[method](...params);
|
|
126
|
+
}
|
|
127
|
+
/* ---------------- INDEX OPS ---------------- */
|
|
128
|
+
case "index": {
|
|
129
|
+
const { db, col, method, params } = args;
|
|
130
|
+
const collection = (await this.manager.db(db)).collection(col);
|
|
131
|
+
return await collection[method](...params);
|
|
132
|
+
}
|
|
133
|
+
/* ---------------- COMPACTION ---------------- */
|
|
134
|
+
case "compact:collection": {
|
|
135
|
+
const { db, col } = args;
|
|
136
|
+
const collection = (await this.manager.db(db)).collection(col);
|
|
137
|
+
await collection.compact();
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
case "compact:db": {
|
|
141
|
+
const { db } = args;
|
|
142
|
+
const database = await this.manager.db(db);
|
|
143
|
+
await database.compactAll();
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
case "compact:all": {
|
|
147
|
+
for (const db of this.manager.openDBs.values()) {
|
|
148
|
+
await db.compactAll();
|
|
149
|
+
}
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
/* ---------------- SNAPSHOT ---------------- */
|
|
153
|
+
case "snapshot":
|
|
154
|
+
await this.manager.snapshot(args.path);
|
|
155
|
+
return true;
|
|
156
|
+
case "restore":
|
|
157
|
+
await this.manager.restore(args.path);
|
|
158
|
+
return true;
|
|
159
|
+
/* ---------------- CONTROL ---------------- */
|
|
160
|
+
case "shutdown":
|
|
161
|
+
await this.shutdown();
|
|
162
|
+
return true;
|
|
163
|
+
default:
|
|
164
|
+
throw new Error(`Unknown action: ${action}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/* ------------------------------ SHUTDOWN ------------------------------ */
|
|
168
|
+
async shutdown() {
|
|
169
|
+
if (this.destroyed) return;
|
|
170
|
+
this.destroyed = true;
|
|
171
|
+
await this.manager.closeAll();
|
|
172
|
+
await this.pool.shutdown();
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
var dbQueue = new DBQueue();
|
|
176
|
+
export {
|
|
177
|
+
DBQueue,
|
|
178
|
+
dbQueue
|
|
179
|
+
};
|
package/package.json
CHANGED
package/src/LioranManager.ts
CHANGED
|
@@ -4,29 +4,34 @@ import process from "process";
|
|
|
4
4
|
import { LioranDB } from "./core/database.js";
|
|
5
5
|
import { setEncryptionKey } from "./utils/encryption.js";
|
|
6
6
|
import { getDefaultRootPath } from "./utils/rootpath.js";
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
|
|
8
|
+
/* ---------------- PROCESS MODE ---------------- */
|
|
9
9
|
|
|
10
10
|
enum ProcessMode {
|
|
11
11
|
PRIMARY = "primary",
|
|
12
|
-
CLIENT = "client"
|
|
12
|
+
CLIENT = "client",
|
|
13
|
+
READONLY = "readonly"
|
|
13
14
|
}
|
|
14
15
|
|
|
16
|
+
/* ---------------- OPTIONS ---------------- */
|
|
17
|
+
|
|
15
18
|
export interface LioranManagerOptions {
|
|
16
19
|
rootPath?: string;
|
|
17
20
|
encryptionKey?: string | Buffer;
|
|
21
|
+
ipc?: "primary" | "client" | "readonly";
|
|
18
22
|
}
|
|
19
23
|
|
|
24
|
+
/* ---------------- MANAGER ---------------- */
|
|
25
|
+
|
|
20
26
|
export class LioranManager {
|
|
21
27
|
rootPath: string;
|
|
22
28
|
openDBs: Map<string, LioranDB>;
|
|
23
29
|
private closed = false;
|
|
24
30
|
private mode: ProcessMode;
|
|
25
31
|
private lockFd?: number;
|
|
26
|
-
private ipcServer?: IPCServer;
|
|
27
32
|
|
|
28
33
|
constructor(options: LioranManagerOptions = {}) {
|
|
29
|
-
const { rootPath, encryptionKey } = options;
|
|
34
|
+
const { rootPath, encryptionKey, ipc } = options;
|
|
30
35
|
|
|
31
36
|
this.rootPath = rootPath || getDefaultRootPath();
|
|
32
37
|
|
|
@@ -40,17 +45,48 @@ export class LioranManager {
|
|
|
40
45
|
|
|
41
46
|
this.openDBs = new Map();
|
|
42
47
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
/* ---------------- MODE RESOLUTION ---------------- */
|
|
49
|
+
|
|
50
|
+
if (ipc === "readonly") {
|
|
51
|
+
this.mode = ProcessMode.READONLY;
|
|
52
|
+
} else if (ipc === "client") {
|
|
53
|
+
this.mode = ProcessMode.CLIENT;
|
|
54
|
+
} else if (ipc === "primary") {
|
|
55
|
+
this.mode = ProcessMode.PRIMARY;
|
|
56
|
+
this.tryAcquireLock();
|
|
57
|
+
} else {
|
|
58
|
+
// auto-detect (default behavior)
|
|
59
|
+
this.mode = this.tryAcquireLock()
|
|
60
|
+
? ProcessMode.PRIMARY
|
|
61
|
+
: ProcessMode.CLIENT;
|
|
62
|
+
}
|
|
46
63
|
|
|
47
64
|
if (this.mode === ProcessMode.PRIMARY) {
|
|
48
|
-
this.ipcServer = new IPCServer(this, this.rootPath);
|
|
49
|
-
this.ipcServer.start();
|
|
50
65
|
this._registerShutdownHooks();
|
|
51
66
|
}
|
|
52
67
|
}
|
|
53
68
|
|
|
69
|
+
/* ---------------- MODE HELPERS ---------------- */
|
|
70
|
+
|
|
71
|
+
isPrimary() {
|
|
72
|
+
return this.mode === ProcessMode.PRIMARY;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
isClient() {
|
|
76
|
+
return this.mode === ProcessMode.CLIENT;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
isReadOnly() {
|
|
80
|
+
return this.mode === ProcessMode.READONLY;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* ---------------- QUEUE HELPER ---------------- */
|
|
84
|
+
|
|
85
|
+
private async getQueue() {
|
|
86
|
+
const { dbQueue } = await import("./ipc/queue.js");
|
|
87
|
+
return dbQueue;
|
|
88
|
+
}
|
|
89
|
+
|
|
54
90
|
/* ---------------- LOCK MANAGEMENT ---------------- */
|
|
55
91
|
|
|
56
92
|
private isProcessAlive(pid: number): boolean {
|
|
@@ -78,7 +114,7 @@ export class LioranManager {
|
|
|
78
114
|
fs.writeSync(this.lockFd, String(process.pid));
|
|
79
115
|
return true;
|
|
80
116
|
}
|
|
81
|
-
} catch {
|
|
117
|
+
} catch {}
|
|
82
118
|
return false;
|
|
83
119
|
}
|
|
84
120
|
}
|
|
@@ -87,9 +123,11 @@ export class LioranManager {
|
|
|
87
123
|
|
|
88
124
|
async db(name: string): Promise<LioranDB> {
|
|
89
125
|
if (this.mode === ProcessMode.CLIENT) {
|
|
90
|
-
await
|
|
126
|
+
const queue = await this.getQueue();
|
|
127
|
+
await queue.exec("db", { db: name });
|
|
91
128
|
return new IPCDatabase(name) as any;
|
|
92
129
|
}
|
|
130
|
+
|
|
93
131
|
return this.openDatabase(name);
|
|
94
132
|
}
|
|
95
133
|
|
|
@@ -108,44 +146,51 @@ export class LioranManager {
|
|
|
108
146
|
return db;
|
|
109
147
|
}
|
|
110
148
|
|
|
111
|
-
/* ---------------- SNAPSHOT
|
|
149
|
+
/* ---------------- SNAPSHOT ---------------- */
|
|
112
150
|
|
|
113
|
-
/**
|
|
114
|
-
* Create TAR snapshot of full DB directory
|
|
115
|
-
*/
|
|
116
151
|
async snapshot(snapshotPath: string) {
|
|
117
152
|
if (this.mode === ProcessMode.CLIENT) {
|
|
118
|
-
|
|
153
|
+
const queue = await this.getQueue();
|
|
154
|
+
return queue.exec("snapshot", { path: snapshotPath });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (this.mode === ProcessMode.READONLY) {
|
|
158
|
+
throw new Error("Snapshot not allowed in readonly mode");
|
|
119
159
|
}
|
|
120
160
|
|
|
121
|
-
// Flush all DBs safely
|
|
122
161
|
for (const db of this.openDBs.values()) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
162
|
+
try {
|
|
163
|
+
await db.close();
|
|
164
|
+
} catch {}
|
|
126
165
|
}
|
|
127
166
|
|
|
128
|
-
// Ensure backup directory exists
|
|
129
167
|
fs.mkdirSync(path.dirname(snapshotPath), { recursive: true });
|
|
130
168
|
|
|
131
169
|
const tar = await import("tar");
|
|
132
170
|
|
|
133
|
-
await tar.c(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
171
|
+
await tar.c(
|
|
172
|
+
{
|
|
173
|
+
gzip: true,
|
|
174
|
+
file: snapshotPath,
|
|
175
|
+
cwd: this.rootPath,
|
|
176
|
+
portable: true
|
|
177
|
+
},
|
|
178
|
+
["./"]
|
|
179
|
+
);
|
|
139
180
|
|
|
140
181
|
return true;
|
|
141
182
|
}
|
|
142
183
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
*/
|
|
184
|
+
/* ---------------- RESTORE ---------------- */
|
|
185
|
+
|
|
146
186
|
async restore(snapshotPath: string) {
|
|
147
187
|
if (this.mode === ProcessMode.CLIENT) {
|
|
148
|
-
|
|
188
|
+
const queue = await this.getQueue();
|
|
189
|
+
return queue.exec("restore", { path: snapshotPath });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (this.mode === ProcessMode.READONLY) {
|
|
193
|
+
throw new Error("Restore not allowed in readonly mode");
|
|
149
194
|
}
|
|
150
195
|
|
|
151
196
|
await this.closeAll();
|
|
@@ -171,22 +216,26 @@ export class LioranManager {
|
|
|
171
216
|
this.closed = true;
|
|
172
217
|
|
|
173
218
|
if (this.mode === ProcessMode.CLIENT) {
|
|
174
|
-
await
|
|
219
|
+
const queue = await this.getQueue();
|
|
220
|
+
await queue.shutdown();
|
|
175
221
|
return;
|
|
176
222
|
}
|
|
177
223
|
|
|
178
224
|
for (const db of this.openDBs.values()) {
|
|
179
|
-
try {
|
|
225
|
+
try {
|
|
226
|
+
await db.close();
|
|
227
|
+
} catch {}
|
|
180
228
|
}
|
|
181
229
|
|
|
182
230
|
this.openDBs.clear();
|
|
183
231
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
232
|
+
// Only primary owns lock
|
|
233
|
+
if (this.mode === ProcessMode.PRIMARY) {
|
|
234
|
+
try {
|
|
235
|
+
if (this.lockFd) fs.closeSync(this.lockFd);
|
|
236
|
+
fs.unlinkSync(path.join(this.rootPath, ".lioran.lock"));
|
|
237
|
+
} catch {}
|
|
238
|
+
}
|
|
190
239
|
}
|
|
191
240
|
|
|
192
241
|
async close(): Promise<void> {
|
|
@@ -213,7 +262,7 @@ export class LioranManager {
|
|
|
213
262
|
/* ---------------- IPC PROXY DB ---------------- */
|
|
214
263
|
|
|
215
264
|
class IPCDatabase {
|
|
216
|
-
constructor(private name: string) {
|
|
265
|
+
constructor(private name: string) {}
|
|
217
266
|
|
|
218
267
|
collection(name: string) {
|
|
219
268
|
return new IPCCollection(this.name, name);
|
|
@@ -224,10 +273,16 @@ class IPCCollection {
|
|
|
224
273
|
constructor(
|
|
225
274
|
private db: string,
|
|
226
275
|
private col: string
|
|
227
|
-
) {
|
|
276
|
+
) {}
|
|
277
|
+
|
|
278
|
+
private async getQueue() {
|
|
279
|
+
const { dbQueue } = await import("./ipc/queue.js");
|
|
280
|
+
return dbQueue;
|
|
281
|
+
}
|
|
228
282
|
|
|
229
|
-
private call(method: string, params: any[]) {
|
|
230
|
-
|
|
283
|
+
private async call(method: string, params: any[]) {
|
|
284
|
+
const queue = await this.getQueue();
|
|
285
|
+
return queue.exec("op", {
|
|
231
286
|
db: this.db,
|
|
232
287
|
col: this.col,
|
|
233
288
|
method,
|
package/src/core/collection.ts
CHANGED
|
@@ -23,6 +23,10 @@ export interface UpdateOptions {
|
|
|
23
23
|
upsert?: boolean;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
export interface CollectionOptions {
|
|
27
|
+
readonly?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
26
30
|
export class Collection<T = any> {
|
|
27
31
|
dir: string;
|
|
28
32
|
db: ClassicLevel<string, string>;
|
|
@@ -33,16 +37,31 @@ export class Collection<T = any> {
|
|
|
33
37
|
private migrations: Migration<T>[] = [];
|
|
34
38
|
|
|
35
39
|
private indexes = new Map<string, Index>();
|
|
40
|
+
private readonlyMode: boolean;
|
|
36
41
|
|
|
37
42
|
constructor(
|
|
38
43
|
dir: string,
|
|
39
44
|
schema?: ZodSchema<T>,
|
|
40
|
-
schemaVersion: number = 1
|
|
45
|
+
schemaVersion: number = 1,
|
|
46
|
+
options?: CollectionOptions
|
|
41
47
|
) {
|
|
42
48
|
this.dir = dir;
|
|
43
|
-
this.db = new ClassicLevel(dir, { valueEncoding: "utf8" });
|
|
44
49
|
this.schema = schema;
|
|
45
50
|
this.schemaVersion = schemaVersion;
|
|
51
|
+
this.readonlyMode = options?.readonly ?? false;
|
|
52
|
+
|
|
53
|
+
this.db = new ClassicLevel(dir, {
|
|
54
|
+
valueEncoding: "utf8",
|
|
55
|
+
readOnly: this.readonlyMode
|
|
56
|
+
} as any);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* ===================== INTERNAL ===================== */
|
|
60
|
+
|
|
61
|
+
private assertWritable() {
|
|
62
|
+
if (this.readonlyMode) {
|
|
63
|
+
throw new Error("Collection is in readonly replica mode");
|
|
64
|
+
}
|
|
46
65
|
}
|
|
47
66
|
|
|
48
67
|
/* ===================== SCHEMA ===================== */
|
|
@@ -106,6 +125,7 @@ export class Collection<T = any> {
|
|
|
106
125
|
}
|
|
107
126
|
|
|
108
127
|
private async _updateIndexes(oldDoc: any, newDoc: any) {
|
|
128
|
+
if (this.readonlyMode) return;
|
|
109
129
|
for (const index of this.indexes.values()) {
|
|
110
130
|
await index.update(oldDoc, newDoc);
|
|
111
131
|
}
|
|
@@ -114,6 +134,8 @@ export class Collection<T = any> {
|
|
|
114
134
|
/* ===================== COMPACTION ===================== */
|
|
115
135
|
|
|
116
136
|
async compact(): Promise<void> {
|
|
137
|
+
this.assertWritable();
|
|
138
|
+
|
|
117
139
|
return this._enqueue(async () => {
|
|
118
140
|
try { await this.db.close(); } catch {}
|
|
119
141
|
|
|
@@ -145,6 +167,8 @@ export class Collection<T = any> {
|
|
|
145
167
|
/* ===================== STORAGE ===================== */
|
|
146
168
|
|
|
147
169
|
private async _insertOne(doc: any) {
|
|
170
|
+
this.assertWritable();
|
|
171
|
+
|
|
148
172
|
const _id = doc._id ?? uuid();
|
|
149
173
|
const final = this.validate({
|
|
150
174
|
_id,
|
|
@@ -159,6 +183,8 @@ export class Collection<T = any> {
|
|
|
159
183
|
}
|
|
160
184
|
|
|
161
185
|
private async _insertMany(docs: any[]) {
|
|
186
|
+
this.assertWritable();
|
|
187
|
+
|
|
162
188
|
const batch: any[] = [];
|
|
163
189
|
const out = [];
|
|
164
190
|
|
|
@@ -214,14 +240,13 @@ export class Collection<T = any> {
|
|
|
214
240
|
}
|
|
215
241
|
|
|
216
242
|
private async _readAndMigrate(id: string) {
|
|
217
|
-
const enc = await this.db.get(id);
|
|
243
|
+
const enc = await this.db.get(id).catch(() => null);
|
|
218
244
|
if (!enc) return null;
|
|
219
245
|
|
|
220
246
|
const raw = decryptData(enc);
|
|
221
247
|
const migrated = this.migrateIfNeeded(raw);
|
|
222
248
|
|
|
223
|
-
|
|
224
|
-
if (raw.__v !== this.schemaVersion) {
|
|
249
|
+
if (!this.readonlyMode && raw.__v !== this.schemaVersion) {
|
|
225
250
|
await this.db.put(id, encryptData(migrated));
|
|
226
251
|
await this._updateIndexes(raw, migrated);
|
|
227
252
|
}
|
|
@@ -247,9 +272,7 @@ export class Collection<T = any> {
|
|
|
247
272
|
|
|
248
273
|
private async _findOne(query: any) {
|
|
249
274
|
if (query?._id) {
|
|
250
|
-
|
|
251
|
-
return await this._readAndMigrate(String(query._id));
|
|
252
|
-
} catch { return null; }
|
|
275
|
+
return this._readAndMigrate(String(query._id));
|
|
253
276
|
}
|
|
254
277
|
|
|
255
278
|
const ids = await this._getCandidateIds(query);
|
|
@@ -285,6 +308,8 @@ export class Collection<T = any> {
|
|
|
285
308
|
/* ===================== UPDATE ===================== */
|
|
286
309
|
|
|
287
310
|
private async _updateOne(filter: any, update: any, options: UpdateOptions) {
|
|
311
|
+
this.assertWritable();
|
|
312
|
+
|
|
288
313
|
const ids = await this._getCandidateIds(filter);
|
|
289
314
|
|
|
290
315
|
for (const id of ids) {
|
|
@@ -313,6 +338,8 @@ export class Collection<T = any> {
|
|
|
313
338
|
}
|
|
314
339
|
|
|
315
340
|
private async _updateMany(filter: any, update: any) {
|
|
341
|
+
this.assertWritable();
|
|
342
|
+
|
|
316
343
|
const ids = await this._getCandidateIds(filter);
|
|
317
344
|
const out = [];
|
|
318
345
|
|
|
@@ -340,6 +367,8 @@ export class Collection<T = any> {
|
|
|
340
367
|
/* ===================== DELETE ===================== */
|
|
341
368
|
|
|
342
369
|
private async _deleteOne(filter: any) {
|
|
370
|
+
this.assertWritable();
|
|
371
|
+
|
|
343
372
|
const ids = await this._getCandidateIds(filter);
|
|
344
373
|
|
|
345
374
|
for (const id of ids) {
|
|
@@ -357,6 +386,8 @@ export class Collection<T = any> {
|
|
|
357
386
|
}
|
|
358
387
|
|
|
359
388
|
private async _deleteMany(filter: any) {
|
|
389
|
+
this.assertWritable();
|
|
390
|
+
|
|
360
391
|
const ids = await this._getCandidateIds(filter);
|
|
361
392
|
let count = 0;
|
|
362
393
|
|