@liorandb/core 1.0.17 → 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.
@@ -5,117 +5,164 @@ import { Collection } from "./collection.js";
5
5
  import { Index } from "./index.js";
6
6
  import { decryptData } from "../utils/encryption.js";
7
7
 
8
+ /* ---------------------------------------------------------
9
+ CONSTANTS
10
+ --------------------------------------------------------- */
11
+
8
12
  const TMP_SUFFIX = "__compact_tmp";
9
- const OLD_SUFFIX = "__old";
13
+ const OLD_SUFFIX = "__compact_old";
14
+ const INDEX_DIR = "__indexes";
15
+
16
+ /* ---------------------------------------------------------
17
+ PUBLIC ENTRY
18
+ --------------------------------------------------------- */
10
19
 
11
20
  /**
12
- * Entry point: safe compaction wrapper
21
+ * Full safe compaction pipeline:
22
+ * 1. Crash recovery
23
+ * 2. Snapshot rebuild
24
+ * 3. Atomic directory swap
25
+ * 4. Index rebuild
13
26
  */
14
27
  export async function compactCollectionEngine(col: Collection) {
15
- await crashRecovery(col.dir);
16
-
17
28
  const baseDir = col.dir;
18
29
  const tmpDir = baseDir + TMP_SUFFIX;
19
30
  const oldDir = baseDir + OLD_SUFFIX;
20
31
 
21
- // Cleanup stale dirs
32
+ // Recover from any previous crash mid-compaction
33
+ await crashRecovery(baseDir);
34
+
35
+ // Clean leftovers (paranoia safety)
22
36
  safeRemove(tmpDir);
23
37
  safeRemove(oldDir);
24
38
 
25
- // Snapshot rebuild
39
+ // Step 1: rebuild snapshot
26
40
  await snapshotRebuild(col, tmpDir);
27
41
 
28
- // Atomic swap
42
+ // Step 2: atomic swap
29
43
  atomicSwap(baseDir, tmpDir, oldDir);
30
44
 
31
- // Cleanup old data
45
+ // Cleanup
32
46
  safeRemove(oldDir);
33
47
  }
34
48
 
49
+ /* ---------------------------------------------------------
50
+ SNAPSHOT REBUILD
51
+ --------------------------------------------------------- */
52
+
35
53
  /**
36
- * Copies only live keys into fresh DB
54
+ * Rebuilds DB by copying only live keys
55
+ * WAL is assumed already checkpointed
37
56
  */
38
57
  async function snapshotRebuild(col: Collection, tmpDir: string) {
39
58
  fs.mkdirSync(tmpDir, { recursive: true });
40
59
 
41
- const tmpDB = new ClassicLevel(tmpDir, { valueEncoding: "utf8" });
60
+ const tmpDB = new ClassicLevel(tmpDir, {
61
+ valueEncoding: "utf8"
62
+ });
42
63
 
43
64
  for await (const [key, val] of col.db.iterator()) {
44
- await tmpDB.put(key, val);
65
+ if (val !== undefined) {
66
+ await tmpDB.put(key, val);
67
+ }
45
68
  }
46
69
 
47
70
  await tmpDB.close();
48
71
  await col.db.close();
49
72
  }
50
73
 
74
+ /* ---------------------------------------------------------
75
+ ATOMIC SWAP
76
+ --------------------------------------------------------- */
77
+
51
78
  /**
52
- * Atomic directory replace
79
+ * Atomic directory replacement (POSIX safe)
53
80
  */
54
81
  function atomicSwap(base: string, tmp: string, old: string) {
55
82
  fs.renameSync(base, old);
56
83
  fs.renameSync(tmp, base);
57
84
  }
58
85
 
86
+ /* ---------------------------------------------------------
87
+ CRASH RECOVERY
88
+ --------------------------------------------------------- */
89
+
59
90
  /**
60
- * Crash recovery handler
91
+ * Handles all partial-compaction states
61
92
  */
