@liorandb/core 1.0.13 → 1.0.14
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/index.d.ts +60 -1
- package/dist/index.js +278 -45
- package/package.json +1 -1
- package/src/core/collection.ts +105 -5
- package/src/core/database.ts +89 -2
- package/src/core/index.ts +140 -0
- package/src/core/query.ts +105 -2
- package/src/ipc/index.ts +19 -3
- package/src/types/index.ts +51 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
import { ClassicLevel } from 'classic-level';
|
|
2
2
|
import { ZodSchema } from 'zod';
|
|
3
3
|
|
|
4
|
+
interface IndexOptions {
|
|
5
|
+
unique?: boolean;
|
|
6
|
+
}
|
|
7
|
+
declare class Index {
|
|
8
|
+
readonly field: string;
|
|
9
|
+
readonly unique: boolean;
|
|
10
|
+
readonly dir: string;
|
|
11
|
+
readonly db: ClassicLevel<string, string>;
|
|
12
|
+
constructor(baseDir: string, field: string, options?: IndexOptions);
|
|
13
|
+
private normalizeKey;
|
|
14
|
+
private getRaw;
|
|
15
|
+
private setRaw;
|
|
16
|
+
private delRaw;
|
|
17
|
+
insert(doc: any): Promise<void>;
|
|
18
|
+
delete(doc: any): Promise<void>;
|
|
19
|
+
update(oldDoc: any, newDoc: any): Promise<void>;
|
|
20
|
+
find(value: any): Promise<string[]>;
|
|
21
|
+
close(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
|
|
4
24
|
interface UpdateOptions$1 {
|
|
5
25
|
upsert?: boolean;
|
|
6
26
|
}
|
|
@@ -9,7 +29,10 @@ declare class Collection<T = any> {
|
|
|
9
29
|
db: ClassicLevel<string, string>;
|
|
10
30
|
private queue;
|
|
11
31
|
private schema?;
|
|
32
|
+
private indexes;
|
|
12
33
|
constructor(dir: string, schema?: ZodSchema<T>);
|
|
34
|
+
registerIndex(index: Index): void;
|
|
35
|
+
getIndex(field: string): Index | undefined;
|
|
13
36
|
setSchema(schema: ZodSchema<T>): void;
|
|
14
37
|
private validate;
|
|
15
38
|
private _enqueue;
|
|
@@ -28,6 +51,7 @@ declare class Collection<T = any> {
|
|
|
28
51
|
deleteOne(filter: any): Promise<any>;
|
|
29
52
|
deleteMany(filter: any): Promise<any>;
|
|
30
53
|
countDocuments(filter?: any): Promise<any>;
|
|
54
|
+
private _updateIndexes;
|
|
31
55
|
private _insertOne;
|
|
32
56
|
private _insertMany;
|
|
33
57
|
private _findOne;
|
|
@@ -68,13 +92,18 @@ declare class LioranDB {
|
|
|
68
92
|
manager: LioranManager;
|
|
69
93
|
collections: Map<string, Collection>;
|
|
70
94
|
private walPath;
|
|
95
|
+
private metaPath;
|
|
96
|
+
private meta;
|
|
71
97
|
private static TX_SEQ;
|
|
72
98
|
constructor(basePath: string, dbName: string, manager: LioranManager);
|
|
99
|
+
private loadMeta;
|
|
100
|
+
private saveMeta;
|
|
73
101
|
writeWAL(entries: WALEntry[]): Promise<void>;
|
|
74
102
|
clearWAL(): Promise<void>;
|
|
75
103
|
private recoverFromWAL;
|
|
76
104
|
applyTransaction(ops: TXOp[]): Promise<void>;
|
|
77
105
|
collection<T = any>(name: string, schema?: ZodSchema<T>): Collection<T>;
|
|
106
|
+
createIndex(collection: string, field: string, options?: IndexOptions): Promise<void>;
|
|
78
107
|
transaction<T>(fn: (tx: DBTransactionContext) => Promise<T>): Promise<T>;
|
|
79
108
|
close(): Promise<void>;
|
|
80
109
|
}
|
|
@@ -114,5 +143,35 @@ interface UpdateOptions {
|
|
|
114
143
|
type Query<T = any> = Partial<T> & {
|
|
115
144
|
[key: string]: any;
|
|
116
145
|
};
|
|
146
|
+
type IndexType = "hash" | "btree";
|
|
147
|
+
interface IndexDefinition<T = any> {
|
|
148
|
+
field: keyof T | string;
|
|
149
|
+
unique?: boolean;
|
|
150
|
+
sparse?: boolean;
|
|
151
|
+
type?: IndexType;
|
|
152
|
+
}
|
|
153
|
+
interface IndexMetadata {
|
|
154
|
+
field: string;
|
|
155
|
+
unique: boolean;
|
|
156
|
+
sparse: boolean;
|
|
157
|
+
type: IndexType;
|
|
158
|
+
createdAt: number;
|
|
159
|
+
}
|
|
160
|
+
interface QueryExplainPlan {
|
|
161
|
+
indexUsed?: string;
|
|
162
|
+
indexType?: IndexType;
|
|
163
|
+
scannedDocuments: number;
|
|
164
|
+
returnedDocuments: number;
|
|
165
|
+
executionTimeMs: number;
|
|
166
|
+
}
|
|
167
|
+
interface CollectionIndexAPI<T = any> {
|
|
168
|
+
createIndex(def: IndexDefinition<T>): Promise<void>;
|
|
169
|
+
dropIndex(field: keyof T | string): Promise<void>;
|
|
170
|
+
listIndexes(): Promise<IndexMetadata[]>;
|
|
171
|
+
rebuildIndexes(): Promise<void>;
|
|
172
|
+
}
|
|
173
|
+
interface DatabaseIndexAPI {
|
|
174
|
+
rebuildAllIndexes(): Promise<void>;
|
|
175
|
+
}
|
|
117
176
|
|
|
118
|
-
export { LioranDB, LioranManager, type LioranManagerOptions, type Query, type UpdateOptions, getBaseDBFolder };
|
|
177
|
+
export { type CollectionIndexAPI, type DatabaseIndexAPI, type IndexDefinition, type IndexMetadata, type IndexType, LioranDB, LioranManager, type LioranManagerOptions, type Query, type QueryExplainPlan, type UpdateOptions, getBaseDBFolder };
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
// src/LioranManager.ts
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import path5 from "path";
|
|
3
|
+
import fs5 from "fs";
|
|
4
4
|
import process2 from "process";
|
|
5
5
|
|
|
6
6
|
// src/core/database.ts
|
|
7
|
-
import
|
|
8
|
-
import
|
|
7
|
+
import path2 from "path";
|
|
8
|
+
import fs2 from "fs";
|
|
9
9
|
|
|
10
10
|
// src/core/collection.ts
|
|
11
11
|
import { ClassicLevel } from "classic-level";
|
|
12
12
|
|
|
13
13
|
// src/core/query.ts
|
|
14
|
-
function getByPath(obj,
|
|
15
|
-
return
|
|
14
|
+
function getByPath(obj, path6) {
|
|
15
|
+
return path6.split(".").reduce((o, p) => o ? o[p] : void 0, obj);
|
|
16
16
|
}
|
|
17
17
|
function matchDocument(doc, query) {
|
|
18
18
|
for (const key of Object.keys(query)) {
|
|
@@ -27,8 +27,7 @@ function matchDocument(doc, query) {
|
|
|
27
27
|
if (op === "$lte" && !(val <= v)) return false;
|
|
28
28
|
if (op === "$ne" && val === v) return false;
|
|
29
29
|
if (op === "$eq" && val !== v) return false;
|
|
30
|
-
if (op === "$in" && (!Array.isArray(v) || !v.includes(val)))
|
|
31
|
-
return false;
|
|
30
|
+
if (op === "$in" && (!Array.isArray(v) || !v.includes(val))) return false;
|
|
32
31
|
}
|
|
33
32
|
} else {
|
|
34
33
|
if (val !== cond) return false;
|
|
@@ -67,6 +66,19 @@ function applyUpdate(oldDoc, update) {
|
|
|
67
66
|
}
|
|
68
67
|
return doc;
|
|
69
68
|
}
|
|
69
|
+
function extractIndexQuery(query) {
|
|
70
|
+
for (const key of Object.keys(query)) {
|
|
71
|
+
const cond = query[key];
|
|
72
|
+
if (key === "_id") continue;
|
|
73
|
+
if (!cond || typeof cond !== "object" || Array.isArray(cond)) {
|
|
74
|
+
return { field: key, value: cond };
|
|
75
|
+
}
|
|
76
|
+
if ("$eq" in cond) {
|
|
77
|
+
return { field: key, value: cond.$eq };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
70
82
|
|
|
71
83
|
// src/core/collection.ts
|
|
72
84
|
import { v4 as uuid } from "uuid";
|
|
@@ -150,11 +162,20 @@ var Collection = class {
|
|
|
150
162
|
db;
|
|
151
163
|
queue = Promise.resolve();
|
|
152
164
|
schema;
|
|
165
|
+
indexes = /* @__PURE__ */ new Map();
|
|
153
166
|
constructor(dir, schema) {
|
|
154
167
|
this.dir = dir;
|
|
155
168
|
this.db = new ClassicLevel(dir, { valueEncoding: "utf8" });
|
|
156
169
|
this.schema = schema;
|
|
157
170
|
}
|
|
171
|
+
/* ---------------------- INDEX MANAGEMENT ---------------------- */
|
|
172
|
+
registerIndex(index) {
|
|
173
|
+
this.indexes.set(index.field, index);
|
|
174
|
+
}
|
|
175
|
+
getIndex(field) {
|
|
176
|
+
return this.indexes.get(field);
|
|
177
|
+
}
|
|
178
|
+
/* -------------------------- CORE -------------------------- */
|
|
158
179
|
setSchema(schema) {
|
|
159
180
|
this.schema = schema;
|
|
160
181
|
}
|
|
@@ -166,6 +187,12 @@ var Collection = class {
|
|
|
166
187
|
return this.queue;
|
|
167
188
|
}
|
|
168
189
|
async close() {
|
|
190
|
+
for (const idx of this.indexes.values()) {
|
|
191
|
+
try {
|
|
192
|
+
await idx.close();
|
|
193
|
+
} catch {
|
|
194
|
+
}
|
|
195
|
+
}
|
|
169
196
|
try {
|
|
170
197
|
await this.db.close();
|
|
171
198
|
} catch {
|
|
@@ -195,6 +222,7 @@ var Collection = class {
|
|
|
195
222
|
throw new Error(`Unknown operation: ${op}`);
|
|
196
223
|
}
|
|
197
224
|
}
|
|
225
|
+
/* --------------------- PUBLIC API --------------------- */
|
|
198
226
|
insertOne(doc) {
|
|
199
227
|
return this._enqueue(() => this._exec("insertOne", [doc]));
|
|
200
228
|
}
|
|
@@ -232,11 +260,18 @@ var Collection = class {
|
|
|
232
260
|
() => this._exec("countDocuments", [filter])
|
|
233
261
|
);
|
|
234
262
|
}
|
|
263
|
+
/* ------------------ INDEX HOOK ------------------ */
|
|
264
|
+
async _updateIndexes(oldDoc, newDoc) {
|
|
265
|
+
for (const index of this.indexes.values()) {
|
|
266
|
+
await index.update(oldDoc, newDoc);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
235
269
|
/* ---------------- Storage ---------------- */
|
|
236
270
|
async _insertOne(doc) {
|
|
237
271
|
const _id = doc._id ?? uuid();
|
|
238
272
|
const final = this.validate({ _id, ...doc });
|
|
239
273
|
await this.db.put(String(_id), encryptData(final));
|
|
274
|
+
await this._updateIndexes(null, final);
|
|
240
275
|
return final;
|
|
241
276
|
}
|
|
242
277
|
async _insertMany(docs) {
|
|
@@ -245,10 +280,17 @@ var Collection = class {
|
|
|
245
280
|
for (const d of docs) {
|
|
246
281
|
const _id = d._id ?? uuid();
|
|
247
282
|
const final = this.validate({ _id, ...d });
|
|
248
|
-
batch.push({
|
|
283
|
+
batch.push({
|
|
284
|
+
type: "put",
|
|
285
|
+
key: String(_id),
|
|
286
|
+
value: encryptData(final)
|
|
287
|
+
});
|
|
249
288
|
out.push(final);
|
|
250
289
|
}
|
|
251
290
|
await this.db.batch(batch);
|
|
291
|
+
for (const doc of out) {
|
|
292
|
+
await this._updateIndexes(null, doc);
|
|
293
|
+
}
|
|
252
294
|
return out;
|
|
253
295
|
}
|
|
254
296
|
async _findOne(query) {
|
|
@@ -260,6 +302,17 @@ var Collection = class {
|
|
|
260
302
|
return null;
|
|
261
303
|
}
|
|
262
304
|
}
|
|
305
|
+
const iq = extractIndexQuery(query);
|
|
306
|
+
if (iq && this.indexes.has(iq.field)) {
|
|
307
|
+
const ids = await this.indexes.get(iq.field).find(iq.value);
|
|
308
|
+
if (!ids.length) return null;
|
|
309
|
+
try {
|
|
310
|
+
const enc = await this.db.get(ids[0]);
|
|
311
|
+
return enc ? decryptData(enc) : null;
|
|
312
|
+
} catch {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
263
316
|
for await (const [, enc] of this.db.iterator()) {
|
|
264
317
|
const v = decryptData(enc);
|
|
265
318
|
if (matchDocument(v, query)) return v;
|
|
@@ -273,12 +326,17 @@ var Collection = class {
|
|
|
273
326
|
const updated = this.validate(applyUpdate(value, update));
|
|
274
327
|
updated._id = value._id;
|
|
275
328
|
await this.db.put(key, encryptData(updated));
|
|
329
|
+
await this._updateIndexes(value, updated);
|
|
276
330
|
return updated;
|
|
277
331
|
}
|
|
278
332
|
}
|
|
279
333
|
if (options?.upsert) {
|
|
280
|
-
const doc = this.validate({
|
|
334
|
+
const doc = this.validate({
|
|
335
|
+
_id: uuid(),
|
|
336
|
+
...applyUpdate({}, update)
|
|
337
|
+
});
|
|
281
338
|
await this.db.put(String(doc._id), encryptData(doc));
|
|
339
|
+
await this._updateIndexes(null, doc);
|
|
282
340
|
return doc;
|
|
283
341
|
}
|
|
284
342
|
return null;
|
|
@@ -291,12 +349,26 @@ var Collection = class {
|
|
|
291
349
|
const updated = this.validate(applyUpdate(value, update));
|
|
292
350
|
updated._id = value._id;
|
|
293
351
|
await this.db.put(key, encryptData(updated));
|
|
352
|
+
await this._updateIndexes(value, updated);
|
|
294
353
|
out.push(updated);
|
|
295
354
|
}
|
|
296
355
|
}
|
|
297
356
|
return out;
|
|
298
357
|
}
|
|
299
358
|
async _find(query) {
|
|
359
|
+
const iq = extractIndexQuery(query);
|
|
360
|
+
if (iq && this.indexes.has(iq.field)) {
|
|
361
|
+
const ids = await this.indexes.get(iq.field).find(iq.value);
|
|
362
|
+
const out2 = [];
|
|
363
|
+
for (const id of ids) {
|
|
364
|
+
try {
|
|
365
|
+
const enc = await this.db.get(id);
|
|
366
|
+
if (enc) out2.push(decryptData(enc));
|
|
367
|
+
} catch {
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return out2;
|
|
371
|
+
}
|
|
300
372
|
const out = [];
|
|
301
373
|
for await (const [, enc] of this.db.iterator()) {
|
|
302
374
|
const v = decryptData(enc);
|
|
@@ -306,8 +378,10 @@ var Collection = class {
|
|
|
306
378
|
}
|
|
307
379
|
async _deleteOne(filter) {
|
|
308
380
|
for await (const [key, enc] of this.db.iterator()) {
|
|
309
|
-
|
|
381
|
+
const value = decryptData(enc);
|
|
382
|
+
if (matchDocument(value, filter)) {
|
|
310
383
|
await this.db.del(key);
|
|
384
|
+
await this._updateIndexes(value, null);
|
|
311
385
|
return true;
|
|
312
386
|
}
|
|
313
387
|
}
|
|
@@ -316,8 +390,10 @@ var Collection = class {
|
|
|
316
390
|
async _deleteMany(filter) {
|
|
317
391
|
let count = 0;
|
|
318
392
|
for await (const [key, enc] of this.db.iterator()) {
|
|
319
|
-
|
|
393
|
+
const value = decryptData(enc);
|
|
394
|
+
if (matchDocument(value, filter)) {
|
|
320
395
|
await this.db.del(key);
|
|
396
|
+
await this._updateIndexes(value, null);
|
|
321
397
|
count++;
|
|
322
398
|
}
|
|
323
399
|
}
|
|
@@ -325,6 +401,10 @@ var Collection = class {
|
|
|
325
401
|
}
|
|
326
402
|
async _countDocuments(filter) {
|
|
327
403
|
let c = 0;
|
|
404
|
+
const iq = extractIndexQuery(filter);
|
|
405
|
+
if (iq && this.indexes.has(iq.field)) {
|
|
406
|
+
return (await this.indexes.get(iq.field).find(iq.value)).length;
|
|
407
|
+
}
|
|
328
408
|
for await (const [, enc] of this.db.iterator()) {
|
|
329
409
|
if (matchDocument(decryptData(enc), filter)) c++;
|
|
330
410
|
}
|
|
@@ -332,7 +412,115 @@ var Collection = class {
|
|
|
332
412
|
}
|
|
333
413
|
};
|
|
334
414
|
|
|
415
|
+
// src/core/index.ts
|
|
416
|
+
import path from "path";
|
|
417
|
+
import fs from "fs";
|
|
418
|
+
import { ClassicLevel as ClassicLevel2 } from "classic-level";
|
|
419
|
+
var Index = class {
|
|
420
|
+
field;
|
|
421
|
+
unique;
|
|
422
|
+
dir;
|
|
423
|
+
db;
|
|
424
|
+
constructor(baseDir, field, options = {}) {
|
|
425
|
+
this.field = field;
|
|
426
|
+
this.unique = !!options.unique;
|
|
427
|
+
this.dir = path.join(baseDir, "__indexes", field + ".idx");
|
|
428
|
+
fs.mkdirSync(this.dir, { recursive: true });
|
|
429
|
+
this.db = new ClassicLevel2(this.dir, { valueEncoding: "utf8" });
|
|
430
|
+
}
|
|
431
|
+
/* ------------------------- INTERNAL ------------------------- */
|
|
432
|
+
normalizeKey(value) {
|
|
433
|
+
if (value === null || value === void 0) return "__null__";
|
|
434
|
+
if (typeof value === "object") {
|
|
435
|
+
return JSON.stringify(value);
|
|
436
|
+
}
|
|
437
|
+
return String(value);
|
|
438
|
+
}
|
|
439
|
+
async getRaw(key) {
|
|
440
|
+
try {
|
|
441
|
+
const v = await this.db.get(key);
|
|
442
|
+
if (v === void 0) return null;
|
|
443
|
+
return JSON.parse(v);
|
|
444
|
+
} catch {
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
async setRaw(key, value) {
|
|
449
|
+
await this.db.put(key, JSON.stringify(value));
|
|
450
|
+
}
|
|
451
|
+
async delRaw(key) {
|
|
452
|
+
try {
|
|
453
|
+
await this.db.del(key);
|
|
454
|
+
} catch {
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
/* --------------------------- API --------------------------- */
|
|
458
|
+
async insert(doc) {
|
|
459
|
+
const val = doc[this.field];
|
|
460
|
+
if (val === void 0) return;
|
|
461
|
+
const key = this.normalizeKey(val);
|
|
462
|
+
if (this.unique) {
|
|
463
|
+
const existing = await this.getRaw(key);
|
|
464
|
+
if (existing) {
|
|
465
|
+
throw new Error(
|
|
466
|
+
`Unique index violation on "${this.field}" = ${val}`
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
await this.setRaw(key, doc._id);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const arr = await this.getRaw(key);
|
|
473
|
+
if (!arr) {
|
|
474
|
+
await this.setRaw(key, [doc._id]);
|
|
475
|
+
} else {
|
|
476
|
+
if (!arr.includes(doc._id)) {
|
|
477
|
+
arr.push(doc._id);
|
|
478
|
+
await this.setRaw(key, arr);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
async delete(doc) {
|
|
483
|
+
const val = doc[this.field];
|
|
484
|
+
if (val === void 0) return;
|
|
485
|
+
const key = this.normalizeKey(val);
|
|
486
|
+
if (this.unique) {
|
|
487
|
+
await this.delRaw(key);
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
const arr = await this.getRaw(key);
|
|
491
|
+
if (!arr) return;
|
|
492
|
+
const next = arr.filter((id) => id !== doc._id);
|
|
493
|
+
if (next.length === 0) {
|
|
494
|
+
await this.delRaw(key);
|
|
495
|
+
} else {
|
|
496
|
+
await this.setRaw(key, next);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
async update(oldDoc, newDoc) {
|
|
500
|
+
const oldVal = oldDoc?.[this.field];
|
|
501
|
+
const newVal = newDoc?.[this.field];
|
|
502
|
+
if (oldVal === newVal) return;
|
|
503
|
+
if (oldDoc) await this.delete(oldDoc);
|
|
504
|
+
if (newDoc) await this.insert(newDoc);
|
|
505
|
+
}
|
|
506
|
+
async find(value) {
|
|
507
|
+
const key = this.normalizeKey(value);
|
|
508
|
+
const raw = await this.getRaw(key);
|
|
509
|
+
if (!raw) return [];
|
|
510
|
+
if (this.unique) return [raw];
|
|
511
|
+
return raw;
|
|
512
|
+
}
|
|
513
|
+
async close() {
|
|
514
|
+
try {
|
|
515
|
+
await this.db.close();
|
|
516
|
+
} catch {
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
|
|
335
521
|
// src/core/database.ts
|
|
522
|
+
var META_FILE = "__db_meta.json";
|
|
523
|
+
var META_VERSION = 1;
|
|
336
524
|
var DBTransactionContext = class {
|
|
337
525
|
constructor(db, txId) {
|
|
338
526
|
this.db = db;
|
|
@@ -367,18 +555,39 @@ var LioranDB = class _LioranDB {
|
|
|
367
555
|
manager;
|
|
368
556
|
collections;
|
|
369
557
|
walPath;
|
|
558
|
+
metaPath;
|
|
559
|
+
meta;
|
|
370
560
|
static TX_SEQ = 0;
|
|
371
561
|
constructor(basePath, dbName, manager) {
|
|
372
562
|
this.basePath = basePath;
|
|
373
563
|
this.dbName = dbName;
|
|
374
564
|
this.manager = manager;
|
|
375
565
|
this.collections = /* @__PURE__ */ new Map();
|
|
376
|
-
this.walPath =
|
|
377
|
-
|
|
566
|
+
this.walPath = path2.join(basePath, "__tx_wal.log");
|
|
567
|
+
this.metaPath = path2.join(basePath, META_FILE);
|
|
568
|
+
fs2.mkdirSync(basePath, { recursive: true });
|
|
569
|
+
this.loadMeta();
|
|
378
570
|
this.recoverFromWAL().catch(console.error);
|
|
379
571
|
}
|
|
572
|
+
/* ------------------------- META ------------------------- */
|
|
573
|
+
loadMeta() {
|
|
574
|
+
if (!fs2.existsSync(this.metaPath)) {
|
|
575
|
+
this.meta = { version: META_VERSION, indexes: {} };
|
|
576
|
+
this.saveMeta();
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
try {
|
|
580
|
+
this.meta = JSON.parse(fs2.readFileSync(this.metaPath, "utf8"));
|
|
581
|
+
} catch {
|
|
582
|
+
throw new Error("Database metadata corrupted");
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
saveMeta() {
|
|
586
|
+
fs2.writeFileSync(this.metaPath, JSON.stringify(this.meta, null, 2));
|
|
587
|
+
}
|
|
588
|
+
/* ------------------------- WAL ------------------------- */
|
|
380
589
|
async writeWAL(entries) {
|
|
381
|
-
const fd = await
|
|
590
|
+
const fd = await fs2.promises.open(this.walPath, "a");
|
|
382
591
|
for (const e of entries) {
|
|
383
592
|
await fd.write(JSON.stringify(e) + "\n");
|
|
384
593
|
}
|
|
@@ -387,13 +596,13 @@ var LioranDB = class _LioranDB {
|
|
|
387
596
|
}
|
|
388
597
|
async clearWAL() {
|
|
389
598
|
try {
|
|
390
|
-
await
|
|
599
|
+
await fs2.promises.unlink(this.walPath);
|
|
391
600
|
} catch {
|
|
392
601
|
}
|
|
393
602
|
}
|
|
394
603
|
async recoverFromWAL() {
|
|
395
|
-
if (!
|
|
396
|
-
const raw = await
|
|
604
|
+
if (!fs2.existsSync(this.walPath)) return;
|
|
605
|
+
const raw = await fs2.promises.readFile(this.walPath, "utf8");
|
|
397
606
|
const committed = /* @__PURE__ */ new Set();
|
|
398
607
|
const applied = /* @__PURE__ */ new Set();
|
|
399
608
|
const ops = /* @__PURE__ */ new Map();
|
|
@@ -424,18 +633,41 @@ var LioranDB = class _LioranDB {
|
|
|
424
633
|
await collection._exec(op, args);
|
|
425
634
|
}
|
|
426
635
|
}
|
|
636
|
+
/* ------------------------- COLLECTION ------------------------- */
|
|
427
637
|
collection(name, schema) {
|
|
428
638
|
if (this.collections.has(name)) {
|
|
429
639
|
const col2 = this.collections.get(name);
|
|
430
640
|
if (schema) col2.setSchema(schema);
|
|
431
641
|
return col2;
|
|
432
642
|
}
|
|
433
|
-
const colPath =
|
|
434
|
-
|
|
643
|
+
const colPath = path2.join(this.basePath, name);
|
|
644
|
+
fs2.mkdirSync(colPath, { recursive: true });
|
|
435
645
|
const col = new Collection(colPath, schema);
|
|
646
|
+
const metas = this.meta.indexes[name] ?? [];
|
|
647
|
+
for (const m of metas) {
|
|
648
|
+
col.registerIndex(new Index(colPath, m.field, m.options));
|
|
649
|
+
}
|
|
436
650
|
this.collections.set(name, col);
|
|
437
651
|
return col;
|
|
438
652
|
}
|
|
653
|
+
/* ------------------------- INDEX API ------------------------- */
|
|
654
|
+
async createIndex(collection, field, options = {}) {
|
|
655
|
+
const col = this.collection(collection);
|
|
656
|
+
const existing = this.meta.indexes[collection]?.find((i) => i.field === field);
|
|
657
|
+
if (existing) return;
|
|
658
|
+
const index = new Index(col.dir, field, options);
|
|
659
|
+
for await (const [, enc] of col.db.iterator()) {
|
|
660
|
+
const doc = JSON.parse(Buffer.from(enc, "base64").subarray(32).toString("utf8"));
|
|
661
|
+
await index.insert(doc);
|
|
662
|
+
}
|
|
663
|
+
col.registerIndex(index);
|
|
664
|
+
if (!this.meta.indexes[collection]) {
|
|
665
|
+
this.meta.indexes[collection] = [];
|
|
666
|
+
}
|
|
667
|
+
this.meta.indexes[collection].push({ field, options });
|
|
668
|
+
this.saveMeta();
|
|
669
|
+
}
|
|
670
|
+
/* ------------------------- TX API ------------------------- */
|
|
439
671
|
async transaction(fn) {
|
|
440
672
|
const txId = ++_LioranDB.TX_SEQ;
|
|
441
673
|
const tx = new DBTransactionContext(this, txId);
|
|
@@ -443,6 +675,7 @@ var LioranDB = class _LioranDB {
|
|
|
443
675
|
await tx.commit();
|
|
444
676
|
return result;
|
|
445
677
|
}
|
|
678
|
+
/* ------------------------- SHUTDOWN ------------------------- */
|
|
446
679
|
async close() {
|
|
447
680
|
for (const col of this.collections.values()) {
|
|
448
681
|
try {
|
|
@@ -456,15 +689,15 @@ var LioranDB = class _LioranDB {
|
|
|
456
689
|
|
|
457
690
|
// src/utils/rootpath.ts
|
|
458
691
|
import os2 from "os";
|
|
459
|
-
import
|
|
460
|
-
import
|
|
692
|
+
import path3 from "path";
|
|
693
|
+
import fs3 from "fs";
|
|
461
694
|
function getDefaultRootPath() {
|
|
462
695
|
let dbPath = process.env.LIORANDB_PATH;
|
|
463
696
|
if (!dbPath) {
|
|
464
697
|
const homeDir = os2.homedir();
|
|
465
|
-
dbPath =
|
|
466
|
-
if (!
|
|
467
|
-
|
|
698
|
+
dbPath = path3.join(homeDir, "LioranDB", "db");
|
|
699
|
+
if (!fs3.existsSync(dbPath)) {
|
|
700
|
+
fs3.mkdirSync(dbPath, { recursive: true });
|
|
468
701
|
}
|
|
469
702
|
process.env.LIORANDB_PATH = dbPath;
|
|
470
703
|
}
|
|
@@ -479,24 +712,24 @@ import net from "net";
|
|
|
479
712
|
|
|
480
713
|
// src/ipc/socketPath.ts
|
|
481
714
|
import os3 from "os";
|
|
482
|
-
import
|
|
715
|
+
import path4 from "path";
|
|
483
716
|
function getIPCSocketPath(rootPath) {
|
|
484
717
|
if (os3.platform() === "win32") {
|
|
485
718
|
return `\\\\.\\pipe\\liorandb_${rootPath.replace(/[:\\\/]/g, "_")}`;
|
|
486
719
|
}
|
|
487
|
-
return
|
|
720
|
+
return path4.join(rootPath, ".lioran.sock");
|
|
488
721
|
}
|
|
489
722
|
|
|
490
723
|
// src/ipc/client.ts
|
|
491
724
|
function delay(ms) {
|
|
492
725
|
return new Promise((r) => setTimeout(r, ms));
|
|
493
726
|
}
|
|
494
|
-
async function connectWithRetry(
|
|
727
|
+
async function connectWithRetry(path6) {
|
|
495
728
|
let attempt = 0;
|
|
496
729
|
while (true) {
|
|
497
730
|
try {
|
|
498
731
|
return await new Promise((resolve, reject) => {
|
|
499
|
-
const socket = net.connect(
|
|
732
|
+
const socket = net.connect(path6, () => resolve(socket));
|
|
500
733
|
socket.once("error", reject);
|
|
501
734
|
});
|
|
502
735
|
} catch (err) {
|
|
@@ -580,7 +813,7 @@ var dbQueue = new DBQueue();
|
|
|
580
813
|
|
|
581
814
|
// src/ipc/server.ts
|
|
582
815
|
import net2 from "net";
|
|
583
|
-
import
|
|
816
|
+
import fs4 from "fs";
|
|
584
817
|
var IPCServer = class {
|
|
585
818
|
server;
|
|
586
819
|
manager;
|
|
@@ -591,7 +824,7 @@ var IPCServer = class {
|
|
|
591
824
|
}
|
|
592
825
|
start() {
|
|
593
826
|
if (!this.socketPath.startsWith("\\\\.\\")) {
|
|
594
|
-
if (
|
|
827
|
+
if (fs4.existsSync(this.socketPath)) fs4.unlinkSync(this.socketPath);
|
|
595
828
|
}
|
|
596
829
|
this.server = net2.createServer((socket) => {
|
|
597
830
|
let buffer = "";
|
|
@@ -641,7 +874,7 @@ var IPCServer = class {
|
|
|
641
874
|
if (this.server) this.server.close();
|
|
642
875
|
if (!this.socketPath.startsWith("\\\\.\\")) {
|
|
643
876
|
try {
|
|
644
|
-
|
|
877
|
+
fs4.unlinkSync(this.socketPath);
|
|
645
878
|
} catch {
|
|
646
879
|
}
|
|
647
880
|
}
|
|
@@ -659,8 +892,8 @@ var LioranManager = class {
|
|
|
659
892
|
constructor(options = {}) {
|
|
660
893
|
const { rootPath, encryptionKey } = options;
|
|
661
894
|
this.rootPath = rootPath || getDefaultRootPath();
|
|
662
|
-
if (!
|
|
663
|
-
|
|
895
|
+
if (!fs5.existsSync(this.rootPath)) {
|
|
896
|
+
fs5.mkdirSync(this.rootPath, { recursive: true });
|
|
664
897
|
}
|
|
665
898
|
if (encryptionKey) {
|
|
666
899
|
setEncryptionKey(encryptionKey);
|
|
@@ -682,18 +915,18 @@ var LioranManager = class {
|
|
|
682
915
|
}
|
|
683
916
|
}
|
|
684
917
|
tryAcquireLock() {
|
|
685
|
-
const lockPath =
|
|
918
|
+
const lockPath = path5.join(this.rootPath, ".lioran.lock");
|
|
686
919
|
try {
|
|
687
|
-
this.lockFd =
|
|
688
|
-
|
|
920
|
+
this.lockFd = fs5.openSync(lockPath, "wx");
|
|
921
|
+
fs5.writeSync(this.lockFd, String(process2.pid));
|
|
689
922
|
return true;
|
|
690
923
|
} catch {
|
|
691
924
|
try {
|
|
692
|
-
const pid = Number(
|
|
925
|
+
const pid = Number(fs5.readFileSync(lockPath, "utf8"));
|
|
693
926
|
if (!this.isProcessAlive(pid)) {
|
|
694
|
-
|
|
695
|
-
this.lockFd =
|
|
696
|
-
|
|
927
|
+
fs5.unlinkSync(lockPath);
|
|
928
|
+
this.lockFd = fs5.openSync(lockPath, "wx");
|
|
929
|
+
fs5.writeSync(this.lockFd, String(process2.pid));
|
|
697
930
|
return true;
|
|
698
931
|
}
|
|
699
932
|
} catch {
|
|
@@ -713,8 +946,8 @@ var LioranManager = class {
|
|
|
713
946
|
if (this.openDBs.has(name)) {
|
|
714
947
|
return this.openDBs.get(name);
|
|
715
948
|
}
|
|
716
|
-
const dbPath =
|
|
717
|
-
await
|
|
949
|
+
const dbPath = path5.join(this.rootPath, name);
|
|
950
|
+
await fs5.promises.mkdir(dbPath, { recursive: true });
|
|
718
951
|
const db = new LioranDB(dbPath, name, this);
|
|
719
952
|
this.openDBs.set(name, db);
|
|
720
953
|
return db;
|
|
@@ -734,8 +967,8 @@ var LioranManager = class {
|
|
|
734
967
|
}
|
|
735
968
|
this.openDBs.clear();
|
|
736
969
|
try {
|
|
737
|
-
if (this.lockFd)
|
|
738
|
-
|
|
970
|
+
if (this.lockFd) fs5.closeSync(this.lockFd);
|
|
971
|
+
fs5.unlinkSync(path5.join(this.rootPath, ".lioran.lock"));
|
|
739
972
|
} catch {
|
|
740
973
|
}
|
|
741
974
|
await this.ipcServer?.close();
|
package/package.json
CHANGED