@liorandb/core 1.0.15 → 1.0.17
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 +13 -6
- package/dist/index.js +289 -71
- package/package.json +4 -1
- package/src/LioranManager.ts +67 -8
- package/src/core/database.ts +82 -39
- package/src/core/migration.store.ts +40 -0
- package/src/core/migration.ts +162 -0
- package/src/core/migration.types.ts +22 -0
- package/src/ipc/index.ts +20 -0
- package/src/ipc/queue.ts +17 -1
- package/src/ipc/server.ts +21 -3
- package/src/utils/tar.ts +89 -0
package/dist/index.d.ts
CHANGED
|
@@ -92,23 +92,22 @@ declare class LioranDB {
|
|
|
92
92
|
private walPath;
|
|
93
93
|
private metaPath;
|
|
94
94
|
private meta;
|
|
95
|
+
private migrator;
|
|
95
96
|
private static TX_SEQ;
|
|
96
97
|
constructor(basePath: string, dbName: string, manager: LioranManager);
|
|
97
98
|
private loadMeta;
|
|
98
99
|
private saveMeta;
|
|
100
|
+
getSchemaVersion(): string;
|
|
101
|
+
setSchemaVersion(v: string): void;
|
|
102
|
+
migrate(from: string, to: string, fn: (db: LioranDB) => Promise<void>): void;
|
|
103
|
+
applyMigrations(targetVersion: string): Promise<void>;
|
|
99
104
|
writeWAL(entries: WALEntry[]): Promise<void>;
|
|
100
105
|
clearWAL(): Promise<void>;
|
|
101
106
|
private recoverFromWAL;
|
|
102
107
|
applyTransaction(ops: TXOp[]): Promise<void>;
|
|
103
108
|
collection<T = any>(name: string, schema?: ZodSchema<T>): Collection<T>;
|
|
104
109
|
createIndex(collection: string, field: string, options?: IndexOptions): Promise<void>;
|
|
105
|
-
/**
|
|
106
|
-
* Compact single collection safely (WAL + TX safe)
|
|
107
|
-
*/
|
|
108
110
|
compactCollection(name: string): Promise<void>;
|
|
109
|
-
/**
|
|
110
|
-
* Compact entire database safely
|
|
111
|
-
*/
|
|
112
111
|
compactAll(): Promise<void>;
|
|
113
112
|
transaction<T>(fn: (tx: DBTransactionContext) => Promise<T>): Promise<T>;
|
|
114
113
|
close(): Promise<void>;
|
|
@@ -130,6 +129,14 @@ declare class LioranManager {
|
|
|
130
129
|
private tryAcquireLock;
|
|
131
130
|
db(name: string): Promise<LioranDB>;
|
|
132
131
|
openDatabase(name: string): Promise<LioranDB>;
|
|
132
|
+
/**
|
|
133
|
+
* Create TAR snapshot of full DB directory
|
|
134
|
+
*/
|
|
135
|
+
snapshot(snapshotPath: string): Promise<unknown>;
|
|
136
|
+
/**
|
|
137
|
+
* Restore TAR snapshot safely
|
|
138
|
+
*/
|
|
139
|
+
restore(snapshotPath: string): Promise<unknown>;
|
|
133
140
|
closeAll(): Promise<void>;
|
|
134
141
|
close(): Promise<void>;
|
|
135
142
|
private _registerShutdownHooks;
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
// src/LioranManager.ts
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import path7 from "path";
|
|
3
|
+
import fs7 from "fs";
|
|
4
4
|
import process2 from "process";
|
|
5
5
|
|
|
6
6
|
// src/core/database.ts
|
|
7
|
-
import
|
|
8
|
-
import
|
|
7
|
+
import path4 from "path";
|
|
8
|
+
import fs4 from "fs";
|
|
9
|
+
import { execFile } from "child_process";
|
|
10
|
+
import { promisify } from "util";
|
|
9
11
|
|
|
10
12
|
// src/core/collection.ts
|
|
11
13
|
import { ClassicLevel as ClassicLevel3 } from "classic-level";
|
|
12
14
|
|
|
13
15
|
// src/core/query.ts
|
|
14
|
-
function getByPath(obj,
|
|
15
|
-
return
|
|
16
|
+
function getByPath(obj, path8) {
|
|
17
|
+
return path8.split(".").reduce((o, p) => o ? o[p] : void 0, obj);
|
|
16
18
|
}
|
|
17
19
|
function matchDocument(doc, query) {
|
|
18
20
|
for (const key of Object.keys(query)) {
|
|
@@ -668,9 +670,134 @@ var Collection = class {
|
|
|
668
670
|
}
|
|
669
671
|
};
|
|
670
672
|
|
|
673
|
+
// src/core/migration.ts
|
|
674
|
+
import fs3 from "fs";
|
|
675
|
+
import path3 from "path";
|
|
676
|
+
import crypto3 from "crypto";
|
|
677
|
+
var LOCK_FILE = "__migration.lock";
|
|
678
|
+
var HISTORY_FILE = "__migration_history.json";
|
|
679
|
+
var MigrationEngine = class {
|
|
680
|
+
constructor(db) {
|
|
681
|
+
this.db = db;
|
|
682
|
+
}
|
|
683
|
+
migrations = /* @__PURE__ */ new Map();
|
|
684
|
+
/* ------------------------------------------------------------ */
|
|
685
|
+
/* Public API */
|
|
686
|
+
/* ------------------------------------------------------------ */
|
|
687
|
+
register(from, to, fn) {
|
|
688
|
+
const key = `${from}\u2192${to}`;
|
|
689
|
+
if (this.migrations.has(key)) {
|
|
690
|
+
throw new Error(`Duplicate migration: ${key}`);
|
|
691
|
+
}
|
|
692
|
+
this.migrations.set(key, fn);
|
|
693
|
+
}
|
|
694
|
+
async migrate(from, to, fn) {
|
|
695
|
+
this.register(from, to, fn);
|
|
696
|
+
await this.execute();
|
|
697
|
+
}
|
|
698
|
+
async upgradeToLatest() {
|
|
699
|
+
await this.execute();
|
|
700
|
+
}
|
|
701
|
+
/* ------------------------------------------------------------ */
|
|
702
|
+
/* Core Execution Logic */
|
|
703
|
+
/* ------------------------------------------------------------ */
|
|
704
|
+
async execute() {
|
|
705
|
+
let current = this.db.getSchemaVersion();
|
|
706
|
+
while (true) {
|
|
707
|
+
const next = this.findNext(current);
|
|
708
|
+
if (!next) break;
|
|
709
|
+
const fn = this.migrations.get(`${current}\u2192${next}`);
|
|
710
|
+
await this.runMigration(current, next, fn);
|
|
711
|
+
current = next;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
findNext(current) {
|
|
715
|
+
for (const key of this.migrations.keys()) {
|
|
716
|
+
const [from, to] = key.split("\u2192");
|
|
717
|
+
if (from === current) return to;
|
|
718
|
+
}
|
|
719
|
+
return null;
|
|
720
|
+
}
|
|
721
|
+
/* ------------------------------------------------------------ */
|
|
722
|
+
/* Atomic Migration Execution */
|
|
723
|
+
/* ------------------------------------------------------------ */
|
|
724
|
+
async runMigration(from, to, fn) {
|
|
725
|
+
const current = this.db.getSchemaVersion();
|
|
726
|
+
if (current !== from) {
|
|
727
|
+
throw new Error(
|
|
728
|
+
`Schema mismatch: DB=${current}, expected=${from}`
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
const lockPath = path3.join(this.db.basePath, LOCK_FILE);
|
|
732
|
+
if (fs3.existsSync(lockPath)) {
|
|
733
|
+
throw new Error(
|
|
734
|
+
"Previous migration interrupted. Resolve manually before continuing."
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
this.acquireLock(lockPath);
|
|
738
|
+
try {
|
|
739
|
+
await this.db.transaction(async () => {
|
|
740
|
+
await fn(this.db);
|
|
741
|
+
this.writeHistory(from, to, fn);
|
|
742
|
+
this.db.setSchemaVersion(to);
|
|
743
|
+
});
|
|
744
|
+
} finally {
|
|
745
|
+
this.releaseLock(lockPath);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
/* ------------------------------------------------------------ */
|
|
749
|
+
/* Locking */
|
|
750
|
+
/* ------------------------------------------------------------ */
|
|
751
|
+
acquireLock(file) {
|
|
752
|
+
const token = crypto3.randomBytes(16).toString("hex");
|
|
753
|
+
fs3.writeFileSync(
|
|
754
|
+
file,
|
|
755
|
+
JSON.stringify({
|
|
756
|
+
pid: process.pid,
|
|
757
|
+
token,
|
|
758
|
+
time: Date.now()
|
|
759
|
+
})
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
releaseLock(file) {
|
|
763
|
+
if (fs3.existsSync(file)) fs3.unlinkSync(file);
|
|
764
|
+
}
|
|
765
|
+
/* ------------------------------------------------------------ */
|
|
766
|
+
/* Migration History */
|
|
767
|
+
/* ------------------------------------------------------------ */
|
|
768
|
+
historyPath() {
|
|
769
|
+
return path3.join(this.db.basePath, HISTORY_FILE);
|
|
770
|
+
}
|
|
771
|
+
readHistory() {
|
|
772
|
+
if (!fs3.existsSync(this.historyPath())) return [];
|
|
773
|
+
return JSON.parse(fs3.readFileSync(this.historyPath(), "utf8"));
|
|
774
|
+
}
|
|
775
|
+
writeHistory(from, to, fn) {
|
|
776
|
+
const history = this.readHistory();
|
|
777
|
+
history.push({
|
|
778
|
+
from,
|
|
779
|
+
to,
|
|
780
|
+
checksum: this.hash(fn.toString()),
|
|
781
|
+
appliedAt: Date.now()
|
|
782
|
+
});
|
|
783
|
+
fs3.writeFileSync(this.historyPath(), JSON.stringify(history, null, 2));
|
|
784
|
+
}
|
|
785
|
+
hash(data) {
|
|
786
|
+
return crypto3.createHash("sha256").update(data).digest("hex");
|
|
787
|
+
}
|
|
788
|
+
/* ------------------------------------------------------------ */
|
|
789
|
+
/* Diagnostics */
|
|
790
|
+
/* ------------------------------------------------------------ */
|
|
791
|
+
getHistory() {
|
|
792
|
+
return this.readHistory();
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
|
|
671
796
|
// src/core/database.ts
|
|
797
|
+
var exec = promisify(execFile);
|
|
672
798
|
var META_FILE = "__db_meta.json";
|
|
673
799
|
var META_VERSION = 1;
|
|
800
|
+
var DEFAULT_SCHEMA_VERSION = "v1";
|
|
674
801
|
var DBTransactionContext = class {
|
|
675
802
|
constructor(db, txId) {
|
|
676
803
|
this.db = db;
|
|
@@ -707,37 +834,60 @@ var LioranDB = class _LioranDB {
|
|
|
707
834
|
walPath;
|
|
708
835
|
metaPath;
|
|
709
836
|
meta;
|
|
837
|
+
migrator;
|
|
710
838
|
static TX_SEQ = 0;
|
|
711
839
|
constructor(basePath, dbName, manager) {
|
|
712
840
|
this.basePath = basePath;
|
|
713
841
|
this.dbName = dbName;
|
|
714
842
|
this.manager = manager;
|
|
715
843
|
this.collections = /* @__PURE__ */ new Map();
|
|
716
|
-
this.walPath =
|
|
717
|
-
this.metaPath =
|
|
718
|
-
|
|
844
|
+
this.walPath = path4.join(basePath, "__tx_wal.log");
|
|
845
|
+
this.metaPath = path4.join(basePath, META_FILE);
|
|
846
|
+
fs4.mkdirSync(basePath, { recursive: true });
|
|
719
847
|
this.loadMeta();
|
|
848
|
+
this.migrator = new MigrationEngine(this);
|
|
720
849
|
this.recoverFromWAL().catch(console.error);
|
|
721
850
|
}
|
|
722
851
|
/* ------------------------- META ------------------------- */
|
|
723
852
|
loadMeta() {
|
|
724
|
-
if (!
|
|
725
|
-
this.meta = {
|
|
853
|
+
if (!fs4.existsSync(this.metaPath)) {
|
|
854
|
+
this.meta = {
|
|
855
|
+
version: META_VERSION,
|
|
856
|
+
indexes: {},
|
|
857
|
+
schemaVersion: DEFAULT_SCHEMA_VERSION
|
|
858
|
+
};
|
|
726
859
|
this.saveMeta();
|
|
727
860
|
return;
|
|
728
861
|
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
862
|
+
this.meta = JSON.parse(fs4.readFileSync(this.metaPath, "utf8"));
|
|
863
|
+
if (!this.meta.schemaVersion) {
|
|
864
|
+
this.meta.schemaVersion = DEFAULT_SCHEMA_VERSION;
|
|
865
|
+
this.saveMeta();
|
|
733
866
|
}
|
|
734
867
|
}
|
|
735
868
|
saveMeta() {
|
|
736
|
-
|
|
869
|
+
fs4.writeFileSync(this.metaPath, JSON.stringify(this.meta, null, 2));
|
|
870
|
+
}
|
|
871
|
+
getSchemaVersion() {
|
|
872
|
+
return this.meta.schemaVersion;
|
|
873
|
+
}
|
|
874
|
+
setSchemaVersion(v) {
|
|
875
|
+
this.meta.schemaVersion = v;
|
|
876
|
+
this.saveMeta();
|
|
877
|
+
}
|
|
878
|
+
/* ------------------------- MIGRATION API ------------------------- */
|
|
879
|
+
migrate(from, to, fn) {
|
|
880
|
+
this.migrator.register(from, to, async (db) => {
|
|
881
|
+
await fn(db);
|
|
882
|
+
db.setSchemaVersion(to);
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
async applyMigrations(targetVersion) {
|
|
886
|
+
await this.migrator.upgradeToLatest();
|
|
737
887
|
}
|
|
738
888
|
/* ------------------------- WAL ------------------------- */
|
|
739
889
|
async writeWAL(entries) {
|
|
740
|
-
const fd = await
|
|
890
|
+
const fd = await fs4.promises.open(this.walPath, "a");
|
|
741
891
|
for (const e of entries) {
|
|
742
892
|
await fd.write(JSON.stringify(e) + "\n");
|
|
743
893
|
}
|
|
@@ -746,28 +896,24 @@ var LioranDB = class _LioranDB {
|
|
|
746
896
|
}
|
|
747
897
|
async clearWAL() {
|
|
748
898
|
try {
|
|
749
|
-
await
|
|
899
|
+
await fs4.promises.unlink(this.walPath);
|
|
750
900
|
} catch {
|
|
751
901
|
}
|
|
752
902
|
}
|
|
753
903
|
async recoverFromWAL() {
|
|
754
|
-
if (!
|
|
755
|
-
const raw = await
|
|
904
|
+
if (!fs4.existsSync(this.walPath)) return;
|
|
905
|
+
const raw = await fs4.promises.readFile(this.walPath, "utf8");
|
|
756
906
|
const committed = /* @__PURE__ */ new Set();
|
|
757
907
|
const applied = /* @__PURE__ */ new Set();
|
|
758
908
|
const ops = /* @__PURE__ */ new Map();
|
|
759
909
|
for (const line of raw.split("\n")) {
|
|
760
910
|
if (!line.trim()) continue;
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
ops.get(entry.tx).push(entry);
|
|
768
|
-
}
|
|
769
|
-
} catch {
|
|
770
|
-
break;
|
|
911
|
+
const entry = JSON.parse(line);
|
|
912
|
+
if ("commit" in entry) committed.add(entry.tx);
|
|
913
|
+
else if ("applied" in entry) applied.add(entry.tx);
|
|
914
|
+
else {
|
|
915
|
+
if (!ops.has(entry.tx)) ops.set(entry.tx, []);
|
|
916
|
+
ops.get(entry.tx).push(entry);
|
|
771
917
|
}
|
|
772
918
|
}
|
|
773
919
|
for (const tx of committed) {
|
|
@@ -790,8 +936,8 @@ var LioranDB = class _LioranDB {
|
|
|
790
936
|
if (schema) col2.setSchema(schema);
|
|
791
937
|
return col2;
|
|
792
938
|
}
|
|
793
|
-
const colPath =
|
|
794
|
-
|
|
939
|
+
const colPath = path4.join(this.basePath, name);
|
|
940
|
+
fs4.mkdirSync(colPath, { recursive: true });
|
|
795
941
|
const col = new Collection(colPath, schema);
|
|
796
942
|
const metas = this.meta.indexes[name] ?? [];
|
|
797
943
|
for (const m of metas) {
|
|
@@ -806,9 +952,15 @@ var LioranDB = class _LioranDB {
|
|
|
806
952
|
const existing = this.meta.indexes[collection]?.find((i) => i.field === field);
|
|
807
953
|
if (existing) return;
|
|
808
954
|
const index = new Index(col.dir, field, options);
|
|
809
|
-
for await (const [, enc] of col.db.iterator()) {
|
|
810
|
-
|
|
811
|
-
|
|
955
|
+
for await (const [key, enc] of col.db.iterator()) {
|
|
956
|
+
if (!enc) continue;
|
|
957
|
+
try {
|
|
958
|
+
const doc = decryptData2(enc);
|
|
959
|
+
await index.insert(doc);
|
|
960
|
+
} catch (err) {
|
|
961
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
962
|
+
console.warn(`Could not decrypt document ${key} during index build: ${errorMessage}`);
|
|
963
|
+
}
|
|
812
964
|
}
|
|
813
965
|
col.registerIndex(index);
|
|
814
966
|
if (!this.meta.indexes[collection]) {
|
|
@@ -817,18 +969,12 @@ var LioranDB = class _LioranDB {
|
|
|
817
969
|
this.meta.indexes[collection].push({ field, options });
|
|
818
970
|
this.saveMeta();
|
|
819
971
|
}
|
|
820
|
-
/*
|
|
821
|
-
/**
|
|
822
|
-
* Compact single collection safely (WAL + TX safe)
|
|
823
|
-
*/
|
|
972
|
+
/* ------------------------- COMPACTION ------------------------- */
|
|
824
973
|
async compactCollection(name) {
|
|
825
|
-
const col = this.collection(name);
|
|
826
974
|
await this.clearWAL();
|
|
975
|
+
const col = this.collection(name);
|
|
827
976
|
await col.compact();
|
|
828
977
|
}
|
|
829
|
-
/**
|
|
830
|
-
* Compact entire database safely
|
|
831
|
-
*/
|
|
832
978
|
async compactAll() {
|
|
833
979
|
await this.clearWAL();
|
|
834
980
|
for (const name of this.collections.keys()) {
|
|
@@ -854,18 +1000,21 @@ var LioranDB = class _LioranDB {
|
|
|
854
1000
|
this.collections.clear();
|
|
855
1001
|
}
|
|
856
1002
|
};
|
|
1003
|
+
function decryptData2(enc) {
|
|
1004
|
+
throw new Error("Function not implemented.");
|
|
1005
|
+
}
|
|
857
1006
|
|
|
858
1007
|
// src/utils/rootpath.ts
|
|
859
1008
|
import os2 from "os";
|
|
860
|
-
import
|
|
861
|
-
import
|
|
1009
|
+
import path5 from "path";
|
|
1010
|
+
import fs5 from "fs";
|
|
862
1011
|
function getDefaultRootPath() {
|
|
863
1012
|
let dbPath = process.env.LIORANDB_PATH;
|
|
864
1013
|
if (!dbPath) {
|
|
865
1014
|
const homeDir = os2.homedir();
|
|
866
|
-
dbPath =
|
|
867
|
-
if (!
|
|
868
|
-
|
|
1015
|
+
dbPath = path5.join(homeDir, "LioranDB", "db");
|
|
1016
|
+
if (!fs5.existsSync(dbPath)) {
|
|
1017
|
+
fs5.mkdirSync(dbPath, { recursive: true });
|
|
869
1018
|
}
|
|
870
1019
|
process.env.LIORANDB_PATH = dbPath;
|
|
871
1020
|
}
|
|
@@ -880,24 +1029,24 @@ import net from "net";
|
|
|
880
1029
|
|
|
881
1030
|
// src/ipc/socketPath.ts
|
|
882
1031
|
import os3 from "os";
|
|
883
|
-
import
|
|
1032
|
+
import path6 from "path";
|
|
884
1033
|
function getIPCSocketPath(rootPath) {
|
|
885
1034
|
if (os3.platform() === "win32") {
|
|
886
1035
|
return `\\\\.\\pipe\\liorandb_${rootPath.replace(/[:\\\/]/g, "_")}`;
|
|
887
1036
|
}
|
|
888
|
-
return
|
|
1037
|
+
return path6.join(rootPath, ".lioran.sock");
|
|
889
1038
|
}
|
|
890
1039
|
|
|
891
1040
|
// src/ipc/client.ts
|
|
892
1041
|
function delay(ms) {
|
|
893
1042
|
return new Promise((r) => setTimeout(r, ms));
|
|
894
1043
|
}
|
|
895
|
-
async function connectWithRetry(
|
|
1044
|
+
async function connectWithRetry(path8) {
|
|
896
1045
|
let attempt = 0;
|
|
897
1046
|
while (true) {
|
|
898
1047
|
try {
|
|
899
1048
|
return await new Promise((resolve, reject) => {
|
|
900
|
-
const socket = net.connect(
|
|
1049
|
+
const socket = net.connect(path8, () => resolve(socket));
|
|
901
1050
|
socket.once("error", reject);
|
|
902
1051
|
});
|
|
903
1052
|
} catch (err) {
|
|
@@ -979,6 +1128,13 @@ var DBQueue = class {
|
|
|
979
1128
|
compactAll() {
|
|
980
1129
|
return this.exec("compact:all", {});
|
|
981
1130
|
}
|
|
1131
|
+
/* ----------------------------- SNAPSHOT API ----------------------------- */
|
|
1132
|
+
snapshot(path8) {
|
|
1133
|
+
return this.exec("snapshot", { path: path8 });
|
|
1134
|
+
}
|
|
1135
|
+
restore(path8) {
|
|
1136
|
+
return this.exec("restore", { path: path8 });
|
|
1137
|
+
}
|
|
982
1138
|
/* ------------------------------ SHUTDOWN ------------------------------ */
|
|
983
1139
|
async shutdown() {
|
|
984
1140
|
try {
|
|
@@ -992,7 +1148,7 @@ var dbQueue = new DBQueue();
|
|
|
992
1148
|
|
|
993
1149
|
// src/ipc/server.ts
|
|
994
1150
|
import net2 from "net";
|
|
995
|
-
import
|
|
1151
|
+
import fs6 from "fs";
|
|
996
1152
|
var IPCServer = class {
|
|
997
1153
|
server;
|
|
998
1154
|
manager;
|
|
@@ -1003,7 +1159,7 @@ var IPCServer = class {
|
|
|
1003
1159
|
}
|
|
1004
1160
|
start() {
|
|
1005
1161
|
if (!this.socketPath.startsWith("\\\\.\\")) {
|
|
1006
|
-
if (
|
|
1162
|
+
if (fs6.existsSync(this.socketPath)) fs6.unlinkSync(this.socketPath);
|
|
1007
1163
|
}
|
|
1008
1164
|
this.server = net2.createServer((socket) => {
|
|
1009
1165
|
let buffer = "";
|
|
@@ -1016,7 +1172,7 @@ var IPCServer = class {
|
|
|
1016
1172
|
try {
|
|
1017
1173
|
const msg = JSON.parse(raw);
|
|
1018
1174
|
await this.handleMessage(socket, msg);
|
|
1019
|
-
} catch
|
|
1175
|
+
} catch {
|
|
1020
1176
|
socket.write(JSON.stringify({
|
|
1021
1177
|
id: null,
|
|
1022
1178
|
ok: false,
|
|
@@ -1059,12 +1215,27 @@ var IPCServer = class {
|
|
|
1059
1215
|
case "compact:db": {
|
|
1060
1216
|
const { db } = args;
|
|
1061
1217
|
const database = await this.manager.db(db);
|
|
1062
|
-
await database.
|
|
1218
|
+
await database.compactAll();
|
|
1063
1219
|
result = true;
|
|
1064
1220
|
break;
|
|
1065
1221
|
}
|
|
1066
1222
|
case "compact:all": {
|
|
1067
|
-
|
|
1223
|
+
for (const db of this.manager.openDBs.values()) {
|
|
1224
|
+
await db.compactAll();
|
|
1225
|
+
}
|
|
1226
|
+
result = true;
|
|
1227
|
+
break;
|
|
1228
|
+
}
|
|
1229
|
+
/* ---------------- SNAPSHOT ---------------- */
|
|
1230
|
+
case "snapshot": {
|
|
1231
|
+
const { path: snapshotPath } = args;
|
|
1232
|
+
await this.manager.snapshot(snapshotPath);
|
|
1233
|
+
result = true;
|
|
1234
|
+
break;
|
|
1235
|
+
}
|
|
1236
|
+
case "restore": {
|
|
1237
|
+
const { path: snapshotPath } = args;
|
|
1238
|
+
await this.manager.restore(snapshotPath);
|
|
1068
1239
|
result = true;
|
|
1069
1240
|
break;
|
|
1070
1241
|
}
|
|
@@ -1090,7 +1261,7 @@ var IPCServer = class {
|
|
|
1090
1261
|
if (this.server) this.server.close();
|
|
1091
1262
|
if (!this.socketPath.startsWith("\\\\.\\")) {
|
|
1092
1263
|
try {
|
|
1093
|
-
|
|
1264
|
+
fs6.unlinkSync(this.socketPath);
|
|
1094
1265
|
} catch {
|
|
1095
1266
|
}
|
|
1096
1267
|
}
|
|
@@ -1108,8 +1279,8 @@ var LioranManager = class {
|
|
|
1108
1279
|
constructor(options = {}) {
|
|
1109
1280
|
const { rootPath, encryptionKey } = options;
|
|
1110
1281
|
this.rootPath = rootPath || getDefaultRootPath();
|
|
1111
|
-
if (!
|
|
1112
|
-
|
|
1282
|
+
if (!fs7.existsSync(this.rootPath)) {
|
|
1283
|
+
fs7.mkdirSync(this.rootPath, { recursive: true });
|
|
1113
1284
|
}
|
|
1114
1285
|
if (encryptionKey) {
|
|
1115
1286
|
setEncryptionKey(encryptionKey);
|
|
@@ -1122,6 +1293,7 @@ var LioranManager = class {
|
|
|
1122
1293
|
this._registerShutdownHooks();
|
|
1123
1294
|
}
|
|
1124
1295
|
}
|
|
1296
|
+
/* ---------------- LOCK MANAGEMENT ---------------- */
|
|
1125
1297
|
isProcessAlive(pid) {
|
|
1126
1298
|
try {
|
|
1127
1299
|
process2.kill(pid, 0);
|
|
@@ -1131,18 +1303,18 @@ var LioranManager = class {
|
|
|
1131
1303
|
}
|
|
1132
1304
|
}
|
|
1133
1305
|
tryAcquireLock() {
|
|
1134
|
-
const lockPath =
|
|
1306
|
+
const lockPath = path7.join(this.rootPath, ".lioran.lock");
|
|
1135
1307
|
try {
|
|
1136
|
-
this.lockFd =
|
|
1137
|
-
|
|
1308
|
+
this.lockFd = fs7.openSync(lockPath, "wx");
|
|
1309
|
+
fs7.writeSync(this.lockFd, String(process2.pid));
|
|
1138
1310
|
return true;
|
|
1139
1311
|
} catch {
|
|
1140
1312
|
try {
|
|
1141
|
-
const pid = Number(
|
|
1313
|
+
const pid = Number(fs7.readFileSync(lockPath, "utf8"));
|
|
1142
1314
|
if (!this.isProcessAlive(pid)) {
|
|
1143
|
-
|
|
1144
|
-
this.lockFd =
|
|
1145
|
-
|
|
1315
|
+
fs7.unlinkSync(lockPath);
|
|
1316
|
+
this.lockFd = fs7.openSync(lockPath, "wx");
|
|
1317
|
+
fs7.writeSync(this.lockFd, String(process2.pid));
|
|
1146
1318
|
return true;
|
|
1147
1319
|
}
|
|
1148
1320
|
} catch {
|
|
@@ -1150,6 +1322,7 @@ var LioranManager = class {
|
|
|
1150
1322
|
return false;
|
|
1151
1323
|
}
|
|
1152
1324
|
}
|
|
1325
|
+
/* ---------------- DB OPEN ---------------- */
|
|
1153
1326
|
async db(name) {
|
|
1154
1327
|
if (this.mode === "client" /* CLIENT */) {
|
|
1155
1328
|
await dbQueue.exec("db", { db: name });
|
|
@@ -1162,12 +1335,57 @@ var LioranManager = class {
|
|
|
1162
1335
|
if (this.openDBs.has(name)) {
|
|
1163
1336
|
return this.openDBs.get(name);
|
|
1164
1337
|
}
|
|
1165
|
-
const dbPath =
|
|
1166
|
-
await
|
|
1338
|
+
const dbPath = path7.join(this.rootPath, name);
|
|
1339
|
+
await fs7.promises.mkdir(dbPath, { recursive: true });
|
|
1167
1340
|
const db = new LioranDB(dbPath, name, this);
|
|
1168
1341
|
this.openDBs.set(name, db);
|
|
1169
1342
|
return db;
|
|
1170
1343
|
}
|
|
1344
|
+
/* ---------------- SNAPSHOT ORCHESTRATION ---------------- */
|
|
1345
|
+
/**
|
|
1346
|
+
* Create TAR snapshot of full DB directory
|
|
1347
|
+
*/
|
|
1348
|
+
async snapshot(snapshotPath) {
|
|
1349
|
+
if (this.mode === "client" /* CLIENT */) {
|
|
1350
|
+
return dbQueue.exec("snapshot", { path: snapshotPath });
|
|
1351
|
+
}
|
|
1352
|
+
for (const db of this.openDBs.values()) {
|
|
1353
|
+
for (const col of db.collections.values()) {
|
|
1354
|
+
try {
|
|
1355
|
+
await col.db.close();
|
|
1356
|
+
} catch {
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
fs7.mkdirSync(path7.dirname(snapshotPath), { recursive: true });
|
|
1361
|
+
const tar = await import("tar");
|
|
1362
|
+
await tar.c({
|
|
1363
|
+
gzip: true,
|
|
1364
|
+
file: snapshotPath,
|
|
1365
|
+
cwd: this.rootPath,
|
|
1366
|
+
portable: true
|
|
1367
|
+
}, ["./"]);
|
|
1368
|
+
return true;
|
|
1369
|
+
}
|
|
1370
|
+
/**
|
|
1371
|
+
* Restore TAR snapshot safely
|
|
1372
|
+
*/
|
|
1373
|
+
async restore(snapshotPath) {
|
|
1374
|
+
if (this.mode === "client" /* CLIENT */) {
|
|
1375
|
+
return dbQueue.exec("restore", { path: snapshotPath });
|
|
1376
|
+
}
|
|
1377
|
+
await this.closeAll();
|
|
1378
|
+
fs7.rmSync(this.rootPath, { recursive: true, force: true });
|
|
1379
|
+
fs7.mkdirSync(this.rootPath, { recursive: true });
|
|
1380
|
+
const tar = await import("tar");
|
|
1381
|
+
await tar.x({
|
|
1382
|
+
file: snapshotPath,
|
|
1383
|
+
cwd: this.rootPath
|
|
1384
|
+
});
|
|
1385
|
+
console.log("Restore completed. Restart required.");
|
|
1386
|
+
process2.exit(0);
|
|
1387
|
+
}
|
|
1388
|
+
/* ---------------- SHUTDOWN ---------------- */
|
|
1171
1389
|
async closeAll() {
|
|
1172
1390
|
if (this.closed) return;
|
|
1173
1391
|
this.closed = true;
|
|
@@ -1183,8 +1401,8 @@ var LioranManager = class {
|
|
|
1183
1401
|
}
|
|
1184
1402
|
this.openDBs.clear();
|
|
1185
1403
|
try {
|
|
1186
|
-
if (this.lockFd)
|
|
1187
|
-
|
|
1404
|
+
if (this.lockFd) fs7.closeSync(this.lockFd);
|
|
1405
|
+
fs7.unlinkSync(path7.join(this.rootPath, ".lioran.lock"));
|
|
1188
1406
|
} catch {
|
|
1189
1407
|
}
|
|
1190
1408
|
await this.ipcServer?.close();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liorandb/core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.17",
|
|
4
4
|
"description": "LioranDB Core Module – Lightweight, local-first, peer-to-peer database management for Node.js.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -28,7 +28,10 @@
|
|
|
28
28
|
},
|
|
29
29
|
"homepage": "https://github.com/LioranGroupOfficial/Liorandb#readme",
|
|
30
30
|
"dependencies": {
|
|
31
|
+
"@types/tar-stream": "^3.1.4",
|
|
31
32
|
"classic-level": "^3.0.0",
|
|
33
|
+
"tar": "^7.5.9",
|
|
34
|
+
"tar-stream": "^3.1.7",
|
|
32
35
|
"uuid": "^13.0.0",
|
|
33
36
|
"zod": "^4.3.6"
|
|
34
37
|
},
|