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
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { RocksDatabase, type RocksDatabaseOptions, constants, type Store } from '@harperfast/rocksdb-js';
|
|
2
|
+
|
|
3
|
+
const FRESH_VERSION_FLAG = constants.FRESH_VERSION_FLAG;
|
|
4
|
+
import { WeakLRUCache } from 'weak-lru-cache';
|
|
5
|
+
import { when } from '../utility/when.ts';
|
|
6
|
+
import { entryMap, METADATA, type Entry } from './RecordEncoder.ts';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* RocksDatabase subclass that owns all primary-store behaviour for Harper tables:
|
|
10
|
+
* - RecordEncoder metadata extraction (version, metadataFlags, etc.)
|
|
11
|
+
* - Optional WeakLRUCache keyed on entry version, verified via the process-wide VerificationTable
|
|
12
|
+
*
|
|
13
|
+
* Replaces both the old CachingRocksDatabase and the RocksDB-specific patches applied by
|
|
14
|
+
* handleLocalTimeForGets. Call initStore(rootStore) after open() — or let
|
|
15
|
+
* handleLocalTimeForGets delegate to it automatically via the isPrimaryRocksDatabase marker.
|
|
16
|
+
*
|
|
17
|
+
* Caching is enabled by default; pass { cache: false } to disable (useful for benchmarking
|
|
18
|
+
* or stores where version tracking is not desired).
|
|
19
|
+
*
|
|
20
|
+
* Cache freshness pattern (using the rocksdb-js VT API):
|
|
21
|
+
* 1. verifyVersion(key, cached.version) → true → return cached entry (no disk I/O)
|
|
22
|
+
* 2. verifyVersion(key, cached.version) → false → read from DB, populateVersion, update cache
|
|
23
|
+
*/
|
|
24
|
+
export class PrimaryRocksDatabase extends RocksDatabase {
|
|
25
|
+
readonly isPrimaryRocksDatabase = true;
|
|
26
|
+
#cache?: WeakLRUCache;
|
|
27
|
+
readCount = 0;
|
|
28
|
+
cachePuts = false;
|
|
29
|
+
declare rootStore: any;
|
|
30
|
+
declare decoder: any;
|
|
31
|
+
|
|
32
|
+
get #enc(): any {
|
|
33
|
+
return (this as any).encoder;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
constructor(pathOrStore: string | Store, options?: RocksDatabaseOptions & { cache?: boolean }) {
|
|
37
|
+
const enableCache = (options as any)?.cache === true;
|
|
38
|
+
super(pathOrStore, enableCache ? { ...options, verificationTable: true } : options);
|
|
39
|
+
if (enableCache) {
|
|
40
|
+
this.#cache = new WeakLRUCache();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Initialises encoder/decoder state. Must be called once after open() with the root
|
|
46
|
+
* RocksDatabase. Equivalent to the RocksDB branch of handleLocalTimeForGets, but as
|
|
47
|
+
* a real method rather than instance-level monkey-patching.
|
|
48
|
+
*/
|
|
49
|
+
initStore(rootStore: RocksDatabase) {
|
|
50
|
+
this.readCount = 0;
|
|
51
|
+
this.cachePuts = false;
|
|
52
|
+
this.rootStore = rootStore;
|
|
53
|
+
this.#enc.rootStore = rootStore;
|
|
54
|
+
this.#enc.isRocksDB = true;
|
|
55
|
+
this.decoder = this.#enc;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
#withEntry(entry: Entry, id: any): Entry {
|
|
59
|
+
if (entry.value) {
|
|
60
|
+
if (entry.value.constructor === Object && this.#enc.structPrototype) {
|
|
61
|
+
const originalValue = entry.value;
|
|
62
|
+
entry.value = new this.#enc.structPrototype.constructor();
|
|
63
|
+
Object.assign(entry.value, originalValue);
|
|
64
|
+
}
|
|
65
|
+
if (typeof entry.value === 'object' && entry.value !== null) {
|
|
66
|
+
entryMap.set(entry.value, entry);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
entry.key = id;
|
|
70
|
+
return entry;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
#processEntry(raw: any, id: any): Entry | undefined {
|
|
74
|
+
if (raw == null) return undefined;
|
|
75
|
+
if (raw[METADATA]) {
|
|
76
|
+
raw.metadataFlags = raw[METADATA];
|
|
77
|
+
return this.#withEntry(raw, id);
|
|
78
|
+
}
|
|
79
|
+
const entry = { value: raw, key: id } as Entry;
|
|
80
|
+
// map metadata-less values too, so getEntry guarantees the value→Entry
|
|
81
|
+
// mapping for every object value it returns (getSync/get rely on this)
|
|
82
|
+
if (typeof raw === 'object') entryMap.set(raw, entry);
|
|
83
|
+
return entry;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Core read method. Returns a full Entry (with version, metadataFlags, value, …) or
|
|
88
|
+
* undefined. When caching is enabled, passes `expectedVersion` to the native layer so
|
|
89
|
+
* a single call handles both verification (returns FRESH_VERSION_FLAG on hit) and VT
|
|
90
|
+
* population (auto-seeded on DB read). Only cold reads (no cached version) need a
|
|
91
|
+
* separate populateVersion call.
|
|
92
|
+
*/
|
|
93
|
+
getEntry(id: any, options?: any): any {
|
|
94
|
+
this.readCount++;
|
|
95
|
+
const cache = this.#cache;
|
|
96
|
+
// The cache stores the record *value* (weakly, via setValue) rather than
|
|
97
|
+
// the Entry: a WeakRef-wrapped value lets the LRFU expirer release it once
|
|
98
|
+
// it cycles out of the LRU stages (a raw Entry stored via set() has neither
|
|
99
|
+
// a .deref nor a .cache hook, so it would never be reclaimed — an unbounded
|
|
100
|
+
// leak). The value→Entry WeakMap (entryMap) recovers the Entry metadata
|
|
101
|
+
// (version, flags) needed for the fast path while the value is live.
|
|
102
|
+
const cachedValue = cache?.getValue(id);
|
|
103
|
+
const cached =
|
|
104
|
+
cachedValue != null && typeof cachedValue === 'object'
|
|
105
|
+
? (entryMap.get(cachedValue) as Entry | undefined)
|
|
106
|
+
: undefined;
|
|
107
|
+
const expectedVersion = cached?.version;
|
|
108
|
+
|
|
109
|
+
// Build get options, always merging with caller options to preserve
|
|
110
|
+
// transaction snapshot. Pass expectedVersion when cached:
|
|
111
|
+
// VT hit → native returns FRESH_VERSION_FLAG, no DB read
|
|
112
|
+
// VT miss → native reads DB and auto-populates VT slot
|
|
113
|
+
// For cold reads (no cached version), use populateVersion flag so the
|
|
114
|
+
// native layer seeds the VT slot in the same call.
|
|
115
|
+
let getOptions: any;
|
|
116
|
+
if (expectedVersion != null) {
|
|
117
|
+
getOptions = options ? { ...options, expectedVersion } : { expectedVersion };
|
|
118
|
+
} else if (cache) {
|
|
119
|
+
getOptions = options ? { ...options, populateVersion: true } : { populateVersion: true };
|
|
120
|
+
} else {
|
|
121
|
+
getOptions = options;
|
|
122
|
+
}
|
|
123
|
+
const raw = options?.async ? super.get(id, getOptions) : super.getSync(id, getOptions);
|
|
124
|
+
|
|
125
|
+
return when(raw, (result) => {
|
|
126
|
+
if (result === FRESH_VERSION_FLAG) return cached;
|
|
127
|
+
const entry = this.#processEntry(result, id);
|
|
128
|
+
if (entry == null) {
|
|
129
|
+
if (cache && cachedValue !== undefined) cache.delete(id);
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
// Only object values can be weakly cached and mapped back to their Entry;
|
|
133
|
+
// primitive/empty values fall through uncached (no fast path, still correct).
|
|
134
|
+
if (entry.version != null && cache && entry.value != null && typeof entry.value === 'object') {
|
|
135
|
+
entryMap.set(entry.value, entry);
|
|
136
|
+
cache.setValue(id, entry.value, (entry.size ?? 0) >> 10);
|
|
137
|
+
}
|
|
138
|
+
return entry;
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
getSync(id: any, options?: any): any {
|
|
143
|
+
const entry = this.getEntry(id, options) as Entry;
|
|
144
|
+
return entry?.value;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
get(id: any, options?: any): any {
|
|
148
|
+
return when(this.getEntry(id, { ...options, async: true }), (entry: Entry) => entry?.value);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
getRange(options?: any): any {
|
|
152
|
+
const iterable = super.getRange(options);
|
|
153
|
+
if (options?.valuesForKey) return iterable.map((v: any) => v?.value);
|
|
154
|
+
if (options?.values === false || options?.onlyCount) return iterable;
|
|
155
|
+
if (!this.#enc.isRocksDB) return iterable;
|
|
156
|
+
const enc = this.#enc;
|
|
157
|
+
return iterable.map((entry: any) => {
|
|
158
|
+
if (entry.value?.[METADATA]) {
|
|
159
|
+
entry.metadataFlags = entry.value[METADATA];
|
|
160
|
+
Object.assign(entry, entry.value);
|
|
161
|
+
}
|
|
162
|
+
if (entry.value?.constructor === Object && enc.structPrototype) {
|
|
163
|
+
const originalValue = entry.value;
|
|
164
|
+
entry.value = new enc.structPrototype.constructor();
|
|
165
|
+
for (const key in originalValue) entry.value[key] = originalValue[key];
|
|
166
|
+
}
|
|
167
|
+
return entry;
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
putSync(id: any, value: any, options?: any): any {
|
|
172
|
+
this.#cache?.delete(id);
|
|
173
|
+
return super.putSync(id, value, options);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
removeSync(id: any, options?: any): any {
|
|
177
|
+
this.#cache?.delete(id);
|
|
178
|
+
return super.removeSync(id, options);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
clearSync(): void {
|
|
182
|
+
// clearSync wipes every record; the per-instance cache must be dropped
|
|
183
|
+
// too, otherwise a cached entry would survive the clear and be served as
|
|
184
|
+
// a stale "still exists" hit on the next read.
|
|
185
|
+
this.#cache?.clear();
|
|
186
|
+
return super.clearSync();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
clear(): Promise<void> {
|
|
190
|
+
this.#cache?.clear();
|
|
191
|
+
return super.clear();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
open(): PrimaryRocksDatabase {
|
|
195
|
+
return super.open() as PrimaryRocksDatabase;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
static open(pathOrStore: string | Store, options?: RocksDatabaseOptions): PrimaryRocksDatabase {
|
|
199
|
+
return new PrimaryRocksDatabase(pathOrStore, options).open();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
LOCAL_ONLY,
|
|
19
19
|
} from './auditStore.ts';
|
|
20
20
|
import * as harperLogger from '../utility/logging/harper_logger.ts';
|
|
21
|
+
import { getNextMonotonicTime } from '../utility/lmdb/commonUtility.ts';
|
|
21
22
|
import './blob.ts';
|
|
22
23
|
import {
|
|
23
24
|
blobsWereEncoded,
|
|
@@ -118,6 +119,12 @@ export class RecordEncoder extends StructonEncoder {
|
|
|
118
119
|
isRocksDB: boolean;
|
|
119
120
|
name: string;
|
|
120
121
|
useVersions: boolean;
|
|
122
|
+
// Self-versioning mode for stores whose writes never stage a timestamp (HNSW/custom-index
|
|
123
|
+
// object stores). When set, each encode stamps a fresh monotonic version into the 8-byte
|
|
124
|
+
// metadata prefix so the value carries a version the RocksDB Verification Table can extract —
|
|
125
|
+
// enabling race-safe cache verification of graph nodes during traversal. Unlike primary-record
|
|
126
|
+
// writes, this never reads or clears the shared timestampNextEncoding global (harper#1307).
|
|
127
|
+
autoVersion = false;
|
|
121
128
|
constructor(options) {
|
|
122
129
|
options.useBigIntExtension = true;
|
|
123
130
|
// Bound the per-encoder typed-structure dictionary. It is append-only and pinned on the
|
|
@@ -169,6 +176,33 @@ export class RecordEncoder extends StructonEncoder {
|
|
|
169
176
|
lastValueEncoding = superEncode.call(this, record, options);
|
|
170
177
|
return lastValueEncoding;
|
|
171
178
|
}
|
|
179
|
+
if (this.autoVersion && this.isRocksDB) {
|
|
180
|
+
// Self-versioned index store (RocksDB only — the Verification Table is RocksDB-specific):
|
|
181
|
+
// prefix the record-encoder metadata header so each node carries a version the VT can
|
|
182
|
+
// extract and the decode path recognises. The header is [8-byte BE float64 version]
|
|
183
|
+
// [4-byte BE metadata word], exactly the format decode() expects: the version's first
|
|
184
|
+
// byte (0x42 for current-era ms timestamps) triggers metadata detection, and the
|
|
185
|
+
// ACTION_32_BIT-tagged flags word (flags = 0 — no expiration/residency/etc.) is required
|
|
186
|
+
// so decode consumes a metadata word rather than mis-reading the struct's first byte as
|
|
187
|
+
// flags — the flags word being mandatory was the actual bug behind earlier failures,
|
|
188
|
+
// NOT any struct incompatibility. Use the same reserve-start mechanism the primary
|
|
189
|
+
// metadata path below uses (the 2048|valueStart option reserves valueStart bytes at the
|
|
190
|
+
// front of the encode buffer; we write the header in place), which works fine with the
|
|
191
|
+
// forced randomAccessStructure/typed-struct mode of custom-object indexes — verified
|
|
192
|
+
// against the cold/frozen-read (#1161) and concurrent multi-worker (#386) suites. This
|
|
193
|
+
// avoids a per-write extra allocation + full-payload copy. Never touch the shared
|
|
194
|
+
// timestampNextEncoding/metadataInNextEncoding globals — those belong to the enclosing
|
|
195
|
+
// primary-record write whose commit nests this index encode (harper#1307).
|
|
196
|
+
const valueStart = 12; // 8-byte version + 4-byte metadata word
|
|
197
|
+
const encoded = superEncode.call(this, record, options | 2048 | valueStart);
|
|
198
|
+
const position = encoded.start || 0;
|
|
199
|
+
const dataView =
|
|
200
|
+
encoded.dataView || (encoded.dataView = new DataView(encoded.buffer, encoded.byteOffset, encoded.byteLength));
|
|
201
|
+
dataView.setFloat64(position, getNextMonotonicTime()); // version (big-endian, rocksdb stores it directly)
|
|
202
|
+
dataView.setUint32(position + 8, ACTION_32_BIT << 24); // metadata word: ACTION_32_BIT tag, flags = 0
|
|
203
|
+
lastValueEncoding = encoded.subarray(position + valueStart, encoded.end);
|
|
204
|
+
return encoded;
|
|
205
|
+
}
|
|
172
206
|
// this handles our custom metadata encoding, prefixing the record with metadata, including the local
|
|
173
207
|
// timestamp into the audit record, invalidation status and residency information
|
|
174
208
|
if (timestampNextEncoding || metadataInNextEncoding >= 0) {
|
|
@@ -457,52 +491,37 @@ function getTimestamp() {
|
|
|
457
491
|
}
|
|
458
492
|
|
|
459
493
|
export function handleLocalTimeForGets(store, rootStore) {
|
|
460
|
-
|
|
494
|
+
if ((store as any).isPrimaryRocksDatabase) {
|
|
495
|
+
(store as any).initStore(rootStore);
|
|
496
|
+
return store;
|
|
497
|
+
}
|
|
461
498
|
store.readCount = 0;
|
|
462
499
|
store.cachePuts = false;
|
|
463
500
|
store.rootStore = rootStore;
|
|
464
501
|
store.encoder.rootStore = rootStore;
|
|
465
|
-
store.encoder.isRocksDB = isRocksDB;
|
|
466
502
|
store.decoder = store.encoder;
|
|
467
503
|
const storeGetEntry = store.getEntry;
|
|
468
|
-
const storeGetSync = store.getSync;
|
|
469
|
-
const storeGet = store.get;
|
|
470
504
|
|
|
471
505
|
store.getEntry = function (id, options) {
|
|
472
506
|
store.readCount++;
|
|
473
507
|
lastMetadata = null;
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
)
|
|
486
|
-
|
|
487
|
-
let entry: Entry = storeGetEntry.call(this, id, options);
|
|
488
|
-
if (lastMetadata) {
|
|
489
|
-
entry.metadataFlags = lastMetadata[METADATA];
|
|
490
|
-
entry.localTime = lastMetadata.localTime;
|
|
491
|
-
entry.residencyId = lastMetadata.residencyId;
|
|
492
|
-
entry.nodeId = lastMetadata.nodeId;
|
|
493
|
-
entry.additionalAuditRefs = lastMetadata.additionalAuditRefs;
|
|
494
|
-
entry.size = lastMetadata.size;
|
|
495
|
-
if (lastMetadata.expiresAt >= 0) {
|
|
496
|
-
entry.expiresAt = lastMetadata.expiresAt;
|
|
497
|
-
}
|
|
498
|
-
if (isRocksDB) entry.version = lastMetadata.localTime;
|
|
499
|
-
if (entry.value) {
|
|
500
|
-
entryMap.set(entry.value, entry); // allow the record to access the entry
|
|
501
|
-
}
|
|
502
|
-
entry.key = id;
|
|
508
|
+
let entry: Entry = storeGetEntry.call(this, id, options);
|
|
509
|
+
if (lastMetadata) {
|
|
510
|
+
entry.metadataFlags = lastMetadata[METADATA];
|
|
511
|
+
entry.localTime = lastMetadata.localTime;
|
|
512
|
+
entry.residencyId = lastMetadata.residencyId;
|
|
513
|
+
entry.nodeId = lastMetadata.nodeId;
|
|
514
|
+
entry.additionalAuditRefs = lastMetadata.additionalAuditRefs;
|
|
515
|
+
entry.size = lastMetadata.size;
|
|
516
|
+
if (lastMetadata.expiresAt >= 0) {
|
|
517
|
+
entry.expiresAt = lastMetadata.expiresAt;
|
|
518
|
+
}
|
|
519
|
+
if (entry.value) {
|
|
520
|
+
entryMap.set(entry.value, entry); // allow the record to access the entry
|
|
503
521
|
}
|
|
504
|
-
|
|
522
|
+
entry.key = id;
|
|
505
523
|
}
|
|
524
|
+
return entry && withEntry(entry);
|
|
506
525
|
// if we have decoded with metadata, we want to pull it out and assign to this entry
|
|
507
526
|
function withEntry(entry) {
|
|
508
527
|
if (entry.value) {
|
|
@@ -547,15 +566,9 @@ export function handleLocalTimeForGets(store, rootStore) {
|
|
|
547
566
|
if (options.values === false || options.onlyCount) return iterable;
|
|
548
567
|
return iterable.map((entry) => {
|
|
549
568
|
// if we have metadata, move the metadata to the entry
|
|
550
|
-
if (
|
|
551
|
-
if (entry.value?.[METADATA]) {
|
|
552
|
-
entry.metadataFlags = entry.value[METADATA];
|
|
553
|
-
Object.assign(entry, entry.value);
|
|
554
|
-
}
|
|
555
|
-
} else if (lastMetadata) {
|
|
569
|
+
if (lastMetadata) {
|
|
556
570
|
entry.metadataFlags = lastMetadata[METADATA];
|
|
557
571
|
entry.localTime = lastMetadata.localTime;
|
|
558
|
-
if (isRocksDB) entry.version = lastMetadata.localTime;
|
|
559
572
|
entry.residencyId = lastMetadata.residencyId;
|
|
560
573
|
entry.nodeId = lastMetadata.nodeId;
|
|
561
574
|
entry.additionalAuditRefs = lastMetadata.additionalAuditRefs;
|
|
@@ -575,36 +588,35 @@ export function handleLocalTimeForGets(store, rootStore) {
|
|
|
575
588
|
});
|
|
576
589
|
};
|
|
577
590
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
}
|
|
591
|
+
// add read transaction tracking (LMDB only — RocksDB primary stores are PrimaryRocksDatabase and
|
|
592
|
+
// return early above; only LMDB stores reach here)
|
|
593
|
+
const txn = store.useReadTransaction();
|
|
594
|
+
txn.done();
|
|
595
|
+
if (!txn.done.isTracked) {
|
|
596
|
+
const Txn = txn.constructor;
|
|
597
|
+
const use = txn.use;
|
|
598
|
+
const done = txn.done;
|
|
599
|
+
Txn.prototype.use = function () {
|
|
600
|
+
if (!this.timerTracked) {
|
|
601
|
+
this.timerTracked = true;
|
|
602
|
+
trackedTxns.push(new WeakRef(this));
|
|
603
|
+
}
|
|
604
|
+
use.call(this);
|
|
605
|
+
};
|
|
606
|
+
Txn.prototype.done = function () {
|
|
607
|
+
if (this.isDone) return;
|
|
608
|
+
done.call(this);
|
|
609
|
+
this.openTimer = 0; // reset so idle pool time doesn't accumulate toward the stale-open threshold
|
|
610
|
+
if (this.isDone) {
|
|
611
|
+
for (let i = 0; i < trackedTxns.length; i++) {
|
|
612
|
+
const txn = trackedTxns[i].deref();
|
|
613
|
+
if (!txn || txn.isDone || txn.isCommitted) {
|
|
614
|
+
trackedTxns.splice(i--, 1);
|
|
603
615
|
}
|
|
604
616
|
}
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
Txn.prototype.done.isTracked = true;
|
|
608
620
|
}
|
|
609
621
|
|
|
610
622
|
return store;
|
|
@@ -56,6 +56,8 @@ export class RequestTarget extends URLSearchParams {
|
|
|
56
56
|
declare previousResidency?: string[];
|
|
57
57
|
|
|
58
58
|
// Action tracking
|
|
59
|
+
/** Cache disposition of this get on a caching table: true if loaded from the source, false if
|
|
60
|
+
* served from cache. Set per-get; read it on the RequestTarget you passed to the get. */
|
|
59
61
|
declare loadedFromSource?: boolean;
|
|
60
62
|
declare createdNewId?: string;
|
|
61
63
|
|
|
@@ -65,6 +67,25 @@ export class RequestTarget extends URLSearchParams {
|
|
|
65
67
|
declare allowFullScan?: boolean;
|
|
66
68
|
declare allowConditionsOnDynamicAttributes?: boolean;
|
|
67
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Predicate-aware vector search (#1241). A `(record) => boolean` filter evaluated during HNSW
|
|
72
|
+
* traversal (and as a post-filter for other paths), so a vector sort keeps exploring until it has
|
|
73
|
+
* enough MATCHING nearest neighbors instead of post-filtering an under-filled candidate set. The
|
|
74
|
+
* function must be synchronous and side-effect free; the record it receives is frozen. JS-API only —
|
|
75
|
+
* never parsed from a REST query string (no eval of user-supplied code over HTTP).
|
|
76
|
+
*/
|
|
77
|
+
declare vectorFilter?: (record: any) => boolean;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* When `false`, the query reads against the latest committed data without holding a consistent
|
|
81
|
+
* read snapshot open for the duration of the iteration. This trades read consistency (rows
|
|
82
|
+
* written after the scan starts may be observed) for not pinning a snapshot that blocks
|
|
83
|
+
* compaction and ties up resources — useful for long-running scans such as analytics queries.
|
|
84
|
+
* Defaults to `true` (a stable snapshot is held). Currently only honored by RocksDB-backed
|
|
85
|
+
* tables; see `DatabaseTransaction.getReadTxn`.
|
|
86
|
+
*/
|
|
87
|
+
declare snapshot?: boolean;
|
|
88
|
+
|
|
68
89
|
constructor(target?: string) {
|
|
69
90
|
let searchIndex: number | undefined;
|
|
70
91
|
let path: string | undefined;
|
package/resources/Resource.ts
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
RequestTargetOrId,
|
|
11
11
|
} from './ResourceInterface.ts';
|
|
12
12
|
import { randomUUID } from 'crypto';
|
|
13
|
-
import { DatabaseTransaction, type Transaction } from './DatabaseTransaction.ts';
|
|
13
|
+
import { DatabaseTransaction, TRANSACTION_STATE, type Transaction } from './DatabaseTransaction.ts';
|
|
14
14
|
import { IterableEventQueue } from './IterableEventQueue.ts';
|
|
15
15
|
import { _assignPackageExport } from '../globals.js';
|
|
16
16
|
import { ClientError, AccessViolation } from '../utility/errors/hdbError.ts';
|
|
@@ -18,6 +18,7 @@ import { transaction, contextStorage } from './transaction.ts';
|
|
|
18
18
|
import { parseQuery } from './search.ts';
|
|
19
19
|
import { RequestTarget } from './RequestTarget.ts';
|
|
20
20
|
import { when, promiseNormalize } from '../utility/when.ts';
|
|
21
|
+
import { registerLiveSubscription } from '../server/liveSubscriptionAuth.ts';
|
|
21
22
|
import type { JsonSchemaFragment } from './jsonSchemaTypes.ts';
|
|
22
23
|
|
|
23
24
|
const EXTENSION_TYPES = {
|
|
@@ -68,10 +69,6 @@ export class Resource<Record extends object = any> implements ResourceInterface<
|
|
|
68
69
|
return true; // Subclasses should override if needed
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
wasLoadedFromSource(): boolean | void {
|
|
72
|
-
// Subclasses should override if needed
|
|
73
|
-
}
|
|
74
|
-
|
|
75
72
|
addTo(_property: keyof Record, _value: Record[keyof Record]): void {
|
|
76
73
|
throw new Error('Not implemented');
|
|
77
74
|
}
|
|
@@ -661,8 +658,34 @@ function transactional(
|
|
|
661
658
|
if (isCollection) resourceOptions.isCollection = true;
|
|
662
659
|
} else resourceOptions = options;
|
|
663
660
|
const loadAsInstance = this.loadAsInstance;
|
|
664
|
-
if (
|
|
665
|
-
|
|
661
|
+
// Only join an existing transaction if it is still genuinely OPEN (mirrors the reuse check
|
|
662
|
+
// resources/transaction.ts's transaction() helper already applies to itself). A `context`
|
|
663
|
+
// object can carry a *stale* `.transaction` left over from an earlier, unrelated call that
|
|
664
|
+
// already ran to completion: ambient contexts obtained via contextStorage.getStore() are
|
|
665
|
+
// no longer guaranteed to be fresh, one-shot objects now that processLocalTransaction (#1591/
|
|
666
|
+
// #1592) installs one shared, long-lived context for the lifetime of an entire operation
|
|
667
|
+
// handler. Without this check, a second, logically-independent static Resource API call made
|
|
668
|
+
// later in the same handler (no explicit context of its own) would see that leftover
|
|
669
|
+
// `.transaction` reference and wrongly treat it as "still open," silently folding its write
|
|
670
|
+
// into a transaction whose commit lifecycle was already driven to completion by the first
|
|
671
|
+
// call — coalescing two unrelated writes and risking the first one being dropped. See
|
|
672
|
+
// harper-pro's replication/subscriptionManager.ts ensureNode(), which is called twice in
|
|
673
|
+
// sequence (once for this node, once for a peer) from a single add_node/add_node_back
|
|
674
|
+
// operation handler and reproduced this exact loss in a live cluster-formation test.
|
|
675
|
+
//
|
|
676
|
+
// A transaction that is no longer OPEN reached that state one of two ways, and they need
|
|
677
|
+
// opposite treatment:
|
|
678
|
+
// - committed: the prior call ran to completion normally. Safe to silently start a fresh
|
|
679
|
+
// transaction for this independent call (the case above).
|
|
680
|
+
// - aborted due to exceeding storage.maxTransactionOpenTime (#1411, DatabaseTransaction.ts
|
|
681
|
+
// abortDueToTimeout(), marked via the `timedOut` poison flag): the whole logical operation
|
|
682
|
+
// must fail atomically, not have this write quietly land on a brand-new transaction while
|
|
683
|
+
// the earlier write(s) were rolled back. Joining the poisoned transaction here (instead of
|
|
684
|
+
// starting fresh) makes the write throw transactionOpenTooLongError via addWrite()/commit()'s
|
|
685
|
+
// poison check, correctly propagating the abort to the caller. See
|
|
686
|
+
// integrationTests/resources/txn-overtime-atomicity.test.ts.
|
|
687
|
+
if (context?.transaction?.open === TRANSACTION_STATE.OPEN || context?.transaction?.timedOut) {
|
|
688
|
+
// we are already in a transaction (or it was poisoned by a timeout abort and must fail), proceed
|
|
666
689
|
const resource = this.getResource(query, context, resourceOptions);
|
|
667
690
|
return resource.then
|
|
668
691
|
? resource.then(authorizeActionOnResource)
|
|
@@ -683,6 +706,20 @@ function transactional(
|
|
|
683
706
|
);
|
|
684
707
|
}
|
|
685
708
|
function authorizeActionOnResource(resource: ResourceInterface) {
|
|
709
|
+
// For subscribe, register the resulting subscription for continuous re-authorization so it is
|
|
710
|
+
// terminated if the principal later loses access or its token expires (#1414). Non-subscribe
|
|
711
|
+
// actions run unchanged.
|
|
712
|
+
// 'subscribe' is the direct MQTT path; 'connect' is the SSE/WebSocket path (REST CONNECT) —
|
|
713
|
+
// both resolve to the same subscription iterable.
|
|
714
|
+
const isSubscribeAction = options.method === 'subscribe' || options.method === 'connect';
|
|
715
|
+
const runAction = (data: any) => {
|
|
716
|
+
const result = action(resource, query, context, data);
|
|
717
|
+
if (!isSubscribeAction) return result;
|
|
718
|
+
return when(result, (subscription: any) => {
|
|
719
|
+
registerLiveSubscriptionForContext(subscription, resource, query, context);
|
|
720
|
+
return subscription;
|
|
721
|
+
});
|
|
722
|
+
};
|
|
686
723
|
let checkPermission = false;
|
|
687
724
|
if (query.checkPermission) {
|
|
688
725
|
checkPermission = true;
|
|
@@ -697,26 +734,38 @@ function transactional(
|
|
|
697
734
|
if (checkPermission) {
|
|
698
735
|
if (loadAsInstance !== false) {
|
|
699
736
|
// do permission checks, with allow methods
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
? resource.
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
:
|
|
737
|
+
let allowed;
|
|
738
|
+
try {
|
|
739
|
+
allowed =
|
|
740
|
+
options.type === 'read'
|
|
741
|
+
? resource.allowRead(context.user, query, context)
|
|
742
|
+
: options.type === 'update'
|
|
743
|
+
? resource.doesExist?.() === false
|
|
744
|
+
? resource.allowCreate(context.user, data, context)
|
|
745
|
+
: resource.allowUpdate(context.user, data, context)
|
|
746
|
+
: options.type === 'create'
|
|
747
|
+
? resource.allowCreate(context.user, data, context)
|
|
748
|
+
: resource.allowDelete(context.user, query, context);
|
|
749
|
+
} catch {
|
|
750
|
+
// allow* threw — fail closed rather than letting the request proceed
|
|
751
|
+
throw new AccessViolation(context.user);
|
|
752
|
+
}
|
|
710
753
|
if ((allowed as any)?.then) {
|
|
711
|
-
return (allowed as any).then(
|
|
712
|
-
|
|
713
|
-
|
|
754
|
+
return (allowed as any).then(
|
|
755
|
+
(allowed) => {
|
|
756
|
+
query.checkPermission = false;
|
|
757
|
+
if (!allowed) {
|
|
758
|
+
throw new AccessViolation(context.user);
|
|
759
|
+
}
|
|
760
|
+
return when(data, (data) => {
|
|
761
|
+
return runAction(data);
|
|
762
|
+
});
|
|
763
|
+
},
|
|
764
|
+
() => {
|
|
765
|
+
// async allow* rejected — fail closed
|
|
714
766
|
throw new AccessViolation(context.user);
|
|
715
767
|
}
|
|
716
|
-
|
|
717
|
-
return action(resource, query, context, data);
|
|
718
|
-
});
|
|
719
|
-
});
|
|
768
|
+
);
|
|
720
769
|
}
|
|
721
770
|
query.checkPermission = false;
|
|
722
771
|
if (!allowed) {
|
|
@@ -725,11 +774,46 @@ function transactional(
|
|
|
725
774
|
}
|
|
726
775
|
}
|
|
727
776
|
return when(data, (data) => {
|
|
728
|
-
return
|
|
777
|
+
return runAction(data);
|
|
729
778
|
});
|
|
730
779
|
}
|
|
731
780
|
}
|
|
732
781
|
}
|
|
782
|
+
function registerLiveSubscriptionForContext(subscription: any, resource: any, query: any, context: Context) {
|
|
783
|
+
const user: any = context?.user;
|
|
784
|
+
const username = user?.username;
|
|
785
|
+
// Internal watchers, replication and local-bypass have no user principal — nothing to re-authorize.
|
|
786
|
+
if (!username) return;
|
|
787
|
+
const capturedId = query?.id;
|
|
788
|
+
const capturedIsCollection = query?.isCollection;
|
|
789
|
+
const capturedSelect = query?.select;
|
|
790
|
+
registerLiveSubscription({
|
|
791
|
+
subscription,
|
|
792
|
+
username,
|
|
793
|
+
// JWT exp of the bearer credential (set by the auth layer); undefined for password/mTLS/session.
|
|
794
|
+
authExpiresAt: user.authExpiresAt,
|
|
795
|
+
recheck: async () => {
|
|
796
|
+
// Re-fetch current user state — the user/role cache is rebuilt on mutations — so a dropped or
|
|
797
|
+
// role-stripped user no longer authorizes.
|
|
798
|
+
const { findAndValidateUser } = require('../security/user');
|
|
799
|
+
const fresh: any = await findAndValidateUser(username, undefined, false);
|
|
800
|
+
if (!fresh?.role) return false;
|
|
801
|
+
// Advance the subscription's context to the fresh user so downstream checks — context.user
|
|
802
|
+
// and getCurrentUser() (which reads the resource's context) — evaluate against current state,
|
|
803
|
+
// not the stale user captured at subscribe time.
|
|
804
|
+
if (context) (context as any).user = fresh;
|
|
805
|
+
// Re-run the same table/RBAC-level allowRead the subscription was granted with, against the
|
|
806
|
+
// fresh user. No per-record evaluation — this matches how access was originally granted.
|
|
807
|
+
const reTarget: any = new RequestTarget();
|
|
808
|
+
reTarget.id = capturedId;
|
|
809
|
+
reTarget.isCollection = capturedIsCollection;
|
|
810
|
+
reTarget.select = capturedSelect;
|
|
811
|
+
reTarget.checkPermission = fresh.role?.permission;
|
|
812
|
+
return !!(await resource.allowRead(fresh, reTarget, context));
|
|
813
|
+
},
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
|
|
733
817
|
const KNOWN_METHODS = ['get', 'head', 'put', 'post', 'delete', 'patch', 'query', 'move', 'copy'];
|
|
734
818
|
type ClientErrorWithMethods = ClientError & { allow: string[]; method: string };
|
|
735
819
|
export function missingMethod(resource: any, method: string) {
|
|
@@ -49,7 +49,6 @@ export interface ResourceInterface<Record extends object = any>
|
|
|
49
49
|
subscribe?(request: SubscriptionRequest): AsyncIterable<Record> | Promise<AsyncIterable<Record>>;
|
|
50
50
|
|
|
51
51
|
doesExist(): boolean;
|
|
52
|
-
wasLoadedFromSource(): boolean | void;
|
|
53
52
|
|
|
54
53
|
getCurrentUser(): User | undefined;
|
|
55
54
|
}
|
|
@@ -95,7 +94,6 @@ export interface Context {
|
|
|
95
94
|
sourceApply?: boolean;
|
|
96
95
|
originatingOperation?: OperationFunctionName;
|
|
97
96
|
previousResidency?: string[];
|
|
98
|
-
loadedFromSource?: boolean;
|
|
99
97
|
nodeName?: string;
|
|
100
98
|
resourceCache?: Map<Id, any>;
|
|
101
99
|
_freezeRecords?: boolean; // until v5, we conditionally freeze records for back-compat
|
|
@@ -177,6 +175,12 @@ interface TypedDirectCondition<Record extends object, Property extends keyof Rec
|
|
|
177
175
|
* full scan unless paired with another indexed condition.
|
|
178
176
|
*/
|
|
179
177
|
negated?: boolean;
|
|
178
|
+
/**
|
|
179
|
+
* Internal (#1241): a `(primaryKey) => boolean` predicate composed by the query executor from
|
|
180
|
+
* companion conditions, a caller `vectorFilter`, and record-level RBAC, pushed into a filterable
|
|
181
|
+
* custom index (HNSW) so filtering happens during traversal. Not part of the public query surface.
|
|
182
|
+
*/
|
|
183
|
+
recordFilter?: (primaryKey: Id) => boolean;
|
|
180
184
|
}
|
|
181
185
|
|
|
182
186
|
interface ConditionGroup<Record extends object = any> {
|
package/resources/Resources.ts
CHANGED
|
@@ -17,9 +17,7 @@ export interface ResourceEntry {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export type RouteSegment =
|
|
20
|
-
| { type: '
|
|
21
|
-
| { type: 'param'; value: string }
|
|
22
|
-
| { type: 'wildcard'; value: string };
|
|
20
|
+
{ type: 'static'; value: string } | { type: 'param'; value: string } | { type: 'wildcard'; value: string };
|
|
23
21
|
|
|
24
22
|
export interface CompiledRoute {
|
|
25
23
|
/** Normalized pattern (leading/trailing slashes stripped) — used as the identity key for the route. */
|