62
93
  export async function crashRecovery(baseDir: string) {
63
94
  const tmp = baseDir + TMP_SUFFIX;
64
95
  const old = baseDir + OLD_SUFFIX;
65
96
 
66
- // If both exist → compaction mid-swap
67
- if (fs.existsSync(tmp) && fs.existsSync(old)) {
97
+ const baseExists = fs.existsSync(baseDir);
98
+ const tmpExists = fs.existsSync(tmp);
99
+ const oldExists = fs.existsSync(old);
100
+
101
+ // Case 1: swap interrupted → tmp is valid snapshot
102
+ if (tmpExists && oldExists) {
68
103
  safeRemove(baseDir);
69
104
  fs.renameSync(tmp, baseDir);
70
105
  safeRemove(old);
106
+ return;
71
107
  }
72
108
 
73
- // If only old exists swap incomplete
74
- if (fs.existsSync(old) && !fs.existsSync(baseDir)) {
109
+ // Case 2: rename(base → old) happened, but tmp missing
110
+ if (!baseExists && oldExists) {
75
111
  fs.renameSync(old, baseDir);
112
+ return;
76
113
  }
77
114
 
78
- // If only tmp exists → rebuild incomplete
79
- if (fs.existsSync(tmp) && !fs.existsSync(old)) {
115
+ // Case 3: rebuild interrupted
116
+ if (tmpExists && !oldExists) {
80
117
  safeRemove(tmp);
81
118
  }
82
119
  }
83
120
 
121
+ /* ---------------------------------------------------------
122
+ INDEX REBUILD
123
+ --------------------------------------------------------- */
124
+
84
125
  /**
85
- * Index rebuild engine
126
+ * Rebuilds all indexes from compacted DB
127
+ * Guarantees index consistency
86
128
  */
87
129
  export async function rebuildIndexes(col: Collection) {
88
- const indexRoot = path.join(col.dir, "__indexes");
89
-
90
- // Destroy existing indexes
91
- safeRemove(indexRoot);
92
- fs.mkdirSync(indexRoot, { recursive: true });
130
+ const indexRoot = path.join(col.dir, INDEX_DIR);
93
131
 
132
+ // Close existing index handles
94
133
  for (const idx of col["indexes"].values()) {
95
- try { await idx.close(); } catch {}
134
+ try {
135
+ await idx.close();
136
+ } catch {}
96
137
  }
97
138
 
139
+ // Destroy index directory
140
+ safeRemove(indexRoot);
141
+ fs.mkdirSync(indexRoot, { recursive: true });
142
+
98
143
  const newIndexes = new Map<string, Index>();
99
144
 
100
145
  for (const idx of col["indexes"].values()) {
101
- const fresh = new Index(col.dir, idx.field, {
146
+ const rebuilt = new Index(col.dir, idx.field, {
102
147
  unique: idx.unique
103
148
  });
104
149
 
105
150
  for await (const [, enc] of col.db.iterator()) {
151
+ if (!enc) continue;
106
152
  const doc = decryptData(enc);
107
- await fresh.insert(doc);
153
+ await rebuilt.insert(doc);
108
154
  }
109
155
 
110
- newIndexes.set(idx.field, fresh);
156
+ newIndexes.set(idx.field, rebuilt);
111
157
  }
112
158
 
113
159
  col["indexes"] = newIndexes;
114
160
  }
115
161
 
116
- /**
117
- * Safe recursive remove
118
- */
162
+ /* ---------------------------------------------------------
163
+ UTIL
164
+ --------------------------------------------------------- */
165
+
119
166
  function safeRemove(p: string) {
120
167
  if (fs.existsSync(p)) {
121
168
  fs.rmSync(p, { recursive: true, force: true });
@@ -1,14 +1,14 @@
1
1
  import path from "path";
2
2
  import fs from "fs";
3
- import { execFile } from "child_process";
4
- import { promisify } from "util";
5
3
  import { Collection } from "./collection.js";
6
4
  import { Index, IndexOptions } from "./index.js";
7
5
  import { MigrationEngine } from "./migration.js";
8
6
  import type { LioranManager } from "../LioranManager.js";
9
7
  import type { ZodSchema } from "zod";
8
+ import { decryptData } from "../utils/encryption.js";
10
9
 
11
- const exec = promisify(execFile);
10
+ import { WALManager } from "./wal.js";
11
+ import { CheckpointManager } from "./checkpoint.js";
12
12
 
13
13
  /* ----------------------------- TYPES ----------------------------- */
14
14
 
@@ -29,7 +29,7 @@ type DBMeta = {
29
29
  };
30
30
 
31
31
  const META_FILE = "__db_meta.json";
32
- const META_VERSION = 1;
32
+ const META_VERSION = 2;
33
33
  const DEFAULT_SCHEMA_VERSION = "v1";
34
34
 
35
35
  /* ---------------------- TRANSACTION CONTEXT ---------------------- */
@@ -40,7 +40,7 @@ class DBTransactionContext {
40
40
  constructor(
41
41
  private db: LioranDB,
42
42
  public readonly txId: number
43
- ) { }
43
+ ) {}
44
44
 
45
45
  collection(name: string) {
46
46
  return new Proxy({}, {
@@ -58,11 +58,30 @@ class DBTransactionContext {
58
58
  }
59
59
 
60
60
  async commit() {
61
- await this.db.writeWAL(this.ops);
62
- await this.db.writeWAL([{ tx: this.txId, commit: true }]);
61
+ for (const op of this.ops) {
62
+ const recordOp: any = {
63
+ tx: this.txId,
64
+ type: "op",
65
+ payload: op
66
+ };
67
+ await this.db.wal.append(recordOp);
68
+ }
69
+
70
+ const commitRecord: any = {
71
+ tx: this.txId,
72
+ type: "commit"
73
+ };
74
+ await this.db.wal.append(commitRecord);
75
+
63
76
  await this.db.applyTransaction(this.ops);
64
- await this.db.writeWAL([{ tx: this.txId, applied: true }]);
65
- await this.db.clearWAL();
77
+
78
+ const appliedRecord: any = {
79
+ tx: this.txId,
80
+ type: "applied"
81
+ };
82
+ await this.db.wal.append(appliedRecord);
83
+
84
+ await this.db.postCommitMaintenance();
66
85
  }
67
86
  }
68
87
 
@@ -74,29 +93,68 @@ export class LioranDB {
74
93
  manager: LioranManager;
75
94
  collections: Map<string, Collection>;
76
95
 
77
- private walPath: string;
78
96
  private metaPath: string;
79
97
  private meta!: DBMeta;
80
98
 
81
99
  private migrator: MigrationEngine;
82
-
83
100
  private static TX_SEQ = 0;
84
101
 
102
+ public wal: WALManager;
103
+ private checkpoint: CheckpointManager;
104
+
85
105
  constructor(basePath: string, dbName: string, manager: LioranManager) {
86
106
  this.basePath = basePath;
87
107
  this.dbName = dbName;
88
108
  this.manager = manager;
89
109
  this.collections = new Map();
90
110
 
91
- this.walPath = path.join(basePath, "__tx_wal.log");
92
111
  this.metaPath = path.join(basePath, META_FILE);
93
112
 
94
113
  fs.mkdirSync(basePath, { recursive: true });
95
114
 
96
115
  this.loadMeta();
116
+
117
+ this.wal = new WALManager(basePath);
118
+ this.checkpoint = new CheckpointManager(basePath);
119
+
97
120
  this.migrator = new MigrationEngine(this);
98
121
 
99
- this.recoverFromWAL().catch(console.error);
122
+ this.initialize().catch(console.error);
123
+ }
124
+
125
+ /* ------------------------- INIT & RECOVERY ------------------------- */
126
+
127
+ private async initialize() {
128
+ await this.recoverFromWAL();
129
+ }
130
+
131
+ private async recoverFromWAL() {
132
+ const checkpointData = this.checkpoint.get();
133
+ const fromLSN = checkpointData.lsn;
134
+
135
+ const committed = new Set<number>();
136
+ const applied = new Set<number>();
137
+ const ops = new Map<number, TXOp[]>();
138
+
139
+ await this.wal.replay(fromLSN, async (record) => {
140
+ if (record.type === "commit") {
141
+ committed.add(record.tx);
142
+ } else if (record.type === "applied") {
143
+ applied.add(record.tx);
144
+ } else if (record.type === "op") {
145
+ if (!ops.has(record.tx)) ops.set(record.tx, []);
146
+ ops.get(record.tx)!.push(record.payload);
147
+ }
148
+ });
149
+
150
+ for (const tx of committed) {
151
+ if (applied.has(tx)) continue;
152
+
153
+ const txOps = ops.get(tx);
154
+ if (txOps) {
155
+ await this.applyTransaction(txOps);
156
+ }
157
+ }
100
158
  }
101
159
 
102
160
  /* ------------------------- META ------------------------- */
@@ -133,7 +191,7 @@ export class LioranDB {
133
191
  this.saveMeta();
134
192
  }
135
193
 
136
- /* ------------------------- MIGRATION API ------------------------- */
194
+ /* ------------------------- DB MIGRATIONS ------------------------- */
137
195
 
138
196
  migrate(from: string, to: string, fn: (db: LioranDB) => Promise<void>) {
139
197
  this.migrator.register(from, to, async db => {
@@ -146,51 +204,7 @@ export class LioranDB {
146
204
  await this.migrator.upgradeToLatest();
147
205
  }
148
206
 
149
- /* ------------------------- WAL ------------------------- */
150
-
151
- async writeWAL(entries: WALEntry[]) {
152
- const fd = await fs.promises.open(this.walPath, "a");
153
- for (const e of entries) {
154
- await fd.write(JSON.stringify(e) + "\n");
155
- }
156
- await fd.sync();
157
- await fd.close();
158
- }
159
-
160
- async clearWAL() {
161
- try { await fs.promises.unlink(this.walPath); } catch { }
162
- }
163
-
164
- private async recoverFromWAL() {
165
- if (!fs.existsSync(this.walPath)) return;
166
-
167
- const raw = await fs.promises.readFile(this.walPath, "utf8");
168
-
169
- const committed = new Set<number>();
170
- const applied = new Set<number>();
171
- const ops = new Map<number, TXOp[]>();
172
-
173
- for (const line of raw.split("\n")) {
174
- if (!line.trim()) continue;
175
-
176
- const entry: WALEntry = JSON.parse(line);
177
-
178
- if ("commit" in entry) committed.add(entry.tx);
179
- else if ("applied" in entry) applied.add(entry.tx);
180
- else {
181
- if (!ops.has(entry.tx)) ops.set(entry.tx, []);
182
- ops.get(entry.tx)!.push(entry);
183
- }
184
- }
185
-
186
- for (const tx of committed) {
187
- if (applied.has(tx)) continue;
188
- const txOps = ops.get(tx);
189
- if (txOps) await this.applyTransaction(txOps);
190
- }
191
-
192
- await this.clearWAL();
193
- }
207
+ /* ------------------------- TX APPLY ------------------------- */
194
208
 
195
209
  async applyTransaction(ops: TXOp[]) {
196
210
  for (const { col, op, args } of ops) {
@@ -201,17 +215,27 @@ export class LioranDB {
201
215
 
202
216
  /* ------------------------- COLLECTION ------------------------- */
203
217
 
204
- collection<T = any>(name: string, schema?: ZodSchema<T>): Collection<T> {
218
+ collection<T = any>(
219
+ name: string,
220
+ schema?: ZodSchema<T>,
221
+ schemaVersion?: number
222
+ ): Collection<T> {
205
223
  if (this.collections.has(name)) {
206
224
  const col = this.collections.get(name)!;
207
- if (schema) col.setSchema(schema);
225
+ if (schema && schemaVersion !== undefined) {
226
+ col.setSchema(schema, schemaVersion);
227
+ }
208
228
  return col as Collection<T>;
209
229
  }
210
230
 
211
231
  const colPath = path.join(this.basePath, name);
212
232
  fs.mkdirSync(colPath, { recursive: true });
213
233
 
214
- const col = new Collection<T>(colPath, schema);
234
+ const col = new Collection<T>(
235
+ colPath,
236
+ schema,
237
+ schemaVersion ?? 1
238
+ );
215
239
 
216
240
  const metas = this.meta.indexes[name] ?? [];
217
241
  for (const m of metas) {
@@ -236,25 +260,14 @@ export class LioranDB {
236
260
 
237
261
  const index = new Index(col.dir, field, options);
238
262
 
239
- // for await (const [, enc] of col.db.iterator()) {
240
- // // const doc = JSON.parse(
241
- // // Buffer.from(enc, "base64").subarray(32).toString("utf8")
242
- // // );
243
- // const payload = Buffer.from(enc, "utf8").subarray(32);
244
- // const doc = JSON.parse(payload.toString("utf8"));
245
- // await index.insert(doc);
246
- // }
247
-
248
263
  for await (const [key, enc] of col.db.iterator()) {
249
264
  if (!enc) continue;
250
-
251
265
  try {
252
- const doc = decryptData(enc); // ← this does base64 → AES-GCM → JSON
266
+ const doc = decryptData(enc);
253
267
  await index.insert(doc);
254
268
  } catch (err) {
255
- const errorMessage = err instanceof Error ? err.message : String(err);
256
- console.warn(`Could not decrypt document ${key} during index build: ${errorMessage}`);
257
- // You can continue, or collect bad keys for later inspection
269
+ const msg = err instanceof Error ? err.message : String(err);
270
+ console.warn(`Index build skipped doc ${key}: ${msg}`);
258
271
  }
259
272
  }
260
273
 
@@ -271,13 +284,11 @@ export class LioranDB {
271
284
  /* ------------------------- COMPACTION ------------------------- */
272
285
 
273
286
  async compactCollection(name: string) {
274
- await this.clearWAL();
275
287
  const col = this.collection(name);
276
288
  await col.compact();
277
289
  }
278
290
 
279
291
  async compactAll() {
280
- await this.clearWAL();
281
292
  for (const name of this.collections.keys()) {
282
293
  await this.compactCollection(name);
283
294
  }
@@ -293,16 +304,19 @@ export class LioranDB {
293
304
  return result;
294
305
  }
295
306
 
307
+ /* ------------------------- POST COMMIT ------------------------- */
308
+
309
+ public async postCommitMaintenance() {
310
+ // Custom maintenance can be added here
311
+ }
312
+
296
313
  /* ------------------------- SHUTDOWN ------------------------- */
297
314
 
298
315
  async close(): Promise<void> {
299
316
  for (const col of this.collections.values()) {
300
- try { await col.close(); } catch { }
317
+ try { await col.close(); } catch {}
301
318
  }
319
+
302
320
  this.collections.clear();
303
321
  }
304
- }
305
-
306
- function decryptData(enc: string) {
307
- throw new Error("Function not implemented.");
308
- }
322
+ }
@@ -0,0 +1,213 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ /* =========================
5
+ WAL RECORD TYPES
6
+ ========================= */
7
+
8
+ export type WALRecord =
9
+ | { lsn: number; tx: number; type: "op"; payload: any }
10
+ | { lsn: number; tx: number; type: "commit" }
11
+ | { lsn: number; tx: number; type: "applied" };
12
+
13
+ type StoredRecord = WALRecord & { crc: number };
14
+
15
+ /* =========================
16
+ CONSTANTS
17
+ ========================= */
18
+
19
+ const MAX_WAL_SIZE = 16 * 1024 * 1024; // 16 MB
20
+ const WAL_DIR = "__wal";
21
+
22
+ /* =========================
23
+ CRC32 IMPLEMENTATION
24
+ (no dependencies)
25
+ ========================= */
26
+
27
+ const CRC32_TABLE = (() => {
28
+ const table = new Uint32Array(256);
29
+ for (let i = 0; i < 256; i++) {
30
+ let c = i;
31
+ for (let k = 0; k < 8; k++) {
32
+ c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1);
33
+ }
34
+ table[i] = c >>> 0;
35
+ }
36
+ return table;
37
+ })();
38
+
39
+ function crc32(input: string): number {
40
+ let crc = 0xFFFFFFFF;
41
+ for (let i = 0; i < input.length; i++) {
42
+ const byte = input.charCodeAt(i);
43
+ crc = CRC32_TABLE[(crc ^ byte) & 0xFF] ^ (crc >>> 8);
44
+ }
45
+ return (crc ^ 0xFFFFFFFF) >>> 0;
46
+ }
47
+
48
+ /* =========================
49
+ WAL MANAGER
50
+ ========================= */
51
+
52
+ export class WALManager {
53
+ private walDir: string;
54
+ private currentGen = 1;
55
+ private lsn = 0;
56
+ private fd: fs.promises.FileHandle | null = null;
57
+
58
+ constructor(baseDir: string) {
59
+ this.walDir = path.join(baseDir, WAL_DIR);
60
+ fs.mkdirSync(this.walDir, { recursive: true });
61
+ this.currentGen = this.detectLastGeneration();
62
+ }
63
+
64
+ /* -------------------------
65
+ INTERNAL HELPERS
66
+ ------------------------- */
67
+
68
+ private walPath(gen = this.currentGen) {
69
+ return path.join(
70
+ this.walDir,
71
+ `wal-${String(gen).padStart(6, "0")}.log`
72
+ );
73
+ }
74
+
75
+ private detectLastGeneration(): number {
76
+ if (!fs.existsSync(this.walDir)) return 1;
77
+
78
+ const files = fs.readdirSync(this.walDir);
79
+ let max = 0;
80
+
81
+ for (const f of files) {
82
+ const m = f.match(/^wal-(\d+)\.log$/);
83
+ if (m) max = Math.max(max, Number(m[1]));
84
+ }
85
+
86
+ return max || 1;
87
+ }
88
+
89
+ private async open() {
90
+ if (!this.fd) {
91
+ this.fd = await fs.promises.open(this.walPath(), "a");
92
+ }
93
+ }
94
+
95
+ private async rotate() {
96
+ if (this.fd) {
97
+ await this.fd.close();
98
+ this.fd = null;
99
+ }
100
+ this.currentGen++;
101
+ }
102
+
103
+ /* -------------------------
104
+ APPEND
105
+ ------------------------- */
106
+
107
+ async append(record: Omit<WALRecord, "lsn">): Promise<number> {
108
+ await this.open();
109
+
110
+ const full: WALRecord = {
111
+ ...(record as any),
112
+ lsn: ++this.lsn
113
+ };
114
+
115
+ const body = JSON.stringify(full);
116
+ const stored: StoredRecord = {
117
+ ...full,
118
+ crc: crc32(body)
119
+ };
120
+
121
+ await this.fd!.write(JSON.stringify(stored) + "\n");
122
+ await this.fd!.sync();
123
+
124
+ const stat = await this.fd!.stat();
125
+ if (stat.size >= MAX_WAL_SIZE) {
126
+ await this.rotate();
127
+ }
128
+
129
+ return full.lsn;
130
+ }
131
+
132
+ /* -------------------------
133
+ REPLAY
134
+ ------------------------- */
135
+
136
+ async replay(
137
+ fromLSN: number,
138
+ apply: (r: WALRecord) => Promise<void>
139
+ ): Promise<void> {
140
+ if (!fs.existsSync(this.walDir)) return;
141
+
142
+ const files = fs
143
+ .readdirSync(this.walDir)
144
+ .filter(f => f.startsWith("wal-"))
145
+ .sort();
146
+
147
+ for (const file of files) {
148
+ const filePath = path.join(this.walDir, file);
149
+ const data = fs.readFileSync(filePath, "utf8");
150
+ const lines = data.split("\n");
151
+
152
+ for (let i = 0; i < lines.length; i++) {
153
+ const line = lines[i];
154
+ if (!line.trim()) continue;
155
+
156
+ let parsed: StoredRecord;
157
+ try {
158
+ parsed = JSON.parse(line);
159
+ } catch {
160
+ console.error("WAL parse error, stopping replay");
161
+ return;
162
+ }
163
+
164
+ const { crc, ...record } = parsed;
165
+ const expected = crc32(JSON.stringify(record));
166
+
167
+ if (expected !== crc) {
168
+ console.error(
169
+ "WAL checksum mismatch, stopping replay",
170
+ { file, line: i + 1 }
171
+ );
172
+ return;
173
+ }
174
+
175
+ if (record.lsn <= fromLSN) continue;
176
+
177
+ this.lsn = Math.max(this.lsn, record.lsn);
178
+ await apply(record);
179
+ }
180
+ }
181
+ }
182
+
183
+ /* -------------------------
184
+ CLEANUP
185
+ ------------------------- */
186
+
187
+ async cleanup(beforeGen: number) {
188
+ if (!fs.existsSync(this.walDir)) return;
189
+
190
+ const files = fs.readdirSync(this.walDir);
191
+ for (const f of files) {
192
+ const m = f.match(/^wal-(\d+)\.log$/);
193
+ if (!m) continue;
194
+
195
+ const gen = Number(m[1]);
196
+ if (gen < beforeGen) {
197
+ fs.unlinkSync(path.join(this.walDir, f));
198
+ }
199
+ }
200
+ }
201
+
202
+ /* -------------------------
203
+ GETTERS
204
+ ------------------------- */
205
+
206
+ getCurrentLSN() {
207
+ return this.lsn;
208
+ }
209
+
210
+ getCurrentGen() {
211
+ return this.currentGen;
212
+ }
213
+ }