@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
|
@@ -27,6 +27,7 @@ import { errorToString } from '../../utility/logging/harper_logger.ts';
|
|
|
27
27
|
import { RocksDatabase } from '@harperfast/rocksdb-js';
|
|
28
28
|
import { BridgeMethods } from './BridgeMethods.ts';
|
|
29
29
|
import lmdbGetBackup from './lmdbBridge/lmdbMethods/lmdbGetBackup.js';
|
|
30
|
+
import { createBackupStream, resolveSingleRootStore } from '../rocksdbBackup.ts';
|
|
30
31
|
import { DeleteTransactionLogsBeforeResults } from './DeleteTransactionLogsBeforeResults.ts';
|
|
31
32
|
import type { Readable } from 'node:stream';
|
|
32
33
|
|
|
@@ -491,12 +492,25 @@ export class ResourceBridge extends BridgeMethods {
|
|
|
491
492
|
: typeof deleteObj.timestamp === 'string'
|
|
492
493
|
? Number.parseInt(deleteObj.timestamp)
|
|
493
494
|
: deleteObj.timestamp;
|
|
495
|
+
const databaseName = deleteObj.database || deleteObj.schema || DEFAULT_DATABASE;
|
|
494
496
|
const table = getTable(deleteObj);
|
|
497
|
+
// A nonexistent table must not fall through to the no-table branch below — on RocksDB that
|
|
498
|
+
// widens a table-scoped request (e.g. a typo) into a whole-database log purge (#2049).
|
|
499
|
+
// Presence check, not truthiness: a table named "0" addressed numerically is still table-scoped.
|
|
500
|
+
if (deleteObj.table != null && !table)
|
|
501
|
+
throw handleHDBError(
|
|
502
|
+
new Error(),
|
|
503
|
+
HDB_ERROR_MSGS.TABLE_NOT_FOUND(databaseName, deleteObj.table),
|
|
504
|
+
404,
|
|
505
|
+
undefined,
|
|
506
|
+
undefined,
|
|
507
|
+
true
|
|
508
|
+
);
|
|
495
509
|
if (!table) {
|
|
496
510
|
// no table, check if any of the tables are RocksDB
|
|
497
511
|
// since all tables share the same transaction log store, we break after the first
|
|
498
512
|
// RocksDB table is found
|
|
499
|
-
const tables = getDatabases()[
|
|
513
|
+
const tables = getDatabases()[databaseName];
|
|
500
514
|
if (tables) {
|
|
501
515
|
for (const table of Object.values(tables)) {
|
|
502
516
|
if (table.primaryStore instanceof RocksDatabase) {
|
|
@@ -508,9 +522,16 @@ export class ResourceBridge extends BridgeMethods {
|
|
|
508
522
|
}
|
|
509
523
|
}
|
|
510
524
|
} else if (table.primaryStore instanceof RocksDatabase) {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
525
|
+
// All tables in a RocksDB database share one transaction log with no per-table purge
|
|
526
|
+
// granularity; honoring `table` here would silently purge every sibling table's log (#2049).
|
|
527
|
+
throw handleHDBError(
|
|
528
|
+
new Error(),
|
|
529
|
+
`Table-level transaction log deletion is not supported for RocksDB tables because all tables in a database share one transaction log; to delete the transaction logs for the entire '${databaseName}' database, use delete_transaction_logs_before with only 'database' and 'timestamp'`,
|
|
530
|
+
400,
|
|
531
|
+
undefined,
|
|
532
|
+
undefined,
|
|
533
|
+
true
|
|
534
|
+
);
|
|
514
535
|
} else {
|
|
515
536
|
totalResults.entries_deleted += await table.deleteHistory(before, deleteObj.cleanup_deleted_records);
|
|
516
537
|
}
|
|
@@ -563,7 +584,49 @@ export class ResourceBridge extends BridgeMethods {
|
|
|
563
584
|
schema?: string;
|
|
564
585
|
table?: string;
|
|
565
586
|
tables?: string[];
|
|
587
|
+
include_audit?: boolean;
|
|
588
|
+
gzip?: boolean;
|
|
589
|
+
exclude_blobs?: boolean;
|
|
566
590
|
}): Promise<Readable> {
|
|
591
|
+
const databaseName = getBackupObj.database || getBackupObj.schema || 'data';
|
|
592
|
+
const database = getDatabases()[databaseName];
|
|
593
|
+
if (!database) {
|
|
594
|
+
throw new ClientError(`Database '${databaseName}' does not exist`, 404);
|
|
595
|
+
}
|
|
596
|
+
const firstTable = database[Object.keys(database)[0]];
|
|
597
|
+
if (!firstTable) {
|
|
598
|
+
throw new ClientError(`Database '${databaseName}' has no tables to back up`);
|
|
599
|
+
}
|
|
600
|
+
if (firstTable.primaryStore.rootStore instanceof RocksDatabase) {
|
|
601
|
+
// RocksDB: stream a fresh full-snapshot tar of the database's current state — no
|
|
602
|
+
// scratch disk, nothing to clean up. Per-engine params are validated descriptively.
|
|
603
|
+
if (getBackupObj.tables || getBackupObj.table) {
|
|
604
|
+
throw new ClientError(`'tables'/'table' are LMDB-only options; RocksDB backups are always whole-database`);
|
|
605
|
+
}
|
|
606
|
+
if (getBackupObj.include_audit !== undefined) {
|
|
607
|
+
throw new ClientError(
|
|
608
|
+
`'include_audit' is an LMDB-only option; RocksDB backups always include the transaction log`
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
if (getBackupObj.gzip !== undefined && typeof getBackupObj.gzip !== 'boolean') {
|
|
612
|
+
throw new ClientError(`'gzip' must be a boolean`);
|
|
613
|
+
}
|
|
614
|
+
if (getBackupObj.exclude_blobs !== undefined && typeof getBackupObj.exclude_blobs !== 'boolean') {
|
|
615
|
+
throw new ClientError(`'exclude_blobs' must be a boolean`);
|
|
616
|
+
}
|
|
617
|
+
const rootStore = resolveSingleRootStore(databaseName);
|
|
618
|
+
// gzip defaults on (it compresses the snapshot substantially); gzip=false opts out.
|
|
619
|
+
// blobs are included by default; exclude_blobs=true streams an engine-only tar.
|
|
620
|
+
return createBackupStream(
|
|
621
|
+
rootStore,
|
|
622
|
+
databaseName,
|
|
623
|
+
getBackupObj.gzip !== false,
|
|
624
|
+
getBackupObj.exclude_blobs === true
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
if (getBackupObj.gzip !== undefined) {
|
|
628
|
+
throw new ClientError(`'gzip' is a RocksDB-only option; LMDB backups are gzipped per the accept-encoding header`);
|
|
629
|
+
}
|
|
567
630
|
return lmdbGetBackup(getBackupObj);
|
|
568
631
|
}
|
|
569
632
|
}
|
|
@@ -655,7 +718,8 @@ function getTable(operationObject: { database?: string; schema?: string; table?:
|
|
|
655
718
|
const databaseName = operationObject.database || operationObject.schema || DEFAULT_DATABASE;
|
|
656
719
|
const tables = getDatabases()[databaseName];
|
|
657
720
|
if (!tables) throw handleHDBError(new Error(), HDB_ERROR_MSGS.SCHEMA_NOT_FOUND(databaseName), 404);
|
|
658
|
-
|
|
721
|
+
// Presence check, not truthiness, so a table named "0" resolves when addressed numerically.
|
|
722
|
+
return operationObject.table != null ? tables[operationObject.table] : undefined;
|
|
659
723
|
}
|
|
660
724
|
|
|
661
725
|
/**
|
|
@@ -183,15 +183,23 @@ export async function getVersionUpdateInfo() {
|
|
|
183
183
|
`You have installed a version lower than the version that your data was created on or was upgraded to. This may cause issues and is currently not supported.${os.EOL}${hdbTerms.SUPPORT_HELP_MSG}`
|
|
184
184
|
)
|
|
185
185
|
);
|
|
186
|
+
log.error(
|
|
187
|
+
`This instance's data was last run on version ${dataVersion}, which is newer than this installed version ${upgradeVersion}. Downgrading across major versions is not supported.`
|
|
188
|
+
);
|
|
186
189
|
throw new Error('Trying to downgrade major HDB versions is not supported.');
|
|
187
190
|
}
|
|
188
191
|
if (!hdbUtils.isCompatibleDataVersion(dataVersion.toString(), upgradeVersion.toString(), true)) {
|
|
189
192
|
console.log(chalk.yellow(`This instance's data was last run on version ${dataVersion}`));
|
|
193
|
+
log.warn(
|
|
194
|
+
`This instance's data was last run on version ${dataVersion}, which is newer than this installed version ${upgradeVersion}. Confirmation is required before running the older version against this data.`
|
|
195
|
+
);
|
|
190
196
|
|
|
191
197
|
if (await forceDowngradePrompt(new UpgradeObject(dataVersion, upgradeVersion))) {
|
|
192
198
|
await insertHdbUpgradeInfo(upgradeVersion.toString());
|
|
199
|
+
log.notify(`Downgrade confirmed; data version recorded as ${upgradeVersion}.`);
|
|
193
200
|
} else {
|
|
194
201
|
console.log('Cancelled downgrade, closing Harper');
|
|
202
|
+
log.notify('Cancelled downgrade, closing Harper');
|
|
195
203
|
process.exit(0);
|
|
196
204
|
}
|
|
197
205
|
}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
closeSync,
|
|
5
|
+
existsSync,
|
|
6
|
+
fsyncSync,
|
|
7
|
+
mkdirSync,
|
|
8
|
+
openSync,
|
|
9
|
+
readdirSync,
|
|
10
|
+
readFileSync,
|
|
11
|
+
unlinkSync,
|
|
12
|
+
writeSync,
|
|
13
|
+
} from 'node:fs';
|
|
14
|
+
import { basename, dirname, join } from 'node:path';
|
|
15
|
+
import { createHash } from 'node:crypto';
|
|
16
|
+
import { tryFileLock, fileLockRelease } from '@harperfast/rocksdb-js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Restore lock + marker protocol for RocksDB database restores (online operation and offline CLI),
|
|
20
|
+
* and the shared per-database exclusion used by `dropDatabase` so a drop and a restore can never
|
|
21
|
+
* mutate the same directory concurrently.
|
|
22
|
+
*
|
|
23
|
+
* Restore metadata lives in an isolated `` `restore` `` directory *beside* the database directory
|
|
24
|
+
* (never inside it, since a restore purges the destination). Each database's two files are keyed by
|
|
25
|
+
* a hash of the database directory name rather than being suffixed onto the name itself. That keeps
|
|
26
|
+
* them out of the database-name namespace — a legal database literally named `orders.restoring`
|
|
27
|
+
* would otherwise be mistaken for the restore marker of `orders`, and a 250-character name plus a
|
|
28
|
+
* `.restore.lock` suffix would exceed the 255-byte `NAME_MAX` on most filesystems. The directory
|
|
29
|
+
* name deliberately contains a backtick: `schemaRegex` (the database-name validator) forbids only
|
|
30
|
+
* `/` and `` ` `` among filesystem-legal characters, so no legal database can ever occupy this path
|
|
31
|
+
* — including a database literally named `.restore` (which *is* a legal name, so a plain `.restore`
|
|
32
|
+
* directory would collide with it and land the markers inside the live database). The directory is
|
|
33
|
+
* not itself a RocksDB/LMDB database (no `CURRENT`/`MANIFEST-`/`.mdb`), so the startup scan ignores
|
|
34
|
+
* it, and no user can create a database that resolves to it.
|
|
35
|
+
*
|
|
36
|
+
* - `<meta-dir>/<key>.lock` — an OS-level exclusive file lock (via rocksdb-js `tryFileLock`),
|
|
37
|
+
* effective across processes, containers, and worker threads, auto-released on process exit.
|
|
38
|
+
* Only *held-ness* is meaningful; the file itself persists after release (harmless). Held for the
|
|
39
|
+
* duration of a restore, and briefly by `dropDatabase` so the two serialize on the same primitive.
|
|
40
|
+
* Known limitation: the lock is owned by the process, so if the restore job's worker *thread*
|
|
41
|
+
* dies without the process exiting, the lock stays held (restores 409) until Harper restarts.
|
|
42
|
+
* - `<meta-dir>/<key>.restoring` — the completion marker. Written (and fsynced) after the lock is
|
|
43
|
+
* acquired and before the destructive restore begins; deleted only after the restore completes
|
|
44
|
+
* successfully, while still holding the lock. Its *existence* means "a restore started and has
|
|
45
|
+
* not finished successfully". Its first line records the database directory name so the startup
|
|
46
|
+
* scan can map a marker back to the database it blocks without decoding the hashed key.
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
// The backtick makes this an illegal database name (schemaRegex rejects `/` and backtick only), so
|
|
50
|
+
// it can never collide with a real database directory — see the module header.
|
|
51
|
+
export const RESTORE_META_DIR = '`restore`';
|
|
52
|
+
export const RESTORE_LOCK_SUFFIX = '.lock';
|
|
53
|
+
export const RESTORING_MARKER_SUFFIX = '.restoring';
|
|
54
|
+
|
|
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
|
+
export function restoreMetaDir(dbPath: string): string {
|
|
62
|
+
return join(dirname(dbPath), RESTORE_META_DIR);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Filesystem-safe, length-bounded key for a database's restore metadata files. Hashing the
|
|
67
|
+
* database directory name (not the full path, so it is stable regardless of where the databases
|
|
68
|
+
* root lives) keeps the metadata filenames short and collision-free while staying independent of
|
|
69
|
+
* the database-name namespace. Database directory names are unique within a databases root, so
|
|
70
|
+
* their hashes are too.
|
|
71
|
+
*/
|
|
72
|
+
function restoreMetaKey(dbPath: string): string {
|
|
73
|
+
return createHash('sha256').update(basename(dbPath)).digest('hex').slice(0, 32);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function restoreLockPath(dbPath: string): string {
|
|
77
|
+
return join(restoreMetaDir(dbPath), restoreMetaKey(dbPath) + RESTORE_LOCK_SUFFIX);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function restoringMarkerPath(dbPath: string): string {
|
|
81
|
+
return join(restoreMetaDir(dbPath), restoreMetaKey(dbPath) + RESTORING_MARKER_SUFFIX);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export type RestoreState = 'in-progress' | 'incomplete' | 'clear';
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Whether a `.restoring` marker exists for a database. Cheaper than `checkRestoreState` and, unlike
|
|
88
|
+
* it, safe to call while *this* thread holds the restore lock: `checkRestoreState` would re-probe
|
|
89
|
+
* the lock (which reads as held from the same thread) and report 'in-progress' rather than telling
|
|
90
|
+
* a caller that a *leftover* marker is present. `dropDatabase` uses this after acquiring the lock to
|
|
91
|
+
* distinguish debris from a crashed restore.
|
|
92
|
+
*/
|
|
93
|
+
export function restoreMarkerPresent(dbPath: string): boolean {
|
|
94
|
+
return existsSync(restoringMarkerPath(dbPath));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** The lock and (optional) marker held by a begin/acquire call, threaded back to complete/abandon. */
|
|
98
|
+
export type RestoreLock = {
|
|
99
|
+
/** rocksdb-js file-lock token; non-zero. */
|
|
100
|
+
token: number;
|
|
101
|
+
/** The database directory this lock guards. */
|
|
102
|
+
dbPath: string;
|
|
103
|
+
/**
|
|
104
|
+
* True when a `.restoring` marker already existed at `beginRestore` time — i.e. this restore is a
|
|
105
|
+
* recovery attempt over a possibly half-purged directory. A pre-existing marker must never be
|
|
106
|
+
* cleared by a *failed* recovery attempt, or the directory could be reloaded as healthy while
|
|
107
|
+
* still partial. Only set on `beginRestore`; always false for a bare `acquireRestoreLock`.
|
|
108
|
+
*/
|
|
109
|
+
preexisting: boolean;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Determine the restore state of a database directory. Used by startup database detection and
|
|
114
|
+
* the open-database guards:
|
|
115
|
+
* - 'in-progress': marker present and the restore lock is held (a restore is running in some
|
|
116
|
+
* process) — do not load.
|
|
117
|
+
* - 'incomplete': marker present but the lock is free (crashed mid-restore; the directory may
|
|
118
|
+
* be partial garbage) — do not load; rerun the restore.
|
|
119
|
+
* - 'clear': no marker — load normally (a stale, unheld lock file alone is fine).
|
|
120
|
+
*
|
|
121
|
+
* The marker is checked FIRST and the lock is only probed when the marker exists. Probing takes
|
|
122
|
+
* and releases the flock, and probes are mutually exclusive across threads — if every rescan on
|
|
123
|
+
* every thread probed the (persistent) lock file of a long-ago-restored database, concurrent
|
|
124
|
+
* rescans would collide and misclassify healthy databases as 'in-progress'. Marker-first is
|
|
125
|
+
* safe: `beginRestore` writes (and fsyncs) the marker immediately after taking the lock and
|
|
126
|
+
* before any destructive step, so a database without a marker has nothing to protect yet.
|
|
127
|
+
*/
|
|
128
|
+
export function checkRestoreState(dbPath: string): RestoreState {
|
|
129
|
+
if (!existsSync(restoringMarkerPath(dbPath))) return 'clear';
|
|
130
|
+
const lockPath = restoreLockPath(dbPath);
|
|
131
|
+
if (existsSync(lockPath)) {
|
|
132
|
+
const token = tryFileLock(lockPath);
|
|
133
|
+
if (token === 0) return 'in-progress';
|
|
134
|
+
fileLockRelease(token);
|
|
135
|
+
}
|
|
136
|
+
return 'incomplete';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* fsync a directory so a create/unlink of an entry within it is durable. Best-effort: Windows (and
|
|
141
|
+
* some filesystems) reject opening a directory for fsync with EPERM/EISDIR/ENOTSUP — the durability
|
|
142
|
+
* flush is a POSIX nicety, so treat those as a no-op rather than failing the restore.
|
|
143
|
+
*/
|
|
144
|
+
function fsyncDir(dir: string): void {
|
|
145
|
+
let dirFd: number;
|
|
146
|
+
try {
|
|
147
|
+
dirFd = openSync(dir, 'r');
|
|
148
|
+
} catch (error: any) {
|
|
149
|
+
if (error.code === 'EPERM' || error.code === 'EISDIR' || error.code === 'ENOTSUP') return;
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
fsyncSync(dirFd);
|
|
154
|
+
} finally {
|
|
155
|
+
closeSync(dirFd);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Take the per-database restore lock without writing a marker. Used by `dropDatabase` so a drop and
|
|
161
|
+
* a restore serialize on the same primitive: whichever takes the lock first runs to completion; the
|
|
162
|
+
* other gets a 409. Throws (statusCode 409) if the lock is already held.
|
|
163
|
+
*/
|
|
164
|
+
export function acquireRestoreLock(dbPath: string): RestoreLock {
|
|
165
|
+
mkdirSync(restoreMetaDir(dbPath), { recursive: true });
|
|
166
|
+
const token = tryFileLock(restoreLockPath(dbPath));
|
|
167
|
+
if (token === 0) {
|
|
168
|
+
const error: any = new Error(`Restore already in progress for database at ${dbPath}`);
|
|
169
|
+
error.statusCode = 409;
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
return { token, dbPath, preexisting: false };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Release a lock taken by `acquireRestoreLock` (no marker to remove).
|
|
177
|
+
*/
|
|
178
|
+
export function releaseRestoreLock(lock: RestoreLock): void {
|
|
179
|
+
fileLockRelease(lock.token);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Acquire the per-database restore lock and write the restoring marker. Call before any
|
|
184
|
+
* destructive step. Returns the lock (with `preexisting` set when a marker was already present, so
|
|
185
|
+
* a failed recovery attempt knows not to clear it). Throws (statusCode 409) if another restore
|
|
186
|
+
* already holds the lock.
|
|
187
|
+
*/
|
|
188
|
+
export function beginRestore(dbPath: string): RestoreLock {
|
|
189
|
+
const markerPath = restoringMarkerPath(dbPath);
|
|
190
|
+
const preexisting = existsSync(markerPath);
|
|
191
|
+
const lock = acquireRestoreLock(dbPath);
|
|
192
|
+
try {
|
|
193
|
+
const fd = openSync(markerPath, 'w');
|
|
194
|
+
try {
|
|
195
|
+
// first line is the database directory name so the startup scan can map this marker back to
|
|
196
|
+
// the database it blocks without reversing the hashed key
|
|
197
|
+
writeSync(fd, `${basename(dbPath)}\nrestore started ${new Date().toISOString()}\n`);
|
|
198
|
+
fsyncSync(fd);
|
|
199
|
+
} finally {
|
|
200
|
+
closeSync(fd);
|
|
201
|
+
}
|
|
202
|
+
// fsync the metadata directory so the marker's directory entry is durable — without this a
|
|
203
|
+
// power loss can lose the entry, and a half-purged database would load as healthy
|
|
204
|
+
fsyncDir(restoreMetaDir(dbPath));
|
|
205
|
+
} catch (error) {
|
|
206
|
+
fileLockRelease(lock.token);
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
return { ...lock, preexisting };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Mark the restore successful: delete the marker (while still holding the lock), then release
|
|
214
|
+
* the lock.
|
|
215
|
+
*/
|
|
216
|
+
export function completeRestore(lock: RestoreLock): void {
|
|
217
|
+
try {
|
|
218
|
+
unlinkSync(restoringMarkerPath(lock.dbPath));
|
|
219
|
+
// fsync the metadata directory so the marker's *removal* is durable — symmetric with the
|
|
220
|
+
// creation fsync in beginRestore. Without it, a power loss could resurrect the marker's
|
|
221
|
+
// directory entry and misclassify a fully-restored database as incomplete.
|
|
222
|
+
fsyncDir(restoreMetaDir(lock.dbPath));
|
|
223
|
+
} finally {
|
|
224
|
+
fileLockRelease(lock.token);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Release the lock after a failed restore, leaving the marker in place so the database is
|
|
230
|
+
* detected as an incomplete restore (and not loaded) until a rerun succeeds.
|
|
231
|
+
*/
|
|
232
|
+
export function abandonRestore(lock: RestoreLock): void {
|
|
233
|
+
fileLockRelease(lock.token);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Remove a database's restore marker if one is present, then release the lock. Used by
|
|
238
|
+
* `dropDatabase`: a dropped database that carried an incomplete-restore marker should not leave the
|
|
239
|
+
* marker behind to block a future database of the same name. No-op on the marker if none exists.
|
|
240
|
+
*/
|
|
241
|
+
export function clearRestoreMarker(lock: RestoreLock): void {
|
|
242
|
+
try {
|
|
243
|
+
const markerPath = restoringMarkerPath(lock.dbPath);
|
|
244
|
+
if (existsSync(markerPath)) {
|
|
245
|
+
unlinkSync(markerPath);
|
|
246
|
+
fsyncDir(restoreMetaDir(lock.dbPath));
|
|
247
|
+
}
|
|
248
|
+
} finally {
|
|
249
|
+
fileLockRelease(lock.token);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Scan a databases root's reserved `` `restore` `` metadata directory and report every database currently blocked from
|
|
255
|
+
* loading, mapping each surviving marker back to its database name via the marker's first line.
|
|
256
|
+
* Returns `[dbName, state]` pairs for markers whose state is `in-progress` or `incomplete`
|
|
257
|
+
* (a `clear` result means the marker was removed concurrently and the database is loadable).
|
|
258
|
+
*/
|
|
259
|
+
export function scanBlockedRestores(databasesRoot: string): Array<[string, RestoreState]> {
|
|
260
|
+
const metaDir = join(databasesRoot, RESTORE_META_DIR);
|
|
261
|
+
if (!existsSync(metaDir)) return [];
|
|
262
|
+
const blocked: Array<[string, RestoreState]> = [];
|
|
263
|
+
for (const entry of readdirSync(metaDir, { withFileTypes: true })) {
|
|
264
|
+
if (!entry.isFile() || !entry.name.endsWith(RESTORING_MARKER_SUFFIX)) continue;
|
|
265
|
+
let dbName: string;
|
|
266
|
+
try {
|
|
267
|
+
dbName = readFileSync(join(metaDir, entry.name), 'utf8').split('\n', 1)[0];
|
|
268
|
+
} catch {
|
|
269
|
+
continue; // marker removed concurrently
|
|
270
|
+
}
|
|
271
|
+
if (!dbName) continue;
|
|
272
|
+
const state = checkRestoreState(join(databasesRoot, dbName));
|
|
273
|
+
if (state !== 'clear') blocked.push([dbName, state]);
|
|
274
|
+
}
|
|
275
|
+
return blocked;
|
|
276
|
+
}
|