@liorandb/core 1.0.18 → 1.0.19
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 +36 -13
- package/dist/index.js +347 -109
- package/package.json +1 -1
- package/src/core/checkpoint.ts +111 -0
- package/src/core/compaction.ts +79 -32
- package/src/core/database.ts +78 -58
- package/src/core/wal.ts +213 -0
package/dist/index.js
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
// src/LioranManager.ts
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import path9 from "path";
|
|
3
|
+
import fs9 from "fs";
|
|
4
4
|
import process2 from "process";
|
|
5
5
|
|
|
6
6
|
// src/core/database.ts
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import { execFile } from "child_process";
|
|
10
|
-
import { promisify } from "util";
|
|
7
|
+
import path6 from "path";
|
|
8
|
+
import fs6 from "fs";
|
|
11
9
|
|
|
12
10
|
// src/core/collection.ts
|
|
13
11
|
import { ClassicLevel as ClassicLevel3 } from "classic-level";
|
|
14
12
|
|
|
15
13
|
// src/core/query.ts
|
|
16
|
-
function getByPath(obj,
|
|
17
|
-
return
|
|
14
|
+
function getByPath(obj, path10) {
|
|
15
|
+
return path10.split(".").reduce((o, p) => o ? o[p] : void 0, obj);
|
|
18
16
|
}
|
|
19
17
|
function matchDocument(doc, query) {
|
|
20
18
|
for (const key of Object.keys(query)) {
|
|
@@ -295,12 +293,13 @@ var Index = class {
|
|
|
295
293
|
|
|
296
294
|
// src/core/compaction.ts
|
|
297
295
|
var TMP_SUFFIX = "__compact_tmp";
|
|
298
|
-
var OLD_SUFFIX = "
|
|
296
|
+
var OLD_SUFFIX = "__compact_old";
|
|
297
|
+
var INDEX_DIR = "__indexes";
|
|
299
298
|
async function compactCollectionEngine(col) {
|
|
300
|
-
await crashRecovery(col.dir);
|
|
301
299
|
const baseDir = col.dir;
|
|
302
300
|
const tmpDir = baseDir + TMP_SUFFIX;
|
|
303
301
|
const oldDir = baseDir + OLD_SUFFIX;
|
|
302
|
+
await crashRecovery(baseDir);
|
|
304
303
|
safeRemove(tmpDir);
|
|
305
304
|
safeRemove(oldDir);
|
|
306
305
|
await snapshotRebuild(col, tmpDir);
|
|
@@ -309,9 +308,13 @@ async function compactCollectionEngine(col) {
|
|
|
309
308
|
}
|
|
310
309
|
async function snapshotRebuild(col, tmpDir) {
|
|
311
310
|
fs2.mkdirSync(tmpDir, { recursive: true });
|
|
312
|
-
const tmpDB = new ClassicLevel2(tmpDir, {
|
|
311
|
+
const tmpDB = new ClassicLevel2(tmpDir, {
|
|
312
|
+
valueEncoding: "utf8"
|
|
313
|
+
});
|
|
313
314
|
for await (const [key, val] of col.db.iterator()) {
|
|
314
|
-
|
|
315
|
+
if (val !== void 0) {
|
|
316
|
+
await tmpDB.put(key, val);
|
|
317
|
+
}
|
|
315
318
|
}
|
|
316
319
|
await tmpDB.close();
|
|
317
320
|
await col.db.close();
|
|
@@ -323,38 +326,44 @@ function atomicSwap(base, tmp, old) {
|
|
|
323
326
|
async function crashRecovery(baseDir) {
|
|
324
327
|
const tmp = baseDir + TMP_SUFFIX;
|
|
325
328
|
const old = baseDir + OLD_SUFFIX;
|
|
326
|
-
|
|
329
|
+
const baseExists = fs2.existsSync(baseDir);
|
|
330
|
+
const tmpExists = fs2.existsSync(tmp);
|
|
331
|
+
const oldExists = fs2.existsSync(old);
|
|
332
|
+
if (tmpExists && oldExists) {
|
|
327
333
|
safeRemove(baseDir);
|
|
328
334
|
fs2.renameSync(tmp, baseDir);
|
|
329
335
|
safeRemove(old);
|
|
336
|
+
return;
|
|
330
337
|
}
|
|
331
|
-
if (
|
|
338
|
+
if (!baseExists && oldExists) {
|
|
332
339
|
fs2.renameSync(old, baseDir);
|
|
340
|
+
return;
|
|
333
341
|
}
|
|
334
|
-
if (
|
|
342
|
+
if (tmpExists && !oldExists) {
|
|
335
343
|
safeRemove(tmp);
|
|
336
344
|
}
|
|
337
345
|
}
|
|
338
346
|
async function rebuildIndexes(col) {
|
|
339
|
-
const indexRoot = path2.join(col.dir,
|
|
340
|
-
safeRemove(indexRoot);
|
|
341
|
-
fs2.mkdirSync(indexRoot, { recursive: true });
|
|
347
|
+
const indexRoot = path2.join(col.dir, INDEX_DIR);
|
|
342
348
|
for (const idx of col["indexes"].values()) {
|
|
343
349
|
try {
|
|
344
350
|
await idx.close();
|
|
345
351
|
} catch {
|
|
346
352
|
}
|
|
347
353
|
}
|
|
354
|
+
safeRemove(indexRoot);
|
|
355
|
+
fs2.mkdirSync(indexRoot, { recursive: true });
|
|
348
356
|
const newIndexes = /* @__PURE__ */ new Map();
|
|
349
357
|
for (const idx of col["indexes"].values()) {
|
|
350
|
-
const
|
|
358
|
+
const rebuilt = new Index(col.dir, idx.field, {
|
|
351
359
|
unique: idx.unique
|
|
352
360
|
});
|
|
353
361
|
for await (const [, enc] of col.db.iterator()) {
|
|
362
|
+
if (!enc) continue;
|
|
354
363
|
const doc = decryptData(enc);
|
|
355
|
-
await
|
|
364
|
+
await rebuilt.insert(doc);
|
|
356
365
|
}
|
|
357
|
-
newIndexes.set(idx.field,
|
|
366
|
+
newIndexes.set(idx.field, rebuilt);
|
|
358
367
|
}
|
|
359
368
|
col["indexes"] = newIndexes;
|
|
360
369
|
}
|
|
@@ -820,10 +829,230 @@ var MigrationEngine = class {
|
|
|
820
829
|
}
|
|
821
830
|
};
|
|
822
831
|
|
|
832
|
+
// src/core/wal.ts
|
|
833
|
+
import fs4 from "fs";
|
|
834
|
+
import path4 from "path";
|
|
835
|
+
var MAX_WAL_SIZE = 16 * 1024 * 1024;
|
|
836
|
+
var WAL_DIR = "__wal";
|
|
837
|
+
var CRC32_TABLE = (() => {
|
|
838
|
+
const table = new Uint32Array(256);
|
|
839
|
+
for (let i = 0; i < 256; i++) {
|
|
840
|
+
let c = i;
|
|
841
|
+
for (let k = 0; k < 8; k++) {
|
|
842
|
+
c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
843
|
+
}
|
|
844
|
+
table[i] = c >>> 0;
|
|
845
|
+
}
|
|
846
|
+
return table;
|
|
847
|
+
})();
|
|
848
|
+
function crc32(input) {
|
|
849
|
+
let crc = 4294967295;
|
|
850
|
+
for (let i = 0; i < input.length; i++) {
|
|
851
|
+
const byte = input.charCodeAt(i);
|
|
852
|
+
crc = CRC32_TABLE[(crc ^ byte) & 255] ^ crc >>> 8;
|
|
853
|
+
}
|
|
854
|
+
return (crc ^ 4294967295) >>> 0;
|
|
855
|
+
}
|
|
856
|
+
var WALManager = class {
|
|
857
|
+
walDir;
|
|
858
|
+
currentGen = 1;
|
|
859
|
+
lsn = 0;
|
|
860
|
+
fd = null;
|
|
861
|
+
constructor(baseDir) {
|
|
862
|
+
this.walDir = path4.join(baseDir, WAL_DIR);
|
|
863
|
+
fs4.mkdirSync(this.walDir, { recursive: true });
|
|
864
|
+
this.currentGen = this.detectLastGeneration();
|
|
865
|
+
}
|
|
866
|
+
/* -------------------------
|
|
867
|
+
INTERNAL HELPERS
|
|
868
|
+
------------------------- */
|
|
869
|
+
walPath(gen = this.currentGen) {
|
|
870
|
+
return path4.join(
|
|
871
|
+
this.walDir,
|
|
872
|
+
`wal-${String(gen).padStart(6, "0")}.log`
|
|
873
|
+
);
|
|
874
|
+
}
|
|
875
|
+
detectLastGeneration() {
|
|
876
|
+
if (!fs4.existsSync(this.walDir)) return 1;
|
|
877
|
+
const files = fs4.readdirSync(this.walDir);
|
|
878
|
+
let max = 0;
|
|
879
|
+
for (const f of files) {
|
|
880
|
+
const m = f.match(/^wal-(\d+)\.log$/);
|
|
881
|
+
if (m) max = Math.max(max, Number(m[1]));
|
|
882
|
+
}
|
|
883
|
+
return max || 1;
|
|
884
|
+
}
|
|
885
|
+
async open() {
|
|
886
|
+
if (!this.fd) {
|
|
887
|
+
this.fd = await fs4.promises.open(this.walPath(), "a");
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
async rotate() {
|
|
891
|
+
if (this.fd) {
|
|
892
|
+
await this.fd.close();
|
|
893
|
+
this.fd = null;
|
|
894
|
+
}
|
|
895
|
+
this.currentGen++;
|
|
896
|
+
}
|
|
897
|
+
/* -------------------------
|
|
898
|
+
APPEND
|
|
899
|
+
------------------------- */
|
|
900
|
+
async append(record) {
|
|
901
|
+
await this.open();
|
|
902
|
+
const full = {
|
|
903
|
+
...record,
|
|
904
|
+
lsn: ++this.lsn
|
|
905
|
+
};
|
|
906
|
+
const body = JSON.stringify(full);
|
|
907
|
+
const stored = {
|
|
908
|
+
...full,
|
|
909
|
+
crc: crc32(body)
|
|
910
|
+
};
|
|
911
|
+
await this.fd.write(JSON.stringify(stored) + "\n");
|
|
912
|
+
await this.fd.sync();
|
|
913
|
+
const stat = await this.fd.stat();
|
|
914
|
+
if (stat.size >= MAX_WAL_SIZE) {
|
|
915
|
+
await this.rotate();
|
|
916
|
+
}
|
|
917
|
+
return full.lsn;
|
|
918
|
+
}
|
|
919
|
+
/* -------------------------
|
|
920
|
+
REPLAY
|
|
921
|
+
------------------------- */
|
|
922
|
+
async replay(fromLSN, apply) {
|
|
923
|
+
if (!fs4.existsSync(this.walDir)) return;
|
|
924
|
+
const files = fs4.readdirSync(this.walDir).filter((f) => f.startsWith("wal-")).sort();
|
|
925
|
+
for (const file of files) {
|
|
926
|
+
const filePath = path4.join(this.walDir, file);
|
|
927
|
+
const data = fs4.readFileSync(filePath, "utf8");
|
|
928
|
+
const lines = data.split("\n");
|
|
929
|
+
for (let i = 0; i < lines.length; i++) {
|
|
930
|
+
const line = lines[i];
|
|
931
|
+
if (!line.trim()) continue;
|
|
932
|
+
let parsed;
|
|
933
|
+
try {
|
|
934
|
+
parsed = JSON.parse(line);
|
|
935
|
+
} catch {
|
|
936
|
+
console.error("WAL parse error, stopping replay");
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
const { crc, ...record } = parsed;
|
|
940
|
+
const expected = crc32(JSON.stringify(record));
|
|
941
|
+
if (expected !== crc) {
|
|
942
|
+
console.error(
|
|
943
|
+
"WAL checksum mismatch, stopping replay",
|
|
944
|
+
{ file, line: i + 1 }
|
|
945
|
+
);
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
if (record.lsn <= fromLSN) continue;
|
|
949
|
+
this.lsn = Math.max(this.lsn, record.lsn);
|
|
950
|
+
await apply(record);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
/* -------------------------
|
|
955
|
+
CLEANUP
|
|
956
|
+
------------------------- */
|
|
957
|
+
async cleanup(beforeGen) {
|
|
958
|
+
if (!fs4.existsSync(this.walDir)) return;
|
|
959
|
+
const files = fs4.readdirSync(this.walDir);
|
|
960
|
+
for (const f of files) {
|
|
961
|
+
const m = f.match(/^wal-(\d+)\.log$/);
|
|
962
|
+
if (!m) continue;
|
|
963
|
+
const gen = Number(m[1]);
|
|
964
|
+
if (gen < beforeGen) {
|
|
965
|
+
fs4.unlinkSync(path4.join(this.walDir, f));
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
/* -------------------------
|
|
970
|
+
GETTERS
|
|
971
|
+
------------------------- */
|
|
972
|
+
getCurrentLSN() {
|
|
973
|
+
return this.lsn;
|
|
974
|
+
}
|
|
975
|
+
getCurrentGen() {
|
|
976
|
+
return this.currentGen;
|
|
977
|
+
}
|
|
978
|
+
};
|
|
979
|
+
|
|
980
|
+
// src/core/checkpoint.ts
|
|
981
|
+
import fs5 from "fs";
|
|
982
|
+
import path5 from "path";
|
|
983
|
+
var CHECKPOINT_FILE = "__checkpoint.json";
|
|
984
|
+
var TMP_SUFFIX2 = ".tmp";
|
|
985
|
+
var FORMAT_VERSION = 1;
|
|
986
|
+
var CheckpointManager = class {
|
|
987
|
+
filePath;
|
|
988
|
+
data;
|
|
989
|
+
constructor(baseDir) {
|
|
990
|
+
this.filePath = path5.join(baseDir, CHECKPOINT_FILE);
|
|
991
|
+
this.data = {
|
|
992
|
+
lsn: 0,
|
|
993
|
+
walGen: 1,
|
|
994
|
+
time: 0,
|
|
995
|
+
version: FORMAT_VERSION
|
|
996
|
+
};
|
|
997
|
+
this.load();
|
|
998
|
+
}
|
|
999
|
+
/* -------------------------
|
|
1000
|
+
LOAD (Crash-safe)
|
|
1001
|
+
------------------------- */
|
|
1002
|
+
load() {
|
|
1003
|
+
if (!fs5.existsSync(this.filePath)) {
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
try {
|
|
1007
|
+
const raw = fs5.readFileSync(this.filePath, "utf8");
|
|
1008
|
+
const parsed = JSON.parse(raw);
|
|
1009
|
+
if (typeof parsed.lsn === "number" && typeof parsed.walGen === "number") {
|
|
1010
|
+
this.data = parsed;
|
|
1011
|
+
}
|
|
1012
|
+
} catch {
|
|
1013
|
+
console.error("Checkpoint corrupted, starting from zero");
|
|
1014
|
+
this.data = {
|
|
1015
|
+
lsn: 0,
|
|
1016
|
+
walGen: 1,
|
|
1017
|
+
time: 0,
|
|
1018
|
+
version: FORMAT_VERSION
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
/* -------------------------
|
|
1023
|
+
SAVE (Atomic Write)
|
|
1024
|
+
------------------------- */
|
|
1025
|
+
save(lsn, walGen) {
|
|
1026
|
+
const newData = {
|
|
1027
|
+
lsn,
|
|
1028
|
+
walGen,
|
|
1029
|
+
time: Date.now(),
|
|
1030
|
+
version: FORMAT_VERSION
|
|
1031
|
+
};
|
|
1032
|
+
const tmpPath = this.filePath + TMP_SUFFIX2;
|
|
1033
|
+
try {
|
|
1034
|
+
fs5.writeFileSync(
|
|
1035
|
+
tmpPath,
|
|
1036
|
+
JSON.stringify(newData, null, 2),
|
|
1037
|
+
{ encoding: "utf8" }
|
|
1038
|
+
);
|
|
1039
|
+
fs5.renameSync(tmpPath, this.filePath);
|
|
1040
|
+
this.data = newData;
|
|
1041
|
+
} catch (err) {
|
|
1042
|
+
console.error("Failed to write checkpoint:", err);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
/* -------------------------
|
|
1046
|
+
GET CURRENT
|
|
1047
|
+
------------------------- */
|
|
1048
|
+
get() {
|
|
1049
|
+
return this.data;
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
|
|
823
1053
|
// src/core/database.ts
|
|
824
|
-
var exec = promisify(execFile);
|
|
825
1054
|
var META_FILE = "__db_meta.json";
|
|
826
|
-
var META_VERSION =
|
|
1055
|
+
var META_VERSION = 2;
|
|
827
1056
|
var DEFAULT_SCHEMA_VERSION = "v1";
|
|
828
1057
|
var DBTransactionContext = class {
|
|
829
1058
|
constructor(db, txId) {
|
|
@@ -846,11 +1075,26 @@ var DBTransactionContext = class {
|
|
|
846
1075
|
});
|
|
847
1076
|
}
|
|
848
1077
|
async commit() {
|
|
849
|
-
|
|
850
|
-
|
|
1078
|
+
for (const op of this.ops) {
|
|
1079
|
+
const recordOp = {
|
|
1080
|
+
tx: this.txId,
|
|
1081
|
+
type: "op",
|
|
1082
|
+
payload: op
|
|
1083
|
+
};
|
|
1084
|
+
await this.db.wal.append(recordOp);
|
|
1085
|
+
}
|
|
1086
|
+
const commitRecord = {
|
|
1087
|
+
tx: this.txId,
|
|
1088
|
+
type: "commit"
|
|
1089
|
+
};
|
|
1090
|
+
await this.db.wal.append(commitRecord);
|
|
851
1091
|
await this.db.applyTransaction(this.ops);
|
|
852
|
-
|
|
853
|
-
|
|
1092
|
+
const appliedRecord = {
|
|
1093
|
+
tx: this.txId,
|
|
1094
|
+
type: "applied"
|
|
1095
|
+
};
|
|
1096
|
+
await this.db.wal.append(appliedRecord);
|
|
1097
|
+
await this.db.postCommitMaintenance();
|
|
854
1098
|
}
|
|
855
1099
|
};
|
|
856
1100
|
var LioranDB = class _LioranDB {
|
|
@@ -858,26 +1102,56 @@ var LioranDB = class _LioranDB {
|
|
|
858
1102
|
dbName;
|
|
859
1103
|
manager;
|
|
860
1104
|
collections;
|
|
861
|
-
walPath;
|
|
862
1105
|
metaPath;
|
|
863
1106
|
meta;
|
|
864
1107
|
migrator;
|
|
865
1108
|
static TX_SEQ = 0;
|
|
1109
|
+
wal;
|
|
1110
|
+
checkpoint;
|
|
866
1111
|
constructor(basePath, dbName, manager) {
|
|
867
1112
|
this.basePath = basePath;
|
|
868
1113
|
this.dbName = dbName;
|
|
869
1114
|
this.manager = manager;
|
|
870
1115
|
this.collections = /* @__PURE__ */ new Map();
|
|
871
|
-
this.
|
|
872
|
-
|
|
873
|
-
fs4.mkdirSync(basePath, { recursive: true });
|
|
1116
|
+
this.metaPath = path6.join(basePath, META_FILE);
|
|
1117
|
+
fs6.mkdirSync(basePath, { recursive: true });
|
|
874
1118
|
this.loadMeta();
|
|
1119
|
+
this.wal = new WALManager(basePath);
|
|
1120
|
+
this.checkpoint = new CheckpointManager(basePath);
|
|
875
1121
|
this.migrator = new MigrationEngine(this);
|
|
876
|
-
this.
|
|
1122
|
+
this.initialize().catch(console.error);
|
|
1123
|
+
}
|
|
1124
|
+
/* ------------------------- INIT & RECOVERY ------------------------- */
|
|
1125
|
+
async initialize() {
|
|
1126
|
+
await this.recoverFromWAL();
|
|
1127
|
+
}
|
|
1128
|
+
async recoverFromWAL() {
|
|
1129
|
+
const checkpointData = this.checkpoint.get();
|
|
1130
|
+
const fromLSN = checkpointData.lsn;
|
|
1131
|
+
const committed = /* @__PURE__ */ new Set();
|
|
1132
|
+
const applied = /* @__PURE__ */ new Set();
|
|
1133
|
+
const ops = /* @__PURE__ */ new Map();
|
|
1134
|
+
await this.wal.replay(fromLSN, async (record) => {
|
|
1135
|
+
if (record.type === "commit") {
|
|
1136
|
+
committed.add(record.tx);
|
|
1137
|
+
} else if (record.type === "applied") {
|
|
1138
|
+
applied.add(record.tx);
|
|
1139
|
+
} else if (record.type === "op") {
|
|
1140
|
+
if (!ops.has(record.tx)) ops.set(record.tx, []);
|
|
1141
|
+
ops.get(record.tx).push(record.payload);
|
|
1142
|
+
}
|
|
1143
|
+
});
|
|
1144
|
+
for (const tx of committed) {
|
|
1145
|
+
if (applied.has(tx)) continue;
|
|
1146
|
+
const txOps = ops.get(tx);
|
|
1147
|
+
if (txOps) {
|
|
1148
|
+
await this.applyTransaction(txOps);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
877
1151
|
}
|
|
878
1152
|
/* ------------------------- META ------------------------- */
|
|
879
1153
|
loadMeta() {
|
|
880
|
-
if (!
|
|
1154
|
+
if (!fs6.existsSync(this.metaPath)) {
|
|
881
1155
|
this.meta = {
|
|
882
1156
|
version: META_VERSION,
|
|
883
1157
|
indexes: {},
|
|
@@ -886,14 +1160,14 @@ var LioranDB = class _LioranDB {
|
|
|
886
1160
|
this.saveMeta();
|
|
887
1161
|
return;
|
|
888
1162
|
}
|
|
889
|
-
this.meta = JSON.parse(
|
|
1163
|
+
this.meta = JSON.parse(fs6.readFileSync(this.metaPath, "utf8"));
|
|
890
1164
|
if (!this.meta.schemaVersion) {
|
|
891
1165
|
this.meta.schemaVersion = DEFAULT_SCHEMA_VERSION;
|
|
892
1166
|
this.saveMeta();
|
|
893
1167
|
}
|
|
894
1168
|
}
|
|
895
1169
|
saveMeta() {
|
|
896
|
-
|
|
1170
|
+
fs6.writeFileSync(this.metaPath, JSON.stringify(this.meta, null, 2));
|
|
897
1171
|
}
|
|
898
1172
|
getSchemaVersion() {
|
|
899
1173
|
return this.meta.schemaVersion;
|
|
@@ -912,44 +1186,7 @@ var LioranDB = class _LioranDB {
|
|
|
912
1186
|
async applyMigrations(targetVersion) {
|
|
913
1187
|
await this.migrator.upgradeToLatest();
|
|
914
1188
|
}
|
|
915
|
-
/* -------------------------
|
|
916
|
-
async writeWAL(entries) {
|
|
917
|
-
const fd = await fs4.promises.open(this.walPath, "a");
|
|
918
|
-
for (const e of entries) {
|
|
919
|
-
await fd.write(JSON.stringify(e) + "\n");
|
|
920
|
-
}
|
|
921
|
-
await fd.sync();
|
|
922
|
-
await fd.close();
|
|
923
|
-
}
|
|
924
|
-
async clearWAL() {
|
|
925
|
-
try {
|
|
926
|
-
await fs4.promises.unlink(this.walPath);
|
|
927
|
-
} catch {
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
async recoverFromWAL() {
|
|
931
|
-
if (!fs4.existsSync(this.walPath)) return;
|
|
932
|
-
const raw = await fs4.promises.readFile(this.walPath, "utf8");
|
|
933
|
-
const committed = /* @__PURE__ */ new Set();
|
|
934
|
-
const applied = /* @__PURE__ */ new Set();
|
|
935
|
-
const ops = /* @__PURE__ */ new Map();
|
|
936
|
-
for (const line of raw.split("\n")) {
|
|
937
|
-
if (!line.trim()) continue;
|
|
938
|
-
const entry = JSON.parse(line);
|
|
939
|
-
if ("commit" in entry) committed.add(entry.tx);
|
|
940
|
-
else if ("applied" in entry) applied.add(entry.tx);
|
|
941
|
-
else {
|
|
942
|
-
if (!ops.has(entry.tx)) ops.set(entry.tx, []);
|
|
943
|
-
ops.get(entry.tx).push(entry);
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
for (const tx of committed) {
|
|
947
|
-
if (applied.has(tx)) continue;
|
|
948
|
-
const txOps = ops.get(tx);
|
|
949
|
-
if (txOps) await this.applyTransaction(txOps);
|
|
950
|
-
}
|
|
951
|
-
await this.clearWAL();
|
|
952
|
-
}
|
|
1189
|
+
/* ------------------------- TX APPLY ------------------------- */
|
|
953
1190
|
async applyTransaction(ops) {
|
|
954
1191
|
for (const { col, op, args } of ops) {
|
|
955
1192
|
const collection = this.collection(col);
|
|
@@ -965,8 +1202,8 @@ var LioranDB = class _LioranDB {
|
|
|
965
1202
|
}
|
|
966
1203
|
return col2;
|
|
967
1204
|
}
|
|
968
|
-
const colPath =
|
|
969
|
-
|
|
1205
|
+
const colPath = path6.join(this.basePath, name);
|
|
1206
|
+
fs6.mkdirSync(colPath, { recursive: true });
|
|
970
1207
|
const col = new Collection(
|
|
971
1208
|
colPath,
|
|
972
1209
|
schema,
|
|
@@ -1004,12 +1241,10 @@ var LioranDB = class _LioranDB {
|
|
|
1004
1241
|
}
|
|
1005
1242
|
/* ------------------------- COMPACTION ------------------------- */
|
|
1006
1243
|
async compactCollection(name) {
|
|
1007
|
-
await this.clearWAL();
|
|
1008
1244
|
const col = this.collection(name);
|
|
1009
1245
|
await col.compact();
|
|
1010
1246
|
}
|
|
1011
1247
|
async compactAll() {
|
|
1012
|
-
await this.clearWAL();
|
|
1013
1248
|
for (const name of this.collections.keys()) {
|
|
1014
1249
|
await this.compactCollection(name);
|
|
1015
1250
|
}
|
|
@@ -1022,6 +1257,9 @@ var LioranDB = class _LioranDB {
|
|
|
1022
1257
|
await tx.commit();
|
|
1023
1258
|
return result;
|
|
1024
1259
|
}
|
|
1260
|
+
/* ------------------------- POST COMMIT ------------------------- */
|
|
1261
|
+
async postCommitMaintenance() {
|
|
1262
|
+
}
|
|
1025
1263
|
/* ------------------------- SHUTDOWN ------------------------- */
|
|
1026
1264
|
async close() {
|
|
1027
1265
|
for (const col of this.collections.values()) {
|
|
@@ -1036,15 +1274,15 @@ var LioranDB = class _LioranDB {
|
|
|
1036
1274
|
|
|
1037
1275
|
// src/utils/rootpath.ts
|
|
1038
1276
|
import os2 from "os";
|
|
1039
|
-
import
|
|
1040
|
-
import
|
|
1277
|
+
import path7 from "path";
|
|
1278
|
+
import fs7 from "fs";
|
|
1041
1279
|
function getDefaultRootPath() {
|
|
1042
1280
|
let dbPath = process.env.LIORANDB_PATH;
|
|
1043
1281
|
if (!dbPath) {
|
|
1044
1282
|
const homeDir = os2.homedir();
|
|
1045
|
-
dbPath =
|
|
1046
|
-
if (!
|
|
1047
|
-
|
|
1283
|
+
dbPath = path7.join(homeDir, "LioranDB", "db");
|
|
1284
|
+
if (!fs7.existsSync(dbPath)) {
|
|
1285
|
+
fs7.mkdirSync(dbPath, { recursive: true });
|
|
1048
1286
|
}
|
|
1049
1287
|
process.env.LIORANDB_PATH = dbPath;
|
|
1050
1288
|
}
|
|
@@ -1059,24 +1297,24 @@ import net from "net";
|
|
|
1059
1297
|
|
|
1060
1298
|
// src/ipc/socketPath.ts
|
|
1061
1299
|
import os3 from "os";
|
|
1062
|
-
import
|
|
1300
|
+
import path8 from "path";
|
|
1063
1301
|
function getIPCSocketPath(rootPath) {
|
|
1064
1302
|
if (os3.platform() === "win32") {
|
|
1065
1303
|
return `\\\\.\\pipe\\liorandb_${rootPath.replace(/[:\\\/]/g, "_")}`;
|
|
1066
1304
|
}
|
|
1067
|
-
return
|
|
1305
|
+
return path8.join(rootPath, ".lioran.sock");
|
|
1068
1306
|
}
|
|
1069
1307
|
|
|
1070
1308
|
// src/ipc/client.ts
|
|
1071
1309
|
function delay(ms) {
|
|
1072
1310
|
return new Promise((r) => setTimeout(r, ms));
|
|
1073
1311
|
}
|
|
1074
|
-
async function connectWithRetry(
|
|
1312
|
+
async function connectWithRetry(path10) {
|
|
1075
1313
|
let attempt = 0;
|
|
1076
1314
|
while (true) {
|
|
1077
1315
|
try {
|
|
1078
1316
|
return await new Promise((resolve, reject) => {
|
|
1079
|
-
const socket = net.connect(
|
|
1317
|
+
const socket = net.connect(path10, () => resolve(socket));
|
|
1080
1318
|
socket.once("error", reject);
|
|
1081
1319
|
});
|
|
1082
1320
|
} catch (err) {
|
|
@@ -1159,11 +1397,11 @@ var DBQueue = class {
|
|
|
1159
1397
|
return this.exec("compact:all", {});
|
|
1160
1398
|
}
|
|
1161
1399
|
/* ----------------------------- SNAPSHOT API ----------------------------- */
|
|
1162
|
-
snapshot(
|
|
1163
|
-
return this.exec("snapshot", { path:
|
|
1400
|
+
snapshot(path10) {
|
|
1401
|
+
return this.exec("snapshot", { path: path10 });
|
|
1164
1402
|
}
|
|
1165
|
-
restore(
|
|
1166
|
-
return this.exec("restore", { path:
|
|
1403
|
+
restore(path10) {
|
|
1404
|
+
return this.exec("restore", { path: path10 });
|
|
1167
1405
|
}
|
|
1168
1406
|
/* ------------------------------ SHUTDOWN ------------------------------ */
|
|
1169
1407
|
async shutdown() {
|
|
@@ -1178,7 +1416,7 @@ var dbQueue = new DBQueue();
|
|
|
1178
1416
|
|
|
1179
1417
|
// src/ipc/server.ts
|
|
1180
1418
|
import net2 from "net";
|
|
1181
|
-
import
|
|
1419
|
+
import fs8 from "fs";
|
|
1182
1420
|
var IPCServer = class {
|
|
1183
1421
|
server;
|
|
1184
1422
|
manager;
|
|
@@ -1189,7 +1427,7 @@ var IPCServer = class {
|
|
|
1189
1427
|
}
|
|
1190
1428
|
start() {
|
|
1191
1429
|
if (!this.socketPath.startsWith("\\\\.\\")) {
|
|
1192
|
-
if (
|
|
1430
|
+
if (fs8.existsSync(this.socketPath)) fs8.unlinkSync(this.socketPath);
|
|
1193
1431
|
}
|
|
1194
1432
|
this.server = net2.createServer((socket) => {
|
|
1195
1433
|
let buffer = "";
|
|
@@ -1291,7 +1529,7 @@ var IPCServer = class {
|
|
|
1291
1529
|
if (this.server) this.server.close();
|
|
1292
1530
|
if (!this.socketPath.startsWith("\\\\.\\")) {
|
|
1293
1531
|
try {
|
|
1294
|
-
|
|
1532
|
+
fs8.unlinkSync(this.socketPath);
|
|
1295
1533
|
} catch {
|
|
1296
1534
|
}
|
|
1297
1535
|
}
|
|
@@ -1309,8 +1547,8 @@ var LioranManager = class {
|
|
|
1309
1547
|
constructor(options = {}) {
|
|
1310
1548
|
const { rootPath, encryptionKey } = options;
|
|
1311
1549
|
this.rootPath = rootPath || getDefaultRootPath();
|
|
1312
|
-
if (!
|
|
1313
|
-
|
|
1550
|
+
if (!fs9.existsSync(this.rootPath)) {
|
|
1551
|
+
fs9.mkdirSync(this.rootPath, { recursive: true });
|
|
1314
1552
|
}
|
|
1315
1553
|
if (encryptionKey) {
|
|
1316
1554
|
setEncryptionKey(encryptionKey);
|
|
@@ -1333,18 +1571,18 @@ var LioranManager = class {
|
|
|
1333
1571
|
}
|
|
1334
1572
|
}
|
|
1335
1573
|
tryAcquireLock() {
|
|
1336
|
-
const lockPath =
|
|
1574
|
+
const lockPath = path9.join(this.rootPath, ".lioran.lock");
|
|
1337
1575
|
try {
|
|
1338
|
-
this.lockFd =
|
|
1339
|
-
|
|
1576
|
+
this.lockFd = fs9.openSync(lockPath, "wx");
|
|
1577
|
+
fs9.writeSync(this.lockFd, String(process2.pid));
|
|
1340
1578
|
return true;
|
|
1341
1579
|
} catch {
|
|
1342
1580
|
try {
|
|
1343
|
-
const pid = Number(
|
|
1581
|
+
const pid = Number(fs9.readFileSync(lockPath, "utf8"));
|
|
1344
1582
|
if (!this.isProcessAlive(pid)) {
|
|
1345
|
-
|
|
1346
|
-
this.lockFd =
|
|
1347
|
-
|
|
1583
|
+
fs9.unlinkSync(lockPath);
|
|
1584
|
+
this.lockFd = fs9.openSync(lockPath, "wx");
|
|
1585
|
+
fs9.writeSync(this.lockFd, String(process2.pid));
|
|
1348
1586
|
return true;
|
|
1349
1587
|
}
|
|
1350
1588
|
} catch {
|
|
@@ -1365,8 +1603,8 @@ var LioranManager = class {
|
|
|
1365
1603
|
if (this.openDBs.has(name)) {
|
|
1366
1604
|
return this.openDBs.get(name);
|
|
1367
1605
|
}
|
|
1368
|
-
const dbPath =
|
|
1369
|
-
await
|
|
1606
|
+
const dbPath = path9.join(this.rootPath, name);
|
|
1607
|
+
await fs9.promises.mkdir(dbPath, { recursive: true });
|
|
1370
1608
|
const db = new LioranDB(dbPath, name, this);
|
|
1371
1609
|
this.openDBs.set(name, db);
|
|
1372
1610
|
return db;
|
|
@@ -1387,7 +1625,7 @@ var LioranManager = class {
|
|
|
1387
1625
|
}
|
|
1388
1626
|
}
|
|
1389
1627
|
}
|
|
1390
|
-
|
|
1628
|
+
fs9.mkdirSync(path9.dirname(snapshotPath), { recursive: true });
|
|
1391
1629
|
const tar = await import("tar");
|
|
1392
1630
|
await tar.c({
|
|
1393
1631
|
gzip: true,
|
|
@@ -1405,8 +1643,8 @@ var LioranManager = class {
|
|
|
1405
1643
|
return dbQueue.exec("restore", { path: snapshotPath });
|
|
1406
1644
|
}
|
|
1407
1645
|
await this.closeAll();
|
|
1408
|
-
|
|
1409
|
-
|
|
1646
|
+
fs9.rmSync(this.rootPath, { recursive: true, force: true });
|
|
1647
|
+
fs9.mkdirSync(this.rootPath, { recursive: true });
|
|
1410
1648
|
const tar = await import("tar");
|
|
1411
1649
|
await tar.x({
|
|
1412
1650
|
file: snapshotPath,
|
|
@@ -1431,8 +1669,8 @@ var LioranManager = class {
|
|
|
1431
1669
|
}
|
|
1432
1670
|
this.openDBs.clear();
|
|
1433
1671
|
try {
|
|
1434
|
-
if (this.lockFd)
|
|
1435
|
-
|
|
1672
|
+
if (this.lockFd) fs9.closeSync(this.lockFd);
|
|
1673
|
+
fs9.unlinkSync(path9.join(this.rootPath, ".lioran.lock"));
|
|
1436
1674
|
} catch {
|
|
1437
1675
|
}
|
|
1438
1676
|
await this.ipcServer?.close();
|