harper 5.1.20 → 5.2.0-alpha.5
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/agent/operations.ts +13 -16
- package/components/Application.ts +188 -7
- package/components/ApplicationScope.ts +3 -0
- package/components/Scope.ts +12 -0
- package/components/anthropic/index.ts +9 -2
- package/components/bedrock/index.ts +25 -2
- package/components/componentLoader.ts +56 -7
- package/components/componentSecrets.ts +451 -0
- package/components/deploymentRecorder.ts +7 -1
- package/components/mcp/adapters/fastify.ts +3 -0
- package/components/mcp/adapters/harperHttp.ts +3 -0
- package/components/mcp/audit.ts +21 -6
- package/components/mcp/customResourceRegistry.ts +17 -38
- package/components/mcp/listChanged.ts +37 -20
- package/components/mcp/quota.ts +145 -0
- package/components/mcp/rateLimit.ts +152 -14
- package/components/mcp/resources.ts +2 -4
- package/components/mcp/toolRegistry.ts +73 -4
- package/components/mcp/tools/application.ts +77 -8
- package/components/mcp/tools/operations.ts +106 -36
- package/components/mcp/tools/schemas/operationDescriptions.ts +4 -4
- package/components/mcp/tools/schemas/operations.ts +5 -0
- package/components/mcp/transport.ts +55 -4
- package/components/ollama/index.ts +6 -2
- package/components/openai/index.ts +9 -2
- package/components/operations.js +204 -10
- package/components/operationsValidation.js +173 -0
- package/components/secretOperations.ts +498 -0
- package/config/configUtils.ts +25 -1
- package/config-app.schema.json +81 -0
- package/config-root.schema.json +8 -1
- package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +4 -3
- package/dataLayer/readAuditLog.ts +14 -0
- package/dataLayer/schemaDescribe.ts +1 -0
- package/dist/agent/operations.d.ts +7 -4
- package/dist/agent/operations.js +13 -15
- package/dist/agent/operations.js.map +1 -1
- package/dist/components/Application.d.ts +25 -2
- package/dist/components/Application.js +166 -7
- package/dist/components/Application.js.map +1 -1
- package/dist/components/ApplicationScope.d.ts +2 -0
- package/dist/components/ApplicationScope.js +3 -0
- package/dist/components/ApplicationScope.js.map +1 -1
- package/dist/components/Scope.d.ts +8 -0
- package/dist/components/Scope.js +11 -0
- package/dist/components/Scope.js.map +1 -1
- package/dist/components/anthropic/index.d.ts +4 -1
- package/dist/components/anthropic/index.js +4 -2
- package/dist/components/anthropic/index.js.map +1 -1
- package/dist/components/bedrock/index.d.ts +4 -1
- package/dist/components/bedrock/index.js +22 -2
- package/dist/components/bedrock/index.js.map +1 -1
- package/dist/components/componentLoader.d.ts +2 -0
- package/dist/components/componentLoader.js +52 -10
- package/dist/components/componentLoader.js.map +1 -1
- package/dist/components/componentSecrets.d.ts +63 -0
- package/dist/components/componentSecrets.js +405 -0
- package/dist/components/componentSecrets.js.map +1 -0
- package/dist/components/deploymentRecorder.d.ts +3 -0
- package/dist/components/deploymentRecorder.js +4 -3
- package/dist/components/deploymentRecorder.js.map +1 -1
- package/dist/components/mcp/adapters/fastify.d.ts +2 -0
- package/dist/components/mcp/adapters/fastify.js +1 -0
- package/dist/components/mcp/adapters/fastify.js.map +1 -1
- package/dist/components/mcp/adapters/harperHttp.d.ts +2 -0
- package/dist/components/mcp/adapters/harperHttp.js +1 -0
- package/dist/components/mcp/adapters/harperHttp.js.map +1 -1
- package/dist/components/mcp/audit.d.ts +2 -2
- package/dist/components/mcp/audit.js +20 -5
- package/dist/components/mcp/audit.js.map +1 -1
- package/dist/components/mcp/customResourceRegistry.d.ts +5 -12
- package/dist/components/mcp/customResourceRegistry.js +15 -30
- package/dist/components/mcp/customResourceRegistry.js.map +1 -1
- package/dist/components/mcp/listChanged.d.ts +7 -2
- package/dist/components/mcp/listChanged.js +28 -0
- package/dist/components/mcp/listChanged.js.map +1 -1
- package/dist/components/mcp/quota.d.ts +31 -0
- package/dist/components/mcp/quota.js +155 -0
- package/dist/components/mcp/quota.js.map +1 -0
- package/dist/components/mcp/rateLimit.d.ts +19 -3
- package/dist/components/mcp/rateLimit.js +129 -13
- package/dist/components/mcp/rateLimit.js.map +1 -1
- package/dist/components/mcp/resources.js.map +1 -1
- package/dist/components/mcp/toolRegistry.d.ts +39 -1
- package/dist/components/mcp/toolRegistry.js +60 -5
- package/dist/components/mcp/toolRegistry.js.map +1 -1
- package/dist/components/mcp/tools/application.d.ts +8 -1
- package/dist/components/mcp/tools/application.js +65 -5
- package/dist/components/mcp/tools/application.js.map +1 -1
- package/dist/components/mcp/tools/operations.d.ts +15 -3
- package/dist/components/mcp/tools/operations.js +99 -38
- package/dist/components/mcp/tools/operations.js.map +1 -1
- package/dist/components/mcp/tools/schemas/operationDescriptions.js +4 -4
- package/dist/components/mcp/tools/schemas/operationDescriptions.js.map +1 -1
- package/dist/components/mcp/tools/schemas/operations.js +4 -0
- package/dist/components/mcp/tools/schemas/operations.js.map +1 -1
- package/dist/components/mcp/transport.d.ts +6 -0
- package/dist/components/mcp/transport.js +47 -3
- package/dist/components/mcp/transport.js.map +1 -1
- package/dist/components/ollama/index.d.ts +4 -1
- package/dist/components/ollama/index.js +4 -2
- package/dist/components/ollama/index.js.map +1 -1
- package/dist/components/openai/index.d.ts +4 -1
- package/dist/components/openai/index.js +4 -2
- package/dist/components/openai/index.js.map +1 -1
- package/dist/components/operations.d.ts +39 -0
- package/dist/components/operations.js +194 -2
- package/dist/components/operations.js.map +1 -1
- package/dist/components/operationsValidation.d.ts +38 -0
- package/dist/components/operationsValidation.js +155 -0
- package/dist/components/operationsValidation.js.map +1 -1
- package/dist/components/secretOperations.d.ts +113 -0
- package/dist/components/secretOperations.js +475 -0
- package/dist/components/secretOperations.js.map +1 -0
- package/dist/config/configUtils.d.ts +4 -1
- package/dist/config/configUtils.js +18 -1
- package/dist/config/configUtils.js.map +1 -1
- package/dist/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +4 -3
- package/dist/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js.map +1 -1
- package/dist/dataLayer/readAuditLog.js +11 -0
- package/dist/dataLayer/readAuditLog.js.map +1 -1
- package/dist/dataLayer/schemaDescribe.js +2 -0
- package/dist/dataLayer/schemaDescribe.js.map +1 -1
- package/dist/globals.d.ts +1 -1
- package/dist/globals.js +1 -0
- package/dist/globals.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/json/systemSchema.json +38 -0
- package/dist/resources/DatabaseTransaction.d.ts +31 -3
- package/dist/resources/DatabaseTransaction.js +190 -29
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/LMDBTransaction.d.ts +1 -1
- package/dist/resources/LMDBTransaction.js +43 -5
- package/dist/resources/LMDBTransaction.js.map +1 -1
- package/dist/resources/PrimaryRocksDatabase.d.ts +51 -0
- package/dist/resources/PrimaryRocksDatabase.js +194 -0
- package/dist/resources/PrimaryRocksDatabase.js.map +1 -0
- package/dist/resources/RecordEncoder.d.ts +1 -0
- package/dist/resources/RecordEncoder.js +81 -75
- package/dist/resources/RecordEncoder.js.map +1 -1
- package/dist/resources/RequestTarget.d.ts +19 -0
- package/dist/resources/RequestTarget.js.map +1 -1
- package/dist/resources/Resource.d.ts +0 -1
- package/dist/resources/Resource.js +103 -16
- package/dist/resources/Resource.js.map +1 -1
- package/dist/resources/ResourceInterface.d.ts +6 -2
- package/dist/resources/ResourceInterface.js.map +1 -1
- package/dist/resources/Resources.js.map +1 -1
- package/dist/resources/Table.d.ts +6 -4
- package/dist/resources/Table.js +247 -24
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/analytics/read.js +25 -28
- package/dist/resources/analytics/read.js.map +1 -1
- package/dist/resources/blob.js +165 -22
- package/dist/resources/blob.js.map +1 -1
- package/dist/resources/crdt.d.ts +2 -0
- package/dist/resources/crdt.js +45 -36
- package/dist/resources/crdt.js.map +1 -1
- package/dist/resources/databases.d.ts +1 -0
- package/dist/resources/databases.js +134 -7
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/graphql.js +8 -0
- package/dist/resources/graphql.js.map +1 -1
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +35 -5
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +239 -14
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
- package/dist/resources/jsResource.js +6 -0
- package/dist/resources/jsResource.js.map +1 -1
- package/dist/resources/loadEnv.js +30 -3
- package/dist/resources/loadEnv.js.map +1 -1
- package/dist/resources/login.js +2 -2
- package/dist/resources/login.js.map +1 -1
- package/dist/resources/models/Models.js +14 -3
- package/dist/resources/models/Models.js.map +1 -1
- package/dist/resources/models/embedHook.js +10 -2
- package/dist/resources/models/embedHook.js.map +1 -1
- package/dist/resources/models/openaiStream.d.ts +56 -0
- package/dist/resources/models/openaiStream.js +94 -0
- package/dist/resources/models/openaiStream.js.map +1 -0
- package/dist/resources/replayLogs.js +4 -3
- package/dist/resources/replayLogs.js.map +1 -1
- package/dist/resources/search.d.ts +1 -1
- package/dist/resources/search.js +104 -11
- package/dist/resources/search.js.map +1 -1
- package/dist/resources/secretDecryptor.d.ts +54 -0
- package/dist/resources/secretDecryptor.js +131 -0
- package/dist/resources/secretDecryptor.js.map +1 -0
- package/dist/resources/tracked.js +15 -5
- package/dist/resources/tracked.js.map +1 -1
- package/dist/security/auth.js +71 -8
- package/dist/security/auth.js.map +1 -1
- package/dist/security/jsLoader.js +11 -0
- package/dist/security/jsLoader.js.map +1 -1
- package/dist/security/tokenAuthentication.d.ts +9 -1
- package/dist/security/tokenAuthentication.js +31 -1
- package/dist/security/tokenAuthentication.js.map +1 -1
- package/dist/server/DurableSubscriptionsSession.d.ts +1 -1
- package/dist/server/DurableSubscriptionsSession.js +8 -4
- package/dist/server/DurableSubscriptionsSession.js.map +1 -1
- package/dist/server/REST.js +28 -9
- package/dist/server/REST.js.map +1 -1
- package/dist/server/fastifyRoutes.js +11 -1
- package/dist/server/fastifyRoutes.js.map +1 -1
- package/dist/server/graphqlQuerying.js +4 -3
- package/dist/server/graphqlQuerying.js.map +1 -1
- package/dist/server/http.d.ts +40 -2
- package/dist/server/http.js +560 -77
- package/dist/server/http.js.map +1 -1
- package/dist/server/itc/serverHandlers.js +64 -0
- package/dist/server/itc/serverHandlers.js.map +1 -1
- package/dist/server/liveSubscriptionAuth.d.ts +12 -0
- package/dist/server/liveSubscriptionAuth.js +140 -0
- package/dist/server/liveSubscriptionAuth.js.map +1 -0
- package/dist/server/loadRootComponents.js +2 -1
- package/dist/server/loadRootComponents.js.map +1 -1
- package/dist/server/middlewareChain.d.ts +44 -1
- package/dist/server/middlewareChain.js +86 -10
- package/dist/server/middlewareChain.js.map +1 -1
- package/dist/server/mqtt.js +2 -1
- package/dist/server/mqtt.js.map +1 -1
- package/dist/server/operationsServer.js +1 -1
- package/dist/server/operationsServer.js.map +1 -1
- package/dist/server/serverHelpers/Headers.d.ts +9 -0
- package/dist/server/serverHelpers/Headers.js +22 -0
- package/dist/server/serverHelpers/Headers.js.map +1 -1
- package/dist/server/serverHelpers/JSONStream.js +3 -3
- package/dist/server/serverHelpers/JSONStream.js.map +1 -1
- package/dist/server/serverHelpers/Request.d.ts +52 -1
- package/dist/server/serverHelpers/Request.js +122 -1
- package/dist/server/serverHelpers/Request.js.map +1 -1
- package/dist/server/serverHelpers/contentTypes.js +3 -8
- package/dist/server/serverHelpers/contentTypes.js.map +1 -1
- package/dist/server/serverHelpers/progressEmitter.d.ts +19 -0
- package/dist/server/serverHelpers/progressEmitter.js +52 -4
- package/dist/server/serverHelpers/progressEmitter.js.map +1 -1
- package/dist/server/serverHelpers/registeredOperations.d.ts +40 -0
- package/dist/server/serverHelpers/registeredOperations.js +286 -0
- package/dist/server/serverHelpers/registeredOperations.js.map +1 -0
- package/dist/server/serverHelpers/serverHandlers.js +11 -5
- package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
- package/dist/server/serverHelpers/serverUtilities.d.ts +1 -0
- package/dist/server/serverHelpers/serverUtilities.js +67 -5
- package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
- package/dist/server/serverHelpers/uwsServer.d.ts +80 -0
- package/dist/server/serverHelpers/uwsServer.js +433 -0
- package/dist/server/serverHelpers/uwsServer.js.map +1 -0
- package/dist/server/static.d.ts +0 -15
- package/dist/server/static.js +161 -2
- package/dist/server/static.js.map +1 -1
- package/dist/server/status/index.d.ts +4 -1
- package/dist/server/status/index.js +64 -4
- package/dist/server/status/index.js.map +1 -1
- package/dist/server/threads/manageThreads.d.ts +10 -0
- package/dist/server/threads/manageThreads.js +107 -4
- package/dist/server/threads/manageThreads.js.map +1 -1
- package/dist/server/threads/socketRouter.js +5 -1
- package/dist/server/threads/socketRouter.js.map +1 -1
- package/dist/server/threads/threadServer.js +92 -14
- package/dist/server/threads/threadServer.js.map +1 -1
- package/dist/upgrade/directives/5-2-0.d.ts +7 -0
- package/dist/upgrade/directives/5-2-0.js +108 -0
- package/dist/upgrade/directives/5-2-0.js.map +1 -0
- package/dist/upgrade/directives/directivesController.js +2 -1
- package/dist/upgrade/directives/directivesController.js.map +1 -1
- package/dist/utility/common_utils.js +5 -0
- package/dist/utility/common_utils.js.map +1 -1
- package/dist/utility/envFile.d.ts +41 -0
- package/dist/utility/envFile.js +221 -0
- package/dist/utility/envFile.js.map +1 -0
- package/dist/utility/globalSchema.d.ts +9 -0
- package/dist/utility/hdbTerms.d.ts +25 -0
- package/dist/utility/hdbTerms.js +31 -0
- package/dist/utility/hdbTerms.js.map +1 -1
- package/dist/utility/logging/harper_logger.d.ts +21 -0
- package/dist/utility/logging/harper_logger.js +160 -8
- package/dist/utility/logging/harper_logger.js.map +1 -1
- package/dist/utility/logging/logRotator.js +29 -17
- package/dist/utility/logging/logRotator.js.map +1 -1
- package/dist/utility/logging/readLog.d.ts +1 -1
- package/dist/utility/logging/readLog.js +305 -2
- package/dist/utility/logging/readLog.js.map +1 -1
- package/dist/utility/operationPermissions.d.ts +18 -0
- package/dist/utility/operationPermissions.js +35 -1
- package/dist/utility/operationPermissions.js.map +1 -1
- package/dist/utility/operation_authorization.d.ts +17 -0
- package/dist/utility/operation_authorization.js +38 -0
- package/dist/utility/operation_authorization.js.map +1 -1
- package/dist/utility/secretEnvelope.d.ts +30 -0
- package/dist/utility/secretEnvelope.js +94 -0
- package/dist/utility/secretEnvelope.js.map +1 -0
- package/dist/utility/signalling.d.ts +7 -0
- package/dist/utility/signalling.js +16 -0
- package/dist/utility/signalling.js.map +1 -1
- package/dist/validation/configValidator.js +70 -24
- package/dist/validation/configValidator.js.map +1 -1
- package/index.ts +4 -0
- package/json/systemSchema.json +38 -0
- package/npm-shrinkwrap.json +15066 -9347
- package/package.json +13 -8
- package/resources/DESIGN.md +15 -12
- package/resources/DatabaseTransaction.ts +206 -37
- package/resources/LMDBTransaction.ts +43 -9
- package/resources/PrimaryRocksDatabase.ts +201 -0
- package/resources/RecordEncoder.ts +81 -69
- package/resources/RequestTarget.ts +21 -0
- package/resources/Resource.ts +109 -25
- package/resources/ResourceInterface.ts +6 -2
- package/resources/Resources.ts +1 -3
- package/resources/Table.ts +238 -29
- package/resources/analytics/read.ts +25 -30
- package/resources/blob.ts +159 -23
- package/resources/crdt.ts +37 -31
- package/resources/databases.ts +143 -6
- package/resources/graphql.ts +8 -0
- package/resources/indexes/HierarchicalNavigableSmallWorld.ts +251 -17
- package/resources/jsResource.ts +6 -0
- package/resources/loadEnv.ts +34 -3
- package/resources/login.ts +2 -2
- package/resources/models/Models.ts +19 -3
- package/resources/models/embedHook.ts +13 -2
- package/resources/models/openaiStream.ts +133 -0
- package/resources/replayLogs.ts +4 -3
- package/resources/search.ts +113 -11
- package/resources/secretDecryptor.ts +159 -0
- package/resources/tracked.ts +24 -17
- package/security/auth.ts +70 -10
- package/security/jsLoader.ts +12 -0
- package/security/tokenAuthentication.ts +42 -1
- package/server/DESIGN.md +10 -0
- package/server/DurableSubscriptionsSession.ts +8 -4
- package/server/REST.ts +26 -9
- package/server/fastifyRoutes.ts +10 -1
- package/server/graphqlQuerying.ts +4 -3
- package/server/http.ts +563 -77
- package/server/itc/serverHandlers.js +70 -0
- package/server/liveSubscriptionAuth.ts +152 -0
- package/server/loadRootComponents.js +2 -1
- package/server/middlewareChain.ts +107 -17
- package/server/mqtt.ts +2 -1
- package/server/operationsServer.ts +2 -2
- package/server/serverHelpers/Headers.ts +24 -0
- package/server/serverHelpers/JSONStream.ts +3 -3
- package/server/serverHelpers/Request.ts +127 -0
- package/server/serverHelpers/contentTypes.ts +3 -8
- package/server/serverHelpers/progressEmitter.ts +55 -5
- package/server/serverHelpers/registeredOperations.ts +259 -0
- package/server/serverHelpers/serverHandlers.js +11 -5
- package/server/serverHelpers/serverUtilities.ts +88 -5
- package/server/serverHelpers/uwsServer.ts +488 -0
- package/server/static.ts +180 -2
- package/server/status/index.ts +72 -4
- package/server/threads/manageThreads.js +102 -4
- package/server/threads/socketRouter.ts +5 -1
- package/server/threads/threadServer.js +93 -10
- package/studio/web/assets/{Chat-SdD1EZca.js → Chat-Cv_2paZE.js} +2 -2
- package/studio/web/assets/{Chat-SdD1EZca.js.map → Chat-Cv_2paZE.js.map} +1 -1
- package/studio/web/assets/{FloatingChat-GMq6tHK9.js → FloatingChat-CPyPIcVR.js} +4 -4
- package/studio/web/assets/{FloatingChat-GMq6tHK9.js.map → FloatingChat-CPyPIcVR.js.map} +1 -1
- package/studio/web/assets/{applications-B5hhv7uA.js → applications-C0jT2vdZ.js} +2 -2
- package/studio/web/assets/{applications-B5hhv7uA.js.map → applications-C0jT2vdZ.js.map} +1 -1
- package/studio/web/assets/{index-VFcpNWgq.js → index-D_GKOkhn.js} +6 -6
- package/studio/web/assets/index-D_GKOkhn.js.map +1 -0
- package/studio/web/assets/{index.lazy-DgcJojIA.js → index.lazy-CAmCIA65.js} +4 -4
- package/studio/web/assets/{index.lazy-DgcJojIA.js.map → index.lazy-CAmCIA65.js.map} +1 -1
- package/studio/web/assets/{profile-BqN-8vzm.js → profile-CaF-4aZe.js} +2 -2
- package/studio/web/assets/{profile-BqN-8vzm.js.map → profile-CaF-4aZe.js.map} +1 -1
- package/studio/web/assets/{setComponentFile-CHFvFq9u.js → setComponentFile-DLW1DHCt.js} +2 -2
- package/studio/web/assets/{setComponentFile-CHFvFq9u.js.map → setComponentFile-DLW1DHCt.js.map} +1 -1
- package/studio/web/assets/{setup-CQ3vWJSr.js → setup-TGCErIhq.js} +2 -2
- package/studio/web/assets/{setup-CQ3vWJSr.js.map → setup-TGCErIhq.js.map} +1 -1
- package/studio/web/assets/{status-BsW5fjuh.js → status-DG0Maoao.js} +2 -2
- package/studio/web/assets/{status-BsW5fjuh.js.map → status-DG0Maoao.js.map} +1 -1
- package/studio/web/assets/{swagger-ui-react-DITdeB9-.js → swagger-ui-react-8T4SgQ1m.js} +2 -2
- package/studio/web/assets/{swagger-ui-react-DITdeB9-.js.map → swagger-ui-react-8T4SgQ1m.js.map} +1 -1
- package/studio/web/assets/{tsMode-Dyph-OUs.js → tsMode-DJ6Sl24Q.js} +2 -2
- package/studio/web/assets/{tsMode-Dyph-OUs.js.map → tsMode-DJ6Sl24Q.js.map} +1 -1
- package/studio/web/assets/{useEntityRestURL-D0QTUqex.js → useEntityRestURL-JO2mfWTQ.js} +2 -2
- package/studio/web/assets/{useEntityRestURL-D0QTUqex.js.map → useEntityRestURL-JO2mfWTQ.js.map} +1 -1
- package/studio/web/index.html +1 -1
- package/upgrade/directives/5-2-0.ts +82 -0
- package/upgrade/directives/directivesController.ts +2 -1
- package/utility/common_utils.ts +5 -0
- package/utility/envFile.ts +218 -0
- package/utility/hdbTerms.ts +31 -0
- package/utility/logging/harper_logger.ts +156 -8
- package/utility/logging/logRotator.ts +24 -14
- package/utility/logging/readLog.ts +323 -2
- package/utility/operationPermissions.ts +35 -1
- package/utility/operation_authorization.ts +74 -1
- package/utility/secretEnvelope.ts +113 -0
- package/utility/signalling.ts +15 -0
- package/validation/configValidator.ts +78 -25
- package/studio/web/assets/index-VFcpNWgq.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "harper",
|
|
3
3
|
"description": "Harper is an open-source Node.js performance platform that unifies database, cache, application, and messaging layers into one in-memory process.",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.2.0-alpha.5",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://harper.fast",
|
|
7
7
|
"bugs": {
|
|
@@ -54,6 +54,8 @@
|
|
|
54
54
|
"scripts": {
|
|
55
55
|
"build": "tsc --project tsconfig.build.json",
|
|
56
56
|
"build:watch": "npm run build -- --watch --incremental",
|
|
57
|
+
"typecheck": "tsc --project tsconfig.json",
|
|
58
|
+
"typecheck:fast": "npx -y -p typescript@7.0.2 tsc --noEmit --project tsconfig.json",
|
|
57
59
|
"package": "./build-tools/build.sh",
|
|
58
60
|
"lint": "oxlint --format stylish --deny-warnings .",
|
|
59
61
|
"lint:required": "oxlint --format stylish --quiet .",
|
|
@@ -69,7 +71,8 @@
|
|
|
69
71
|
"test:unit:all": "npm run test:unit:main && npm run test:unit:apitests && npm run test:unit:resources && npm run test:unit:lmdb",
|
|
70
72
|
"test:unit:lmdb": "HARPER_STORAGE_ENGINE=lmdb npm run test:unit:resources && HARPER_STORAGE_ENGINE=lmdb npm run test:unit:apitests && HARPER_STORAGE_ENGINE=lmdb npm run test:unit:bin",
|
|
71
73
|
"test:unit:components": "mocha \"unitTests/components/**/*.js\"",
|
|
72
|
-
"test:unit:resources": "mocha \"unitTests/resources/**/*.js\"",
|
|
74
|
+
"test:unit:resources": "mocha \"unitTests/resources/**/*.js\" --exclude \"unitTests/resources/**/*.bench.js\"",
|
|
75
|
+
"bench": "mocha \"unitTests/resources/caching-rocks-database.bench.js\"",
|
|
73
76
|
"test:unit:bin": "mocha \"unitTests/bin/**/*.js\"",
|
|
74
77
|
"test:unit:apitests": "node ./dist/bin/harper.js stop && mocha \"unitTests/apiTests/**/*-test.mjs\"",
|
|
75
78
|
"test:unit:logging": "mocha \"unitTests/utility/logging/*.js\"",
|
|
@@ -136,7 +139,7 @@
|
|
|
136
139
|
"@types/micromatch": "^4.0.9",
|
|
137
140
|
"@types/mocha": "^10.0.10",
|
|
138
141
|
"@types/properties-reader": "^2.1.3",
|
|
139
|
-
"@types/sinon": "^
|
|
142
|
+
"@types/sinon": "^22.0.0",
|
|
140
143
|
"@types/tar-fs": "^2.0.4",
|
|
141
144
|
"@typescript-eslint/parser": "^8.48.1",
|
|
142
145
|
"axios": "1.18.1",
|
|
@@ -144,13 +147,13 @@
|
|
|
144
147
|
"chai-as-promised": "^8.0.2",
|
|
145
148
|
"chai-integer": "^0.1.0",
|
|
146
149
|
"eventsource": "^4.0.0",
|
|
147
|
-
"globals": "^
|
|
150
|
+
"globals": "^17.0.0",
|
|
148
151
|
"intercept-stdout": "0.1.2",
|
|
149
152
|
"mkcert": "^3.2.0",
|
|
150
153
|
"mocha": "^11.7.5",
|
|
151
154
|
"mqtt": "^5.15.1",
|
|
152
155
|
"oxlint": "^1.31.0",
|
|
153
|
-
"prettier": "~3.
|
|
156
|
+
"prettier": "~3.9.0",
|
|
154
157
|
"rewire": "^9.0.1",
|
|
155
158
|
"sinon": "21.0.3",
|
|
156
159
|
"sinon-chai": "^4.0.1",
|
|
@@ -163,14 +166,14 @@
|
|
|
163
166
|
},
|
|
164
167
|
"dependencies": {
|
|
165
168
|
"@aws-sdk/client-s3": "^3.1012.0",
|
|
166
|
-
"@aws-sdk/lib-storage": "3.
|
|
169
|
+
"@aws-sdk/lib-storage": "3.1080.0",
|
|
167
170
|
"@endo/static-module-record": "^1.1.2",
|
|
168
171
|
"@fastify/autoload": "^6.3.1",
|
|
169
172
|
"@fastify/compress": "^8.3.1",
|
|
170
173
|
"@fastify/cors": "^11.2.0",
|
|
171
174
|
"@fastify/static": "^9.1.3",
|
|
172
175
|
"@harperfast/extended-iterable": "^1.0.1",
|
|
173
|
-
"@harperfast/rocksdb-js": "^2.
|
|
176
|
+
"@harperfast/rocksdb-js": "^2.4.0",
|
|
174
177
|
"@turf/area": "6.5.0",
|
|
175
178
|
"@turf/boolean-contains": "6.5.0",
|
|
176
179
|
"@turf/boolean-disjoint": "6.5.0",
|
|
@@ -195,7 +198,7 @@
|
|
|
195
198
|
"fast-glob": "3.3.3",
|
|
196
199
|
"fastify": "^5.8.2",
|
|
197
200
|
"fastify-plugin": "^5.1.0",
|
|
198
|
-
"fs-extra": "11.3.
|
|
201
|
+
"fs-extra": "11.3.6",
|
|
199
202
|
"graphql": "^16.10.0",
|
|
200
203
|
"graphql-http": "^1.22.4",
|
|
201
204
|
"gunzip-maybe": "1.4.2",
|
|
@@ -207,6 +210,7 @@
|
|
|
207
210
|
"jsonata": "1.8.7",
|
|
208
211
|
"jsonwebtoken": "9.0.3",
|
|
209
212
|
"lmdb": "3.5.6",
|
|
213
|
+
"weak-lru-cache": "^1.2.2",
|
|
210
214
|
"lodash": "^4.17.23",
|
|
211
215
|
"mathjs": "11.12.0",
|
|
212
216
|
"micromatch": "^4.0.8",
|
|
@@ -251,6 +255,7 @@
|
|
|
251
255
|
"optionalDependencies": {
|
|
252
256
|
"bufferutil": "^4.0.9",
|
|
253
257
|
"segfault-handler": "^1.3.0",
|
|
258
|
+
"uWebSockets.js": "https://github.com/uNetworking/uWebSockets.js/archive/624987739d4da0acb628aaf2a10fc43e7ce27c5c.tar.gz",
|
|
254
259
|
"utf-8-validate": "^5.0.10"
|
|
255
260
|
},
|
|
256
261
|
"peerDependencies": {
|
package/resources/DESIGN.md
CHANGED
|
@@ -75,18 +75,21 @@ One giant `makeTable()` factory that returns a `TableResource extends Resource`
|
|
|
75
75
|
|
|
76
76
|
## "Where is X" cheat sheet
|
|
77
77
|
|
|
78
|
-
| Question | Where
|
|
79
|
-
| --------------------------------------------------- |
|
|
80
|
-
| How is a CRUD request authorized? | `Table.ts → #section: authz-hooks`; defaults in `Resource.ts` (`allowRead` etc.)
|
|
81
|
-
| Where does versioning / conflict resolution happen? | `Table.ts → _writeUpdate` (`#section: write-path-internals`)
|
|
82
|
-
| How does `search()` choose an index? | `Table.ts → search` (`#section: search-query`)
|
|
83
|
-
| How are subscriptions replayed? | `Table.ts → subscribe` (`#section: pub-sub`)
|
|
84
|
-
| How is the response body shaped (select clause)? | `Table.ts → transformEntryForSelect` (`#section: search-query`)
|
|
85
|
-
| Where is record-level TTL evaluated? | `Table.ts → setTTLExpiration` (`#section: lifecycle-admin`); `Updatable.getExpiresAt` (`#section: setup-and-factory`)
|
|
86
|
-
| How are residencies enforced (replication)? | `Table.ts → #section: lifecycle-admin` (residency block: `getResidencyRecord`, `setResidency`, `setResidencyById`, `getResidency`)
|
|
87
|
-
| How is the RecordObject prototype applied? | `RecordEncoder.ts` (see `../DESIGN.md`)
|
|
88
|
-
| Where is the per-request transaction stored? | `transaction.ts` + `contextStorage` (AsyncLocalStorage)
|
|
89
|
-
| How does a
|
|
78
|
+
| Question | Where |
|
|
79
|
+
| --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
80
|
+
| How is a CRUD request authorized? | `Table.ts → #section: authz-hooks`; defaults in `Resource.ts` (`allowRead` etc.) |
|
|
81
|
+
| Where does versioning / conflict resolution happen? | `Table.ts → _writeUpdate` (`#section: write-path-internals`) |
|
|
82
|
+
| How does `search()` choose an index? | `Table.ts → search` (`#section: search-query`) |
|
|
83
|
+
| How are subscriptions replayed? | `Table.ts → subscribe` (`#section: pub-sub`) |
|
|
84
|
+
| How is the response body shaped (select clause)? | `Table.ts → transformEntryForSelect` (`#section: search-query`) |
|
|
85
|
+
| Where is record-level TTL evaluated? | `Table.ts → setTTLExpiration` (`#section: lifecycle-admin`); `Updatable.getExpiresAt` (`#section: setup-and-factory`) |
|
|
86
|
+
| How are residencies enforced (replication)? | `Table.ts → #section: lifecycle-admin` (residency block: `getResidencyRecord`, `setResidency`, `setResidencyById`, `getResidency`) |
|
|
87
|
+
| How is the RecordObject prototype applied? | `RecordEncoder.ts` (see `../DESIGN.md`) |
|
|
88
|
+
| Where is the per-request transaction stored? | `transaction.ts` + `contextStorage` (AsyncLocalStorage) |
|
|
89
|
+
| How does a query opt out of a read snapshot? | Pass `snapshot: false` on the search request (e.g. `get_analytics`). `Table.ts → search` calls `txn.useReadTxn(snapshot === false)`; on RocksDB `DatabaseTransaction.getReadTxn` then builds the read txn with `{ disableSnapshot: true }` so a long scan reads latest without pinning a snapshot. No-op on LMDB (`LMDBTransaction.useReadTxn`). |
|
|
90
|
+
| How does a URL path map to a Resource? | `Resources.ts → getMatch` (exact/prefix fast path) then `matchParamRoute` (parameterised routes); see "Path routing" below |
|
|
91
|
+
| How does HNSW keep the graph connected on delete? | `indexes/HierarchicalNavigableSmallWorld.ts → index()` delete path: zero-degree orphans reindexed via `needsReindexing`; severed multi-node islands detected and reconnected by `repairSeveredNeighbors` (#1712) |
|
|
92
|
+
| How is a filter applied _during_ a vector search? | Predicate-aware traversal (#1241): `search.ts → executeConditions` composes companion AND conditions + a request `vectorFilter` + a static `allowReadRecord` RBAC hook into one `(primaryKey) => boolean` (`composeRecordFilter`) and passes it to `HierarchicalNavigableSmallWorld.search(cond, ctx, filter)`. The filter gates result admission at layer 0 only (routing ignores it, ACORN-style); a visit budget (`filterExpansion`) bounds the under-filled/selective case. Very selective _condition_ filters are instead diverted to the exact brute-force path by the query planner's `estimateCountAsSort` ordering. |
|
|
90
93
|
|
|
91
94
|
---
|
|
92
95
|
|
|
@@ -9,7 +9,8 @@ import { CONFIG_PARAMS } from '../utility/hdbTerms.ts';
|
|
|
9
9
|
import { convertToMS } from '../utility/common_utils.ts';
|
|
10
10
|
import { when } from '../utility/when.ts';
|
|
11
11
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
12
|
-
import { Transaction as RocksTransaction, type Store as RocksStore } from '@harperfast/rocksdb-js';
|
|
12
|
+
import { Transaction as RocksTransaction, type Store as RocksStore, constants } from '@harperfast/rocksdb-js';
|
|
13
|
+
const RETRY_NOW_VALUE = constants.RETRY_NOW_VALUE;
|
|
13
14
|
import type { RootDatabaseKind } from './databases.ts';
|
|
14
15
|
import type { Entry } from './RecordEncoder.ts';
|
|
15
16
|
|
|
@@ -34,6 +35,23 @@ let txnExpiration = envMngr.get(CONFIG_PARAMS.STORAGE_MAXTRANSACTIONOPENTIME) ??
|
|
|
34
35
|
|
|
35
36
|
class StartedTransaction extends Error {}
|
|
36
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Built when the long-transaction monitor aborts a write-bearing transaction that stayed open past the
|
|
40
|
+
* limit (STORAGE_MAXTRANSACTIONOPENTIME). Surfacing this instead of silently force-committing a partial
|
|
41
|
+
* write set preserves atomicity and avoids the index corruption described in issue #1407: the
|
|
42
|
+
* application gets an actionable error and owns how it splits long-running work into smaller
|
|
43
|
+
* transactions, while core keeps the consistency guarantee.
|
|
44
|
+
*/
|
|
45
|
+
export function transactionOpenTooLongError(): ServerError {
|
|
46
|
+
// 422 rather than 503: the condition is deterministic for a given transaction shape, so a retryable
|
|
47
|
+
// status (503/408) would invite clients and gateways to auto-retry the same doomed long transaction.
|
|
48
|
+
// 422 signals the request itself must change (split the work), which is the actionable response.
|
|
49
|
+
return new ServerError(
|
|
50
|
+
'Transaction was aborted after exceeding the open-transaction limit; split long-running work into smaller transactions',
|
|
51
|
+
422
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
37
55
|
type MaybePromise<T> = T | Promise<T>;
|
|
38
56
|
|
|
39
57
|
export type CommitOptions = {
|
|
@@ -72,6 +90,9 @@ export type TransactionWrite = {
|
|
|
72
90
|
// the commit handler's most recent decision: true means it took an early-return that left savedBlobs unreferenced.
|
|
73
91
|
// reset at the top of each commit-handler invocation so retries see a fresh state.
|
|
74
92
|
skipped?: boolean;
|
|
93
|
+
// sticky: a non-isRetry staging of this write appended its audit entry (set in save(); the retry
|
|
94
|
+
// dedup guards in the commit handler read it to ignore the write's own orphaned entry)
|
|
95
|
+
appendedAuditEntry?: boolean;
|
|
75
96
|
};
|
|
76
97
|
|
|
77
98
|
type RocksTransactionWithRetry = RocksTransaction & { isRetry?: boolean };
|
|
@@ -109,8 +130,12 @@ export class DatabaseTransaction implements Transaction {
|
|
|
109
130
|
// An explicit marker rather than overloading `retries`, which is also bumped by transient
|
|
110
131
|
// conflict retries and never reset, so it cannot reliably signal "this is a replay".
|
|
111
132
|
declare isReplay?: boolean;
|
|
133
|
+
// Set by the long-transaction monitor when it aborts a write-bearing transaction that exceeded the
|
|
134
|
+
// open-transaction limit. Once poisoned, any further addWrite/commit throws transactionOpenTooLongError
|
|
135
|
+
// so the request rolls back cleanly instead of silently committing a partial write set (issue #1407).
|
|
136
|
+
declare timedOut?: boolean;
|
|
112
137
|
|
|
113
|
-
getReadTxn(): ReadTransaction {
|
|
138
|
+
getReadTxn(disableSnapshot?: boolean): ReadTransaction {
|
|
114
139
|
this.readTxnRefCount = (this.readTxnRefCount || 0) + 1;
|
|
115
140
|
this.timeout = txnExpiration; // reset the timeout
|
|
116
141
|
if (this.transaction) {
|
|
@@ -119,7 +144,12 @@ export class DatabaseTransaction implements Transaction {
|
|
|
119
144
|
}
|
|
120
145
|
if (this.open !== TRANSACTION_STATE.OPEN) return; // can not start a new read transaction as there is no future commit that will take place, just have to allow the read to latest database state
|
|
121
146
|
|
|
122
|
-
|
|
147
|
+
// `disableSnapshot` (requested via `snapshot: false` on a query) reads against the latest
|
|
148
|
+
// committed data without pinning a consistent snapshot — so a long scan does not hold a
|
|
149
|
+
// snapshot that blocks compaction. Only applied when creating the transaction fresh; an
|
|
150
|
+
// already-open transaction keeps whatever snapshot mode it was created with.
|
|
151
|
+
// `coordinatedRetry` signals IsBusy write conflicts as RETRY_NOW rather than ERR_BUSY.
|
|
152
|
+
this.transaction = new RocksTransaction(this.db.store, { coordinatedRetry: true, disableSnapshot });
|
|
123
153
|
|
|
124
154
|
if (this.timestamp) {
|
|
125
155
|
this.transaction.setTimestamp(this.timestamp);
|
|
@@ -134,8 +164,8 @@ export class DatabaseTransaction implements Transaction {
|
|
|
134
164
|
return this.transaction;
|
|
135
165
|
}
|
|
136
166
|
|
|
137
|
-
useReadTxn() {
|
|
138
|
-
const readTxn = this.getReadTxn();
|
|
167
|
+
useReadTxn(disableSnapshot?: boolean) {
|
|
168
|
+
const readTxn = this.getReadTxn(disableSnapshot);
|
|
139
169
|
if (DEBUG_LONG_TXNS) this.stackTraces.push(new StartedTransaction());
|
|
140
170
|
this.readTxnsUsed++;
|
|
141
171
|
return readTxn;
|
|
@@ -173,6 +203,7 @@ export class DatabaseTransaction implements Transaction {
|
|
|
173
203
|
}
|
|
174
204
|
|
|
175
205
|
addWrite(operation: TransactionWrite) {
|
|
206
|
+
if (this.timedOut) throw transactionOpenTooLongError();
|
|
176
207
|
this.writes.push(operation);
|
|
177
208
|
if (!operation.deferSave) {
|
|
178
209
|
// Setting saved to false means to defer saving
|
|
@@ -187,7 +218,7 @@ export class DatabaseTransaction implements Transaction {
|
|
|
187
218
|
return operation;
|
|
188
219
|
}
|
|
189
220
|
|
|
190
|
-
save(operation: TransactionWrite, transaction?: RocksTransaction, reloadEntry = false) {
|
|
221
|
+
save(operation: TransactionWrite, transaction?: RocksTransaction, reloadEntry = false, options?: CommitOptions) {
|
|
191
222
|
let txnTime = this.timestamp;
|
|
192
223
|
transaction ??= this.transaction;
|
|
193
224
|
let immediateCommit = false;
|
|
@@ -205,11 +236,13 @@ export class DatabaseTransaction implements Transaction {
|
|
|
205
236
|
if (txnTime) {
|
|
206
237
|
transaction.setTimestamp(txnTime);
|
|
207
238
|
}
|
|
208
|
-
} else {
|
|
209
239
|
}
|
|
210
|
-
if (this.
|
|
211
|
-
//
|
|
212
|
-
|
|
240
|
+
if (this.isReplay) {
|
|
241
|
+
// Replayed writes came FROM the transaction log; never re-append them —
|
|
242
|
+
// replay iterates that same log, so re-appending prevents convergence
|
|
243
|
+
// (boot hangs replaying its own output). Conflict retries stamp isRetry
|
|
244
|
+
// at the retry sites in commit(); this is the replay-path equivalent.
|
|
245
|
+
(transaction as RocksTransactionWithRetry).isRetry = true;
|
|
213
246
|
}
|
|
214
247
|
if (!txnTime) txnTime = this.timestamp = transaction.getTimestamp();
|
|
215
248
|
if (reloadEntry || operation.entry === undefined) {
|
|
@@ -228,8 +261,16 @@ export class DatabaseTransaction implements Transaction {
|
|
|
228
261
|
if (result?.then) this.completions.push(result);
|
|
229
262
|
}
|
|
230
263
|
operation.commit(txnTime, operation.entry, this.retries > 0, transaction);
|
|
264
|
+
// Sticky record that THIS write staged with its audit entry appended (log entries are written
|
|
265
|
+
// at staging and are not part of the transaction, so they survive an abort). isRetry stagings
|
|
266
|
+
// skip the log write, so they never set it. The retry dedup guards in the commit handler key
|
|
267
|
+
// off this: a launderable proxy (like last attempt's skipped state) breaks under multi-round
|
|
268
|
+
// retries where a recommit round self-skips before a fresh-transaction replay.
|
|
269
|
+
if (!operation.skipped && !(transaction as RocksTransactionWithRetry).isRetry) {
|
|
270
|
+
operation.appendedAuditEntry = true;
|
|
271
|
+
}
|
|
231
272
|
if (immediateCommit) {
|
|
232
|
-
return this.commit({ transaction }); // immediately commit if the harper transaction is closed
|
|
273
|
+
return this.commit({ ...options, transaction }); // immediately commit if the harper transaction is closed
|
|
233
274
|
}
|
|
234
275
|
}
|
|
235
276
|
|
|
@@ -237,11 +278,12 @@ export class DatabaseTransaction implements Transaction {
|
|
|
237
278
|
* Resolves with information on the timestamp and success of the commit
|
|
238
279
|
*/
|
|
239
280
|
commit(options: CommitOptions = {}): MaybePromise<CommitResolution> {
|
|
281
|
+
if (this.timedOut) throw transactionOpenTooLongError();
|
|
240
282
|
let transaction = options.transaction ?? this.transaction; // we need to preserve this transaction as we might to resurrect it if we have to retry
|
|
241
283
|
for (let i = 0; i < this.writes.length; i++) {
|
|
242
284
|
let operation = this.writes[i];
|
|
243
285
|
if (!operation || (this.retries === 0 && operation.saved)) continue;
|
|
244
|
-
this.save(operation, transaction, i < this.validated);
|
|
286
|
+
this.save(operation, transaction, i < this.validated, options);
|
|
245
287
|
}
|
|
246
288
|
this.validated = this.writes.length;
|
|
247
289
|
const completions = this.completions;
|
|
@@ -254,7 +296,9 @@ export class DatabaseTransaction implements Transaction {
|
|
|
254
296
|
return this.commit(options);
|
|
255
297
|
}
|
|
256
298
|
this.open = TRANSACTION_STATE.CLOSED;
|
|
257
|
-
|
|
299
|
+
// RocksTransaction.commit() resolves with RETRY_NOW_VALUE (a number) under
|
|
300
|
+
// coordinatedRetry, or void on a normal commit/abort.
|
|
301
|
+
let commitResolution: Promise<number | void> | void;
|
|
258
302
|
if (--this.readTxnsUsed > 0) {
|
|
259
303
|
// we still have outstanding iterators using the transaction, we can't just commit/abort it, we will still
|
|
260
304
|
// need to use it
|
|
@@ -279,10 +323,11 @@ export class DatabaseTransaction implements Transaction {
|
|
|
279
323
|
if (transaction) {
|
|
280
324
|
this.writes = this.writes.filter((write) => write); // filter out removed entries
|
|
281
325
|
if (this.writes.length > 0) {
|
|
282
|
-
//
|
|
283
|
-
//
|
|
284
|
-
//
|
|
285
|
-
//
|
|
326
|
+
// The transaction was created with coordinatedRetry:true (see
|
|
327
|
+
// getReadTxn), so commit() can resolve to RETRY_NOW_VALUE.
|
|
328
|
+
// That sentinel is handled in the resolve callback below and the
|
|
329
|
+
// cast to Promise<void> is safe — the sentinel never propagates
|
|
330
|
+
// past that branch.
|
|
286
331
|
commitResolution = transaction.commit() as Promise<void>;
|
|
287
332
|
} else {
|
|
288
333
|
try {
|
|
@@ -312,7 +357,29 @@ export class DatabaseTransaction implements Transaction {
|
|
|
312
357
|
}
|
|
313
358
|
const completions = [];
|
|
314
359
|
return commitResolution.then(
|
|
315
|
-
() => {
|
|
360
|
+
(commitResult) => {
|
|
361
|
+
if (commitResult === RETRY_NOW_VALUE) {
|
|
362
|
+
this.retries++;
|
|
363
|
+
harperLogger.debug?.('coordinated retry', transaction.id, this.retries);
|
|
364
|
+
// Mark this specific native transaction as a retry so RocksTransactionLogStore
|
|
365
|
+
// skips re-writing its already-staged txn-log entries (#2).
|
|
366
|
+
(transaction as RocksTransactionWithRetry).isRetry = true;
|
|
367
|
+
// Mirror the ERR_BUSY cap/warn policy: non-sourceApply transactions abort
|
|
368
|
+
// at MAX_RETRIES; sourceApply transactions keep retrying with periodic warn.
|
|
369
|
+
if (this.retries > MAX_RETRIES) {
|
|
370
|
+
if (!this.sourceApply) {
|
|
371
|
+
throw new ServerError(
|
|
372
|
+
`After ${MAX_RETRIES} coordinated retries, unable to commit transaction, transaction is in conflict with ongoing writes`
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
if (this.retries % MAX_RETRIES === 0) {
|
|
376
|
+
harperLogger.warn?.(
|
|
377
|
+
`Source-applied transaction ${transaction.id} still in conflict after ${this.retries} coordinated retries; continuing to retry`
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return this.commit({ ...options, transaction });
|
|
382
|
+
}
|
|
316
383
|
// onCommit may be async (e.g. RocksTransactionLogStore emits 'aftercommit'). Surface a
|
|
317
384
|
// rejection — or a synchronous throw — via logging rather than failing the commit, since
|
|
318
385
|
// the write is already durable.
|
|
@@ -351,6 +418,7 @@ export class DatabaseTransaction implements Transaction {
|
|
|
351
418
|
cleanupUnusedBlobs(write.savedBlobs, collectRetainedFileIds(write.store.getEntry(write.key)?.value));
|
|
352
419
|
}
|
|
353
420
|
// now reset transactions tracking; this transaction be reused and committed again
|
|
421
|
+
this.retries = 0; // reset per-native-transaction retry counter so a reused DatabaseTransaction's next batch starts fresh
|
|
354
422
|
this.writes = [];
|
|
355
423
|
if (this.#context?.resourceCache) this.#context.resourceCache = null;
|
|
356
424
|
this.next = null;
|
|
@@ -363,6 +431,13 @@ export class DatabaseTransaction implements Transaction {
|
|
|
363
431
|
});
|
|
364
432
|
},
|
|
365
433
|
(error) => {
|
|
434
|
+
// Coordinated transactions surface conflicts as RETRY_NOW (handled in the
|
|
435
|
+
// resolve branch above) and never reach here with ERR_BUSY. But not every
|
|
436
|
+
// write transaction is coordinated — a write that reaches save() with no
|
|
437
|
+
// prior getReadTxn() (immediate/publish/invalidate writes) is created
|
|
438
|
+
// without coordinatedRetry and still rejects with ERR_BUSY on conflict.
|
|
439
|
+
// Keep the backoff retry as the fallback for those paths.
|
|
440
|
+
//
|
|
366
441
|
// ERR_BUSY: optimistic-transaction write conflict. ERR_TRY_AGAIN: RocksDB kTryAgain —
|
|
367
442
|
// the transaction's snapshot sequence fell outside the memtable conflict-check window
|
|
368
443
|
// (max_write_buffer_size_to_maintain), which happens under bulk-ingest bursts such as a
|
|
@@ -374,6 +449,36 @@ export class DatabaseTransaction implements Transaction {
|
|
|
374
449
|
// for future transactions
|
|
375
450
|
this.retries++;
|
|
376
451
|
harperLogger.debug?.('retrying', transaction.id, this.retries);
|
|
452
|
+
if (error.code === 'ERR_TRY_AGAIN') {
|
|
453
|
+
// ERR_BUSY recovers on recommit: the save loop re-writes each key, re-tracking it at
|
|
454
|
+
// the current sequence, so validation passes once the contention clears. ERR_TRY_AGAIN
|
|
455
|
+
// never does: the memtable history validation needs is gone (flushed during a
|
|
456
|
+
// bulk-ingest burst), and recommitting re-checks the same stranded snapshot, so it
|
|
457
|
+
// fails forever even on an idle database. A source-apply transaction's uncapped retry
|
|
458
|
+
// then spins for good and wedges the replication apply loop at its commit await,
|
|
459
|
+
// freezing every leg of that database on the node. Replay onto a fresh transaction
|
|
460
|
+
// instead: the save loop reloads each entry through it and re-resolves against
|
|
461
|
+
// current state. Carry over the commit hook the transaction-log store attached
|
|
462
|
+
// (aftercommit emit / structure watermarks; it reads its state off the original
|
|
463
|
+
// transaction object, which abort() leaves intact); the retry-site isRetry stamp
|
|
464
|
+
// below keeps the log entries themselves from being re-added.
|
|
465
|
+
const retryTransaction: RocksTransactionWithRetry = new RocksTransaction(
|
|
466
|
+
(this.writes.find((write) => write)?.store.store ?? this.db.store) as RocksStore
|
|
467
|
+
);
|
|
468
|
+
if (this.timestamp) retryTransaction.setTimestamp(this.timestamp);
|
|
469
|
+
(retryTransaction as any).onCommit = (transaction as any).onCommit;
|
|
470
|
+
try {
|
|
471
|
+
transaction.abort();
|
|
472
|
+
} catch (abortError) {
|
|
473
|
+
// usually already released by the failed commit; log for the unexpected case
|
|
474
|
+
harperLogger.debug?.('aborting stranded transaction after failed commit', abortError);
|
|
475
|
+
}
|
|
476
|
+
transaction = retryTransaction;
|
|
477
|
+
}
|
|
478
|
+
// Mark the native transaction as a retry so RocksTransactionLogStore skips re-staging entries.
|
|
479
|
+
// Stamp AFTER the possible ERR_TRY_AGAIN swap above so the fresh replay transaction is
|
|
480
|
+
// marked too; otherwise it would re-stage audit/change-feed log entries on recommit.
|
|
481
|
+
(transaction as RocksTransactionWithRetry).isRetry = true;
|
|
377
482
|
if (this.retries > 2) {
|
|
378
483
|
// Transactions applying data from a canonical source of truth (replication peer or
|
|
379
484
|
// external caching source) must never drop a write on a transient conflict: there is no
|
|
@@ -384,6 +489,13 @@ export class DatabaseTransaction implements Transaction {
|
|
|
384
489
|
const neverDropOnConflict = this.sourceApply;
|
|
385
490
|
if (this.retries > MAX_RETRIES) {
|
|
386
491
|
if (!neverDropOnConflict) {
|
|
492
|
+
// giving up: release the current transaction (original or the fresh replay above)
|
|
493
|
+
// so the throw does not leak its native handle
|
|
494
|
+
try {
|
|
495
|
+
transaction.abort();
|
|
496
|
+
} catch (abortError) {
|
|
497
|
+
harperLogger.debug?.('aborting conflicted transaction after exhausting retries', abortError);
|
|
498
|
+
}
|
|
387
499
|
throw new ServerError(
|
|
388
500
|
`After ${MAX_RETRIES} retries, unable to commit transaction, transaction is in conflict with ongoing writes`
|
|
389
501
|
);
|
|
@@ -398,10 +510,10 @@ export class DatabaseTransaction implements Transaction {
|
|
|
398
510
|
}
|
|
399
511
|
// start delaying, back off to try to space out transactions and avoid excessive conflicts
|
|
400
512
|
return delay(Math.min(this.retries * this.retries, MAX_RETRY_DELAY_MS)).then(() =>
|
|
401
|
-
this.commit({ transaction })
|
|
513
|
+
this.commit({ ...options, transaction })
|
|
402
514
|
);
|
|
403
515
|
}
|
|
404
|
-
return this.commit({ transaction }); // try again
|
|
516
|
+
return this.commit({ ...options, transaction }); // try again
|
|
405
517
|
} else throw error;
|
|
406
518
|
}
|
|
407
519
|
);
|
|
@@ -445,6 +557,45 @@ export class DatabaseTransaction implements Transaction {
|
|
|
445
557
|
this.writes = [];
|
|
446
558
|
if (this.#context?.resourceCache) this.#context.resourceCache = null;
|
|
447
559
|
}
|
|
560
|
+
/**
|
|
561
|
+
* True if this transaction — or any database in its multi-store `next` chain — has writes accumulated
|
|
562
|
+
* that have not yet been committed. Writes to a second database live on `next` (see txnForContext), so a
|
|
563
|
+
* transaction that reads database A (head, tracked via its read snapshot, empty `writes`) and writes
|
|
564
|
+
* database B (`next`) must still count as write-bearing, or the monitor would misclassify it as read-only
|
|
565
|
+
* and force-commit B's writes via the commit cascade (issue #1407, multi-store path).
|
|
566
|
+
*/
|
|
567
|
+
hasPendingWrites(): boolean {
|
|
568
|
+
for (let txn: DatabaseTransaction = this; txn; txn = txn.next) {
|
|
569
|
+
if (txn.writes.some((write) => write)) return true;
|
|
570
|
+
}
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Abort and poison this transaction because it exceeded the open-transaction limit. The next write or
|
|
575
|
+
* commit throws transactionOpenTooLongError so the request fails cleanly and rolls back, rather than the
|
|
576
|
+
* monitor force-committing a partial write set on the application's behalf (issue #1407). The whole
|
|
577
|
+
* multi-store `next` chain is poisoned and aborted: writes to a second database live on `next`, so
|
|
578
|
+
* leaving it un-poisoned would let the head's commit cascade force-commit them (or orphan its resources
|
|
579
|
+
* until it self-times-out).
|
|
580
|
+
*/
|
|
581
|
+
abortDueToTimeout(): void {
|
|
582
|
+
// Poison every link first, then abort each, so a throw from one link's abort() can't leave later links
|
|
583
|
+
// in the chain un-poisoned (and thus eligible to be force-committed by a later commit cascade).
|
|
584
|
+
for (let txn: DatabaseTransaction = this; txn; txn = txn.next) {
|
|
585
|
+
txn.timedOut = true;
|
|
586
|
+
// Force the CLOSED path when releasing the read snapshot: doneReadTxn() flushes lingering writes via
|
|
587
|
+
// commit() while open === LINGERING, and commit() now throws transactionOpenTooLongError once poisoned.
|
|
588
|
+
// Closing first makes the release discard (abort) the uncommitted writes instead, which is the intent.
|
|
589
|
+
txn.open = TRANSACTION_STATE.CLOSED;
|
|
590
|
+
}
|
|
591
|
+
for (let txn: DatabaseTransaction = this; txn; txn = txn.next) {
|
|
592
|
+
try {
|
|
593
|
+
txn.abort();
|
|
594
|
+
} catch (error) {
|
|
595
|
+
harperLogger.debug?.(`Error aborting timed-out transaction in chain: ${error.message}`);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
448
599
|
directCommitSync(): void {
|
|
449
600
|
trackedTxns.delete(this);
|
|
450
601
|
this.transaction?.commitSync();
|
|
@@ -504,25 +655,43 @@ function startMonitoringTxns() {
|
|
|
504
655
|
for (const txn of trackedTxns) {
|
|
505
656
|
if (txn.timeout <= 0) {
|
|
506
657
|
const url = (txn.getContext() as any)?.url;
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
})
|
|
658
|
+
if (txn.hasPendingWrites() && !txn.sourceApply && !txn.isReplay) {
|
|
659
|
+
// Abort and surface an error rather than force-committing a partial write set: silently
|
|
660
|
+
// committing on the application's behalf breaks atomicity and can leave orphaned
|
|
661
|
+
// secondary-index entries that only a full index rebuild repairs (issue #1407). The app
|
|
662
|
+
// owns long-running work (split into smaller transactions); core owns consistency.
|
|
663
|
+
// Canonical-source applies (replication peer / external caching source) and crash-recovery
|
|
664
|
+
// replay are excluded: they have no resubscribe/resume path, so aborting a write would drop
|
|
665
|
+
// it while the resume cursor advances past it — a permanent divergence (harper-pro#348). For
|
|
666
|
+
// those, keep the prior force-commit behavior below.
|
|
667
|
+
harperLogger.error(
|
|
668
|
+
`Transaction was open too long and has been aborted after exceeding the open-transaction limit, from table: ${
|
|
669
|
+
(txn.db as any)?.name + (url ? ' path: ' + url : '')
|
|
670
|
+
}`,
|
|
671
|
+
...(txn.startedFrom ? [`was started from ${txn.startedFrom.resourceName}.${txn.startedFrom.method}`] : []),
|
|
672
|
+
...(DEBUG_LONG_TXNS ? ['starting stack trace', txn.stackTraces] : [])
|
|
673
|
+
);
|
|
674
|
+
try {
|
|
675
|
+
txn.abortDueToTimeout();
|
|
676
|
+
} catch (error) {
|
|
677
|
+
harperLogger.debug?.(`Error aborting timed out transaction: ${error.message}`);
|
|
678
|
+
}
|
|
679
|
+
} else {
|
|
680
|
+
// Read-only long transaction (no atomicity/index risk — e.g. a large scan or export), or a
|
|
681
|
+
// canonical-source apply/replay that must never drop a write: preserve the prior behavior of
|
|
682
|
+
// committing to close out the snapshot without poisoning the transaction.
|
|
683
|
+
try {
|
|
684
|
+
const result = txn.commit();
|
|
685
|
+
if ((result as any)?.then) {
|
|
686
|
+
(result as any).catch((error) => {
|
|
687
|
+
harperLogger.debug?.(`Error committing timed out transaction: ${error.message}`);
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
} catch (error) {
|
|
691
|
+
harperLogger.debug?.(`Error committing timed out transaction: ${error.message}`);
|
|
521
692
|
}
|
|
522
|
-
|
|
523
|
-
harperLogger.debug?.(`Error committing timed out transaction: ${error.message}`);
|
|
693
|
+
txn.timeout = txnExpiration;
|
|
524
694
|
}
|
|
525
|
-
txn.timeout = txnExpiration;
|
|
526
695
|
} else {
|
|
527
696
|
txn.timeout -= txnExpiration;
|
|
528
697
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DatabaseTransaction,
|
|
3
|
+
transactionOpenTooLongError,
|
|
3
4
|
type CommitOptions,
|
|
4
5
|
type TransactionWrite,
|
|
5
6
|
type CommitResolution,
|
|
@@ -54,7 +55,11 @@ export class LMDBTransaction extends DatabaseTransaction {
|
|
|
54
55
|
return this.readTxn;
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
|
|
58
|
+
// `disableSnapshot` is accepted for parity with the RocksDB transaction, but LMDB has no
|
|
59
|
+
// per-transaction snapshot toggle (its only lever is `snapshot: false` on `getRange`, which
|
|
60
|
+
// throws on the dupSort secondary-index stores we use). So LMDB-backed reads always hold a
|
|
61
|
+
// snapshot for now; `snapshot: false` queries are honored on RocksDB-backed tables.
|
|
62
|
+
useReadTxn(_disableSnapshot?: boolean) {
|
|
58
63
|
this.getReadTxn();
|
|
59
64
|
if (this.readTxn) {
|
|
60
65
|
(this.readTxn as any).use();
|
|
@@ -83,6 +88,7 @@ export class LMDBTransaction extends DatabaseTransaction {
|
|
|
83
88
|
}
|
|
84
89
|
|
|
85
90
|
addWrite(operation: TransactionWrite): any {
|
|
91
|
+
if (this.timedOut) throw transactionOpenTooLongError();
|
|
86
92
|
if (this.open === TRANSACTION_STATE.CLOSED) {
|
|
87
93
|
throw new Error('Can not use a transaction that is no longer open');
|
|
88
94
|
}
|
|
@@ -112,6 +118,7 @@ export class LMDBTransaction extends DatabaseTransaction {
|
|
|
112
118
|
* Resolves with information on the timestamp and success of the commit
|
|
113
119
|
*/
|
|
114
120
|
commit(options: CommitOptions = {}): any {
|
|
121
|
+
if (this.timedOut) throw transactionOpenTooLongError();
|
|
115
122
|
options = options || {};
|
|
116
123
|
let txnTime = this.timestamp;
|
|
117
124
|
if (!txnTime) txnTime = this.timestamp = options.timestamp || getNextMonotonicTime();
|
|
@@ -347,14 +354,41 @@ function startMonitoringTxns() {
|
|
|
347
354
|
for (const txn of trackedTxns) {
|
|
348
355
|
if (txn.timeout <= 0) {
|
|
349
356
|
const url = (txn.getContext() as any)?.url;
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
357
|
+
if (txn.hasPendingWrites() && !txn.sourceApply && !txn.isReplay) {
|
|
358
|
+
// Abort and surface an error rather than force-committing a partial write set: silently
|
|
359
|
+
// committing on the application's behalf breaks atomicity and can leave orphaned
|
|
360
|
+
// secondary-index entries that only a full index rebuild repairs (issue #1407). The app
|
|
361
|
+
// owns long-running work (split into smaller transactions); core owns consistency.
|
|
362
|
+
// Canonical-source applies (replication peer / external caching source) and crash-recovery
|
|
363
|
+
// replay are excluded: they have no resubscribe/resume path, so aborting a write would drop
|
|
364
|
+
// it while the resume cursor advances past it — a permanent divergence (harper-pro#348). For
|
|
365
|
+
// those, keep the prior force-commit behavior below.
|
|
366
|
+
harperLogger.error(
|
|
367
|
+
`Transaction was open too long and has been aborted after exceeding the open-transaction limit, from table: ${
|
|
368
|
+
(txn.db as any)?.name + (url ? ' path: ' + url : '')
|
|
369
|
+
}`
|
|
370
|
+
);
|
|
371
|
+
try {
|
|
372
|
+
txn.abortDueToTimeout();
|
|
373
|
+
} catch (error) {
|
|
374
|
+
harperLogger.debug?.(`Error aborting timed out transaction: ${error.message}`);
|
|
375
|
+
}
|
|
376
|
+
} else {
|
|
377
|
+
// Read-only long transaction (no atomicity/index risk — e.g. a large scan or export), or a
|
|
378
|
+
// canonical-source apply/replay that must never drop a write: preserve the prior behavior of
|
|
379
|
+
// committing to close out the snapshot without poisoning the transaction.
|
|
380
|
+
try {
|
|
381
|
+
const result = txn.commit();
|
|
382
|
+
if ((result as any)?.then) {
|
|
383
|
+
(result as any).catch((error) => {
|
|
384
|
+
harperLogger.debug?.(`Error committing timed out transaction: ${error.message}`);
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
} catch (error) {
|
|
388
|
+
harperLogger.debug?.(`Error committing timed out transaction: ${error.message}`);
|
|
389
|
+
}
|
|
390
|
+
txn.timeout = txnExpiration;
|
|
391
|
+
}
|
|
358
392
|
} else {
|
|
359
393
|
txn.timeout -= txnExpiration;
|
|
360
394
|
}
|