@harperfast/harper 5.2.0-beta.4 → 5.2.0
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/bin/backup.ts +169 -0
- package/bin/cliOperations.ts +92 -73
- package/bin/harper.ts +25 -6
- package/components/Scope.ts +52 -6
- package/components/componentLoader.ts +107 -9
- package/components/mcp/toolRegistry.ts +10 -0
- package/components/mcp/tools/application.ts +12 -5
- package/components/mcp/tools/operations.ts +3 -0
- package/components/mcp/tools/schemas/operationDescriptions.ts +3 -0
- package/components/mcp/tools/schemas/operations.ts +9 -0
- package/components/operations.js +1 -0
- package/components/operationsValidation.js +32 -2
- package/components/scopeMount.ts +150 -0
- package/config-root.schema.json +4 -0
- package/dataLayer/backupManifest.ts +102 -0
- package/dataLayer/blobBackup.ts +286 -0
- package/dataLayer/harperBridge/ResourceBridge.ts +43 -0
- package/dataLayer/restoreMarker.ts +276 -0
- package/dataLayer/rocksdbBackup.ts +1100 -0
- package/dist/bin/backup.d.ts +9 -0
- package/dist/bin/backup.js +192 -0
- package/dist/bin/backup.js.map +1 -0
- package/dist/bin/cliOperations.d.ts +13 -0
- package/dist/bin/cliOperations.js +89 -70
- package/dist/bin/cliOperations.js.map +1 -1
- package/dist/bin/harper.d.ts +7 -0
- package/dist/bin/harper.js +27 -6
- package/dist/bin/harper.js.map +1 -1
- package/dist/components/Scope.d.ts +37 -1
- package/dist/components/Scope.js +48 -6
- package/dist/components/Scope.js.map +1 -1
- package/dist/components/componentLoader.d.ts +2 -6
- package/dist/components/componentLoader.js +91 -8
- package/dist/components/componentLoader.js.map +1 -1
- package/dist/components/mcp/toolRegistry.d.ts +7 -0
- package/dist/components/mcp/toolRegistry.js +10 -0
- package/dist/components/mcp/toolRegistry.js.map +1 -1
- package/dist/components/mcp/tools/application.js +11 -5
- package/dist/components/mcp/tools/application.js.map +1 -1
- package/dist/components/mcp/tools/operations.js +3 -0
- package/dist/components/mcp/tools/operations.js.map +1 -1
- package/dist/components/mcp/tools/schemas/operationDescriptions.js +2 -0
- package/dist/components/mcp/tools/schemas/operationDescriptions.js.map +1 -1
- package/dist/components/mcp/tools/schemas/operations.js +9 -0
- package/dist/components/mcp/tools/schemas/operations.js.map +1 -1
- package/dist/components/operations.js +2 -0
- package/dist/components/operations.js.map +1 -1
- package/dist/components/operationsValidation.js +34 -2
- package/dist/components/operationsValidation.js.map +1 -1
- package/dist/components/scopeMount.d.ts +86 -0
- package/dist/components/scopeMount.js +131 -0
- package/dist/components/scopeMount.js.map +1 -0
- package/dist/dataLayer/backupManifest.d.ts +26 -0
- package/dist/dataLayer/backupManifest.js +97 -0
- package/dist/dataLayer/backupManifest.js.map +1 -0
- package/dist/dataLayer/blobBackup.d.ts +87 -0
- package/dist/dataLayer/blobBackup.js +282 -0
- package/dist/dataLayer/blobBackup.js.map +1 -0
- package/dist/dataLayer/harperBridge/ResourceBridge.d.ts +3 -0
- package/dist/dataLayer/harperBridge/ResourceBridge.js +33 -0
- package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
- package/dist/dataLayer/restoreMarker.d.ts +122 -0
- package/dist/dataLayer/restoreMarker.js +261 -0
- package/dist/dataLayer/restoreMarker.js.map +1 -0
- package/dist/dataLayer/rocksdbBackup.d.ts +127 -0
- package/dist/dataLayer/rocksdbBackup.js +1039 -0
- package/dist/dataLayer/rocksdbBackup.js.map +1 -0
- package/dist/resources/DatabaseTransaction.js +0 -6
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/ResourceInterface.d.ts +0 -3
- package/dist/resources/ResourceInterface.js.map +1 -1
- package/dist/resources/Table.js +22 -22
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/blob.d.ts +8 -0
- package/dist/resources/blob.js +16 -7
- package/dist/resources/blob.js.map +1 -1
- package/dist/resources/databases.d.ts +42 -1
- package/dist/resources/databases.js +276 -40
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/transaction.js +0 -3
- package/dist/resources/transaction.js.map +1 -1
- package/dist/server/REST.js +25 -9
- package/dist/server/REST.js.map +1 -1
- package/dist/server/fastifyRoutes.js +15 -1
- package/dist/server/fastifyRoutes.js.map +1 -1
- package/dist/server/itc/serverHandlers.js +7 -1
- package/dist/server/itc/serverHandlers.js.map +1 -1
- package/dist/server/jobs/jobProcess.js +20 -1
- package/dist/server/jobs/jobProcess.js.map +1 -1
- package/dist/server/jobs/jobRunner.js +10 -0
- package/dist/server/jobs/jobRunner.js.map +1 -1
- package/dist/server/jobs/jobs.js +11 -0
- package/dist/server/jobs/jobs.js.map +1 -1
- package/dist/server/middlewareChain.d.ts +10 -1
- package/dist/server/middlewareChain.js +81 -21
- package/dist/server/middlewareChain.js.map +1 -1
- package/dist/server/serverHelpers/serverHandlers.js +8 -4
- package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
- package/dist/server/serverHelpers/serverUtilities.js +11 -0
- package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
- package/dist/server/static.js +11 -3
- package/dist/server/static.js.map +1 -1
- package/dist/utility/OperationFunctionCaller.js +25 -6
- package/dist/utility/OperationFunctionCaller.js.map +1 -1
- package/dist/utility/hdbTerms.d.ts +11 -1
- package/dist/utility/hdbTerms.js +11 -1
- package/dist/utility/hdbTerms.js.map +1 -1
- package/dist/utility/logging/harper_logger.d.ts +40 -0
- package/dist/utility/logging/harper_logger.js +746 -14
- package/dist/utility/logging/harper_logger.js.map +1 -1
- package/dist/utility/operation_authorization.js +13 -3
- package/dist/utility/operation_authorization.js.map +1 -1
- package/npm-shrinkwrap.json +3 -2
- package/package.json +4 -1
- package/resources/DESIGN.md +2 -0
- package/resources/DatabaseTransaction.ts +0 -3
- package/resources/ResourceInterface.ts +0 -3
- package/resources/Table.ts +20 -20
- package/resources/blob.ts +16 -6
- package/resources/databases.ts +272 -42
- package/resources/transaction.ts +0 -3
- package/server/DESIGN.md +16 -0
- package/server/REST.ts +25 -9
- package/server/fastifyRoutes.ts +20 -1
- package/server/itc/serverHandlers.js +7 -1
- package/server/jobs/jobProcess.ts +18 -1
- package/server/jobs/jobRunner.ts +10 -0
- package/server/jobs/jobs.ts +11 -0
- package/server/middlewareChain.ts +79 -20
- package/server/serverHelpers/serverHandlers.js +8 -4
- package/server/serverHelpers/serverUtilities.ts +19 -0
- package/server/static.ts +12 -3
- package/static/defaultConfig.yaml +1 -0
- package/studio/web/assets/{Chat-DK3GlWEb.js → Chat-DoVWScmq.js} +2 -2
- package/studio/web/assets/{Chat-DK3GlWEb.js.map → Chat-DoVWScmq.js.map} +1 -1
- package/studio/web/assets/{FloatingChat-fBcC1Ew_.js → FloatingChat-UZ2NsUOZ.js} +4 -4
- package/studio/web/assets/{FloatingChat-fBcC1Ew_.js.map → FloatingChat-UZ2NsUOZ.js.map} +1 -1
- package/studio/web/assets/{apiToken-DJo1nakA.js → apiToken-BUI_04o7.js} +2 -2
- package/studio/web/assets/{apiToken-DJo1nakA.js.map → apiToken-BUI_04o7.js.map} +1 -1
- package/studio/web/assets/{applications-BDfH8urd.js → applications-D03NA7wW.js} +2 -2
- package/studio/web/assets/{applications-BDfH8urd.js.map → applications-D03NA7wW.js.map} +1 -1
- package/studio/web/assets/{index-BHo3c2Gk.js → index-Bh_CNAHr.js} +6 -6
- package/studio/web/assets/index-Bh_CNAHr.js.map +1 -0
- package/studio/web/assets/{index.lazy-BTo0y6UM.js → index.lazy-Dx3MpyDC.js} +4 -4
- package/studio/web/assets/{index.lazy-BTo0y6UM.js.map → index.lazy-Dx3MpyDC.js.map} +1 -1
- package/studio/web/assets/{notifications-CMxvWNnz.js → notifications-0edoFTsb.js} +2 -2
- package/studio/web/assets/{notifications-CMxvWNnz.js.map → notifications-0edoFTsb.js.map} +1 -1
- package/studio/web/assets/{notifications-D3GoB26g.js → notifications-CwKhipK7.js} +2 -2
- package/studio/web/assets/{notifications-D3GoB26g.js.map → notifications-CwKhipK7.js.map} +1 -1
- package/studio/web/assets/{profile-Doj5FVDE.js → profile-DUfEPQtx.js} +2 -2
- package/studio/web/assets/{profile-Doj5FVDE.js.map → profile-DUfEPQtx.js.map} +1 -1
- package/studio/web/assets/{setComponentFile-yinsqJy0.js → setComponentFile-DMPo4UjC.js} +2 -2
- package/studio/web/assets/{setComponentFile-yinsqJy0.js.map → setComponentFile-DMPo4UjC.js.map} +1 -1
- package/studio/web/assets/{setup-DJwR0BHd.js → setup-B56Oz1_u.js} +2 -2
- package/studio/web/assets/{setup-DJwR0BHd.js.map → setup-B56Oz1_u.js.map} +1 -1
- package/studio/web/assets/{status-Br_AbsJs.js → status-BAod7p3o.js} +2 -2
- package/studio/web/assets/{status-Br_AbsJs.js.map → status-BAod7p3o.js.map} +1 -1
- package/studio/web/assets/{swagger-ui-react-02XH5sVf.js → swagger-ui-react-lQrBxfwM.js} +2 -2
- package/studio/web/assets/{swagger-ui-react-02XH5sVf.js.map → swagger-ui-react-lQrBxfwM.js.map} +1 -1
- package/studio/web/assets/{tsMode-D1DMKY7h.js → tsMode-CrHCRjTK.js} +2 -2
- package/studio/web/assets/{tsMode-D1DMKY7h.js.map → tsMode-CrHCRjTK.js.map} +1 -1
- package/studio/web/assets/{useEntityRestURL-DtRblRgw.js → useEntityRestURL-DoaBMEvU.js} +2 -2
- package/studio/web/assets/{useEntityRestURL-DtRblRgw.js.map → useEntityRestURL-DoaBMEvU.js.map} +1 -1
- package/studio/web/index.html +1 -1
- package/utility/OperationFunctionCaller.ts +24 -3
- package/utility/hdbTerms.ts +11 -1
- package/utility/logging/harper_logger.ts +729 -15
- package/utility/operation_authorization.ts +31 -3
- package/studio/web/assets/index-BHo3c2Gk.js.map +0 -1
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RESTORING_MARKER_SUFFIX = exports.RESTORE_LOCK_SUFFIX = exports.RESTORE_META_DIR = void 0;
|
|
4
|
+
exports.restoreMetaDir = restoreMetaDir;
|
|
5
|
+
exports.restoreLockPath = restoreLockPath;
|
|
6
|
+
exports.restoringMarkerPath = restoringMarkerPath;
|
|
7
|
+
exports.restoreMarkerPresent = restoreMarkerPresent;
|
|
8
|
+
exports.checkRestoreState = checkRestoreState;
|
|
9
|
+
exports.acquireRestoreLock = acquireRestoreLock;
|
|
10
|
+
exports.releaseRestoreLock = releaseRestoreLock;
|
|
11
|
+
exports.beginRestore = beginRestore;
|
|
12
|
+
exports.completeRestore = completeRestore;
|
|
13
|
+
exports.abandonRestore = abandonRestore;
|
|
14
|
+
exports.clearRestoreMarker = clearRestoreMarker;
|
|
15
|
+
exports.scanBlockedRestores = scanBlockedRestores;
|
|
16
|
+
const node_fs_1 = require("node:fs");
|
|
17
|
+
const node_path_1 = require("node:path");
|
|
18
|
+
const node_crypto_1 = require("node:crypto");
|
|
19
|
+
const rocksdb_js_1 = require("@harperfast/rocksdb-js");
|
|
20
|
+
/**
|
|
21
|
+
* Restore lock + marker protocol for RocksDB database restores (online operation and offline CLI),
|
|
22
|
+
* and the shared per-database exclusion used by `dropDatabase` so a drop and a restore can never
|
|
23
|
+
* mutate the same directory concurrently.
|
|
24
|
+
*
|
|
25
|
+
* Restore metadata lives in an isolated `` `restore` `` directory *beside* the database directory
|
|
26
|
+
* (never inside it, since a restore purges the destination). Each database's two files are keyed by
|
|
27
|
+
* a hash of the database directory name rather than being suffixed onto the name itself. That keeps
|
|
28
|
+
* them out of the database-name namespace — a legal database literally named `orders.restoring`
|
|
29
|
+
* would otherwise be mistaken for the restore marker of `orders`, and a 250-character name plus a
|
|
30
|
+
* `.restore.lock` suffix would exceed the 255-byte `NAME_MAX` on most filesystems. The directory
|
|
31
|
+
* name deliberately contains a backtick: `schemaRegex` (the database-name validator) forbids only
|
|
32
|
+
* `/` and `` ` `` among filesystem-legal characters, so no legal database can ever occupy this path
|
|
33
|
+
* — including a database literally named `.restore` (which *is* a legal name, so a plain `.restore`
|
|
34
|
+
* directory would collide with it and land the markers inside the live database). The directory is
|
|
35
|
+
* not itself a RocksDB/LMDB database (no `CURRENT`/`MANIFEST-`/`.mdb`), so the startup scan ignores
|
|
36
|
+
* it, and no user can create a database that resolves to it.
|
|
37
|
+
*
|
|
38
|
+
* - `<meta-dir>/<key>.lock` — an OS-level exclusive file lock (via rocksdb-js `tryFileLock`),
|
|
39
|
+
* effective across processes, containers, and worker threads, auto-released on process exit.
|
|
40
|
+
* Only *held-ness* is meaningful; the file itself persists after release (harmless). Held for the
|
|
41
|
+
* duration of a restore, and briefly by `dropDatabase` so the two serialize on the same primitive.
|
|
42
|
+
* Known limitation: the lock is owned by the process, so if the restore job's worker *thread*
|
|
43
|
+
* dies without the process exiting, the lock stays held (restores 409) until Harper restarts.
|
|
44
|
+
* - `<meta-dir>/<key>.restoring` — the completion marker. Written (and fsynced) after the lock is
|
|
45
|
+
* acquired and before the destructive restore begins; deleted only after the restore completes
|
|
46
|
+
* successfully, while still holding the lock. Its *existence* means "a restore started and has
|
|
47
|
+
* not finished successfully". Its first line records the database directory name so the startup
|
|
48
|
+
* scan can map a marker back to the database it blocks without decoding the hashed key.
|
|
49
|
+
*/
|
|
50
|
+
// The backtick makes this an illegal database name (schemaRegex rejects `/` and backtick only), so
|
|
51
|
+
// it can never collide with a real database directory — see the module header.
|
|
52
|
+
exports.RESTORE_META_DIR = '`restore`';
|
|
53
|
+
exports.RESTORE_LOCK_SUFFIX = '.lock';
|
|
54
|
+
exports.RESTORING_MARKER_SUFFIX = '.restoring';
|
|
55
|
+
/**
|
|
56
|
+
* Directory holding the restore metadata for a database — the reserved `` `restore` `` sibling of
|
|
57
|
+
* the database directory (see the module header for why the name contains a backtick). Shared by
|
|
58
|
+
* every database under the same parent, so a single readdir surfaces all pending restores during
|
|
59
|
+
* the startup scan.
|
|
60
|
+
*/
|
|
61
|
+
function restoreMetaDir(dbPath) {
|
|
62
|
+
return (0, node_path_1.join)((0, node_path_1.dirname)(dbPath), exports.RESTORE_META_DIR);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Filesystem-safe, length-bounded key for a database's restore metadata files. Hashing the
|
|
66
|
+
* database directory name (not the full path, so it is stable regardless of where the databases
|
|
67
|
+
* root lives) keeps the metadata filenames short and collision-free while staying independent of
|
|
68
|
+
* the database-name namespace. Database directory names are unique within a databases root, so
|
|
69
|
+
* their hashes are too.
|
|
70
|
+
*/
|
|
71
|
+
function restoreMetaKey(dbPath) {
|
|
72
|
+
return (0, node_crypto_1.createHash)('sha256').update((0, node_path_1.basename)(dbPath)).digest('hex').slice(0, 32);
|
|
73
|
+
}
|
|
74
|
+
function restoreLockPath(dbPath) {
|
|
75
|
+
return (0, node_path_1.join)(restoreMetaDir(dbPath), restoreMetaKey(dbPath) + exports.RESTORE_LOCK_SUFFIX);
|
|
76
|
+
}
|
|
77
|
+
function restoringMarkerPath(dbPath) {
|
|
78
|
+
return (0, node_path_1.join)(restoreMetaDir(dbPath), restoreMetaKey(dbPath) + exports.RESTORING_MARKER_SUFFIX);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Whether a `.restoring` marker exists for a database. Cheaper than `checkRestoreState` and, unlike
|
|
82
|
+
* it, safe to call while *this* thread holds the restore lock: `checkRestoreState` would re-probe
|
|
83
|
+
* the lock (which reads as held from the same thread) and report 'in-progress' rather than telling
|
|
84
|
+
* a caller that a *leftover* marker is present. `dropDatabase` uses this after acquiring the lock to
|
|
85
|
+
* distinguish debris from a crashed restore.
|
|
86
|
+
*/
|
|
87
|
+
function restoreMarkerPresent(dbPath) {
|
|
88
|
+
return (0, node_fs_1.existsSync)(restoringMarkerPath(dbPath));
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Determine the restore state of a database directory. Used by startup database detection and
|
|
92
|
+
* the open-database guards:
|
|
93
|
+
* - 'in-progress': marker present and the restore lock is held (a restore is running in some
|
|
94
|
+
* process) — do not load.
|
|
95
|
+
* - 'incomplete': marker present but the lock is free (crashed mid-restore; the directory may
|
|
96
|
+
* be partial garbage) — do not load; rerun the restore.
|
|
97
|
+
* - 'clear': no marker — load normally (a stale, unheld lock file alone is fine).
|
|
98
|
+
*
|
|
99
|
+
* The marker is checked FIRST and the lock is only probed when the marker exists. Probing takes
|
|
100
|
+
* and releases the flock, and probes are mutually exclusive across threads — if every rescan on
|
|
101
|
+
* every thread probed the (persistent) lock file of a long-ago-restored database, concurrent
|
|
102
|
+
* rescans would collide and misclassify healthy databases as 'in-progress'. Marker-first is
|
|
103
|
+
* safe: `beginRestore` writes (and fsyncs) the marker immediately after taking the lock and
|
|
104
|
+
* before any destructive step, so a database without a marker has nothing to protect yet.
|
|
105
|
+
*/
|
|
106
|
+
function checkRestoreState(dbPath) {
|
|
107
|
+
if (!(0, node_fs_1.existsSync)(restoringMarkerPath(dbPath)))
|
|
108
|
+
return 'clear';
|
|
109
|
+
const lockPath = restoreLockPath(dbPath);
|
|
110
|
+
if ((0, node_fs_1.existsSync)(lockPath)) {
|
|
111
|
+
const token = (0, rocksdb_js_1.tryFileLock)(lockPath);
|
|
112
|
+
if (token === 0)
|
|
113
|
+
return 'in-progress';
|
|
114
|
+
(0, rocksdb_js_1.fileLockRelease)(token);
|
|
115
|
+
}
|
|
116
|
+
return 'incomplete';
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* fsync a directory so a create/unlink of an entry within it is durable. Best-effort: Windows (and
|
|
120
|
+
* some filesystems) reject opening a directory for fsync with EPERM/EISDIR/ENOTSUP — the durability
|
|
121
|
+
* flush is a POSIX nicety, so treat those as a no-op rather than failing the restore.
|
|
122
|
+
*/
|
|
123
|
+
function fsyncDir(dir) {
|
|
124
|
+
let dirFd;
|
|
125
|
+
try {
|
|
126
|
+
dirFd = (0, node_fs_1.openSync)(dir, 'r');
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
if (error.code === 'EPERM' || error.code === 'EISDIR' || error.code === 'ENOTSUP')
|
|
130
|
+
return;
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
(0, node_fs_1.fsyncSync)(dirFd);
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
(0, node_fs_1.closeSync)(dirFd);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Take the per-database restore lock without writing a marker. Used by `dropDatabase` so a drop and
|
|
142
|
+
* a restore serialize on the same primitive: whichever takes the lock first runs to completion; the
|
|
143
|
+
* other gets a 409. Throws (statusCode 409) if the lock is already held.
|
|
144
|
+
*/
|
|
145
|
+
function acquireRestoreLock(dbPath) {
|
|
146
|
+
(0, node_fs_1.mkdirSync)(restoreMetaDir(dbPath), { recursive: true });
|
|
147
|
+
const token = (0, rocksdb_js_1.tryFileLock)(restoreLockPath(dbPath));
|
|
148
|
+
if (token === 0) {
|
|
149
|
+
const error = new Error(`Restore already in progress for database at ${dbPath}`);
|
|
150
|
+
error.statusCode = 409;
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
return { token, dbPath, preexisting: false };
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Release a lock taken by `acquireRestoreLock` (no marker to remove).
|
|
157
|
+
*/
|
|
158
|
+
function releaseRestoreLock(lock) {
|
|
159
|
+
(0, rocksdb_js_1.fileLockRelease)(lock.token);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Acquire the per-database restore lock and write the restoring marker. Call before any
|
|
163
|
+
* destructive step. Returns the lock (with `preexisting` set when a marker was already present, so
|
|
164
|
+
* a failed recovery attempt knows not to clear it). Throws (statusCode 409) if another restore
|
|
165
|
+
* already holds the lock.
|
|
166
|
+
*/
|
|
167
|
+
function beginRestore(dbPath) {
|
|
168
|
+
const markerPath = restoringMarkerPath(dbPath);
|
|
169
|
+
const preexisting = (0, node_fs_1.existsSync)(markerPath);
|
|
170
|
+
const lock = acquireRestoreLock(dbPath);
|
|
171
|
+
try {
|
|
172
|
+
const fd = (0, node_fs_1.openSync)(markerPath, 'w');
|
|
173
|
+
try {
|
|
174
|
+
// first line is the database directory name so the startup scan can map this marker back to
|
|
175
|
+
// the database it blocks without reversing the hashed key
|
|
176
|
+
(0, node_fs_1.writeSync)(fd, `${(0, node_path_1.basename)(dbPath)}\nrestore started ${new Date().toISOString()}\n`);
|
|
177
|
+
(0, node_fs_1.fsyncSync)(fd);
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
(0, node_fs_1.closeSync)(fd);
|
|
181
|
+
}
|
|
182
|
+
// fsync the metadata directory so the marker's directory entry is durable — without this a
|
|
183
|
+
// power loss can lose the entry, and a half-purged database would load as healthy
|
|
184
|
+
fsyncDir(restoreMetaDir(dbPath));
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
(0, rocksdb_js_1.fileLockRelease)(lock.token);
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
return { ...lock, preexisting };
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Mark the restore successful: delete the marker (while still holding the lock), then release
|
|
194
|
+
* the lock.
|
|
195
|
+
*/
|
|
196
|
+
function completeRestore(lock) {
|
|
197
|
+
try {
|
|
198
|
+
(0, node_fs_1.unlinkSync)(restoringMarkerPath(lock.dbPath));
|
|
199
|
+
// fsync the metadata directory so the marker's *removal* is durable — symmetric with the
|
|
200
|
+
// creation fsync in beginRestore. Without it, a power loss could resurrect the marker's
|
|
201
|
+
// directory entry and misclassify a fully-restored database as incomplete.
|
|
202
|
+
fsyncDir(restoreMetaDir(lock.dbPath));
|
|
203
|
+
}
|
|
204
|
+
finally {
|
|
205
|
+
(0, rocksdb_js_1.fileLockRelease)(lock.token);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Release the lock after a failed restore, leaving the marker in place so the database is
|
|
210
|
+
* detected as an incomplete restore (and not loaded) until a rerun succeeds.
|
|
211
|
+
*/
|
|
212
|
+
function abandonRestore(lock) {
|
|
213
|
+
(0, rocksdb_js_1.fileLockRelease)(lock.token);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Remove a database's restore marker if one is present, then release the lock. Used by
|
|
217
|
+
* `dropDatabase`: a dropped database that carried an incomplete-restore marker should not leave the
|
|
218
|
+
* marker behind to block a future database of the same name. No-op on the marker if none exists.
|
|
219
|
+
*/
|
|
220
|
+
function clearRestoreMarker(lock) {
|
|
221
|
+
try {
|
|
222
|
+
const markerPath = restoringMarkerPath(lock.dbPath);
|
|
223
|
+
if ((0, node_fs_1.existsSync)(markerPath)) {
|
|
224
|
+
(0, node_fs_1.unlinkSync)(markerPath);
|
|
225
|
+
fsyncDir(restoreMetaDir(lock.dbPath));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
(0, rocksdb_js_1.fileLockRelease)(lock.token);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Scan a databases root's reserved `` `restore` `` metadata directory and report every database currently blocked from
|
|
234
|
+
* loading, mapping each surviving marker back to its database name via the marker's first line.
|
|
235
|
+
* Returns `[dbName, state]` pairs for markers whose state is `in-progress` or `incomplete`
|
|
236
|
+
* (a `clear` result means the marker was removed concurrently and the database is loadable).
|
|
237
|
+
*/
|
|
238
|
+
function scanBlockedRestores(databasesRoot) {
|
|
239
|
+
const metaDir = (0, node_path_1.join)(databasesRoot, exports.RESTORE_META_DIR);
|
|
240
|
+
if (!(0, node_fs_1.existsSync)(metaDir))
|
|
241
|
+
return [];
|
|
242
|
+
const blocked = [];
|
|
243
|
+
for (const entry of (0, node_fs_1.readdirSync)(metaDir, { withFileTypes: true })) {
|
|
244
|
+
if (!entry.isFile() || !entry.name.endsWith(exports.RESTORING_MARKER_SUFFIX))
|
|
245
|
+
continue;
|
|
246
|
+
let dbName;
|
|
247
|
+
try {
|
|
248
|
+
dbName = (0, node_fs_1.readFileSync)((0, node_path_1.join)(metaDir, entry.name), 'utf8').split('\n', 1)[0];
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
continue; // marker removed concurrently
|
|
252
|
+
}
|
|
253
|
+
if (!dbName)
|
|
254
|
+
continue;
|
|
255
|
+
const state = checkRestoreState((0, node_path_1.join)(databasesRoot, dbName));
|
|
256
|
+
if (state !== 'clear')
|
|
257
|
+
blocked.push([dbName, state]);
|
|
258
|
+
}
|
|
259
|
+
return blocked;
|
|
260
|
+
}
|
|
261
|
+
//# sourceMappingURL=restoreMarker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"restoreMarker.js","sourceRoot":"","sources":["../../dataLayer/restoreMarker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { PassThrough } from 'node:stream';
|
|
2
|
+
import { RocksDatabase, type BackupInfo } from '@harperfast/rocksdb-js';
|
|
3
|
+
import { ClientError } from '../utility/errors/hdbError.ts';
|
|
4
|
+
/**
|
|
5
|
+
* Shared core for the RocksDB managed-backup operations (`create_backup`, `list_backups`,
|
|
6
|
+
* `verify_backup`, `delete_backup`, `purge_backups`, `restore_backup`) and the RocksDB path of
|
|
7
|
+
* `get_backup`. Used by both the operation API (running server) and the CLI (stopped server) so
|
|
8
|
+
* the two behave identically.
|
|
9
|
+
*
|
|
10
|
+
* Directory backups are confined to `<backupsRoot>/<database>/` where the backups root comes
|
|
11
|
+
* from the `storage.backupPath` config (default `<hdb_root>/backups`); operations never accept
|
|
12
|
+
* arbitrary filesystem paths.
|
|
13
|
+
*/
|
|
14
|
+
export declare class BackupNotFoundError extends ClientError {
|
|
15
|
+
constructor(message: string);
|
|
16
|
+
}
|
|
17
|
+
export declare class BackupInProgressError extends ClientError {
|
|
18
|
+
constructor(message: string);
|
|
19
|
+
}
|
|
20
|
+
export declare function getBackupsRoot(): string;
|
|
21
|
+
/**
|
|
22
|
+
* The database name becomes a path segment under the backups root and the databases root —
|
|
23
|
+
* reject anything that could traverse outside them.
|
|
24
|
+
*/
|
|
25
|
+
export declare function validateDatabaseName(databaseName: any): void;
|
|
26
|
+
export declare function backupDirForDatabase(databaseName: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Resolve the single root store for a database. A database can span multiple root stores when a
|
|
29
|
+
* table has a per-table `path` config; backing up such a database is not supported and errors
|
|
30
|
+
* descriptively. Engine gating (RocksDB vs LMDB) is done inline at each call site.
|
|
31
|
+
*/
|
|
32
|
+
export declare function resolveSingleRootStore(databaseName: string): any;
|
|
33
|
+
export declare function listBackupsInDir(backupDir: string): Promise<BackupInfo[]>;
|
|
34
|
+
export declare function listBackups(request: any): Promise<{
|
|
35
|
+
backup_id: number;
|
|
36
|
+
timestamp: number;
|
|
37
|
+
size: number;
|
|
38
|
+
file_count: number;
|
|
39
|
+
blobs: boolean;
|
|
40
|
+
}[]>;
|
|
41
|
+
export declare function deleteBackup(request: any): Promise<{
|
|
42
|
+
ok: boolean;
|
|
43
|
+
}>;
|
|
44
|
+
export declare function purgeBackups(request: any): Promise<{
|
|
45
|
+
deleted: number;
|
|
46
|
+
remaining: number;
|
|
47
|
+
}>;
|
|
48
|
+
export declare function validateCreateBackup(request: any): Promise<void>;
|
|
49
|
+
export declare function createBackup(request: any): Promise<{
|
|
50
|
+
size?: number;
|
|
51
|
+
timestamp?: any;
|
|
52
|
+
database: string;
|
|
53
|
+
backup_id: any;
|
|
54
|
+
blobs: boolean;
|
|
55
|
+
}>;
|
|
56
|
+
export declare function validateVerifyBackup(request: any): Promise<void>;
|
|
57
|
+
export declare function verifyBackup(request: any): Promise<{
|
|
58
|
+
database: string;
|
|
59
|
+
backup_id: number;
|
|
60
|
+
ok: boolean;
|
|
61
|
+
blobs: boolean;
|
|
62
|
+
}>;
|
|
63
|
+
export declare function validateRestoreBackup(request: any): Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* Online restore of a user database (see the design's restore lock + marker protocol):
|
|
66
|
+
* take the per-database restore lock, write the restoring marker, close the database across all
|
|
67
|
+
* worker threads, restore, delete the marker, release the lock, and reload everywhere.
|
|
68
|
+
*/
|
|
69
|
+
export declare function restoreBackup(request: any): Promise<{
|
|
70
|
+
database: string;
|
|
71
|
+
backup_id: number;
|
|
72
|
+
}>;
|
|
73
|
+
/**
|
|
74
|
+
* Returns a Readable (with `.headers`) streaming a full-snapshot tar (optionally gzipped) of the
|
|
75
|
+
* database's current state. No scratch disk; a consumer error aborts the native backup cleanly.
|
|
76
|
+
* `noCompression` opts out of serverHandlers' accept-encoding auto-gzip — this response must never
|
|
77
|
+
* be compressed by the server.
|
|
78
|
+
*
|
|
79
|
+
* With blobs included (the default; `excludeBlobs` opts out), the database's file-backed blob roots
|
|
80
|
+
* are appended to the same archive under `blobs/<rootIndex>/<relpath>` so a downloaded backup is a
|
|
81
|
+
* complete Harper database. Blob capture is best-effort point-in-time (the archive contains whatever
|
|
82
|
+
* files exist while it streams); a blob deleted mid-stream is skipped.
|
|
83
|
+
*/
|
|
84
|
+
export declare function createBackupStream(rootStore: RocksDatabase, databaseName: string, gzip: boolean, excludeBlobs?: boolean): PassThrough;
|
|
85
|
+
/**
|
|
86
|
+
* Offline create: open the RocksDatabase directly, run an ordinary incremental directory backup
|
|
87
|
+
* into the configured backup root, and close. RocksDB is single-writer, so this collides on the
|
|
88
|
+
* database lock if the server is running — callers guard on the server being stopped.
|
|
89
|
+
*/
|
|
90
|
+
export declare function createBackupOffline(databaseName: string, excludeBlobs?: boolean): Promise<{
|
|
91
|
+
size?: number;
|
|
92
|
+
timestamp?: any;
|
|
93
|
+
database: string;
|
|
94
|
+
backup_id: any;
|
|
95
|
+
blobs: boolean;
|
|
96
|
+
}>;
|
|
97
|
+
/**
|
|
98
|
+
* Offline restore (required for the `system` database; works for any database). Runs the same
|
|
99
|
+
* lock + marker protocol as the online operation so a crashed CLI restore is detected at next
|
|
100
|
+
* server start. `targetDatabase` restores into a different database directory (non-destructive
|
|
101
|
+
* for the source database); the server picks it up on next start via normal engine detection.
|
|
102
|
+
*/
|
|
103
|
+
export declare function restoreBackupOffline(databaseName: string, backupId?: number, targetDatabase?: string): Promise<{
|
|
104
|
+
database: string;
|
|
105
|
+
backup_id: number;
|
|
106
|
+
restored_to: string;
|
|
107
|
+
}>;
|
|
108
|
+
export declare function listBackupsOffline(databaseName: string): Promise<{
|
|
109
|
+
backup_id: number;
|
|
110
|
+
timestamp: number;
|
|
111
|
+
size: number;
|
|
112
|
+
file_count: number;
|
|
113
|
+
blobs: boolean;
|
|
114
|
+
}[]>;
|
|
115
|
+
export declare function verifyBackupOffline(databaseName: string, backupId: number, verifyChecksum?: boolean): Promise<{
|
|
116
|
+
database: string;
|
|
117
|
+
backup_id: number;
|
|
118
|
+
ok: boolean;
|
|
119
|
+
blobs: boolean;
|
|
120
|
+
}>;
|
|
121
|
+
export declare function deleteBackupOffline(databaseName: string, backupId: number): Promise<{
|
|
122
|
+
ok: boolean;
|
|
123
|
+
}>;
|
|
124
|
+
export declare function purgeBackupsOffline(databaseName: string, keepCount: number): Promise<{
|
|
125
|
+
deleted: number;
|
|
126
|
+
remaining: number;
|
|
127
|
+
}>;
|