@dbx-tools/falkor-db 0.0.0-stage → 0.9.43

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.
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Restore and change-aware durable backup lifecycle for embedded FalkorDB.
3
+ *
4
+ * Redis owns dirty detection and RDB creation. This manager only observes
5
+ * completed saves, stages immutable copies, and advances durable storage after
6
+ * a successful upload. Reuse it instead of adding application-local backup
7
+ * timers, manifest ordering, retry, or retention logic.
8
+ *
9
+ * @module
10
+ */
11
+ import { type SnapshotManifest } from "./manifest.ts";
12
+ import { type RedisPersistenceClient } from "./redis-info.ts";
13
+ import type { VolumeStorage } from "./volume.ts";
14
+ /** Configuration for {@link FalkorPersistenceManager}. */
15
+ export interface FalkorPersistenceOptions {
16
+ dataDir: string;
17
+ storage?: VolumeStorage;
18
+ pollIntervalMs?: number;
19
+ retention?: number;
20
+ retryDelaysMs?: readonly number[];
21
+ forceBackupOnShutdown?: boolean;
22
+ shutdownTimeoutMs?: number;
23
+ staleBackupWarningMs?: number;
24
+ }
25
+ /** Observable state for health reporting and metrics adapters. */
26
+ export interface FalkorPersistenceStatus {
27
+ rdbLastSaveTime?: number;
28
+ rdbChangesSinceLastSave?: number;
29
+ backupLastSuccessTime?: string;
30
+ backupLastSequence?: number;
31
+ backupSizeBytes?: number;
32
+ backupDurationMs?: number;
33
+ backupFailures: number;
34
+ restoreDurationMs?: number;
35
+ restoreSnapshotSequence?: number;
36
+ }
37
+ /** Coordinates restore, RDB observation, durable upload, retry, and retention. */
38
+ export declare class FalkorPersistenceManager {
39
+ private readonly options;
40
+ private readonly dumpPath;
41
+ private readonly stagingDirectory;
42
+ private readonly statusValue;
43
+ private manifest;
44
+ private redis;
45
+ private timer;
46
+ private backupPromise;
47
+ private checkAgain;
48
+ private lastUploadedSaveTime;
49
+ private retryAttempt;
50
+ private retryAfter;
51
+ private lastLoggedLocalSaveTime;
52
+ constructor(options: FalkorPersistenceOptions);
53
+ get status(): Readonly<FalkorPersistenceStatus>;
54
+ /** Restore `latest.json` before FalkorDB starts, or keep the local database. */
55
+ restore(): Promise<SnapshotManifest | undefined>;
56
+ /** Begin polling Redis persistence metadata for completed new snapshots. */
57
+ start(redis: RedisPersistenceClient): Promise<void>;
58
+ /** Stop background polling without forcing a save or upload. */
59
+ stop(): void;
60
+ /** Inspect persistence now, coalescing concurrent checks into the newest RDB. */
61
+ checkNow(): Promise<void>;
62
+ /** Optionally force one dirty save/upload, bounded so shutdown cannot hang. */
63
+ prepareShutdown(): Promise<void>;
64
+ private finishShutdownBackup;
65
+ private runChecks;
66
+ private inspectAndBackup;
67
+ private upload;
68
+ private forceDirtySnapshot;
69
+ private observe;
70
+ private warnIfStale;
71
+ }
@@ -0,0 +1,286 @@
1
+ /**
2
+ * Restore and change-aware durable backup lifecycle for embedded FalkorDB.
3
+ *
4
+ * Redis owns dirty detection and RDB creation. This manager only observes
5
+ * completed saves, stages immutable copies, and advances durable storage after
6
+ * a successful upload. Reuse it instead of adding application-local backup
7
+ * timers, manifest ordering, retry, or retention logic.
8
+ *
9
+ * @module
10
+ */
11
+ import { existsSync } from "node:fs";
12
+ import { mkdir, rename, rm } from "node:fs/promises";
13
+ import { join } from "node:path";
14
+ import { asyncUtils, log } from "@dbx-tools/shared-core";
15
+ import { enforceRetention, LATEST_MANIFEST_PATH, nextSnapshotSequence, parseSnapshotManifest, snapshotPath, } from "./manifest.js";
16
+ import { getPersistenceInfo, waitForBackgroundSave, } from "./redis-info.js";
17
+ import { stageSnapshot, verifySnapshot } from "./snapshot.js";
18
+ const logger = log.logger("falkor-db:persistence");
19
+ const DEFAULT_RETRY_DELAYS_MS = [5_000, 15_000, 30_000, 60_000, 300_000];
20
+ /** Coordinates restore, RDB observation, durable upload, retry, and retention. */
21
+ export class FalkorPersistenceManager {
22
+ options;
23
+ dumpPath;
24
+ stagingDirectory;
25
+ statusValue = { backupFailures: 0 };
26
+ manifest;
27
+ redis;
28
+ timer;
29
+ backupPromise;
30
+ checkAgain = false;
31
+ lastUploadedSaveTime = 0;
32
+ retryAttempt = 0;
33
+ retryAfter = 0;
34
+ lastLoggedLocalSaveTime = 0;
35
+ constructor(options) {
36
+ this.options = options;
37
+ this.dumpPath = join(options.dataDir, "dump.rdb");
38
+ this.stagingDirectory = join(options.dataDir, ".dbx-tools-backups");
39
+ }
40
+ get status() {
41
+ return { ...this.statusValue };
42
+ }
43
+ /** Restore `latest.json` before FalkorDB starts, or keep the local database. */
44
+ async restore() {
45
+ await mkdir(this.options.dataDir, { recursive: true });
46
+ if (!this.options.storage) {
47
+ logger.info("durable storage is not configured; using local RDB persistence", {
48
+ dataDir: this.options.dataDir,
49
+ });
50
+ return undefined;
51
+ }
52
+ const raw = await this.options.storage.readJson(LATEST_MANIFEST_PATH);
53
+ if (raw === null) {
54
+ logger.info("no durable FalkorDB snapshot found; using local database", {
55
+ dataDir: this.options.dataDir,
56
+ });
57
+ return undefined;
58
+ }
59
+ const started = Date.now();
60
+ const manifest = parseSnapshotManifest(raw);
61
+ const temporary = `${this.dumpPath}.restore`;
62
+ await rm(temporary, { force: true });
63
+ try {
64
+ await this.options.storage.download(manifest.snapshot, temporary);
65
+ await verifySnapshot(temporary, manifest.sha256);
66
+ await rename(temporary, this.dumpPath);
67
+ }
68
+ catch (error) {
69
+ await rm(temporary, { force: true });
70
+ throw error;
71
+ }
72
+ this.manifest = manifest;
73
+ this.lastUploadedSaveTime = manifest.redisSaveTime ?? 0;
74
+ this.statusValue.restoreDurationMs = Date.now() - started;
75
+ this.statusValue.restoreSnapshotSequence = manifest.sequence;
76
+ logger.info("restored FalkorDB snapshot", {
77
+ sequence: manifest.sequence,
78
+ size: manifest.size,
79
+ durationMs: this.statusValue.restoreDurationMs,
80
+ });
81
+ return manifest;
82
+ }
83
+ /** Begin polling Redis persistence metadata for completed new snapshots. */
84
+ async start(redis) {
85
+ if (this.timer)
86
+ return;
87
+ this.redis = redis;
88
+ const initial = await this.observe(redis);
89
+ if (this.manifest) {
90
+ this.lastUploadedSaveTime = initial.rdbLastSaveTime;
91
+ }
92
+ await this.checkNow();
93
+ this.timer = setInterval(() => {
94
+ void this.checkNow().catch((error) => {
95
+ logger.error("FalkorDB persistence check failed; retrying later", { error });
96
+ });
97
+ }, this.options.pollIntervalMs ?? 10_000);
98
+ this.timer.unref?.();
99
+ }
100
+ /** Stop background polling without forcing a save or upload. */
101
+ stop() {
102
+ if (this.timer)
103
+ clearInterval(this.timer);
104
+ this.timer = undefined;
105
+ }
106
+ /** Inspect persistence now, coalescing concurrent checks into the newest RDB. */
107
+ async checkNow() {
108
+ if (!this.redis)
109
+ return;
110
+ if (this.backupPromise) {
111
+ this.checkAgain = true;
112
+ return this.backupPromise;
113
+ }
114
+ this.backupPromise = this.runChecks();
115
+ try {
116
+ await this.backupPromise;
117
+ }
118
+ finally {
119
+ this.backupPromise = undefined;
120
+ }
121
+ }
122
+ /** Optionally force one dirty save/upload, bounded so shutdown cannot hang. */
123
+ async prepareShutdown() {
124
+ this.stop();
125
+ if (!this.options.forceBackupOnShutdown || !this.redis)
126
+ return;
127
+ const timeoutMs = this.options.shutdownTimeoutMs ?? 30_000;
128
+ const work = this.finishShutdownBackup(this.redis);
129
+ const timeout = new AbortController();
130
+ try {
131
+ await Promise.race([
132
+ work,
133
+ asyncUtils.sleep(timeoutMs, timeout.signal).then(() => {
134
+ throw new Error(`FalkorDB shutdown backup exceeded ${timeoutMs}ms`);
135
+ }),
136
+ ]);
137
+ }
138
+ catch (error) {
139
+ logger.error("shutdown backup failed; continuing shutdown", { error });
140
+ }
141
+ finally {
142
+ timeout.abort();
143
+ }
144
+ }
145
+ async finishShutdownBackup(redis) {
146
+ if (this.backupPromise) {
147
+ try {
148
+ await this.backupPromise;
149
+ }
150
+ catch (error) {
151
+ logger.warn("in-flight FalkorDB backup failed; retrying during shutdown", { error });
152
+ }
153
+ }
154
+ await this.forceDirtySnapshot(redis);
155
+ }
156
+ async runChecks() {
157
+ do {
158
+ this.checkAgain = false;
159
+ await this.inspectAndBackup();
160
+ } while (this.checkAgain);
161
+ }
162
+ async inspectAndBackup() {
163
+ const redis = this.redis;
164
+ if (!redis)
165
+ return;
166
+ const info = await this.observe(redis);
167
+ if (info.rdbBgSaveInProgress)
168
+ return;
169
+ if (info.rdbLastBgSaveStatus !== "ok") {
170
+ logger.error("Redis reported an unsuccessful background save", {
171
+ status: info.rdbLastBgSaveStatus,
172
+ });
173
+ return;
174
+ }
175
+ this.warnIfStale(info);
176
+ if (!this.options.storage) {
177
+ if (info.rdbLastSaveTime > this.lastLoggedLocalSaveTime) {
178
+ this.lastLoggedLocalSaveTime = info.rdbLastSaveTime;
179
+ logger.info("local FalkorDB RDB snapshot completed", {
180
+ lastSaveTime: info.rdbLastSaveTime,
181
+ changesSinceSave: info.rdbChangesSinceLastSave,
182
+ path: this.dumpPath,
183
+ });
184
+ }
185
+ return;
186
+ }
187
+ if (info.rdbLastSaveTime <= this.lastUploadedSaveTime || !existsSync(this.dumpPath))
188
+ return;
189
+ if (Date.now() < this.retryAfter)
190
+ return;
191
+ try {
192
+ await this.upload(info);
193
+ this.retryAttempt = 0;
194
+ this.retryAfter = 0;
195
+ const newest = await this.observe(redis);
196
+ if (newest.rdbLastSaveTime > this.lastUploadedSaveTime)
197
+ this.checkAgain = true;
198
+ }
199
+ catch (error) {
200
+ this.statusValue.backupFailures += 1;
201
+ const delays = this.options.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS;
202
+ const delay = asyncUtils.boundedRetryDelay(this.retryAttempt++, delays);
203
+ this.retryAfter = Date.now() + delay;
204
+ logger.error("durable FalkorDB backup failed; local database remains available", {
205
+ error,
206
+ retryInMs: delay,
207
+ });
208
+ }
209
+ }
210
+ async upload(info) {
211
+ const storage = this.options.storage;
212
+ if (!storage)
213
+ return;
214
+ const started = Date.now();
215
+ const sequence = await nextSnapshotSequence(storage, this.manifest);
216
+ const remote = snapshotPath(sequence);
217
+ const stagedPath = join(this.stagingDirectory, `snapshot-${sequence}.rdb`);
218
+ const staged = await stageSnapshot(this.dumpPath, stagedPath);
219
+ try {
220
+ await storage.upload(staged.path, remote, { overwrite: false });
221
+ const manifest = {
222
+ snapshot: remote,
223
+ createdAt: new Date().toISOString(),
224
+ size: staged.size,
225
+ sha256: staged.sha256,
226
+ sequence,
227
+ redisSaveTime: info.rdbLastSaveTime,
228
+ };
229
+ await storage.writeJson(LATEST_MANIFEST_PATH, manifest);
230
+ this.manifest = manifest;
231
+ this.lastUploadedSaveTime = info.rdbLastSaveTime;
232
+ this.statusValue.backupLastSuccessTime = manifest.createdAt;
233
+ this.statusValue.backupLastSequence = sequence;
234
+ this.statusValue.backupSizeBytes = staged.size;
235
+ this.statusValue.backupDurationMs = Date.now() - started;
236
+ logger.info("uploaded durable FalkorDB snapshot", {
237
+ sequence,
238
+ size: staged.size,
239
+ durationMs: this.statusValue.backupDurationMs,
240
+ });
241
+ await enforceRetention(storage, manifest, this.options.retention ?? 5).catch((error) => {
242
+ logger.warn("FalkorDB snapshot retention cleanup failed", { error });
243
+ });
244
+ }
245
+ finally {
246
+ await rm(stagedPath, { force: true });
247
+ }
248
+ }
249
+ async forceDirtySnapshot(redis) {
250
+ let info = await this.observe(redis);
251
+ if (info.rdbChangesSinceLastSave > 0) {
252
+ const previous = info.rdbLastSaveTime;
253
+ await redis.bgSave();
254
+ info = await waitForBackgroundSave(redis, previous, {
255
+ timeoutMs: this.options.shutdownTimeoutMs ?? 30_000,
256
+ });
257
+ }
258
+ if (this.options.storage && info.rdbLastSaveTime > this.lastUploadedSaveTime) {
259
+ this.retryAfter = 0;
260
+ await this.upload(info);
261
+ }
262
+ }
263
+ async observe(redis) {
264
+ const info = await getPersistenceInfo(redis);
265
+ this.statusValue.rdbLastSaveTime = info.rdbLastSaveTime;
266
+ this.statusValue.rdbChangesSinceLastSave = info.rdbChangesSinceLastSave;
267
+ return info;
268
+ }
269
+ warnIfStale(info) {
270
+ if (!this.options.storage)
271
+ return;
272
+ const threshold = this.options.staleBackupWarningMs;
273
+ if (!threshold || info.rdbChangesSinceLastSave === 0)
274
+ return;
275
+ const last = this.statusValue.backupLastSuccessTime
276
+ ? Date.parse(this.statusValue.backupLastSuccessTime)
277
+ : 0;
278
+ if (Date.now() - last > threshold) {
279
+ logger.warn("FalkorDB has changes without a recent durable backup", {
280
+ changesSinceSave: info.rdbChangesSinceLastSave,
281
+ lastSuccessTime: this.statusValue.backupLastSuccessTime,
282
+ });
283
+ }
284
+ }
285
+ }
286
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFuYWdlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9wZXJzaXN0ZW5jZS9tYW5hZ2VyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7R0FTRztBQUVILE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxTQUFTLENBQUM7QUFDckMsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsRUFBRSxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFDckQsT0FBTyxFQUFFLElBQUksRUFBRSxNQUFNLFdBQVcsQ0FBQztBQUNqQyxPQUFPLEVBQUUsVUFBVSxFQUFFLEdBQUcsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQ3pELE9BQU8sRUFDTCxnQkFBZ0IsRUFDaEIsb0JBQW9CLEVBQ3BCLG9CQUFvQixFQUNwQixxQkFBcUIsRUFDckIsWUFBWSxHQUViLE1BQU0sZUFBZSxDQUFDO0FBQ3ZCLE9BQU8sRUFDTCxrQkFBa0IsRUFDbEIscUJBQXFCLEdBR3RCLE1BQU0saUJBQWlCLENBQUM7QUFDekIsT0FBTyxFQUFFLGFBQWEsRUFBRSxjQUFjLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFHOUQsTUFBTSxNQUFNLEdBQUcsR0FBRyxDQUFDLE1BQU0sQ0FBQyx1QkFBdUIsQ0FBQyxDQUFDO0FBQ25ELE1BQU0sdUJBQXVCLEdBQUcsQ0FBQyxLQUFLLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRSxNQUFNLEVBQUUsT0FBTyxDQUFVLENBQUM7QUEyQmxGLGtGQUFrRjtBQUNsRixNQUFNLE9BQU8sd0JBQXdCO0lBY047SUFiWixRQUFRLENBQVM7SUFDakIsZ0JBQWdCLENBQVM7SUFDekIsV0FBVyxHQUE0QixFQUFFLGNBQWMsRUFBRSxDQUFDLEVBQUUsQ0FBQztJQUN0RSxRQUFRLENBQStCO0lBQ3ZDLEtBQUssQ0FBcUM7SUFDMUMsS0FBSyxDQUE2QztJQUNsRCxhQUFhLENBQTRCO0lBQ3pDLFVBQVUsR0FBRyxLQUFLLENBQUM7SUFD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QUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxPQUFPO1lBQUUsT0FBTztRQUNsQyxNQUFNLFNBQVMsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLG9CQUFvQixDQUFDO1FBQ3BELElBQUksQ0FBQyxTQUFTLElBQUksSUFBSSxDQUFDLHVCQUF1QixLQUFLLENBQUM7WUFBRSxPQUFPO1FBQzdELE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxXQUFXLENBQUMscUJBQXFCO1lBQ2pELENBQUMsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMscUJBQXFCLENBQUM7WUFDcEQsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUNOLElBQUksSUFBSSxDQUFDLEdBQUcsRUFBRSxHQUFHLElBQUksR0FBRyxTQUFTLEVBQUUsQ0FBQztZQUNsQyxNQUFNLENBQUMsSUFBSSxDQUFDLHNEQUFzRCxFQUFFO2dCQUNsRSxnQkFBZ0IsRUFBRSxJQUFJLENBQUMsdUJBQXVCO2dCQUM5QyxlQUFlLEVBQUUsSUFBSSxDQUFDLFdBQVcsQ0FBQyxxQkFBcUI7YUFDeEQsQ0FBQyxDQUFDO1FBQ0wsQ0FBQztJQUNILENBQUM7Q0FDRiIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogUmVzdG9yZSBhbmQgY2hhbmdlLWF3YXJlIGR1cmFibGUgYmFja3VwIGxpZmVjeWNsZSBmb3IgZW1iZWRkZWQgRmFsa29yREIuXG4gKlxuICogUmVkaXMgb3ducyBkaXJ0eSBkZXRlY3Rpb24gYW5kIFJEQiBjcmVhdGlvbi4gVGhpcyBtYW5hZ2VyIG9ubHkgb2JzZXJ2ZXNcbiAqIGNvbXBsZXRlZCBzYXZlcywgc3RhZ2VzIGltbXV0YWJsZSBjb3BpZXMsIGFuZCBhZHZhbmNlcyBkdXJhYmxlIHN0b3JhZ2UgYWZ0ZXJcbiAqIGEgc3VjY2Vzc2Z1bCB1cGxvYWQuIFJldXNlIGl0IGluc3RlYWQgb2YgYWRkaW5nIGFwcGxpY2F0aW9uLWxvY2FsIGJhY2t1cFxuICogdGltZXJzLCBtYW5pZmVzdCBvcmRlcmluZywgcmV0cnksIG9yIHJldGVudGlvbiBsb2dpYy5cbiAqXG4gKiBAbW9kdWxlXG4gKi9cblxuaW1wb3J0IHsgZXhpc3RzU3luYyB9IGZyb20gXCJub2RlOmZzXCI7XG5pbXBvcnQgeyBta2RpciwgcmVuYW1lLCBybSB9IGZyb20gXCJub2RlOmZzL3Byb21pc2VzXCI7XG5pbXBvcnQgeyBqb2luIH0gZnJvbSBcIm5vZGU6cGF0aFwiO1xuaW1wb3J0IHsgYXN5bmNVdGlscywgbG9nIH0gZnJvbSBcIkBkYngtdG9vbHMvc2hhcmVkLWNvcmVcIjtcbmltcG9ydCB7XG4gIGVuZm9yY2VSZXRlbnRpb24sXG4gIExBVEVTVF9NQU5JRkVTVF9QQVRILFxuICBuZXh0U25hcHNob3RTZXF1ZW5jZSxcbiAgcGFyc2VTbmFwc2hvdE1hbmlmZXN0LFxuICBzbmFwc2hvdFBhdGgsXG4gIHR5cGUgU25hcHNob3RNYW5pZmVzdCxcbn0gZnJvbSBcIi4vbWFuaWZlc3QudHNcIjtcbmltcG9ydCB7XG4gIGdldFBlcnNpc3RlbmNlSW5mbyxcbiAgd2FpdEZvckJhY2tncm91bmRTYXZlLFxuICB0eXBlIFJlZGlzUGVyc2lzdGVuY2VDbGllbnQsXG4gIHR5cGUgUmVkaXNQZXJzaXN0ZW5jZUluZm8sXG59IGZyb20gXCIuL3JlZGlzLWluZm8udHNcIjtcbmltcG9ydCB7IHN0YWdlU25hcHNob3QsIHZlcmlmeVNuYXBzaG90IH0gZnJvbSBcIi4vc25hcHNob3QudHNcIjtcbmltcG9ydCB0eXBlIHsgVm9sdW1lU3RvcmFnZSB9IGZyb20gXCIuL3ZvbHVtZS50c1wiO1xuXG5jb25zdCBsb2dnZXIgPSBsb2cubG9nZ2VyKFwiZmFsa29yLWRiOnBlcnNpc3RlbmNlXCIpO1xuY29uc3QgREVGQVVMVF9SRVRSWV9ERUxBWVNfTVMgPSBbNV8wMDAsIDE1XzAwMCwgMzBfMDAwLCA2MF8wMDAsIDMwMF8wMDBdIGFzIGNvbnN0O1xuXG4vKiogQ29uZmlndXJhdGlvbiBmb3Ige0BsaW5rIEZhbGtvclBlcnNpc3RlbmNlTWFuYWdlcn0uICovXG5leHBvcnQgaW50ZXJmYWNlIEZhbGtvclBlcnNpc3RlbmNlT3B0aW9ucyB7XG4gIGRhdGFEaXI6IHN0cmluZztcbiAgc3RvcmFnZT86IFZvbHVtZVN0b3JhZ2U7XG4gIHBvbGxJbnRlcnZhbE1zPzogbnVtYmVyO1xuICByZXRlbnRpb24/OiBudW1iZXI7XG4gIHJldHJ5RGVsYXlzTXM/OiByZWFkb25seSBudW1iZXJbXTtcbiAgZm9yY2VCYWNrdXBPblNodXRkb3duPzogYm9vbGVhbjtcbiAgc2h1dGRvd25UaW1lb3V0TXM/OiBudW1iZXI7XG4gIHN0YWxlQmFja3VwV2FybmluZ01zPzogbnVtYmVyO1xufVxuXG4vKiogT2JzZXJ2YWJsZSBzdGF0ZSBmb3IgaGVhbHRoIHJlcG9ydGluZyBhbmQgbWV0cmljcyBhZGFwdGVycy4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgRmFsa29yUGVyc2lzdGVuY2VTdGF0dXMge1xuICByZGJMYXN0U2F2ZVRpbWU/OiBudW1iZXI7XG4gIHJkYkNoYW5nZXNTaW5jZUxhc3RTYXZlPzogbnVtYmVyO1xuICBiYWNrdXBMYXN0U3VjY2Vzc1RpbWU/OiBzdHJpbmc7XG4gIGJhY2t1cExhc3RTZXF1ZW5jZT86IG51bWJlcjtcbiAgYmFja3VwU2l6ZUJ5dGVzPzogbnVtYmVyO1xuICBiYWNrdXBEdXJhdGlvbk1zPzogbnVtYmVyO1xuICBiYWNrdXBGYWlsdXJlczogbnVtYmVyO1xuICByZXN0b3JlRHVyYXRpb25Ncz86IG51bWJlcjtcbiAgcmVzdG9yZVNuYXBzaG90U2VxdWVuY2U/OiBudW1iZXI7XG59XG5cbi8qKiBDb29yZGluYXRlcyByZXN0b3JlLCBSREIgb2JzZXJ2YXRpb24sIGR1cmFibGUgdXBsb2FkLCByZXRyeSwgYW5kIHJldGVudGlvbi4gKi9cbmV4cG9ydCBjbGFzcyBGYWxrb3JQZXJzaXN0ZW5jZU1hbmFnZXIge1xuICBwcml2YXRlIHJlYWRvbmx5IGR1bXBQYXRoOiBzdHJpbmc7XG4gIHByaXZhdGUgcmVhZG9ubHkgc3RhZ2luZ0RpcmVjdG9yeTogc3RyaW5nO1xuICBwcml2YXRlIHJlYWRvbmx5IHN0YXR1c1ZhbHVlOiBGYWxrb3JQZXJzaXN0ZW5jZVN0YXR1cyA9IHsgYmFja3VwRmFpbHVyZXM6IDAgfTtcbiAgcHJpdmF0ZSBtYW5pZmVzdDogU25hcHNob3RNYW5pZmVzdCB8IHVuZGVmaW5lZDtcbiAgcHJpdmF0ZSByZWRpczogUmVkaXNQZXJzaXN0ZW5jZUNsaWVudCB8IHVuZGVmaW5lZDtcbiAgcHJpdmF0ZSB0aW1lcjogUmV0dXJuVHlwZTx0eXBlb2Ygc2V0SW50ZXJ2YWw+IHwgdW5kZWZpbmVkO1xuICBwcml2YXRlIGJhY2t1cFByb21pc2U6IFByb21pc2U8dm9pZD4gfCB1bmRlZmluZWQ7XG4gIHByaXZhdGUgY2hlY2tBZ2FpbiA9IGZhbHNlO1xuICBwcml2YXRlIGxhc3RVcGxvYWRlZFNhdmVUaW1lID0gMDtcbiAgcHJpdmF0ZSByZXRyeUF0dGVtcHQgPSAwO1xuICBwcml2YXRlIHJldHJ5QWZ0ZXIgPSAwO1xuICBwcml2YXRlIGxhc3RMb2dnZWRMb2NhbFNhdmVUaW1lID0gMDtcblxuICBjb25zdHJ1Y3Rvcihwcml2YXRlIHJlYWRvbmx5IG9wdGlvbnM6IEZhbGtvclBlcnNpc3RlbmNlT3B0aW9ucykge1xuICAgIHRoaXMuZHVtcFBhdGggPSBqb2luKG9wdGlvbnMuZGF0YURpciwgXCJkdW1wLnJkYlwiKTtcbiAgICB0aGlzLnN0YWdpbmdEaXJlY3RvcnkgPSBqb2luKG9wdGlvbnMuZGF0YURpciwgXCIuZGJ4LXRvb2xzLWJhY2t1cHNcIik7XG4gIH1cblxuICBnZXQgc3RhdHVzKCk6IFJlYWRvbmx5PEZhbGtvclBlcnNpc3RlbmNlU3RhdHVzPiB7XG4gICAgcmV0dXJuIHsgLi4udGhpcy5zdGF0dXNWYWx1ZSB9O1xuICB9XG5cbiAgLyoqIFJlc3RvcmUgYGxhdGVzdC5qc29uYCBiZWZvcmUgRmFsa29yREIgc3RhcnRzLCBvciBrZWVwIHRoZSBsb2NhbCBkYXRhYmFzZS4gKi9cbiAgYXN5bmMgcmVzdG9yZSgpOiBQcm9taXNlPFNuYXBzaG90TWFuaWZlc3QgfCB1bmRlZmluZWQ+IHtcbiAgICBhd2FpdCBta2Rpcih0aGlzLm9wdGlvbnMuZGF0YURpciwgeyByZWN1cnNpdmU6IHRydWUgfSk7XG4gICAgaWYgKCF0aGlzLm9wdGlvbnMuc3RvcmFnZSkge1xuICAgICAgbG9nZ2VyLmluZm8oXCJkdXJhYmxlIHN0b3JhZ2UgaXMgbm90IGNvbmZpZ3VyZWQ7IHVzaW5nIGxvY2FsIFJEQiBwZXJzaXN0ZW5jZVwiLCB7XG4gICAgICAgIGRhdGFEaXI6IHRoaXMub3B0aW9ucy5kYXRhRGlyLFxuICAgICAgfSk7XG4gICAgICByZXR1cm4gdW5kZWZpbmVkO1xuICAgIH1cblxuICAgIGNvbnN0IHJhdyA9IGF3YWl0IHRoaXMub3B0aW9ucy5zdG9yYWdlLnJlYWRKc29uPHVua25vd24+KExBVEVTVF9NQU5JRkVTVF9QQVRIKTtcbiAgICBpZiAocmF3ID09PSBudWxsKSB7XG4gICAgICBsb2dnZXIuaW5mbyhcIm5vIGR1cmFibGUgRmFsa29yREIgc25hcHNob3QgZm91bmQ7IHVzaW5nIGxvY2FsIGRhdGFiYXNlXCIsIHtcbiAgICAgICAgZGF0YURpcjogdGhpcy5vcHRpb25zLmRhdGFEaXIsXG4gICAgICB9KTtcbiAgICAgIHJldHVybiB1bmRlZmluZWQ7XG4gICAgfVxuXG4gICAgY29uc3Qgc3RhcnRlZCA9IERhdGUubm93KCk7XG4gICAgY29uc3QgbWFuaWZlc3QgPSBwYXJzZVNuYXBzaG90TWFuaWZlc3QocmF3KTtcbiAgICBjb25zdCB0ZW1wb3JhcnkgPSBgJHt0aGlzLmR1bXBQYXRofS5yZXN0b3JlYDtcbiAgICBhd2FpdCBybSh0ZW1wb3JhcnksIHsgZm9yY2U6IHRydWUgfSk7XG4gICAgdHJ5IHtcbiAgICAgIGF3YWl0IHRoaXMub3B0aW9ucy5zdG9yYWdlLmRvd25sb2FkKG1hbmlmZXN0LnNuYXBzaG90LCB0ZW1wb3JhcnkpO1xuICAgICAgYXdhaXQgdmVyaWZ5U25hcHNob3QodGVtcG9yYXJ5LCBtYW5pZmVzdC5zaGEyNTYpO1xuICAgICAgYXdhaXQgcmVuYW1lKHRlbXBvcmFyeSwgdGhpcy5kdW1wUGF0aCk7XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIGF3YWl0IHJtKHRlbXBvcmFyeSwgeyBmb3JjZTogdHJ1ZSB9KTtcbiAgICAgIHRocm93IGVycm9yO1xuICAgIH1cbiAgICB0aGlzLm1hbmlmZXN0ID0gbWFuaWZlc3Q7XG4gICAgdGhpcy5sYXN0VXBsb2FkZWRTYXZlVGltZSA9IG1hbmlmZXN0LnJlZGlzU2F2ZVRpbWUgPz8gMDtcbiAgICB0aGlzLnN0YXR1c1ZhbHVlLnJlc3RvcmVEdXJhdGlvbk1zID0gRGF0ZS5ub3coKSAtIHN0YXJ0ZWQ7XG4gICAgdGhpcy5zdGF0dXNWYWx1ZS5yZXN0b3JlU25hcHNob3RTZXF1ZW5jZSA9IG1hbmlmZXN0LnNlcXVlbmNlO1xuICAgIGxvZ2dlci5pbmZvKFwicmVzdG9yZWQgRmFsa29yREIgc25hcHNob3RcIiwge1xuICAgICAgc2VxdWVuY2U6IG1hbmlmZXN0LnNlcXVlbmNlLFxuICAgICAgc2l6ZTogbWFuaWZlc3Quc2l6ZSxcbiAgICAgIGR1cmF0aW9uTXM6IHRoaXMuc3RhdHVzVmFsdWUucmVzdG9yZUR1cmF0aW9uTXMsXG4gICAgfSk7XG4gICAgcmV0dXJuIG1hbmlmZXN0O1xuICB9XG5cbiAgLyoqIEJlZ2luIHBvbGxpbmcgUmVkaXMgcGVyc2lzdGVuY2UgbWV0YWRhdGEgZm9yIGNvbXBsZXRlZCBuZXcgc25hcHNob3RzLiAqL1xuICBhc3luYyBzdGFydChyZWRpczogUmVkaXNQZXJzaXN0ZW5jZUNsaWVudCk6IFByb21pc2U8dm9pZD4ge1xuICAgIGlmICh0aGlzLnRpbWVyKSByZXR1cm47XG4gICAgdGhpcy5yZWRpcyA9IHJlZGlzO1xuICAgIGNvbnN0IGluaXRpYWwgPSBhd2FpdCB0aGlzLm9ic2VydmUocmVkaXMpO1xuICAgIGlmICh0aGlzLm1hbmlmZXN0KSB7XG4gICAgICB0aGlzLmxhc3RVcGxvYWRlZFNhdmVUaW1lID0gaW5pdGlhbC5yZGJMYXN0U2F2ZVRpbWU7XG4gICAgfVxuICAgIGF3YWl0IHRoaXMuY2hlY2tOb3coKTtcbiAgICB0aGlzLnRpbWVyID0gc2V0SW50ZXJ2YWwoKCkgPT4ge1xuICAgICAgdm9pZCB0aGlzLmNoZWNrTm93KCkuY2F0Y2goKGVycm9yKSA9PiB7XG4gICAgICAgIGxvZ2dlci5lcnJvcihcIkZhbGtvckRCIHBlcnNpc3RlbmNlIGNoZWNrIGZhaWxlZDsgcmV0cnlpbmcgbGF0ZXJcIiwgeyBlcnJvciB9KTtcbiAgICAgIH0pO1xuICAgIH0sIHRoaXMub3B0aW9ucy5wb2xsSW50ZXJ2YWxNcyA/PyAxMF8wMDApO1xuICAgIHRoaXMudGltZXIudW5yZWY/LigpO1xuICB9XG5cbiAgLyoqIFN0b3AgYmFja2dyb3VuZCBwb2xsaW5nIHdpdGhvdXQgZm9yY2luZyBhIHNhdmUgb3IgdXBsb2FkLiAqL1xuICBzdG9wKCk6IHZvaWQge1xuICAgIGlmICh0aGlzLnRpbWVyKSBjbGVhckludGVydmFsKHRoaXMudGltZXIpO1xuICAgIHRoaXMudGltZXIgPSB1bmRlZmluZWQ7XG4gIH1cblxuICAvKiogSW5zcGVjdCBwZXJzaXN0ZW5jZSBub3csIGNvYWxlc2NpbmcgY29uY3VycmVudCBjaGVja3MgaW50byB0aGUgbmV3ZXN0IFJEQi4gKi9cbiAgYXN5bmMgY2hlY2tOb3coKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgaWYgKCF0aGlzLnJlZGlzKSByZXR1cm47XG4gICAgaWYgKHRoaXMuYmFja3VwUHJvbWlzZSkge1xuICAgICAgdGhpcy5jaGVja0FnYWluID0gdHJ1ZTtcbiAgICAgIHJldHVybiB0aGlzLmJhY2t1cFByb21pc2U7XG4gICAgfVxuICAgIHRoaXMuYmFja3VwUHJvbWlzZSA9IHRoaXMucnVuQ2hlY2tzKCk7XG4gICAgdHJ5IHtcbiAgICAgIGF3YWl0IHRoaXMuYmFja3VwUHJvbWlzZTtcbiAgICB9IGZpbmFsbHkge1xuICAgICAgdGhpcy5iYWNrdXBQcm9taXNlID0gdW5kZWZpbmVkO1xuICAgIH1cbiAgfVxuXG4gIC8qKiBPcHRpb25hbGx5IGZvcmNlIG9uZSBkaXJ0eSBzYXZlL3VwbG9hZCwgYm91bmRlZCBzbyBzaHV0ZG93biBjYW5ub3QgaGFuZy4gKi9cbiAgYXN5bmMgcHJlcGFyZVNodXRkb3duKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIHRoaXMuc3RvcCgpO1xuICAgIGlmICghdGhpcy5vcHRpb25zLmZvcmNlQmFja3VwT25TaHV0ZG93biB8fCAhdGhpcy5yZWRpcykgcmV0dXJuO1xuICAgIGNvbnN0IHRpbWVvdXRNcyA9IHRoaXMub3B0aW9ucy5zaHV0ZG93blRpbWVvdXRNcyA/PyAzMF8wMDA7XG4gICAgY29uc3Qgd29yayA9IHRoaXMuZmluaXNoU2h1dGRvd25CYWNrdXAodGhpcy5yZWRpcyk7XG4gICAgY29uc3QgdGltZW91dCA9IG5ldyBBYm9ydENvbnRyb2xsZXIoKTtcbiAgICB0cnkge1xuICAgICAgYXdhaXQgUHJvbWlzZS5yYWNlKFtcbiAgICAgICAgd29yayxcbiAgICAgICAgYXN5bmNVdGlscy5zbGVlcCh0aW1lb3V0TXMsIHRpbWVvdXQuc2lnbmFsKS50aGVuKCgpID0+IHtcbiAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEZhbGtvckRCIHNodXRkb3duIGJhY2t1cCBleGNlZWRlZCAke3RpbWVvdXRNc31tc2ApO1xuICAgICAgICB9KSxcbiAgICAgIF0pO1xuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICBsb2dnZXIuZXJyb3IoXCJzaHV0ZG93biBiYWNrdXAgZmFpbGVkOyBjb250aW51aW5nIHNodXRkb3duXCIsIHsgZXJyb3IgfSk7XG4gICAgfSBmaW5hbGx5IHtcbiAgICAgIHRpbWVvdXQuYWJvcnQoKTtcbiAgICB9XG4gIH1cblxuICBwcml2YXRlIGFzeW5jIGZpbmlzaFNodXRkb3duQmFja3VwKHJlZGlzOiBSZWRpc1BlcnNpc3RlbmNlQ2xpZW50KTogUHJvbWlzZTx2b2lkPiB7XG4gICAgaWYgKHRoaXMuYmFja3VwUHJvbWlzZSkge1xuICAgICAgdHJ5IHtcbiAgICAgICAgYXdhaXQgdGhpcy5iYWNrdXBQcm9taXNlO1xuICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgbG9nZ2VyLndhcm4oXCJpbi1mbGlnaHQgRmFsa29yREIgYmFja3VwIGZhaWxlZDsgcmV0cnlpbmcgZHVyaW5nIHNodXRkb3duXCIsIHsgZXJyb3IgfSk7XG4gICAgICB9XG4gICAgfVxuICAgIGF3YWl0IHRoaXMuZm9yY2VEaXJ0eVNuYXBzaG90KHJlZGlzKTtcbiAgfVxuXG4gIHByaXZhdGUgYXN5bmMgcnVuQ2hlY2tzKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIGRvIHtcbiAgICAgIHRoaXMuY2hlY2tBZ2FpbiA9IGZhbHNlO1xuICAgICAgYXdhaXQgdGhpcy5pbnNwZWN0QW5kQmFja3VwKCk7XG4gICAgfSB3aGlsZSAodGhpcy5jaGVja0FnYWluKTtcbiAgfVxuXG4gIHByaXZhdGUgYXN5bmMgaW5zcGVjdEFuZEJhY2t1cCgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBjb25zdCByZWRpcyA9IHRoaXMucmVkaXM7XG4gICAgaWYgKCFyZWRpcykgcmV0dXJuO1xuICAgIGNvbnN0IGluZm8gPSBhd2FpdCB0aGlzLm9ic2VydmUocmVkaXMpO1xuICAgIGlmIChpbmZvLnJkYkJnU2F2ZUluUHJvZ3Jlc3MpIHJldHVybjtcbiAgICBpZiAoaW5mby5yZGJMYXN0QmdTYXZlU3RhdHVzICE9PSBcIm9rXCIpIHtcbiAgICAgIGxvZ2dlci5lcnJvcihcIlJlZGlzIHJlcG9ydGVkIGFuIHVuc3VjY2Vzc2Z1bCBiYWNrZ3JvdW5kIHNhdmVcIiwge1xuICAgICAgICBzdGF0dXM6IGluZm8ucmRiTGFzdEJnU2F2ZVN0YXR1cyxcbiAgICAgIH0pO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICB0aGlzLndhcm5JZlN0YWxlKGluZm8pO1xuICAgIGlmICghdGhpcy5vcHRpb25zLnN0b3JhZ2UpIHtcbiAgICAgIGlmIChpbmZvLnJkYkxhc3RTYXZlVGltZSA+IHRoaXMubGFzdExvZ2dlZExvY2FsU2F2ZVRpbWUpIHtcbiAgICAgICAgdGhpcy5sYXN0TG9nZ2VkTG9jYWxTYXZlVGltZSA9IGluZm8ucmRiTGFzdFNhdmVUaW1lO1xuICAgICAgICBsb2dnZXIuaW5mbyhcImxvY2FsIEZhbGtvckRCIFJEQiBzbmFwc2hvdCBjb21wbGV0ZWRcIiwge1xuICAgICAgICAgIGxhc3RTYXZlVGltZTogaW5mby5yZGJMYXN0U2F2ZVRpbWUsXG4gICAgICAgICAgY2hhbmdlc1NpbmNlU2F2ZTogaW5mby5yZGJDaGFuZ2VzU2luY2VMYXN0U2F2ZSxcbiAgICAgICAgICBwYXRoOiB0aGlzLmR1bXBQYXRoLFxuICAgICAgICB9KTtcbiAgICAgIH1cbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgaWYgKGluZm8ucmRiTGFzdFNhdmVUaW1lIDw9IHRoaXMubGFzdFVwbG9hZGVkU2F2ZVRpbWUgfHwgIWV4aXN0c1N5bmModGhpcy5kdW1wUGF0aCkpIHJldHVybjtcbiAgICBpZiAoRGF0ZS5ub3coKSA8IHRoaXMucmV0cnlBZnRlcikgcmV0dXJuO1xuXG4gICAgdHJ5IHtcbiAgICAgIGF3YWl0IHRoaXMudXBsb2FkKGluZm8pO1xuICAgICAgdGhpcy5yZXRyeUF0dGVtcHQgPSAwO1xuICAgICAgdGhpcy5yZXRyeUFmdGVyID0gMDtcbiAgICAgIGNvbnN0IG5ld2VzdCA9IGF3YWl0IHRoaXMub2JzZXJ2ZShyZWRpcyk7XG4gICAgICBpZiAobmV3ZXN0LnJkYkxhc3RTYXZlVGltZSA+IHRoaXMubGFzdFVwbG9hZGVkU2F2ZVRpbWUpIHRoaXMuY2hlY2tBZ2FpbiA9IHRydWU7XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIHRoaXMuc3RhdHVzVmFsdWUuYmFja3VwRmFpbHVyZXMgKz0gMTtcbiAgICAgIGNvbnN0IGRlbGF5cyA9IHRoaXMub3B0aW9ucy5yZXRyeURlbGF5c01zID8/IERFRkFVTFRfUkVUUllfREVMQVlTX01TO1xuICAgICAgY29uc3QgZGVsYXkgPSBhc3luY1V0aWxzLmJvdW5kZWRSZXRyeURlbGF5KHRoaXMucmV0cnlBdHRlbXB0KyssIGRlbGF5cyk7XG4gICAgICB0aGlzLnJldHJ5QWZ0ZXIgPSBEYXRlLm5vdygpICsgZGVsYXk7XG4gICAgICBsb2dnZXIuZXJyb3IoXCJkdXJhYmxlIEZhbGtvckRCIGJhY2t1cCBmYWlsZWQ7IGxvY2FsIGRhdGFiYXNlIHJlbWFpbnMgYXZhaWxhYmxlXCIsIHtcbiAgICAgICAgZXJyb3IsXG4gICAgICAgIHJldHJ5SW5NczogZGVsYXksXG4gICAgICB9KTtcbiAgICB9XG4gIH1cblxuICBwcml2YXRlIGFzeW5jIHVwbG9hZChpbmZvOiBSZWRpc1BlcnNpc3RlbmNlSW5mbyk6IFByb21pc2U8dm9pZD4ge1xuICAgIGNvbnN0IHN0b3JhZ2UgPSB0aGlzLm9wdGlvbnMuc3RvcmFnZTtcbiAgICBpZiAoIXN0b3JhZ2UpIHJldHVybjtcbiAgICBjb25zdCBzdGFydGVkID0gRGF0ZS5ub3coKTtcbiAgICBjb25zdCBzZXF1ZW5jZSA9IGF3YWl0IG5leHRTbmFwc2hvdFNlcXVlbmNlKHN0b3JhZ2UsIHRoaXMubWFuaWZlc3QpO1xuICAgIGNvbnN0IHJlbW90ZSA9IHNuYXBzaG90UGF0aChzZXF1ZW5jZSk7XG4gICAgY29uc3Qgc3RhZ2VkUGF0aCA9IGpvaW4odGhpcy5zdGFnaW5nRGlyZWN0b3J5LCBgc25hcHNob3QtJHtzZXF1ZW5jZX0ucmRiYCk7XG4gICAgY29uc3Qgc3RhZ2VkID0gYXdhaXQgc3RhZ2VTbmFwc2hvdCh0aGlzLmR1bXBQYXRoLCBzdGFnZWRQYXRoKTtcbiAgICB0cnkge1xuICAgICAgYXdhaXQgc3RvcmFnZS51cGxvYWQoc3RhZ2VkLnBhdGgsIHJlbW90ZSwgeyBvdmVyd3JpdGU6IGZhbHNlIH0pO1xuICAgICAgY29uc3QgbWFuaWZlc3Q6IFNuYXBzaG90TWFuaWZlc3QgPSB7XG4gICAgICAgIHNuYXBzaG90OiByZW1vdGUsXG4gICAgICAgIGNyZWF0ZWRBdDogbmV3IERhdGUoKS50b0lTT1N0cmluZygpLFxuICAgICAgICBzaXplOiBzdGFnZWQuc2l6ZSxcbiAgICAgICAgc2hhMjU2OiBzdGFnZWQuc2hhMjU2LFxuICAgICAgICBzZXF1ZW5jZSxcbiAgICAgICAgcmVkaXNTYXZlVGltZTogaW5mby5yZGJMYXN0U2F2ZVRpbWUsXG4gICAgICB9O1xuICAgICAgYXdhaXQgc3RvcmFnZS53cml0ZUpzb24oTEFURVNUX01BTklGRVNUX1BBVEgsIG1hbmlmZXN0KTtcbiAgICAgIHRoaXMubWFuaWZlc3QgPSBtYW5pZmVzdDtcbiAgICAgIHRoaXMubGFzdFVwbG9hZGVkU2F2ZVRpbWUgPSBpbmZvLnJkYkxhc3RTYXZlVGltZTtcbiAgICAgIHRoaXMuc3RhdHVzVmFsdWUuYmFja3VwTGFzdFN1Y2Nlc3NUaW1lID0gbWFuaWZlc3QuY3JlYXRlZEF0O1xuICAgICAgdGhpcy5zdGF0dXNWYWx1ZS5iYWNrdXBMYXN0U2VxdWVuY2UgPSBzZXF1ZW5jZTtcbiAgICAgIHRoaXMuc3RhdHVzVmFsdWUuYmFja3VwU2l6ZUJ5dGVzID0gc3RhZ2VkLnNpemU7XG4gICAgICB0aGlzLnN0YXR1c1ZhbHVlLmJhY2t1cER1cmF0aW9uTXMgPSBEYXRlLm5vdygpIC0gc3RhcnRlZDtcbiAgICAgIGxvZ2dlci5pbmZvKFwidXBsb2FkZWQgZHVyYWJsZSBGYWxrb3JEQiBzbmFwc2hvdFwiLCB7XG4gICAgICAgIHNlcXVlbmNlLFxuICAgICAgICBzaXplOiBzdGFnZWQuc2l6ZSxcbiAgICAgICAgZHVyYXRpb25NczogdGhpcy5zdGF0dXNWYWx1ZS5iYWNrdXBEdXJhdGlvbk1zLFxuICAgICAgfSk7XG4gICAgICBhd2FpdCBlbmZvcmNlUmV0ZW50aW9uKHN0b3JhZ2UsIG1hbmlmZXN0LCB0aGlzLm9wdGlvbnMucmV0ZW50aW9uID8/IDUpLmNhdGNoKChlcnJvcikgPT4ge1xuICAgICAgICBsb2dnZXIud2FybihcIkZhbGtvckRCIHNuYXBzaG90IHJldGVudGlvbiBjbGVhbnVwIGZhaWxlZFwiLCB7IGVycm9yIH0pO1xuICAgICAgfSk7XG4gICAgfSBmaW5hbGx5IHtcbiAgICAgIGF3YWl0IHJtKHN0YWdlZFBhdGgsIHsgZm9yY2U6IHRydWUgfSk7XG4gICAgfVxuICB9XG5cbiAgcHJpdmF0ZSBhc3luYyBmb3JjZURpcnR5U25hcHNob3QocmVkaXM6IFJlZGlzUGVyc2lzdGVuY2VDbGllbnQpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBsZXQgaW5mbyA9IGF3YWl0IHRoaXMub2JzZXJ2ZShyZWRpcyk7XG4gICAgaWYgKGluZm8ucmRiQ2hhbmdlc1NpbmNlTGFzdFNhdmUgPiAwKSB7XG4gICAgICBjb25zdCBwcmV2aW91cyA9IGluZm8ucmRiTGFzdFNhdmVUaW1lO1xuICAgICAgYXdhaXQgcmVkaXMuYmdTYXZlKCk7XG4gICAgICBpbmZvID0gYXdhaXQgd2FpdEZvckJhY2tncm91bmRTYXZlKHJlZGlzLCBwcmV2aW91cywge1xuICAgICAgICB0aW1lb3V0TXM6IHRoaXMub3B0aW9ucy5zaHV0ZG93blRpbWVvdXRNcyA/PyAzMF8wMDAsXG4gICAgICB9KTtcbiAgICB9XG4gICAgaWYgKHRoaXMub3B0aW9ucy5zdG9yYWdlICYmIGluZm8ucmRiTGFzdFNhdmVUaW1lID4gdGhpcy5sYXN0VXBsb2FkZWRTYXZlVGltZSkge1xuICAgICAgdGhpcy5yZXRyeUFmdGVyID0gMDtcbiAgICAgIGF3YWl0IHRoaXMudXBsb2FkKGluZm8pO1xuICAgIH1cbiAgfVxuXG4gIHByaXZhdGUgYXN5bmMgb2JzZXJ2ZShyZWRpczogUmVkaXNQZXJzaXN0ZW5jZUNsaWVudCk6IFByb21pc2U8UmVkaXNQZXJzaXN0ZW5jZUluZm8+IHtcbiAgICBjb25zdCBpbmZvID0gYXdhaXQgZ2V0UGVyc2lzdGVuY2VJbmZvKHJlZGlzKTtcbiAgICB0aGlzLnN0YXR1c1ZhbHVlLnJkYkxhc3RTYXZlVGltZSA9IGluZm8ucmRiTGFzdFNhdmVUaW1lO1xuICAgIHRoaXMuc3RhdHVzVmFsdWUucmRiQ2hhbmdlc1NpbmNlTGFzdFNhdmUgPSBpbmZvLnJkYkNoYW5nZXNTaW5jZUxhc3RTYXZlO1xuICAgIHJldHVybiBpbmZvO1xuICB9XG5cbiAgcHJpdmF0ZSB3YXJuSWZTdGFsZShpbmZvOiBSZWRpc1BlcnNpc3RlbmNlSW5mbyk6IHZvaWQge1xuICAgIGlmICghdGhpcy5vcHRpb25zLnN0b3JhZ2UpIHJldHVybjtcbiAgICBjb25zdCB0aHJlc2hvbGQgPSB0aGlzLm9wdGlvbnMuc3RhbGVCYWNrdXBXYXJuaW5nTXM7XG4gICAgaWYgKCF0aHJlc2hvbGQgfHwgaW5mby5yZGJDaGFuZ2VzU2luY2VMYXN0U2F2ZSA9PT0gMCkgcmV0dXJuO1xuICAgIGNvbnN0IGxhc3QgPSB0aGlzLnN0YXR1c1ZhbHVlLmJhY2t1cExhc3RTdWNjZXNzVGltZVxuICAgICAgPyBEYXRlLnBhcnNlKHRoaXMuc3RhdHVzVmFsdWUuYmFja3VwTGFzdFN1Y2Nlc3NUaW1lKVxuICAgICAgOiAwO1xuICAgIGlmIChEYXRlLm5vdygpIC0gbGFzdCA+IHRocmVzaG9sZCkge1xuICAgICAgbG9nZ2VyLndhcm4oXCJGYWxrb3JEQiBoYXMgY2hhbmdlcyB3aXRob3V0IGEgcmVjZW50IGR1cmFibGUgYmFja3VwXCIsIHtcbiAgICAgICAgY2hhbmdlc1NpbmNlU2F2ZTogaW5mby5yZGJDaGFuZ2VzU2luY2VMYXN0U2F2ZSxcbiAgICAgICAgbGFzdFN1Y2Nlc3NUaW1lOiB0aGlzLnN0YXR1c1ZhbHVlLmJhY2t1cExhc3RTdWNjZXNzVGltZSxcbiAgICAgIH0pO1xuICAgIH1cbiAgfVxufVxuIl19
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Durable snapshot manifest naming, validation, sequencing, and retention.
3
+ *
4
+ * `latest.json` is updated only after its immutable snapshot exists. Reuse this
5
+ * module for manifest policy so restore and cleanup cannot disagree about the
6
+ * current good snapshot.
7
+ *
8
+ * @module
9
+ */
10
+ import type { VolumeStorage } from "./volume.ts";
11
+ /** Durable pointer updated only after a complete immutable snapshot upload. */
12
+ export declare const LATEST_MANIFEST_PATH = "latest.json";
13
+ /** Directory containing immutable, monotonically sequenced RDB files. */
14
+ export declare const SNAPSHOT_DIRECTORY = "snapshots";
15
+ /** Durable pointer to one verified immutable RDB snapshot. */
16
+ export interface SnapshotManifest {
17
+ snapshot: string;
18
+ createdAt: string;
19
+ size: number;
20
+ sha256: string;
21
+ sequence: number;
22
+ /** Redis `rdb_last_save_time` represented by this snapshot. */
23
+ redisSaveTime?: number;
24
+ }
25
+ /** Parse an untrusted manifest read from durable storage. */
26
+ export declare function parseSnapshotManifest(value: unknown): SnapshotManifest;
27
+ /** Remote path for an immutable, monotonically sequenced RDB snapshot. */
28
+ export declare function snapshotPath(sequence: number): string;
29
+ /** Choose a sequence above both the manifest and any orphaned uploads. */
30
+ export declare function nextSnapshotSequence(storage: VolumeStorage, manifest?: SnapshotManifest): Promise<number>;
31
+ /** Delete old immutable snapshots while preserving the manifest target. */
32
+ export declare function enforceRetention(storage: VolumeStorage, manifest: SnapshotManifest, retain: number): Promise<void>;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Durable snapshot manifest naming, validation, sequencing, and retention.
3
+ *
4
+ * `latest.json` is updated only after its immutable snapshot exists. Reuse this
5
+ * module for manifest policy so restore and cleanup cannot disagree about the
6
+ * current good snapshot.
7
+ *
8
+ * @module
9
+ */
10
+ import { posix } from "node:path";
11
+ /** Durable pointer updated only after a complete immutable snapshot upload. */
12
+ export const LATEST_MANIFEST_PATH = "latest.json";
13
+ /** Directory containing immutable, monotonically sequenced RDB files. */
14
+ export const SNAPSHOT_DIRECTORY = "snapshots";
15
+ /** Parse an untrusted manifest read from durable storage. */
16
+ export function parseSnapshotManifest(value) {
17
+ if (!isRecord(value))
18
+ throw new TypeError("FalkorDB latest.json must be an object");
19
+ const manifest = {
20
+ snapshot: requiredString(value.snapshot, "snapshot"),
21
+ createdAt: requiredString(value.createdAt, "createdAt"),
22
+ size: nonNegativeInteger(value.size, "size"),
23
+ sha256: requiredString(value.sha256, "sha256").toLowerCase(),
24
+ sequence: positiveInteger(value.sequence, "sequence"),
25
+ ...(value.redisSaveTime === undefined
26
+ ? {}
27
+ : { redisSaveTime: nonNegativeInteger(value.redisSaveTime, "redisSaveTime") }),
28
+ };
29
+ if (!manifest.snapshot.startsWith(`${SNAPSHOT_DIRECTORY}/`)) {
30
+ throw new TypeError("FalkorDB manifest snapshot must be under snapshots/");
31
+ }
32
+ if (!/^[a-f0-9]{64}$/.test(manifest.sha256)) {
33
+ throw new TypeError("FalkorDB manifest sha256 must be a 64-character hex digest");
34
+ }
35
+ if (!Number.isFinite(Date.parse(manifest.createdAt))) {
36
+ throw new TypeError("FalkorDB manifest createdAt must be an ISO date");
37
+ }
38
+ return manifest;
39
+ }
40
+ /** Remote path for an immutable, monotonically sequenced RDB snapshot. */
41
+ export function snapshotPath(sequence) {
42
+ return posix.join(SNAPSHOT_DIRECTORY, `${String(sequence).padStart(8, "0")}.rdb`);
43
+ }
44
+ /** Choose a sequence above both the manifest and any orphaned uploads. */
45
+ export async function nextSnapshotSequence(storage, manifest) {
46
+ const entries = await storage.list(SNAPSHOT_DIRECTORY);
47
+ let maximum = manifest?.sequence ?? 0;
48
+ for (const entry of entries) {
49
+ const match = /^(\d+)\.rdb$/.exec(entry.name);
50
+ if (match)
51
+ maximum = Math.max(maximum, Number.parseInt(match[1], 10));
52
+ }
53
+ return maximum + 1;
54
+ }
55
+ /** Delete old immutable snapshots while preserving the manifest target. */
56
+ export async function enforceRetention(storage, manifest, retain) {
57
+ const snapshots = (await storage.list(SNAPSHOT_DIRECTORY))
58
+ .map((entry) => ({ entry, match: /^(\d+)\.rdb$/.exec(entry.name) }))
59
+ .filter((item) => item.match !== null)
60
+ .map(({ entry, match }) => ({ entry, sequence: Number.parseInt(match[1], 10) }))
61
+ .sort((left, right) => right.sequence - left.sequence);
62
+ const keep = new Set(snapshots.slice(0, Math.max(1, retain)).map(({ entry }) => entry.path));
63
+ keep.add(manifest.snapshot);
64
+ for (const { entry } of snapshots) {
65
+ if (!keep.has(entry.path))
66
+ await storage.delete(entry.path);
67
+ }
68
+ }
69
+ function isRecord(value) {
70
+ return typeof value === "object" && value !== null && !Array.isArray(value);
71
+ }
72
+ function requiredString(value, name) {
73
+ if (typeof value !== "string" || !value.trim())
74
+ throw new TypeError(`${name} must be a string`);
75
+ return value;
76
+ }
77
+ function nonNegativeInteger(value, name) {
78
+ if (!Number.isSafeInteger(value) || Number(value) < 0) {
79
+ throw new TypeError(`${name} must be a non-negative integer`);
80
+ }
81
+ return Number(value);
82
+ }
83
+ function positiveInteger(value, name) {
84
+ const parsed = nonNegativeInteger(value, name);
85
+ if (parsed < 1)
86
+ throw new TypeError(`${name} must be positive`);
87
+ return parsed;
88
+ }
89
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFuaWZlc3QuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvcGVyc2lzdGVuY2UvbWFuaWZlc3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7O0dBUUc7QUFFSCxPQUFPLEVBQUUsS0FBSyxFQUFFLE1BQU0sV0FBVyxDQUFDO0FBR2xDLCtFQUErRTtBQUMvRSxNQUFNLENBQUMsTUFBTSxvQkFBb0IsR0FBRyxhQUFhLENBQUM7QUFFbEQseUVBQXlFO0FBQ3pFLE1BQU0sQ0FBQyxNQUFNLGtCQUFrQixHQUFHLFdBQVcsQ0FBQztBQWE5Qyw2REFBNkQ7QUFDN0QsTUFBTSxVQUFVLHFCQUFxQixDQUFDLEtBQWM7SUFDbEQsSUFBSSxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUM7UUFBRSxNQUFNLElBQUksU0FBUyxDQUFDLHdDQUF3QyxDQUFDLENBQUM7SUFDcEYsTUFBTSxRQUFRLEdBQXFCO1FBQ2pDLFFBQVEsRUFBRSxjQUFjLENBQUMsS0FBSyxDQUFDLFFBQVEsRUFBRSxVQUFVLENBQUM7UUFDcEQsU0FBUyxFQUFFLGNBQWMsQ0FBQyxLQUFLLENBQUMsU0FBUyxFQUFFLFdBQVcsQ0FBQztRQUN2RCxJQUFJLEVBQUUsa0JBQWtCLENBQUMsS0FBSyxDQUFDLElBQUksRUFBRSxNQUFNLENBQUM7UUFDNUMsTUFBTSxFQUFFLGNBQWMsQ0FBQyxLQUFLLENBQUMsTUFBTSxFQUFFLFFBQVEsQ0FBQyxDQUFDLFdBQVcsRUFBRTtRQUM1RCxRQUFRLEVBQUUsZUFBZSxDQUFDLEtBQUssQ0FBQyxRQUFRLEVBQUUsVUFBVSxDQUFDO1FBQ3JELEdBQUcsQ0FBQyxLQUFLLENBQUMsYUFBYSxLQUFLLFNBQVM7WUFDbkMsQ0FBQyxDQUFDLEVBQUU7WUFDSixDQUFDLENBQUMsRUFBRSxhQUFhLEVBQUUsa0JBQWtCLENBQUMsS0FBSyxDQUFDLGFBQWEsRUFBRSxlQUFlLENBQUMsRUFBRSxDQUFDO0tBQ2pGLENBQUM7SUFDRixJQUFJ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QUMsS0FBSyxFQUFFLElBQUksQ0FBQyxDQUFDO0lBQy9DLElBQUksTUFBTSxHQUFHLENBQUM7UUFBRSxNQUFNLElBQUksU0FBUyxDQUFDLEdBQUcsSUFBSSxtQkFBbUIsQ0FBQyxDQUFDO0lBQ2hFLE9BQU8sTUFBTSxDQUFDO0FBQ2hCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIER1cmFibGUgc25hcHNob3QgbWFuaWZlc3QgbmFtaW5nLCB2YWxpZGF0aW9uLCBzZXF1ZW5jaW5nLCBhbmQgcmV0ZW50aW9uLlxuICpcbiAqIGBsYXRlc3QuanNvbmAgaXMgdXBkYXRlZCBvbmx5IGFmdGVyIGl0cyBpbW11dGFibGUgc25hcHNob3QgZXhpc3RzLiBSZXVzZSB0aGlzXG4gKiBtb2R1bGUgZm9yIG1hbmlmZXN0IHBvbGljeSBzbyByZXN0b3JlIGFuZCBjbGVhbnVwIGNhbm5vdCBkaXNhZ3JlZSBhYm91dCB0aGVcbiAqIGN1cnJlbnQgZ29vZCBzbmFwc2hvdC5cbiAqXG4gKiBAbW9kdWxlXG4gKi9cblxuaW1wb3J0IHsgcG9zaXggfSBmcm9tIFwibm9kZTpwYXRoXCI7XG5pbXBvcnQgdHlwZSB7IFZvbHVtZVN0b3JhZ2UgfSBmcm9tIFwiLi92b2x1bWUudHNcIjtcblxuLyoqIER1cmFibGUgcG9pbnRlciB1cGRhdGVkIG9ubHkgYWZ0ZXIgYSBjb21wbGV0ZSBpbW11dGFibGUgc25hcHNob3QgdXBsb2FkLiAqL1xuZXhwb3J0IGNvbnN0IExBVEVTVF9NQU5JRkVTVF9QQVRIID0gXCJsYXRlc3QuanNvblwiO1xuXG4vKiogRGlyZWN0b3J5IGNvbnRhaW5pbmcgaW1tdXRhYmxlLCBtb25vdG9uaWNhbGx5IHNlcXVlbmNlZCBSREIgZmlsZXMuICovXG5leHBvcnQgY29uc3QgU05BUFNIT1RfRElSRUNUT1JZID0gXCJzbmFwc2hvdHNcIjtcblxuLyoqIER1cmFibGUgcG9pbnRlciB0byBvbmUgdmVyaWZpZWQgaW1tdXRhYmxlIFJEQiBzbmFwc2hvdC4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgU25hcHNob3RNYW5pZmVzdCB7XG4gIHNuYXBzaG90OiBzdHJpbmc7XG4gIGNyZWF0ZWRBdDogc3RyaW5nO1xuICBzaXplOiBudW1iZXI7XG4gIHNoYTI1Njogc3RyaW5nO1xuICBzZXF1ZW5jZTogbnVtYmVyO1xuICAvKiogUmVkaXMgYHJkYl9sYXN0X3NhdmVfdGltZWAgcmVwcmVzZW50ZWQgYnkgdGhpcyBzbmFwc2hvdC4gKi9cbiAgcmVkaXNTYXZlVGltZT86IG51bWJlcjtcbn1cblxuLyoqIFBhcnNlIGFuIHVudHJ1c3RlZCBtYW5pZmVzdCByZWFkIGZyb20gZHVyYWJsZSBzdG9yYWdlLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHBhcnNlU25hcHNob3RNYW5pZmVzdCh2YWx1ZTogdW5rbm93bik6IFNuYXBzaG90TWFuaWZlc3Qge1xuICBpZiAoIWlzUmVjb3JkKHZhbHVlKSkgdGhyb3cgbmV3IFR5cGVFcnJvcihcIkZhbGtvckRCIGxhdGVzdC5qc29uIG11c3QgYmUgYW4gb2JqZWN0XCIpO1xuICBjb25zdCBtYW5pZmVzdDogU25hcHNob3RNYW5pZmVzdCA9IHtcbiAgICBzbmFwc2hvdDogcmVxdWlyZWRTdHJpbmcodmFsdWUuc25hcHNob3QsIFwic25hcHNob3RcIiksXG4gICAgY3JlYXRlZEF0OiByZXF1aXJlZFN0cmluZyh2YWx1ZS5jcmVhdGVkQXQsIFwiY3JlYXRlZEF0XCIpLFxuICAgIHNpemU6IG5vbk5lZ2F0aXZlSW50ZWdlcih2YWx1ZS5zaXplLCBcInNpemVcIiksXG4gICAgc2hhMjU2OiByZXF1aXJlZFN0cmluZyh2YWx1ZS5zaGEyNTYsIFwic2hhMjU2XCIpLnRvTG93ZXJDYXNlKCksXG4gICAgc2VxdWVuY2U6IHBvc2l0aXZlSW50ZWdlcih2YWx1ZS5zZXF1ZW5jZSwgXCJzZXF1ZW5jZVwiKSxcbiAgICAuLi4odmFsdWUucmVkaXNTYXZlVGltZSA9PT0gdW5kZWZpbmVkXG4gICAgICA/IHt9XG4gICAgICA6IHsgcmVkaXNTYXZlVGltZTogbm9uTmVnYXRpdmVJbnRlZ2VyKHZhbHVlLnJlZGlzU2F2ZVRpbWUsIFwicmVkaXNTYXZlVGltZVwiKSB9KSxcbiAgfTtcbiAgaWYgKCFtYW5pZmVzdC5zbmFwc2hvdC5zdGFydHNXaXRoKGAke1NOQVBTSE9UX0RJUkVDVE9SWX0vYCkpIHtcbiAgICB0aHJvdyBuZXcgVHlwZUVycm9yKFwiRmFsa29yREIgbWFuaWZlc3Qgc25hcHNob3QgbXVzdCBiZSB1bmRlciBzbmFwc2hvdHMvXCIpO1xuICB9XG4gIGlmICghL15bYS1mMC05XXs2NH0kLy50ZXN0KG1hbmlmZXN0LnNoYTI1NikpIHtcbiAgICB0aHJvdyBuZXcgVHlwZUVycm9yKFwiRmFsa29yREIgbWFuaWZlc3Qgc2hhMjU2IG11c3QgYmUgYSA2NC1jaGFyYWN0ZXIgaGV4IGRpZ2VzdFwiKTtcbiAgfVxuICBpZiAoIU51bWJlci5pc0Zpbml0ZShEYXRlLnBhcnNlKG1hbmlmZXN0LmNyZWF0ZWRBdCkpKSB7XG4gICAgdGhyb3cgbmV3IFR5cGVFcnJvcihcIkZhbGtvckRCIG1hbmlmZXN0IGNyZWF0ZWRBdCBtdXN0IGJlIGFuIElTTyBkYXRlXCIpO1xuICB9XG4gIHJldHVybiBtYW5pZmVzdDtcbn1cblxuLyoqIFJlbW90ZSBwYXRoIGZvciBhbiBpbW11dGFibGUsIG1vbm90b25pY2FsbHkgc2VxdWVuY2VkIFJEQiBzbmFwc2hvdC4gKi9cbmV4cG9ydCBmdW5jdGlvbiBzbmFwc2hvdFBhdGgoc2VxdWVuY2U6IG51bWJlcik6IHN0cmluZyB7XG4gIHJldHVybiBwb3NpeC5qb2luKFNOQVBTSE9UX0RJUkVDVE9SWSwgYCR7U3RyaW5nKHNlcXVlbmNlKS5wYWRTdGFydCg4LCBcIjBcIil9LnJkYmApO1xufVxuXG4vKiogQ2hvb3NlIGEgc2VxdWVuY2UgYWJvdmUgYm90aCB0aGUgbWFuaWZlc3QgYW5kIGFueSBvcnBoYW5lZCB1cGxvYWRzLiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIG5leHRTbmFwc2hvdFNlcXVlbmNlKFxuICBzdG9yYWdlOiBWb2x1bWVTdG9yYWdlLFxuICBtYW5pZmVzdD86IFNuYXBzaG90TWFuaWZlc3QsXG4pOiBQcm9taXNlPG51bWJlcj4ge1xuICBjb25zdCBlbnRyaWVzID0gYXdhaXQgc3RvcmFnZS5saXN0KFNOQVBTSE9UX0RJUkVDVE9SWSk7XG4gIGxldCBtYXhpbXVtID0gbWFuaWZlc3Q/LnNlcXVlbmNlID8/IDA7XG4gIGZvciAoY29uc3QgZW50cnkgb2YgZW50cmllcykge1xuICAgIGNvbnN0IG1hdGNoID0gL14oXFxkKylcXC5yZGIkLy5leGVjKGVudHJ5Lm5hbWUpO1xuICAgIGlmIChtYXRjaCkgbWF4aW11bSA9IE1hdGgubWF4KG1heGltdW0sIE51bWJlci5wYXJzZUludChtYXRjaFsxXSEsIDEwKSk7XG4gIH1cbiAgcmV0dXJuIG1heGltdW0gKyAxO1xufVxuXG4vKiogRGVsZXRlIG9sZCBpbW11dGFibGUgc25hcHNob3RzIHdoaWxlIHByZXNlcnZpbmcgdGhlIG1hbmlmZXN0IHRhcmdldC4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBlbmZvcmNlUmV0ZW50aW9uKFxuICBzdG9yYWdlOiBWb2x1bWVTdG9yYWdlLFxuICBtYW5pZmVzdDogU25hcHNob3RNYW5pZmVzdCxcbiAgcmV0YWluOiBudW1iZXIsXG4pOiBQcm9taXNlPHZvaWQ+IHtcbiAgY29uc3Qgc25hcHNob3RzID0gKGF3YWl0IHN0b3JhZ2UubGlzdChTTkFQU0hPVF9ESVJFQ1RPUlkpKVxuICAgIC5tYXAoKGVudHJ5KSA9PiAoeyBlbnRyeSwgbWF0Y2g6IC9eKFxcZCspXFwucmRiJC8uZXhlYyhlbnRyeS5uYW1lKSB9KSlcbiAgICAuZmlsdGVyKChpdGVtKTogaXRlbSBpcyB0eXBlb2YgaXRlbSAmIHsgbWF0Y2g6IFJlZ0V4cEV4ZWNBcnJheSB9ID0+IGl0ZW0ubWF0Y2ggIT09IG51bGwpXG4gICAgLm1hcCgoeyBlbnRyeSwgbWF0Y2ggfSkgPT4gKHsgZW50cnksIHNlcXVlbmNlOiBOdW1iZXIucGFyc2VJbnQobWF0Y2hbMV0hLCAxMCkgfSkpXG4gICAgLnNvcnQoKGxlZnQsIHJpZ2h0KSA9PiByaWdodC5zZXF1ZW5jZSAtIGxlZnQuc2VxdWVuY2UpO1xuICBjb25zdCBrZWVwID0gbmV3IFNldChzbmFwc2hvdHMuc2xpY2UoMCwgTWF0aC5tYXgoMSwgcmV0YWluKSkubWFwKCh7IGVudHJ5IH0pID0+IGVudHJ5LnBhdGgpKTtcbiAga2VlcC5hZGQobWFuaWZlc3Quc25hcHNob3QpO1xuICBmb3IgKGNvbnN0IHsgZW50cnkgfSBvZiBzbmFwc2hvdHMpIHtcbiAgICBpZiAoIWtlZXAuaGFzKGVudHJ5LnBhdGgpKSBhd2FpdCBzdG9yYWdlLmRlbGV0ZShlbnRyeS5wYXRoKTtcbiAgfVxufVxuXG5mdW5jdGlvbiBpc1JlY29yZCh2YWx1ZTogdW5rbm93bik6IHZhbHVlIGlzIFJlY29yZDxzdHJpbmcsIHVua25vd24+IHtcbiAgcmV0dXJuIHR5cGVvZiB2YWx1ZSA9PT0gXCJvYmplY3RcIiAmJiB2YWx1ZSAhPT0gbnVsbCAmJiAhQXJyYXkuaXNBcnJheSh2YWx1ZSk7XG59XG5cbmZ1bmN0aW9uIHJlcXVpcmVkU3RyaW5nKHZhbHVlOiB1bmtub3duLCBuYW1lOiBzdHJpbmcpOiBzdHJpbmcge1xuICBpZiAodHlwZW9mIHZhbHVlICE9PSBcInN0cmluZ1wiIHx8ICF2YWx1ZS50cmltKCkpIHRocm93IG5ldyBUeXBlRXJyb3IoYCR7bmFtZX0gbXVzdCBiZSBhIHN0cmluZ2ApO1xuICByZXR1cm4gdmFsdWU7XG59XG5cbmZ1bmN0aW9uIG5vbk5lZ2F0aXZlSW50ZWdlcih2YWx1ZTogdW5rbm93biwgbmFtZTogc3RyaW5nKTogbnVtYmVyIHtcbiAgaWYgKCFOdW1iZXIuaXNTYWZlSW50ZWdlcih2YWx1ZSkgfHwgTnVtYmVyKHZhbHVlKSA8IDApIHtcbiAgICB0aHJvdyBuZXcgVHlwZUVycm9yKGAke25hbWV9IG11c3QgYmUgYSBub24tbmVnYXRpdmUgaW50ZWdlcmApO1xuICB9XG4gIHJldHVybiBOdW1iZXIodmFsdWUpO1xufVxuXG5mdW5jdGlvbiBwb3NpdGl2ZUludGVnZXIodmFsdWU6IHVua25vd24sIG5hbWU6IHN0cmluZyk6IG51bWJlciB7XG4gIGNvbnN0IHBhcnNlZCA9IG5vbk5lZ2F0aXZlSW50ZWdlcih2YWx1ZSwgbmFtZSk7XG4gIGlmIChwYXJzZWQgPCAxKSB0aHJvdyBuZXcgVHlwZUVycm9yKGAke25hbWV9IG11c3QgYmUgcG9zaXRpdmVgKTtcbiAgcmV0dXJuIHBhcnNlZDtcbn1cbiJdfQ==
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Redis persistence metadata parsing and completed-RDB detection.
3
+ *
4
+ * Reuse this module whenever FalkorDB persistence decisions depend on `INFO
5
+ * persistence`; do not infer snapshot completion from filesystem timestamps
6
+ * alone or trigger `BGSAVE` merely to discover whether the graph changed.
7
+ *
8
+ * @module
9
+ */
10
+ /** Redis persistence fields required by durable snapshot orchestration. */
11
+ export interface RedisPersistenceInfo {
12
+ rdbBgSaveInProgress: boolean;
13
+ rdbLastSaveTime: number;
14
+ rdbLastBgSaveStatus: string;
15
+ rdbChangesSinceLastSave: number;
16
+ }
17
+ /** Minimal Redis client surface used by the persistence manager. */
18
+ export interface RedisPersistenceClient {
19
+ info(section: "persistence"): Promise<string>;
20
+ bgSave(): Promise<unknown>;
21
+ }
22
+ /** Parse the fields used by the backup manager from `INFO persistence`. */
23
+ export declare function parsePersistenceInfo(raw: string): RedisPersistenceInfo;
24
+ /** Read and parse the current Redis persistence state. */
25
+ export declare function getPersistenceInfo(redis: RedisPersistenceClient): Promise<RedisPersistenceInfo>;
26
+ /** Wait until a requested background save finishes successfully. */
27
+ export declare function waitForBackgroundSave(redis: RedisPersistenceClient, previousSaveTime: number, options?: {
28
+ intervalMs?: number;
29
+ timeoutMs?: number;
30
+ }): Promise<RedisPersistenceInfo>;