@harperfast/harper 5.2.0-beta.4 → 5.2.1
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/copyDb.ts +21 -4
- package/bin/harper.ts +43 -55
- package/bin/help.ts +216 -0
- package/components/Application.ts +236 -46
- package/components/ApplicationScope.ts +26 -0
- package/components/EntryHandler.ts +410 -105
- package/components/RuntimeModuleTracker.ts +189 -0
- package/components/Scope.ts +120 -30
- package/components/componentLoader.ts +177 -25
- package/components/deployLifecycle.ts +119 -33
- package/components/mcp/toolRegistry.ts +10 -0
- package/components/mcp/tools/application.ts +12 -5
- package/components/mcp/tools/operations.ts +9 -0
- package/components/mcp/tools/schemas/operationDescriptions.ts +3 -0
- package/components/mcp/tools/schemas/operations.ts +9 -0
- package/components/operations.js +5 -6
- package/components/operationsValidation.js +32 -2
- package/components/scopeMount.ts +150 -0
- package/config/configUtils.ts +12 -9
- package/config-root.schema.json +14 -0
- package/dataLayer/backupManifest.ts +102 -0
- package/dataLayer/blobBackup.ts +286 -0
- package/dataLayer/harperBridge/ResourceBridge.ts +69 -5
- package/dataLayer/hdbInfoController.ts +8 -0
- package/dataLayer/restoreMarker.ts +276 -0
- package/dataLayer/rocksdbBackup.ts +1100 -0
- package/dataLayer/schemaDescribe.ts +2 -1
- 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/copyDb.js +13 -2
- package/dist/bin/copyDb.js.map +1 -1
- package/dist/bin/harper.d.ts +13 -0
- package/dist/bin/harper.js +45 -56
- package/dist/bin/harper.js.map +1 -1
- package/dist/bin/help.d.ts +8 -0
- package/dist/bin/help.js +192 -0
- package/dist/bin/help.js.map +1 -0
- package/dist/components/Application.d.ts +16 -1
- package/dist/components/Application.js +210 -38
- package/dist/components/Application.js.map +1 -1
- package/dist/components/ApplicationScope.d.ts +7 -0
- package/dist/components/ApplicationScope.js +22 -0
- package/dist/components/ApplicationScope.js.map +1 -1
- package/dist/components/EntryHandler.d.ts +4 -4
- package/dist/components/EntryHandler.js +386 -95
- package/dist/components/EntryHandler.js.map +1 -1
- package/dist/components/RuntimeModuleTracker.d.ts +11 -0
- package/dist/components/RuntimeModuleTracker.js +189 -0
- package/dist/components/RuntimeModuleTracker.js.map +1 -0
- package/dist/components/Scope.d.ts +38 -1
- package/dist/components/Scope.js +117 -28
- package/dist/components/Scope.js.map +1 -1
- package/dist/components/componentLoader.d.ts +2 -6
- package/dist/components/componentLoader.js +160 -18
- package/dist/components/componentLoader.js.map +1 -1
- package/dist/components/deployLifecycle.d.ts +6 -2
- package/dist/components/deployLifecycle.js +109 -31
- package/dist/components/deployLifecycle.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 +9 -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 +6 -6
- 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/config/configUtils.js +13 -9
- package/dist/config/configUtils.js.map +1 -1
- 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 +45 -5
- package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
- package/dist/dataLayer/hdbInfoController.js +4 -0
- package/dist/dataLayer/hdbInfoController.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/dataLayer/schemaDescribe.js +2 -1
- package/dist/dataLayer/schemaDescribe.js.map +1 -1
- package/dist/resources/DatabaseTransaction.d.ts +55 -0
- package/dist/resources/DatabaseTransaction.js +282 -109
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/ResourceInterface.d.ts +8 -5
- package/dist/resources/ResourceInterface.js.map +1 -1
- package/dist/resources/Resources.js +22 -4
- package/dist/resources/Resources.js.map +1 -1
- package/dist/resources/Table.d.ts +6 -5
- package/dist/resources/Table.js +114 -37
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/analytics/write.js +6 -6
- package/dist/resources/analytics/write.js.map +1 -1
- package/dist/resources/blob.d.ts +8 -1
- package/dist/resources/blob.js +31 -14
- package/dist/resources/blob.js.map +1 -1
- package/dist/resources/databases.d.ts +59 -1
- package/dist/resources/databases.js +386 -41
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/jsResource.d.ts +4 -26
- package/dist/resources/jsResource.js +5 -59
- package/dist/resources/jsResource.js.map +1 -1
- package/dist/resources/models/Models.d.ts +11 -1
- package/dist/resources/models/Models.js +10 -1
- package/dist/resources/models/Models.js.map +1 -1
- package/dist/resources/models/backendRegistry.d.ts +9 -0
- package/dist/resources/models/backendRegistry.js +10 -0
- package/dist/resources/models/backendRegistry.js.map +1 -1
- package/dist/resources/models/openaiStream.d.ts +16 -1
- package/dist/resources/models/openaiStream.js +113 -21
- package/dist/resources/models/openaiStream.js.map +1 -1
- package/dist/resources/models/v1/chatCompletions.d.ts +54 -0
- package/dist/resources/models/v1/chatCompletions.js +115 -0
- package/dist/resources/models/v1/chatCompletions.js.map +1 -0
- package/dist/resources/models/v1/embeddings.d.ts +11 -0
- package/dist/resources/models/v1/embeddings.js +71 -0
- package/dist/resources/models/v1/embeddings.js.map +1 -0
- package/dist/resources/models/v1/errors.d.ts +54 -0
- package/dist/resources/models/v1/errors.js +130 -0
- package/dist/resources/models/v1/errors.js.map +1 -0
- package/dist/resources/models/v1/index.d.ts +36 -0
- package/dist/resources/models/v1/index.js +75 -0
- package/dist/resources/models/v1/index.js.map +1 -0
- package/dist/resources/models/v1/models.d.ts +26 -0
- package/dist/resources/models/v1/models.js +44 -0
- package/dist/resources/models/v1/models.js.map +1 -0
- package/dist/resources/models/v1/translation.d.ts +133 -0
- package/dist/resources/models/v1/translation.js +298 -0
- package/dist/resources/models/v1/translation.js.map +1 -0
- package/dist/resources/roles.d.ts +1 -1
- package/dist/resources/roles.js +54 -7
- package/dist/resources/roles.js.map +1 -1
- package/dist/resources/transaction.js +0 -3
- package/dist/resources/transaction.js.map +1 -1
- package/dist/security/jsLoader.js +84 -33
- package/dist/security/jsLoader.js.map +1 -1
- package/dist/security/role.js +4 -0
- package/dist/security/role.js.map +1 -1
- package/dist/security/superUserGuard.d.ts +7 -0
- package/dist/security/superUserGuard.js +23 -0
- package/dist/security/superUserGuard.js.map +1 -0
- package/dist/security/tokenAuthentication.d.ts +0 -1
- package/dist/security/tokenAuthentication.js +6 -2
- package/dist/security/tokenAuthentication.js.map +1 -1
- package/dist/security/user.d.ts +6 -1
- package/dist/security/user.js +23 -1
- package/dist/security/user.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/http.d.ts +23 -2
- package/dist/server/http.js +99 -12
- package/dist/server/http.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/operationsServer.d.ts +0 -2
- package/dist/server/operationsServer.js.map +1 -1
- package/dist/server/serverHelpers/multipartParser.js +9 -0
- package/dist/server/serverHelpers/multipartParser.js.map +1 -1
- package/dist/server/serverHelpers/operationAuthorizationState.d.ts +2 -0
- package/dist/server/serverHelpers/operationAuthorizationState.js +13 -0
- package/dist/server/serverHelpers/operationAuthorizationState.js.map +1 -0
- package/dist/server/serverHelpers/registeredOperations.d.ts +3 -2
- package/dist/server/serverHelpers/registeredOperations.js +14 -13
- package/dist/server/serverHelpers/registeredOperations.js.map +1 -1
- package/dist/server/serverHelpers/serverHandlers.js +27 -7
- package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
- package/dist/server/serverHelpers/serverUtilities.d.ts +1 -1
- package/dist/server/serverHelpers/serverUtilities.js +21 -7
- package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
- package/dist/server/static.js +68 -21
- package/dist/server/static.js.map +1 -1
- package/dist/server/storageReclamation.d.ts +17 -0
- package/dist/server/storageReclamation.js +90 -6
- package/dist/server/storageReclamation.js.map +1 -1
- package/dist/server/threads/socketRouter.js +20 -0
- package/dist/server/threads/socketRouter.js.map +1 -1
- package/dist/server/threads/threadServer.js +12 -0
- package/dist/server/threads/threadServer.js.map +1 -1
- package/dist/sqlEngine/diff/differential.js +7 -3
- package/dist/sqlEngine/diff/differential.js.map +1 -1
- package/dist/sqlTranslator/index.js +6 -1
- package/dist/sqlTranslator/index.js.map +1 -1
- package/dist/upgrade/upgradePrompt.d.ts +2 -2
- package/dist/upgrade/upgradePrompt.js +22 -3
- package/dist/upgrade/upgradePrompt.js.map +1 -1
- package/dist/utility/OperationFunctionCaller.js +25 -6
- package/dist/utility/OperationFunctionCaller.js.map +1 -1
- package/dist/utility/errors/commonErrors.d.ts +1 -0
- package/dist/utility/errors/commonErrors.js +1 -0
- package/dist/utility/errors/commonErrors.js.map +1 -1
- package/dist/utility/hdbTerms.d.ts +13 -1
- package/dist/utility/hdbTerms.js +13 -1
- package/dist/utility/hdbTerms.js.map +1 -1
- package/dist/utility/install/installer.js +32 -0
- package/dist/utility/install/installer.js.map +1 -1
- package/dist/utility/logging/harper_logger.d.ts +47 -0
- package/dist/utility/logging/harper_logger.js +803 -38
- 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 +198 -193
- package/package.json +12 -7
- package/resources/DESIGN.md +2 -0
- package/resources/DatabaseTransaction.ts +310 -104
- package/resources/ResourceInterface.ts +8 -5
- package/resources/Resources.ts +22 -4
- package/resources/Table.ts +341 -263
- package/resources/analytics/write.ts +22 -20
- package/resources/blob.ts +31 -14
- package/resources/databases.ts +387 -46
- package/resources/jsResource.ts +5 -62
- package/resources/models/Models.ts +14 -1
- package/resources/models/backendRegistry.ts +10 -0
- package/resources/models/openaiStream.ts +131 -19
- package/resources/models/v1/chatCompletions.ts +128 -0
- package/resources/models/v1/embeddings.ts +70 -0
- package/resources/models/v1/errors.ts +141 -0
- package/resources/models/v1/index.ts +72 -0
- package/resources/models/v1/models.ts +53 -0
- package/resources/models/v1/translation.ts +362 -0
- package/resources/roles.ts +67 -7
- package/resources/transaction.ts +0 -3
- package/security/jsLoader.ts +84 -30
- package/security/role.ts +7 -0
- package/security/superUserGuard.ts +20 -0
- package/security/tokenAuthentication.ts +6 -3
- package/security/user.ts +26 -1
- package/server/DESIGN.md +61 -34
- package/server/REST.ts +25 -9
- package/server/fastifyRoutes.ts +20 -1
- package/server/http.ts +100 -13
- 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/operationsServer.ts +0 -2
- package/server/serverHelpers/multipartParser.ts +9 -0
- package/server/serverHelpers/operationAuthorizationState.ts +11 -0
- package/server/serverHelpers/registeredOperations.ts +19 -15
- package/server/serverHelpers/serverHandlers.js +28 -7
- package/server/serverHelpers/serverUtilities.ts +29 -7
- package/server/static.ts +87 -23
- package/server/storageReclamation.ts +104 -8
- package/server/threads/socketRouter.ts +20 -0
- package/server/threads/threadServer.js +11 -0
- package/sqlTranslator/index.ts +6 -1
- package/static/defaultConfig.yaml +3 -0
- package/studio/web/assets/{Chat-DK3GlWEb.js → Chat-aApwhRmz.js} +2 -2
- package/studio/web/assets/{Chat-DK3GlWEb.js.map → Chat-aApwhRmz.js.map} +1 -1
- package/studio/web/assets/{FloatingChat-fBcC1Ew_.js → FloatingChat-sC0H91n9.js} +4 -4
- package/studio/web/assets/{FloatingChat-fBcC1Ew_.js.map → FloatingChat-sC0H91n9.js.map} +1 -1
- package/studio/web/assets/{apiToken-DJo1nakA.js → apiToken-DZ8JhHOI.js} +2 -2
- package/studio/web/assets/{apiToken-DJo1nakA.js.map → apiToken-DZ8JhHOI.js.map} +1 -1
- package/studio/web/assets/{applications-BDfH8urd.js → applications-9SEVLIO5.js} +2 -2
- package/studio/web/assets/{applications-BDfH8urd.js.map → applications-9SEVLIO5.js.map} +1 -1
- package/studio/web/assets/{index-BHo3c2Gk.js → index-Dy3uDGXb.js} +6 -6
- package/studio/web/assets/index-Dy3uDGXb.js.map +1 -0
- package/studio/web/assets/{index.lazy-BTo0y6UM.js → index.lazy-CN1zq4I4.js} +4 -4
- package/studio/web/assets/{index.lazy-BTo0y6UM.js.map → index.lazy-CN1zq4I4.js.map} +1 -1
- package/studio/web/assets/{notifications-CMxvWNnz.js → notifications-BFAF07xr.js} +2 -2
- package/studio/web/assets/{notifications-CMxvWNnz.js.map → notifications-BFAF07xr.js.map} +1 -1
- package/studio/web/assets/{notifications-D3GoB26g.js → notifications-CKlYVvVN.js} +2 -2
- package/studio/web/assets/{notifications-D3GoB26g.js.map → notifications-CKlYVvVN.js.map} +1 -1
- package/studio/web/assets/{profile-Doj5FVDE.js → profile-A1zhEdFG.js} +2 -2
- package/studio/web/assets/{profile-Doj5FVDE.js.map → profile-A1zhEdFG.js.map} +1 -1
- package/studio/web/assets/{setComponentFile-yinsqJy0.js → setComponentFile-SEtBt_GV.js} +2 -2
- package/studio/web/assets/{setComponentFile-yinsqJy0.js.map → setComponentFile-SEtBt_GV.js.map} +1 -1
- package/studio/web/assets/{setup-DJwR0BHd.js → setup-BisINqdH.js} +2 -2
- package/studio/web/assets/{setup-DJwR0BHd.js.map → setup-BisINqdH.js.map} +1 -1
- package/studio/web/assets/{status-Br_AbsJs.js → status-BuQoCc7l.js} +2 -2
- package/studio/web/assets/{status-Br_AbsJs.js.map → status-BuQoCc7l.js.map} +1 -1
- package/studio/web/assets/{swagger-ui-react-02XH5sVf.js → swagger-ui-react-CAi_s1PC.js} +2 -2
- package/studio/web/assets/{swagger-ui-react-02XH5sVf.js.map → swagger-ui-react-CAi_s1PC.js.map} +1 -1
- package/studio/web/assets/{tsMode-D1DMKY7h.js → tsMode-DT74tlkM.js} +2 -2
- package/studio/web/assets/{tsMode-D1DMKY7h.js.map → tsMode-DT74tlkM.js.map} +1 -1
- package/studio/web/assets/{useEntityRestURL-DtRblRgw.js → useEntityRestURL-CGRGc1n7.js} +2 -2
- package/studio/web/assets/{useEntityRestURL-DtRblRgw.js.map → useEntityRestURL-CGRGc1n7.js.map} +1 -1
- package/studio/web/index.html +1 -1
- package/upgrade/upgradePrompt.ts +22 -3
- package/utility/OperationFunctionCaller.ts +24 -3
- package/utility/errors/commonErrors.ts +2 -0
- package/utility/hdbTerms.ts +13 -1
- package/utility/install/installer.ts +37 -0
- package/utility/logging/harper_logger.ts +786 -41
- package/utility/operation_authorization.ts +31 -3
- package/studio/web/assets/index-BHo3c2Gk.js.map +0 -1
|
@@ -4,6 +4,14 @@ import { makeTable } from './Table.ts';
|
|
|
4
4
|
import { RocksDatabase } from '@harperfast/rocksdb-js';
|
|
5
5
|
export declare function isReadOnlyMode(): boolean;
|
|
6
6
|
declare const DEFINED_TABLES: unique symbol;
|
|
7
|
+
export declare function getRocksCompression(): string | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Test-only: un-freezes the resolved codec. Production code never calls this — the freeze is the
|
|
10
|
+
* invariant (see getRocksCompression above) — but a test process runs many unrelated test files in
|
|
11
|
+
* one process, so whichever file happens to open a RocksDatabase first freezes this for everyone
|
|
12
|
+
* after it. Tests that need to exercise config changes call this to get back to the unresolved state.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resetRocksCompression(): void;
|
|
7
15
|
export declare const NON_REPLICATING_SYSTEM_TABLES: string[];
|
|
8
16
|
export type Table = ReturnType<typeof makeTable> & {
|
|
9
17
|
indexingOperation?: any;
|
|
@@ -63,6 +71,23 @@ export type DatabaseWatcherEventMap = {
|
|
|
63
71
|
export declare const databaseEventsEmitter: EventEmitter<DatabaseWatcherEventMap>;
|
|
64
72
|
export declare const tables: Tables;
|
|
65
73
|
export declare const databases: Databases;
|
|
74
|
+
/**
|
|
75
|
+
* Map a persisted (LMDB-era) compression value to what rocksdb-js accepts. Table metadata carries
|
|
76
|
+
* values where a defined falsy value (false, '') means compression was explicitly disabled, and
|
|
77
|
+
* `true` / `{ threshold, ... }` mean enabled with defaults — `storage.compression` defaults to
|
|
78
|
+
* `true` (defaultConfig.yaml), so essentially every pre-existing table asked for compression.
|
|
79
|
+
*
|
|
80
|
+
* "Enabled" resolves to an explicit codec rather than to unset. Unset is not equivalent: RocksDB
|
|
81
|
+
* persists the codec per column family and a reopen that requests nothing inherits what the family
|
|
82
|
+
* already has, applying the build default only when the family does not yet exist. Leaving these
|
|
83
|
+
* unset therefore silently ignores the operator's request on every database created before the
|
|
84
|
+
* native build carried codecs — it keeps writing uncompressed forever, while a brand-new database
|
|
85
|
+
* gets lz4. Naming the codec makes the setting mean the same thing in both cases.
|
|
86
|
+
*
|
|
87
|
+
* This governs newly written files; existing SSTs keep their codec until write traffic rewrites
|
|
88
|
+
* them (`db.compact()` will not — see getRocksCompression above).
|
|
89
|
+
*/
|
|
90
|
+
export declare function toRocksCompression(compression: unknown): unknown;
|
|
66
91
|
/**
|
|
67
92
|
* This gets the set of tables from the default database ("data").
|
|
68
93
|
*/
|
|
@@ -107,6 +132,17 @@ interface TableDefinition {
|
|
|
107
132
|
hidden?: boolean;
|
|
108
133
|
cacheControl?: string | null;
|
|
109
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Resolve the directory that holds (or would hold) a database's storage, from the databases
|
|
137
|
+
* config, storage path config/env, or the hdb root — without opening anything. This is the
|
|
138
|
+
* parent directory selection used by `database()`; a RocksDB database lives at
|
|
139
|
+
* `join(resolveDatabaseStorageRoot(...), databaseName)`.
|
|
140
|
+
*/
|
|
141
|
+
export declare function resolveDatabaseStorageRoot(databaseName: string, tableName?: string): string;
|
|
142
|
+
/**
|
|
143
|
+
* Resolve the directory path of a RocksDB database (whether or not it exists or is loaded).
|
|
144
|
+
*/
|
|
145
|
+
export declare function resolveDatabasePath(databaseName: string): string;
|
|
110
146
|
/**
|
|
111
147
|
* Get root store for a database
|
|
112
148
|
* @param options
|
|
@@ -121,6 +157,28 @@ export declare function database({ database: databaseName, table: tableName }: {
|
|
|
121
157
|
* @param databaseName
|
|
122
158
|
*/
|
|
123
159
|
export declare function dropDatabase(databaseName: any): Promise<void>;
|
|
160
|
+
/**
|
|
161
|
+
* Close a RocksDB database's store handles on the current thread and unregister it, without
|
|
162
|
+
* touching its files. Used by the restore_backup flow: every thread must release its handles so
|
|
163
|
+
* `backups.restore()` can purge and rewrite the (fully closed) database directory. A subsequent
|
|
164
|
+
* `resetDatabases()`/`getDatabases()` rescan reloads it (or skips it while a restore is in
|
|
165
|
+
* progress, per the restore marker checks in the scan).
|
|
166
|
+
*/
|
|
167
|
+
export declare function closeDatabase(databaseName: string): boolean;
|
|
168
|
+
/**
|
|
169
|
+
* Close every RocksDB (user) database this thread has open, releasing its native handles.
|
|
170
|
+
*
|
|
171
|
+
* rocksdb-js's registry is process-global across worker threads, and a thread that exits WITHOUT
|
|
172
|
+
* closing leaks its handles (the process-global refCount never drops), while the only alternative,
|
|
173
|
+
* `shutdown()`, tears down rocksdb for the entire process. So a worker thread that opens databases
|
|
174
|
+
* and then exits — notably a job worker (jobProcess), which opens the whole database graph via
|
|
175
|
+
* `getDatabases()` and exits when the job finishes — must close its handles explicitly, or those
|
|
176
|
+
* handles linger process-wide (and, e.g., block an online `restore_backup` from confirming the
|
|
177
|
+
* database is closed). The `system` database is intentionally left open: it is non-enumerable here
|
|
178
|
+
* (skipped by the loop), is never restored online, and the exiting worker may still touch the job
|
|
179
|
+
* table during teardown. Best-effort: closing failures are swallowed inside `closeDatabase`.
|
|
180
|
+
*/
|
|
181
|
+
export declare function closeLoadedDatabases(): void;
|
|
124
182
|
/**
|
|
125
183
|
* This can be called to ensure that the specified table exists and if it does not exist, it should be created.
|
|
126
184
|
* @param tableName
|
|
@@ -162,7 +220,7 @@ export declare function onRemovedTable(listener: (tableName: string, databaseNam
|
|
|
162
220
|
export declare function onRemovedDB(listener: (databaseName: string) => void): {
|
|
163
221
|
remove(): void;
|
|
164
222
|
};
|
|
165
|
-
export declare function getDefaultCompression(): {
|
|
223
|
+
export declare function getDefaultCompression(): false | {
|
|
166
224
|
startingOffset: number;
|
|
167
225
|
};
|
|
168
226
|
/**
|
|
@@ -38,12 +38,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.databases = exports.tables = exports.databaseEventsEmitter = exports.NON_REPLICATING_SYSTEM_TABLES = void 0;
|
|
40
40
|
exports.isReadOnlyMode = isReadOnlyMode;
|
|
41
|
+
exports.getRocksCompression = getRocksCompression;
|
|
42
|
+
exports.resetRocksCompression = resetRocksCompression;
|
|
43
|
+
exports.toRocksCompression = toRocksCompression;
|
|
41
44
|
exports.getTables = getTables;
|
|
42
45
|
exports.getDatabases = getDatabases;
|
|
43
46
|
exports.readMetaDb = readMetaDb;
|
|
44
47
|
exports.resetDatabases = resetDatabases;
|
|
48
|
+
exports.resolveDatabaseStorageRoot = resolveDatabaseStorageRoot;
|
|
49
|
+
exports.resolveDatabasePath = resolveDatabasePath;
|
|
45
50
|
exports.database = database;
|
|
46
51
|
exports.dropDatabase = dropDatabase;
|
|
52
|
+
exports.closeDatabase = closeDatabase;
|
|
53
|
+
exports.closeLoadedDatabases = closeLoadedDatabases;
|
|
47
54
|
exports.table = table;
|
|
48
55
|
exports.canonicalizeIndexOptions = canonicalizeIndexOptions;
|
|
49
56
|
exports.dropTableMeta = dropTableMeta;
|
|
@@ -86,6 +93,7 @@ const RocksIndexStore_ts_1 = require("./RocksIndexStore.js");
|
|
|
86
93
|
const when_ts_1 = require("../utility/when.js");
|
|
87
94
|
const rocksMemoryConfig_ts_1 = require("../utility/rocksMemoryConfig.js");
|
|
88
95
|
const processManagement_js_1 = require("../utility/processManagement/processManagement.js");
|
|
96
|
+
const restoreMarker_ts_1 = require("../dataLayer/restoreMarker.js");
|
|
89
97
|
/**
|
|
90
98
|
* Check if Harper is running in read-only mode.
|
|
91
99
|
* Read-only mode can be enabled via:
|
|
@@ -133,6 +141,76 @@ const DEFAULT_DATABASE_NAME = 'data';
|
|
|
133
141
|
const DEFINED_TABLES = Symbol('defined-tables');
|
|
134
142
|
const DEFAULT_COMPRESSION_THRESHOLD = ((0, environmentManager_ts_1.get)(hdbTerms_ts_1.CONFIG_PARAMS.STORAGE_PAGESIZE) || 4096) - 60; // larger than this requires multiple pages
|
|
135
143
|
(0, environmentManager_ts_1.initSync)();
|
|
144
|
+
/**
|
|
145
|
+
* The RocksDB block/blob codec for every column family this process opens (`storage.rocks.compression`),
|
|
146
|
+
* or `undefined` to leave rocksdb-js on its own default (lz4 wherever the native build has it).
|
|
147
|
+
*
|
|
148
|
+
* Resolved on the first open and then frozen, deliberately. RocksDB fixes a column family's codec
|
|
149
|
+
* for as long as it is open and rejects a reopen that disagrees, and Harper's worker threads share
|
|
150
|
+
* one process-wide column-family registry — so every open, in every thread, has to resolve the same
|
|
151
|
+
* value. Re-reading config per open does not guarantee that: on a fresh install the system families
|
|
152
|
+
* are created by `mountHdb()` before the config file exists, so the main thread would resolve
|
|
153
|
+
* nothing and the workers would resolve the configured codec, after which `__dbis__` cannot be
|
|
154
|
+
* reopened and Harper fails with "The system database failed to load". The installer stages this
|
|
155
|
+
* value before `mountHdb()` (see utility/install/installer.ts) so that first open already sees it.
|
|
156
|
+
*
|
|
157
|
+
* Unset is NOT "use the build default" for a family that already exists — see toRocksCompression.
|
|
158
|
+
*/
|
|
159
|
+
let resolvedRocksCompression;
|
|
160
|
+
let rocksCompressionResolved = false;
|
|
161
|
+
function getRocksCompression() {
|
|
162
|
+
if (!rocksCompressionResolved) {
|
|
163
|
+
resolvedRocksCompression = readDatabaseCodec();
|
|
164
|
+
rocksCompressionResolved = true;
|
|
165
|
+
}
|
|
166
|
+
return resolvedRocksCompression;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Test-only: un-freezes the resolved codec. Production code never calls this — the freeze is the
|
|
170
|
+
* invariant (see getRocksCompression above) — but a test process runs many unrelated test files in
|
|
171
|
+
* one process, so whichever file happens to open a RocksDatabase first freezes this for everyone
|
|
172
|
+
* after it. Tests that need to exercise config changes call this to get back to the unresolved state.
|
|
173
|
+
*/
|
|
174
|
+
function resetRocksCompression() {
|
|
175
|
+
resolvedRocksCompression = undefined;
|
|
176
|
+
rocksCompressionResolved = false;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* The codec every column family in this process opens under.
|
|
180
|
+
*
|
|
181
|
+
* Compression is a deployment setting, not a per-table one. RocksDB opens all of a database's
|
|
182
|
+
* column families in one call, so the codec has to be decided before the first open — which is
|
|
183
|
+
* before Harper has read any table's metadata (that catalog is itself one of the families being
|
|
184
|
+
* opened). Resolving one codec from configuration and applying it to every family is what makes
|
|
185
|
+
* that possible; it is passed with `compressionForAllColumnFamilies` so families this process
|
|
186
|
+
* never names individually adopt it too, which is what lets a database created before the codec
|
|
187
|
+
* existed start compressing.
|
|
188
|
+
*
|
|
189
|
+
* `storage.rocks.compression` names a codec outright. Otherwise `storage.compression` (default
|
|
190
|
+
* true) decides enabled-or-not and the build default fills in the algorithm. Per-table metadata
|
|
191
|
+
* still records the LMDB-era boolean, but no longer selects: a table persisted as disabled inside
|
|
192
|
+
* a deployment that enables compression would need its own codec, and it cannot have one.
|
|
193
|
+
*/
|
|
194
|
+
function readDatabaseCodec() {
|
|
195
|
+
const explicit = readRocksCompressionConfig();
|
|
196
|
+
if (explicit)
|
|
197
|
+
return explicit;
|
|
198
|
+
return toRocksCompression(getDefaultCompression());
|
|
199
|
+
}
|
|
200
|
+
function readRocksCompressionConfig() {
|
|
201
|
+
const configured = (0, environmentManager_ts_1.get)(hdbTerms_ts_1.CONFIG_PARAMS.STORAGE_ROCKS_COMPRESSION);
|
|
202
|
+
if (configured === undefined || configured === null || configured === '')
|
|
203
|
+
return undefined;
|
|
204
|
+
const requested = String(configured).trim().toLowerCase();
|
|
205
|
+
if (!requested)
|
|
206
|
+
return undefined;
|
|
207
|
+
// Rejected here rather than at the open: an unsupported name throws inside RocksDatabase.open,
|
|
208
|
+
// which surfaces as the system database failing to load partway through startup.
|
|
209
|
+
if (!rocksdb_js_1.supportedCompression.includes(requested)) {
|
|
210
|
+
throw new Error(`storage.rocks.compression="${requested}" is not available in this build of @harperfast/rocksdb-js. Supported: ${rocksdb_js_1.supportedCompression.join(', ')}`);
|
|
211
|
+
}
|
|
212
|
+
return requested;
|
|
213
|
+
}
|
|
136
214
|
// I don't know if this is the best place for this, but somewhere we need to specify which tables
|
|
137
215
|
// replicate by default:
|
|
138
216
|
exports.NON_REPLICATING_SYSTEM_TABLES = [
|
|
@@ -148,8 +226,53 @@ exports.NON_REPLICATING_SYSTEM_TABLES = [
|
|
|
148
226
|
exports.databaseEventsEmitter = new node_events_1.EventEmitter();
|
|
149
227
|
exports.tables = Object.create(null);
|
|
150
228
|
exports.databases = Object.create(null);
|
|
229
|
+
/**
|
|
230
|
+
* Codec used to honor an "enabled, unspecified" compression setting, or `undefined` where the
|
|
231
|
+
* native build cannot provide it (in which case the request degrades to the build default rather
|
|
232
|
+
* than throwing).
|
|
233
|
+
*/
|
|
234
|
+
const DEFAULT_ENABLED_CODEC = rocksdb_js_1.supportedCompression.includes('lz4') ? 'lz4' : undefined;
|
|
235
|
+
/**
|
|
236
|
+
* Map a persisted (LMDB-era) compression value to what rocksdb-js accepts. Table metadata carries
|
|
237
|
+
* values where a defined falsy value (false, '') means compression was explicitly disabled, and
|
|
238
|
+
* `true` / `{ threshold, ... }` mean enabled with defaults — `storage.compression` defaults to
|
|
239
|
+
* `true` (defaultConfig.yaml), so essentially every pre-existing table asked for compression.
|
|
240
|
+
*
|
|
241
|
+
* "Enabled" resolves to an explicit codec rather than to unset. Unset is not equivalent: RocksDB
|
|
242
|
+
* persists the codec per column family and a reopen that requests nothing inherits what the family
|
|
243
|
+
* already has, applying the build default only when the family does not yet exist. Leaving these
|
|
244
|
+
* unset therefore silently ignores the operator's request on every database created before the
|
|
245
|
+
* native build carried codecs — it keeps writing uncompressed forever, while a brand-new database
|
|
246
|
+
* gets lz4. Naming the codec makes the setting mean the same thing in both cases.
|
|
247
|
+
*
|
|
248
|
+
* This governs newly written files; existing SSTs keep their codec until write traffic rewrites
|
|
249
|
+
* them (`db.compact()` will not — see getRocksCompression above).
|
|
250
|
+
*/
|
|
251
|
+
function toRocksCompression(compression) {
|
|
252
|
+
if (compression === undefined)
|
|
253
|
+
return undefined;
|
|
254
|
+
if (!compression)
|
|
255
|
+
return 'none';
|
|
256
|
+
// An object carrying an explicit `algorithm` is already a rocksdb-js request; anything else
|
|
257
|
+
// (`true`, or an LMDB descriptor like { startingOffset, threshold }) is "enabled, unspecified".
|
|
258
|
+
if (compression === true || (typeof compression === 'object' && !compression.algorithm))
|
|
259
|
+
return DEFAULT_ENABLED_CODEC;
|
|
260
|
+
return compression;
|
|
261
|
+
}
|
|
151
262
|
function openRocksDatabase(path, options) {
|
|
152
263
|
options.disableWAL ??= true;
|
|
264
|
+
const legacyOptions = options;
|
|
265
|
+
// A configured codec applies to every column family, overriding whatever per-table metadata
|
|
266
|
+
// carries — that metadata records the LMDB-era boolean, so without this there is no way to
|
|
267
|
+
// select a RocksDB codec for a deployment.
|
|
268
|
+
// One codec for every column family, and applied to every family this open touches — not just
|
|
269
|
+
// the one being named. RocksDB opens them all at once and a family's codec cannot change while
|
|
270
|
+
// it is open, so a family this process never names individually would otherwise stay on
|
|
271
|
+
// whatever it was created with, forever.
|
|
272
|
+
const databaseCodec = getRocksCompression();
|
|
273
|
+
legacyOptions.compression = databaseCodec;
|
|
274
|
+
if (databaseCodec)
|
|
275
|
+
options.compressionForAllColumnFamilies = true;
|
|
153
276
|
// Apply read-only mode if enabled
|
|
154
277
|
if (isReadOnlyMode()) {
|
|
155
278
|
options.readOnly = true;
|
|
@@ -284,12 +407,21 @@ function getDatabases() {
|
|
|
284
407
|
if (databasePath && (0, node_fs_1.existsSync)(databasePath)) {
|
|
285
408
|
// First load all the databases from our main database folder
|
|
286
409
|
// TODO: Load any databases defined with explicit storage paths from the config
|
|
287
|
-
|
|
410
|
+
const entries = (0, node_fs_1.readdirSync)(databasePath, { withFileTypes: true });
|
|
411
|
+
const blockedByRestore = databasesBlockedByRestore(databasePath);
|
|
412
|
+
for (const databaseEntry of entries) {
|
|
288
413
|
// in-progress migration staging dirs are not databases until atomically renamed into place
|
|
289
414
|
if (databaseEntry.name.endsWith(hdbTerms_ts_1.MIGRATING_DIR_SUFFIX))
|
|
290
415
|
continue;
|
|
416
|
+
// the restore-metadata directory is reserved: never load it as a database, even if a
|
|
417
|
+
// (out-of-band) RocksDB directory happens to occupy that reserved name — the API can't
|
|
418
|
+
// create it (schemaRegex forbids the backtick), but the scan opens any CURRENT+MANIFEST dir
|
|
419
|
+
if (databaseEntry.name === restoreMarker_ts_1.RESTORE_META_DIR)
|
|
420
|
+
continue;
|
|
291
421
|
const dbName = (0, path_1.basename)(databaseEntry.name, '.mdb');
|
|
292
422
|
const dbPath = (0, path_1.join)(databasePath, databaseEntry.name);
|
|
423
|
+
if (blockedByRestore.has(dbName))
|
|
424
|
+
continue;
|
|
293
425
|
if (databaseEntry.isFile() &&
|
|
294
426
|
(0, path_1.extname)(databaseEntry.name).toLowerCase() === '.mdb' &&
|
|
295
427
|
!schemaConfigs[dbName]?.path) {
|
|
@@ -334,8 +466,14 @@ function getDatabases() {
|
|
|
334
466
|
const schemaConfig = schemaConfigs[dbName];
|
|
335
467
|
const databasePath = schemaConfig.path;
|
|
336
468
|
if ((0, node_fs_1.existsSync)(databasePath)) {
|
|
337
|
-
|
|
469
|
+
const entries = (0, node_fs_1.readdirSync)(databasePath, { withFileTypes: true });
|
|
470
|
+
const blockedByRestore = databasesBlockedByRestore(databasePath);
|
|
471
|
+
for (const databaseEntry of entries) {
|
|
338
472
|
if (databaseEntry.name.endsWith(hdbTerms_ts_1.MIGRATING_DIR_SUFFIX))
|
|
473
|
+
continue; // migration staging dir
|
|
474
|
+
if (databaseEntry.name === restoreMarker_ts_1.RESTORE_META_DIR)
|
|
475
|
+
continue; // reserved restore-metadata dir
|
|
476
|
+
if (blockedByRestore.has((0, path_1.basename)(databaseEntry.name, '.mdb')))
|
|
339
477
|
continue;
|
|
340
478
|
if (databaseEntry.isFile() && (0, path_1.extname)(databaseEntry.name).toLowerCase() === '.mdb') {
|
|
341
479
|
readMetaDb((0, path_1.join)(databasePath, databaseEntry.name), (0, path_1.basename)(databaseEntry.name, '.mdb'), dbName);
|
|
@@ -413,6 +551,27 @@ function getDatabases() {
|
|
|
413
551
|
}
|
|
414
552
|
return exports.databases;
|
|
415
553
|
}
|
|
554
|
+
/**
|
|
555
|
+
* Scan a databases directory's entries for restore lock/marker files and return the names of
|
|
556
|
+
* databases that must not be loaded: a held restore lock means a restore is in progress in some
|
|
557
|
+
* process; an unheld lock with a surviving `.restoring` marker means a restore was interrupted
|
|
558
|
+
* mid-purge (the directory may be partial garbage) and must be rerun. The files live *next to*
|
|
559
|
+
* the database directory, so this also covers a database whose directory is missing or empty.
|
|
560
|
+
*/
|
|
561
|
+
function databasesBlockedByRestore(databasePath) {
|
|
562
|
+
const blocked = new Set();
|
|
563
|
+
for (const [dbName, state] of (0, restoreMarker_ts_1.scanBlockedRestores)(databasePath)) {
|
|
564
|
+
if (state === 'in-progress') {
|
|
565
|
+
logger.warn(`A restore of database '${dbName}' is in progress; not loading it`);
|
|
566
|
+
blocked.add(dbName);
|
|
567
|
+
}
|
|
568
|
+
else if (state === 'incomplete') {
|
|
569
|
+
logger.error(`Incomplete restore of database '${dbName}' detected (a restore started but did not finish); not loading it — rerun the restore to recover`);
|
|
570
|
+
blocked.add(dbName);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return blocked;
|
|
574
|
+
}
|
|
416
575
|
/**
|
|
417
576
|
* This is responsible for reading the internal dbi of a single database file to get a list of all the tables and
|
|
418
577
|
* their indexed or registered attributes
|
|
@@ -823,19 +982,12 @@ function setTable(tables, tableName, Table) {
|
|
|
823
982
|
return Table;
|
|
824
983
|
}
|
|
825
984
|
/**
|
|
826
|
-
*
|
|
827
|
-
*
|
|
828
|
-
*
|
|
985
|
+
* Resolve the directory that holds (or would hold) a database's storage, from the databases
|
|
986
|
+
* config, storage path config/env, or the hdb root — without opening anything. This is the
|
|
987
|
+
* parent directory selection used by `database()`; a RocksDB database lives at
|
|
988
|
+
* `join(resolveDatabaseStorageRoot(...), databaseName)`.
|
|
829
989
|
*/
|
|
830
|
-
function
|
|
831
|
-
if (!databaseName)
|
|
832
|
-
databaseName = DEFAULT_DATABASE_NAME;
|
|
833
|
-
getDatabases();
|
|
834
|
-
ensureDB(databaseName);
|
|
835
|
-
const definedDatabase = definedDatabases.get(databaseName);
|
|
836
|
-
if (definedDatabase?.rootStore) {
|
|
837
|
-
return definedDatabase.rootStore;
|
|
838
|
-
}
|
|
990
|
+
function resolveDatabaseStorageRoot(databaseName, tableName) {
|
|
839
991
|
const databaseConfig = (0, environmentManager_ts_1.get)(hdbTerms_ts_1.CONFIG_PARAMS.DATABASES) || {};
|
|
840
992
|
if (process.env.SCHEMAS_DATA_PATH) {
|
|
841
993
|
databaseConfig.data = { path: process.env.SCHEMAS_DATA_PATH };
|
|
@@ -854,12 +1006,44 @@ function database({ database: databaseName, table: tableName }) {
|
|
|
854
1006
|
if (!databasePath) {
|
|
855
1007
|
throw new Error(`Unable to determine database storage path. Ensure STORAGE_PATH, HDB_ROOT, or a valid config path is set.`);
|
|
856
1008
|
}
|
|
1009
|
+
return databasePath;
|
|
1010
|
+
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Resolve the directory path of a RocksDB database (whether or not it exists or is loaded).
|
|
1013
|
+
*/
|
|
1014
|
+
function resolveDatabasePath(databaseName) {
|
|
1015
|
+
return (0, path_1.join)(resolveDatabaseStorageRoot(databaseName), databaseName);
|
|
1016
|
+
}
|
|
1017
|
+
/**
|
|
1018
|
+
* Get root store for a database
|
|
1019
|
+
* @param options
|
|
1020
|
+
* @returns
|
|
1021
|
+
*/
|
|
1022
|
+
function database({ database: databaseName, table: tableName }) {
|
|
1023
|
+
if (!databaseName)
|
|
1024
|
+
databaseName = DEFAULT_DATABASE_NAME;
|
|
1025
|
+
getDatabases();
|
|
1026
|
+
ensureDB(databaseName);
|
|
1027
|
+
const definedDatabase = definedDatabases.get(databaseName);
|
|
1028
|
+
if (definedDatabase?.rootStore) {
|
|
1029
|
+
return definedDatabase.rootStore;
|
|
1030
|
+
}
|
|
1031
|
+
const databaseConfig = (0, environmentManager_ts_1.get)(hdbTerms_ts_1.CONFIG_PARAMS.DATABASES) || {};
|
|
1032
|
+
if (process.env.SCHEMAS_DATA_PATH) {
|
|
1033
|
+
databaseConfig.data = { path: process.env.SCHEMAS_DATA_PATH };
|
|
1034
|
+
}
|
|
1035
|
+
const tablePath = tableName && databaseConfig[databaseName]?.tables?.[tableName]?.path;
|
|
1036
|
+
const databasePath = resolveDatabaseStorageRoot(databaseName, tableName);
|
|
857
1037
|
let rootStore;
|
|
858
1038
|
const useRocksdb = (process.env.HARPER_STORAGE_ENGINE || (0, environmentManager_ts_1.get)(hdbTerms_ts_1.CONFIG_PARAMS.STORAGE_ENGINE)) !== 'lmdb';
|
|
859
1039
|
if (useRocksdb) {
|
|
860
1040
|
const path = (0, path_1.join)(databasePath, tablePath ? tableName : databaseName);
|
|
861
1041
|
rootStore = rocksdbDatabaseEnvs.get(path);
|
|
862
1042
|
if (!rootStore || rootStore.status === 'closed') {
|
|
1043
|
+
// this on-demand open (create_table/create_database and friends) must not resurrect a
|
|
1044
|
+
// database that a restore is rewriting (or left half-purged) — the scan-time restore
|
|
1045
|
+
// checks don't cover this path
|
|
1046
|
+
throwIfBlockedByRestore(path, databaseName);
|
|
863
1047
|
rootStore = openRocksDatabase(path, {
|
|
864
1048
|
disableWAL: false,
|
|
865
1049
|
enableStats: true,
|
|
@@ -884,6 +1068,49 @@ function database({ database: databaseName, table: tableName }) {
|
|
|
884
1068
|
definedDatabase.rootStore = rootStore;
|
|
885
1069
|
return rootStore;
|
|
886
1070
|
}
|
|
1071
|
+
function throwIfBlockedByRestore(dbPath, databaseName) {
|
|
1072
|
+
const restoreState = (0, restoreMarker_ts_1.checkRestoreState)(dbPath);
|
|
1073
|
+
if (restoreState !== 'clear') {
|
|
1074
|
+
const error = new Error(restoreState === 'in-progress'
|
|
1075
|
+
? `Database '${databaseName}' is being restored; retry when the restore completes`
|
|
1076
|
+
: `Database '${databaseName}' has an incomplete restore; rerun restore_backup to recover it`);
|
|
1077
|
+
error.statusCode = 409;
|
|
1078
|
+
throw error;
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
/**
|
|
1082
|
+
* Take the per-database restore lock for a drop, refusing (409) if a restore holds it (in-progress)
|
|
1083
|
+
* or a crashed restore left a marker (incomplete). Pushes the acquired lock onto `held` so the
|
|
1084
|
+
* caller releases it after the drop. On refusal, releases anything already held and throws.
|
|
1085
|
+
*
|
|
1086
|
+
* The lock is not reentrant within a process, so a path already in `held` must be skipped — every
|
|
1087
|
+
* table in a RocksDB database shares one root store (and one lock path), and re-acquiring it in the
|
|
1088
|
+
* same drop would spuriously 409 on the second table.
|
|
1089
|
+
*/
|
|
1090
|
+
function lockDatabaseForDrop(dbPath, databaseName, held) {
|
|
1091
|
+
if (held.some((h) => h.dbPath === dbPath))
|
|
1092
|
+
return;
|
|
1093
|
+
let lock;
|
|
1094
|
+
try {
|
|
1095
|
+
lock = (0, restoreMarker_ts_1.acquireRestoreLock)(dbPath);
|
|
1096
|
+
}
|
|
1097
|
+
catch (error) {
|
|
1098
|
+
for (const h of held)
|
|
1099
|
+
(0, restoreMarker_ts_1.releaseRestoreLock)(h);
|
|
1100
|
+
throw error; // 409: a restore is in progress and holds the lock
|
|
1101
|
+
}
|
|
1102
|
+
// We now hold the lock, so no restore is active. A surviving marker is therefore debris from a
|
|
1103
|
+
// crashed restore (incomplete) — refuse rather than delete a directory that still needs recovery.
|
|
1104
|
+
if ((0, restoreMarker_ts_1.restoreMarkerPresent)(dbPath)) {
|
|
1105
|
+
(0, restoreMarker_ts_1.releaseRestoreLock)(lock);
|
|
1106
|
+
for (const h of held)
|
|
1107
|
+
(0, restoreMarker_ts_1.releaseRestoreLock)(h);
|
|
1108
|
+
const error = new Error(`Database '${databaseName}' has an incomplete restore; rerun restore_backup to recover it`);
|
|
1109
|
+
error.statusCode = 409;
|
|
1110
|
+
throw error;
|
|
1111
|
+
}
|
|
1112
|
+
held.push(lock);
|
|
1113
|
+
}
|
|
887
1114
|
/**
|
|
888
1115
|
* Delete the database
|
|
889
1116
|
* @param databaseName
|
|
@@ -893,15 +1120,112 @@ async function dropDatabase(databaseName) {
|
|
|
893
1120
|
throw new Error('Database does not exist');
|
|
894
1121
|
const dbTables = exports.databases[databaseName];
|
|
895
1122
|
let rootStore;
|
|
1123
|
+
// Hold the per-database restore lock across the entire drop so its file deletion can never
|
|
1124
|
+
// interleave with a restore's purge-and-copy on the same directory — a destroy landing after a
|
|
1125
|
+
// restore's copy would gut a "successful" restore, and vice versa. Restore takes the same lock
|
|
1126
|
+
// (before writing its marker), so both operations serialize on this one primitive rather than on
|
|
1127
|
+
// a check-then-act marker probe. Released in the finally below.
|
|
1128
|
+
const restoreLocks = [];
|
|
1129
|
+
try {
|
|
1130
|
+
for (const tableName in dbTables) {
|
|
1131
|
+
const table = dbTables[tableName];
|
|
1132
|
+
rootStore = table.primaryStore.rootStore;
|
|
1133
|
+
if (rootStore instanceof rocksdb_js_1.RocksDatabase)
|
|
1134
|
+
lockDatabaseForDrop(rootStore.path, databaseName, restoreLocks);
|
|
1135
|
+
lmdbDatabaseEnvs.delete(rootStore.path);
|
|
1136
|
+
rocksdbDatabaseEnvs.delete(rootStore.path);
|
|
1137
|
+
}
|
|
1138
|
+
for (const tableName in dbTables) {
|
|
1139
|
+
exports.databaseEventsEmitter.emit('dropTable', tableName, databaseName);
|
|
1140
|
+
}
|
|
1141
|
+
if (databaseName === 'data') {
|
|
1142
|
+
for (const tableName in exports.tables) {
|
|
1143
|
+
delete exports.tables[tableName];
|
|
1144
|
+
}
|
|
1145
|
+
delete exports.tables[DEFINED_TABLES];
|
|
1146
|
+
}
|
|
1147
|
+
delete exports.databases[databaseName];
|
|
1148
|
+
exports.databaseEventsEmitter.emit('dropDatabase', databaseName);
|
|
1149
|
+
if (rootStore) {
|
|
1150
|
+
if (rootStore.status === 'open') {
|
|
1151
|
+
if (rootStore instanceof rocksdb_js_1.RocksDatabase) {
|
|
1152
|
+
rootStore.close();
|
|
1153
|
+
rootStore.destroy();
|
|
1154
|
+
}
|
|
1155
|
+
else {
|
|
1156
|
+
await rootStore.close();
|
|
1157
|
+
await (0, promises_1.unlink)(rootStore.path);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
else {
|
|
1162
|
+
rootStore = database({ database: databaseName, table: null });
|
|
1163
|
+
// a tableless database resolves its root store here rather than in the loop above, so take
|
|
1164
|
+
// the drop lock now (still before any destructive step)
|
|
1165
|
+
if (rootStore instanceof rocksdb_js_1.RocksDatabase)
|
|
1166
|
+
lockDatabaseForDrop(rootStore.path, databaseName, restoreLocks);
|
|
1167
|
+
if (rootStore instanceof rocksdb_js_1.RocksDatabase) {
|
|
1168
|
+
rootStore.close();
|
|
1169
|
+
rootStore.destroy();
|
|
1170
|
+
}
|
|
1171
|
+
else if (rootStore.status === 'open') {
|
|
1172
|
+
await rootStore.close();
|
|
1173
|
+
await (0, promises_1.unlink)(rootStore.path);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
await (0, blob_ts_1.deleteRootBlobPathsForDB)(rootStore);
|
|
1177
|
+
}
|
|
1178
|
+
finally {
|
|
1179
|
+
for (const lock of restoreLocks)
|
|
1180
|
+
(0, restoreMarker_ts_1.releaseRestoreLock)(lock);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* Close a RocksDB database's store handles on the current thread and unregister it, without
|
|
1185
|
+
* touching its files. Used by the restore_backup flow: every thread must release its handles so
|
|
1186
|
+
* `backups.restore()` can purge and rewrite the (fully closed) database directory. A subsequent
|
|
1187
|
+
* `resetDatabases()`/`getDatabases()` rescan reloads it (or skips it while a restore is in
|
|
1188
|
+
* progress, per the restore marker checks in the scan).
|
|
1189
|
+
*/
|
|
1190
|
+
function closeDatabase(databaseName) {
|
|
1191
|
+
const dbTables = exports.databases[databaseName];
|
|
1192
|
+
if (!dbTables)
|
|
1193
|
+
return false;
|
|
1194
|
+
const rootStores = new Set();
|
|
1195
|
+
const closeStore = (store, description) => {
|
|
1196
|
+
try {
|
|
1197
|
+
store?.close?.();
|
|
1198
|
+
}
|
|
1199
|
+
catch (error) {
|
|
1200
|
+
logger.warn(`Error closing ${description} while closing database ${databaseName}:`, error);
|
|
1201
|
+
}
|
|
1202
|
+
};
|
|
896
1203
|
for (const tableName in dbTables) {
|
|
897
1204
|
const table = dbTables[tableName];
|
|
898
|
-
|
|
1205
|
+
if (!table?.primaryStore)
|
|
1206
|
+
continue;
|
|
1207
|
+
if (table.primaryStore.rootStore)
|
|
1208
|
+
rootStores.add(table.primaryStore.rootStore);
|
|
1209
|
+
for (const indexName in table.indices || {}) {
|
|
1210
|
+
closeStore(table.indices[indexName], `index ${tableName}.${indexName}`);
|
|
1211
|
+
}
|
|
1212
|
+
closeStore(table.primaryStore, `table ${tableName}`);
|
|
1213
|
+
}
|
|
1214
|
+
// a database with no tables (an empty schema, or one whose tables were all dropped) still holds
|
|
1215
|
+
// an open root store, tracked only on the defined-database entry rather than any table — include
|
|
1216
|
+
// it so its handles are released too (the Set dedupes it against the per-table root stores above)
|
|
1217
|
+
const definedRoot = definedDatabases?.get(databaseName)?.rootStore;
|
|
1218
|
+
if (definedRoot)
|
|
1219
|
+
rootStores.add(definedRoot);
|
|
1220
|
+
for (const rootStore of rootStores) {
|
|
1221
|
+
closeStore(rootStore.dbisDb, 'attributes store');
|
|
1222
|
+
closeStore(rootStore, 'root store');
|
|
899
1223
|
lmdbDatabaseEnvs.delete(rootStore.path);
|
|
900
1224
|
rocksdbDatabaseEnvs.delete(rootStore.path);
|
|
901
1225
|
}
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
1226
|
+
const definedDatabase = definedDatabases?.get(databaseName);
|
|
1227
|
+
if (definedDatabase)
|
|
1228
|
+
definedDatabase.rootStore = undefined;
|
|
905
1229
|
if (databaseName === 'data') {
|
|
906
1230
|
for (const tableName in exports.tables) {
|
|
907
1231
|
delete exports.tables[tableName];
|
|
@@ -909,31 +1233,42 @@ async function dropDatabase(databaseName) {
|
|
|
909
1233
|
delete exports.tables[DEFINED_TABLES];
|
|
910
1234
|
}
|
|
911
1235
|
delete exports.databases[databaseName];
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1236
|
+
return true;
|
|
1237
|
+
}
|
|
1238
|
+
/**
|
|
1239
|
+
* Close every RocksDB (user) database this thread has open, releasing its native handles.
|
|
1240
|
+
*
|
|
1241
|
+
* rocksdb-js's registry is process-global across worker threads, and a thread that exits WITHOUT
|
|
1242
|
+
* closing leaks its handles (the process-global refCount never drops), while the only alternative,
|
|
1243
|
+
* `shutdown()`, tears down rocksdb for the entire process. So a worker thread that opens databases
|
|
1244
|
+
* and then exits — notably a job worker (jobProcess), which opens the whole database graph via
|
|
1245
|
+
* `getDatabases()` and exits when the job finishes — must close its handles explicitly, or those
|
|
1246
|
+
* handles linger process-wide (and, e.g., block an online `restore_backup` from confirming the
|
|
1247
|
+
* database is closed). The `system` database is intentionally left open: it is non-enumerable here
|
|
1248
|
+
* (skipped by the loop), is never restored online, and the exiting worker may still touch the job
|
|
1249
|
+
* table during teardown. Best-effort: closing failures are swallowed inside `closeDatabase`.
|
|
1250
|
+
*/
|
|
1251
|
+
function closeLoadedDatabases() {
|
|
1252
|
+
// snapshot the names first: closeDatabase() deletes from `databases` as it goes
|
|
1253
|
+
for (const databaseName of Object.keys(exports.databases)) {
|
|
1254
|
+
const dbTables = exports.databases[databaseName];
|
|
1255
|
+
if (!dbTables)
|
|
1256
|
+
continue;
|
|
1257
|
+
let isRocks = false;
|
|
1258
|
+
for (const tableName in dbTables) {
|
|
1259
|
+
if (dbTables[tableName]?.primaryStore?.rootStore instanceof rocksdb_js_1.RocksDatabase) {
|
|
1260
|
+
isRocks = true;
|
|
1261
|
+
break;
|
|
922
1262
|
}
|
|
923
1263
|
}
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
rootStore.close();
|
|
929
|
-
rootStore.destroy();
|
|
930
|
-
}
|
|
931
|
-
else if (rootStore.status === 'open') {
|
|
932
|
-
await rootStore.close();
|
|
933
|
-
await (0, promises_1.unlink)(rootStore.path);
|
|
1264
|
+
// a tableless database exposes no table root store, so also check the defined-database
|
|
1265
|
+
// entry — otherwise its open root store would leak on worker exit
|
|
1266
|
+
if (!isRocks && definedDatabases?.get(databaseName)?.rootStore instanceof rocksdb_js_1.RocksDatabase) {
|
|
1267
|
+
isRocks = true;
|
|
934
1268
|
}
|
|
1269
|
+
if (isRocks)
|
|
1270
|
+
closeDatabase(databaseName);
|
|
935
1271
|
}
|
|
936
|
-
await (0, blob_ts_1.deleteRootBlobPathsForDB)(rootStore);
|
|
937
1272
|
}
|
|
938
1273
|
// HNSW_NO_AUTOVERSION kill-switch: when set, a NEW index initializes as legacy rather than
|
|
939
1274
|
// versioned. process.env values are strings, so a bare truthiness check would treat "0"/"false"
|
|
@@ -1010,7 +1345,11 @@ function openIndex(dbiKey, rootStore, attribute) {
|
|
|
1010
1345
|
// Enable cache (WeakLRUCache + VT) for all custom-object index stores so the VT is
|
|
1011
1346
|
// available before resolveIndexFormat decides the format. Versioned stores need the VT
|
|
1012
1347
|
// for cached traversal; legacy stores pay a small per-write cache.delete() overhead only.
|
|
1013
|
-
dbi = openRocksDatabase(rootStore.path, {
|
|
1348
|
+
dbi = openRocksDatabase(rootStore.path, {
|
|
1349
|
+
...dbiInit,
|
|
1350
|
+
name: dbiKey,
|
|
1351
|
+
cache: isCustomObjectIndex,
|
|
1352
|
+
});
|
|
1014
1353
|
dbi.rootStore = rootStore;
|
|
1015
1354
|
// Custom-index object stores (e.g. HNSW) write graph nodes via plain put() with no staged
|
|
1016
1355
|
// transaction timestamp, so their values carry no version and the PrimaryRocksDatabase
|
|
@@ -1230,6 +1569,10 @@ function table(tableDefinition) {
|
|
|
1230
1569
|
clearInterruptedDropEntries(rootStore.path, tableName);
|
|
1231
1570
|
}
|
|
1232
1571
|
if (rootStore instanceof rocksdb_js_1.RocksDatabase) {
|
|
1572
|
+
// Usually a genuinely new column family (existingTableMeta above found no catalog
|
|
1573
|
+
// entry), but an interrupted drop just completed above can leave the physical CF
|
|
1574
|
+
// behind under its old codec even though the catalog entry is gone — same fallback
|
|
1575
|
+
// as the reconcile paths covers that remnant case too.
|
|
1233
1576
|
primaryStore = openRocksDatabase(rootStore.path, { ...dbiInit, name: dbiName, cache: true });
|
|
1234
1577
|
}
|
|
1235
1578
|
else {
|
|
@@ -1950,7 +2293,9 @@ function getDefaultCompression() {
|
|
|
1950
2293
|
LMDB_COMPRESSION_OPTS['dictionary'] = (0, node_fs_1.readFileSync)(STORAGE_COMPRESSION_DICTIONARY);
|
|
1951
2294
|
if (STORAGE_COMPRESSION_THRESHOLD)
|
|
1952
2295
|
LMDB_COMPRESSION_OPTS['threshold'] = STORAGE_COMPRESSION_THRESHOLD;
|
|
1953
|
-
|
|
2296
|
+
// normalize disabled to false so a falsy config value ('' or null) is never persisted
|
|
2297
|
+
// into table metadata as-is (openRocksDatabase maps defined-falsy to 'none')
|
|
2298
|
+
return LMDB_COMPRESSION ? LMDB_COMPRESSION_OPTS : false;
|
|
1954
2299
|
}
|
|
1955
2300
|
/**
|
|
1956
2301
|
* Force all RocksDB databases to flush to disk.
|