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
|
@@ -16,6 +16,37 @@ export type ProgressListener = (event: ProgressEvent) => void;
|
|
|
16
16
|
export class ProgressEmitter {
|
|
17
17
|
private listeners: ProgressListener[] = [];
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Set by {@link createSSEResponseStream} to a signal that aborts when the client
|
|
21
|
+
* disconnects. Open-ended operations (e.g. a live log tail that never returns on its own)
|
|
22
|
+
* read this to stop producing events and resolve, instead of running until process exit.
|
|
23
|
+
*/
|
|
24
|
+
signal?: AbortSignal;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Backpressure signal for open-ended producers. {@link createSSEResponseStream} sets this
|
|
28
|
+
* `true` when the underlying SSE stream's write buffer is full and clears it on `drain`. A
|
|
29
|
+
* producer that can outrun a slow client (e.g. a log tail on a busy file) should await
|
|
30
|
+
* {@link whenWritable} before emitting more, so buffered frames can't grow without bound.
|
|
31
|
+
* Bounded producers (deploy) simply never check it.
|
|
32
|
+
*/
|
|
33
|
+
paused = false;
|
|
34
|
+
private drainWaiters: Array<() => void> = [];
|
|
35
|
+
|
|
36
|
+
/** Resolves once the SSE stream can accept more writes (immediately when not paused). */
|
|
37
|
+
whenWritable(): Promise<void> {
|
|
38
|
+
if (!this.paused) return Promise.resolve();
|
|
39
|
+
return new Promise((resolve) => this.drainWaiters.push(resolve));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Called by the SSE wrapper on `drain` (and on teardown) to release awaiting producers. */
|
|
43
|
+
resume(): void {
|
|
44
|
+
this.paused = false;
|
|
45
|
+
const waiters = this.drainWaiters;
|
|
46
|
+
this.drainWaiters = [];
|
|
47
|
+
for (const waiter of waiters) waiter();
|
|
48
|
+
}
|
|
49
|
+
|
|
19
50
|
emit(event: string, data: unknown): void {
|
|
20
51
|
// Snapshot before iteration so a listener that unsubscribes itself during dispatch
|
|
21
52
|
// doesn't shift indexes underneath us.
|
|
@@ -54,19 +85,32 @@ export function createSSEResponseStream(emitter: ProgressEmitter, operation: ()
|
|
|
54
85
|
// that consumers ignore, so it's safe filler.
|
|
55
86
|
stream.write(`: stream open\n\n`);
|
|
56
87
|
|
|
88
|
+
// Give the operation a way to observe client disconnect. `cleanup` runs when the stream
|
|
89
|
+
// closes/ends (and after the operation settles); aborting there lets an open-ended
|
|
90
|
+
// operation like a log tail stop and resolve rather than leak until the process exits.
|
|
91
|
+
const abortController = new AbortController();
|
|
92
|
+
emitter.signal = abortController.signal;
|
|
93
|
+
|
|
57
94
|
let active = true;
|
|
58
95
|
let errorEmitted = false;
|
|
59
96
|
const unsubscribe = emitter.subscribe((event) => {
|
|
60
97
|
if (active) {
|
|
61
|
-
|
|
98
|
+
// A `false` return means the stream's buffer is over its high-water mark; flag it so
|
|
99
|
+
// producers that check `whenWritable()` back off until the 'drain' below clears it.
|
|
100
|
+
const canWriteMore = writeSSE(stream, event);
|
|
101
|
+
if (!canWriteMore) emitter.paused = true;
|
|
62
102
|
if (event.event === 'error') errorEmitted = true;
|
|
63
103
|
}
|
|
64
104
|
});
|
|
105
|
+
stream.on('drain', () => emitter.resume());
|
|
65
106
|
|
|
66
107
|
const cleanup = () => {
|
|
67
108
|
if (active) {
|
|
68
109
|
active = false;
|
|
69
110
|
unsubscribe();
|
|
111
|
+
abortController.abort();
|
|
112
|
+
// Release any producer awaiting drain so the operation can settle instead of hanging.
|
|
113
|
+
emitter.resume();
|
|
70
114
|
}
|
|
71
115
|
};
|
|
72
116
|
|
|
@@ -100,11 +144,17 @@ export function createSSEResponseStream(emitter: ProgressEmitter, operation: ()
|
|
|
100
144
|
return stream;
|
|
101
145
|
}
|
|
102
146
|
|
|
103
|
-
|
|
147
|
+
/**
|
|
148
|
+
* Write one SSE record; returns `false` if any of its writes pushed the buffer past its
|
|
149
|
+
* high-water mark (so a dense multi-line payload that tips the buffer on an early `data:`
|
|
150
|
+
* line is still detected, not just the final `\n`). `stream.write(...)` is always the left
|
|
151
|
+
* operand so every line is written regardless of the accumulated backpressure flag.
|
|
152
|
+
*/
|
|
153
|
+
function writeSSE(stream: PassThrough, event: ProgressEvent): boolean {
|
|
104
154
|
const data = typeof event.data === 'string' ? event.data : JSON.stringify(event.data);
|
|
105
|
-
stream.write(`event: ${event.event}\n`);
|
|
155
|
+
let canWrite = stream.write(`event: ${event.event}\n`);
|
|
106
156
|
for (const line of data.split(/\r?\n/)) {
|
|
107
|
-
stream.write(`data: ${line}\n`);
|
|
157
|
+
canWrite = stream.write(`data: ${line}\n`) && canWrite;
|
|
108
158
|
}
|
|
109
|
-
stream.write('\n');
|
|
159
|
+
return stream.write('\n') && canWrite;
|
|
110
160
|
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-thread bridge for `server.registerOperation()` (#1736).
|
|
3
|
+
*
|
|
4
|
+
* Components (jsResource `resources.js`, Plugin API `handleApplication`) load per-worker, so a
|
|
5
|
+
* `registerOperation()` call made there lands in that worker's module-local OPERATION_FUNCTION_MAP.
|
|
6
|
+
* The ops-API HTTP dispatcher runs only on the main thread and reads the main thread's copy of
|
|
7
|
+
* that map, so such operations were unreachable ("Operation '<name>' not found") for every caller.
|
|
8
|
+
*
|
|
9
|
+
* The bridge mirrors the RESOURCE_OPENAPI request/response pattern (operationsServer.ts /
|
|
10
|
+
* itc/serverHandlers.js), with one deliberate difference: executing an operation is side-effecting,
|
|
11
|
+
* so a request is sent to exactly ONE registering worker (never broadcast-first-wins).
|
|
12
|
+
*
|
|
13
|
+
* - Worker: `registerOperation()` announces the name (OPERATION_REGISTERED) to all threads;
|
|
14
|
+
* only the main thread records it, as name -> Set<threadId>.
|
|
15
|
+
* - Main: on an OPERATION_FUNCTION_MAP miss, `getRemoteOperationFunction()` supplies a forwarding
|
|
16
|
+
* function that sends the request body (OPERATION_EXECUTE_REQUEST) to one live registering
|
|
17
|
+
* worker and awaits the correlated OPERATION_EXECUTE_RESPONSE.
|
|
18
|
+
* - Worker: executes through the normal `chooseOperation` + `processLocalTransaction` path, so
|
|
19
|
+
* permission checks run where the operation function (and its metadata) actually exists. The
|
|
20
|
+
* main thread performs authentication only and forwards the resolved `hdb_user`.
|
|
21
|
+
*/
|
|
22
|
+
import { isMainThread, threadId } from 'node:worker_threads';
|
|
23
|
+
import { Readable } from 'node:stream';
|
|
24
|
+
import * as terms from '../../utility/hdbTerms.ts';
|
|
25
|
+
import * as env from '../../utility/environment/environmentManager.ts';
|
|
26
|
+
import harperLogger from '../../utility/logging/harper_logger.ts';
|
|
27
|
+
import { ServerError } from '../../utility/errors/hdbError.ts';
|
|
28
|
+
import { sendItcEvent } from '../threads/itc.js';
|
|
29
|
+
import { onMessageByType, onThreadExit } from '../threads/manageThreads.js';
|
|
30
|
+
|
|
31
|
+
const operationLog = harperLogger.loggerWithTag('operation');
|
|
32
|
+
|
|
33
|
+
// The connected-ports array with `sendToThread` — assigned to the `threads` global by
|
|
34
|
+
// manageThreads via _assignPackageExport (the same access pattern as itc/serverHandlers.js;
|
|
35
|
+
// the globals.js `exports.threads` binding is reassigned after load, so it can't be imported).
|
|
36
|
+
declare const threads: { sendToThread(threadId: number, message: any): boolean };
|
|
37
|
+
|
|
38
|
+
// Fields attached to a request body by the HTTP/auth layer that must not (and often cannot)
|
|
39
|
+
// cross the thread boundary via structured clone.
|
|
40
|
+
const NON_FORWARDABLE_FIELDS = ['baseRequest', 'baseResponse', 'fastifyResponse', 'progress', 'parsed_sql_object'];
|
|
41
|
+
|
|
42
|
+
// Bound how long the main thread waits for a worker to finish a forwarded operation. Past the
|
|
43
|
+
// operations-API connection timeout the client socket is gone anyway; this just prevents a
|
|
44
|
+
// wedged-but-alive worker from leaking pending forwards forever.
|
|
45
|
+
const EXECUTE_TIMEOUT_MS = env.get(terms.CONFIG_PARAMS.OPERATIONSAPI_NETWORK_TIMEOUT) || 120_000;
|
|
46
|
+
|
|
47
|
+
// Dispatch functions injected by serverUtilities at its module load (it statically imports this
|
|
48
|
+
// module, so a plain import here would be a cycle; a runtime require of a .ts path doesn't
|
|
49
|
+
// survive the dist build). A worker can only receive an execute request after announcing a
|
|
50
|
+
// registration — which goes through serverUtilities — so these are always set on that path.
|
|
51
|
+
let localDispatch: {
|
|
52
|
+
chooseOperation: (body: any) => Function;
|
|
53
|
+
processLocalTransaction: (req: any, operationFunction: Function) => Promise<any>;
|
|
54
|
+
};
|
|
55
|
+
export function setLocalOperationDispatch(dispatch: typeof localDispatch) {
|
|
56
|
+
localDispatch = dispatch;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** name -> threadIds of workers that registered it (main thread only) */
|
|
60
|
+
const registeredByWorker = new Map<string, Set<number>>();
|
|
61
|
+
const pendingExecutions = new Map<
|
|
62
|
+
number,
|
|
63
|
+
{ targetThreadId: number; resolve: (result: any) => void; reject: (error: Error) => void }
|
|
64
|
+
>();
|
|
65
|
+
let nextRequestId = 1;
|
|
66
|
+
let mainListenersAttached = false;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Worker side: announce a registration so the main thread can forward calls here. Fire-and-forget —
|
|
70
|
+
* a lost announcement just means the op stays unreachable (the pre-#1736 status quo), and the
|
|
71
|
+
* broadcast has its own ack timeout.
|
|
72
|
+
*/
|
|
73
|
+
export function announceRegisteredOperation(name: string) {
|
|
74
|
+
if (isMainThread) return;
|
|
75
|
+
sendItcEvent({
|
|
76
|
+
type: terms.ITC_EVENT_TYPES.OPERATION_REGISTERED,
|
|
77
|
+
message: { name },
|
|
78
|
+
}).catch((error) => operationLog.error(`Failed to announce registered operation '${name}'`, error));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* ITC handler (all threads receive the broadcast; only main records it).
|
|
83
|
+
*/
|
|
84
|
+
export function operationRegisteredHandler(event: { message: { name: string; originator: number } }) {
|
|
85
|
+
if (!isMainThread) return;
|
|
86
|
+
const { name, originator } = event.message;
|
|
87
|
+
if (typeof name !== 'string' || typeof originator !== 'number') return;
|
|
88
|
+
let workerIds = registeredByWorker.get(name);
|
|
89
|
+
if (!workerIds) registeredByWorker.set(name, (workerIds = new Set()));
|
|
90
|
+
workerIds.add(originator);
|
|
91
|
+
operationLog.debug(`Registered operation '${name}' announced by worker thread ${originator}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let rotation = 0;
|
|
95
|
+
/**
|
|
96
|
+
* Main-thread dispatch fallback: if a worker registered `name`, return a forwarding operation
|
|
97
|
+
* function for it; otherwise undefined (caller falls through to the usual "not found" error).
|
|
98
|
+
* Permission checks are intentionally NOT run on the main thread for forwarded operations — the
|
|
99
|
+
* function and metadata that `verifyPerms` needs only exist on the worker, which runs the full
|
|
100
|
+
* `chooseOperation` check with the forwarded `hdb_user`.
|
|
101
|
+
*/
|
|
102
|
+
export function getRemoteOperationFunction(name: string): ((body: any) => Promise<any>) | undefined {
|
|
103
|
+
if (!isMainThread) return undefined;
|
|
104
|
+
if (!registeredByWorker.has(name)) return undefined;
|
|
105
|
+
return (body: any) => executeRemoteOperation(name, body);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function attachMainListeners() {
|
|
109
|
+
if (mainListenersAttached) return;
|
|
110
|
+
mainListenersAttached = true;
|
|
111
|
+
onMessageByType(terms.ITC_EVENT_TYPES.OPERATION_EXECUTE_RESPONSE, (event: any) => {
|
|
112
|
+
const { requestId, originator, result, error } = event.message ?? {};
|
|
113
|
+
const pending = pendingExecutions.get(requestId);
|
|
114
|
+
if (!pending) return;
|
|
115
|
+
// Defense-in-depth: only the worker the request was sent to may settle it.
|
|
116
|
+
if (originator !== pending.targetThreadId) return;
|
|
117
|
+
pendingExecutions.delete(requestId);
|
|
118
|
+
if (error) pending.reject(new ServerError(error.message, error.statusCode || 500));
|
|
119
|
+
else pending.resolve(result);
|
|
120
|
+
});
|
|
121
|
+
// A worker that dies mid-execution can never respond; fail its in-flight forwards rather
|
|
122
|
+
// than waiting out the timeout, and forget its registrations (a replacement worker
|
|
123
|
+
// re-registers on component load).
|
|
124
|
+
onThreadExit((deadThreadId: number) => {
|
|
125
|
+
for (const [name, workerIds] of registeredByWorker) {
|
|
126
|
+
workerIds.delete(deadThreadId);
|
|
127
|
+
if (workerIds.size === 0) registeredByWorker.delete(name);
|
|
128
|
+
}
|
|
129
|
+
for (const [requestId, pending] of pendingExecutions) {
|
|
130
|
+
if (pending.targetThreadId === deadThreadId) {
|
|
131
|
+
pendingExecutions.delete(requestId);
|
|
132
|
+
pending.reject(new ServerError('The worker thread executing this operation exited', 503));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function executeRemoteOperation(name: string, body: any): Promise<any> {
|
|
139
|
+
attachMainListeners();
|
|
140
|
+
const workerIds = registeredByWorker.get(name);
|
|
141
|
+
const forwardBody = { ...body };
|
|
142
|
+
for (const field of NON_FORWARDABLE_FIELDS) delete forwardBody[field];
|
|
143
|
+
|
|
144
|
+
// Pick one live registering worker (rotating for spread), pruning ids whose port is gone.
|
|
145
|
+
// sendToThread returning false means the worker exited; try the next one.
|
|
146
|
+
while (workerIds?.size) {
|
|
147
|
+
const candidates = [...workerIds];
|
|
148
|
+
const targetThreadId = candidates[rotation++ % candidates.length];
|
|
149
|
+
const requestId = nextRequestId++;
|
|
150
|
+
let sent;
|
|
151
|
+
try {
|
|
152
|
+
sent = threads.sendToThread(targetThreadId, {
|
|
153
|
+
type: terms.ITC_EVENT_TYPES.OPERATION_EXECUTE_REQUEST,
|
|
154
|
+
message: { requestId, body: forwardBody, originator: threadId },
|
|
155
|
+
});
|
|
156
|
+
} catch (error) {
|
|
157
|
+
// Structured-clone failure (e.g. a function or native handle on the request body) is a
|
|
158
|
+
// server-side limitation, not a malformed client request — 500, not 400. No other worker
|
|
159
|
+
// will fare better with the same body.
|
|
160
|
+
operationLog.error(`Failed to forward operation '${name}' to worker thread ${targetThreadId}`, error);
|
|
161
|
+
throw new ServerError(
|
|
162
|
+
`Operation '${name}' request could not be forwarded to a worker thread: ${error.message}`,
|
|
163
|
+
500
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
if (!sent) {
|
|
167
|
+
workerIds.delete(targetThreadId);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
return new Promise((promiseResolve, promiseReject) => {
|
|
171
|
+
const timer = setTimeout(() => {
|
|
172
|
+
pendingExecutions.delete(requestId);
|
|
173
|
+
promiseReject(new ServerError(`Timed out waiting for worker thread to execute operation '${name}'`, 503));
|
|
174
|
+
}, EXECUTE_TIMEOUT_MS);
|
|
175
|
+
timer.unref();
|
|
176
|
+
pendingExecutions.set(requestId, {
|
|
177
|
+
targetThreadId,
|
|
178
|
+
resolve(result) {
|
|
179
|
+
clearTimeout(timer);
|
|
180
|
+
promiseResolve(result);
|
|
181
|
+
},
|
|
182
|
+
reject(error) {
|
|
183
|
+
clearTimeout(timer);
|
|
184
|
+
promiseReject(error);
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
if (registeredByWorker.get(name)?.size === 0) registeredByWorker.delete(name);
|
|
190
|
+
throw new ServerError(
|
|
191
|
+
`Operation '${name}' is registered by a component but no worker thread is available to run it`,
|
|
192
|
+
503
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* ITC handler, worker side: execute a forwarded operation through the normal dispatch path
|
|
198
|
+
* (permission check + processLocalTransaction) and send the result back to the main thread.
|
|
199
|
+
*/
|
|
200
|
+
export async function operationExecuteRequestHandler(event: {
|
|
201
|
+
message: { requestId: number; body: any; originator: number };
|
|
202
|
+
}) {
|
|
203
|
+
const { requestId, body, originator } = event.message;
|
|
204
|
+
let response;
|
|
205
|
+
try {
|
|
206
|
+
if (!localDispatch) throw new ServerError('This worker thread cannot execute operations', 503);
|
|
207
|
+
// bypass_auth is trusted as forwarded: the main thread already strips it from external
|
|
208
|
+
// HTTP requests (handlePostRequest, before any dispatch decision), and an internal caller
|
|
209
|
+
// legitimately setting it (server.operation(op, context, false)) must see the same
|
|
210
|
+
// authorize:false behavior whether the operation happens to run locally or via this bridge.
|
|
211
|
+
// ITC is an internal, same-process trust boundary — a worker able to forge this message
|
|
212
|
+
// already has direct DB access and gains nothing by spoofing bypass_auth.
|
|
213
|
+
const operationFunction = localDispatch.chooseOperation(body);
|
|
214
|
+
const result = await localDispatch.processLocalTransaction({ body }, operationFunction);
|
|
215
|
+
if (result instanceof Readable || typeof result?.pipe === 'function') {
|
|
216
|
+
// Streaming results would need MessagePort transfer plumbing — explicitly unsupported
|
|
217
|
+
// for worker-registered operations for now (#1736), rather than failing opaquely.
|
|
218
|
+
// The handler already ran and the stream may hold an open fd/cursor/socket; destroy it
|
|
219
|
+
// rather than abandoning it to GC finalization.
|
|
220
|
+
result.destroy?.();
|
|
221
|
+
throw new ServerError(
|
|
222
|
+
`Operation '${body.operation}' returned a stream; streaming results are not supported for operations registered from a component`,
|
|
223
|
+
501
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
response = { requestId, result };
|
|
227
|
+
} catch (error) {
|
|
228
|
+
response = {
|
|
229
|
+
requestId,
|
|
230
|
+
error: {
|
|
231
|
+
message: error.http_resp_msg?.error ?? error.http_resp_msg ?? error.message,
|
|
232
|
+
statusCode: error.statusCode,
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
sendExecuteResponse(originator, response);
|
|
238
|
+
} catch (error) {
|
|
239
|
+
// The result itself failed to structured-clone; report that instead of leaving the main
|
|
240
|
+
// thread to time out.
|
|
241
|
+
operationLog.error(`Failed to send result of operation '${body?.operation}' back to the main thread`, error);
|
|
242
|
+
sendExecuteResponse(originator, {
|
|
243
|
+
requestId,
|
|
244
|
+
error: {
|
|
245
|
+
message: `Operation '${body?.operation}' result could not be returned across threads: ${error.message}`,
|
|
246
|
+
statusCode: 500,
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function sendExecuteResponse(requestOriginator: number, message: any) {
|
|
253
|
+
// Stamp this worker's threadId so the main thread can verify the response came from the
|
|
254
|
+
// worker the request was actually sent to.
|
|
255
|
+
message.originator = threadId;
|
|
256
|
+
if (!threads.sendToThread(requestOriginator, { type: terms.ITC_EVENT_TYPES.OPERATION_EXECUTE_RESPONSE, message })) {
|
|
257
|
+
operationLog.trace(`Dropping operation execute response for request ${message.requestId}: main thread unreachable`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
@@ -25,11 +25,17 @@ const { applyImpersonation } = require('../../security/impersonation.ts');
|
|
|
25
25
|
const { createGzip, constants } = require('zlib');
|
|
26
26
|
const { ProgressEmitter, createSSEResponseStream } = require('./progressEmitter.ts');
|
|
27
27
|
|
|
28
|
-
// Operations that support `Accept: text/event-stream` for live
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
|
|
28
|
+
// Operations that support `Accept: text/event-stream` for live streaming. The handler
|
|
29
|
+
// attaches a ProgressEmitter as req.body.progress so the operation can emit events; the
|
|
30
|
+
// response body is the SSE-encoded emitter output. Non-SSE clients see the historical
|
|
31
|
+
// single-response shape because progress is undefined on that path. deploy_component /
|
|
32
|
+
// get_deployment stream a bounded run and end; read_log tails the log and stays open until
|
|
33
|
+
// the client disconnects (the emitter's abort signal), so subscribers see new lines live.
|
|
34
|
+
const SSE_PROGRESS_OPERATIONS = new Set([
|
|
35
|
+
terms.OPERATIONS_ENUM.DEPLOY_COMPONENT,
|
|
36
|
+
terms.OPERATIONS_ENUM.GET_DEPLOYMENT,
|
|
37
|
+
terms.OPERATIONS_ENUM.READ_LOG,
|
|
38
|
+
]);
|
|
33
39
|
|
|
34
40
|
const NO_AUTH_OPERATIONS = [
|
|
35
41
|
terms.OPERATIONS_ENUM.CREATE_AUTHENTICATION_TOKENS,
|
|
@@ -37,6 +37,14 @@ import type { Context } from '../../resources/ResourceInterface.ts';
|
|
|
37
37
|
import * as status from '../status/index.ts';
|
|
38
38
|
import * as regDeprecated from '../../resources/registrationDeprecated.ts';
|
|
39
39
|
import * as deploymentOperations from '../../components/deploymentOperations.ts';
|
|
40
|
+
import * as secretOperations from '../../components/secretOperations.ts';
|
|
41
|
+
import { contextStorage } from '../../resources/transaction.ts';
|
|
42
|
+
import { isMainThread } from 'node:worker_threads';
|
|
43
|
+
import {
|
|
44
|
+
announceRegisteredOperation,
|
|
45
|
+
getRemoteOperationFunction,
|
|
46
|
+
setLocalOperationDispatch,
|
|
47
|
+
} from './registeredOperations.ts';
|
|
40
48
|
|
|
41
49
|
const pSearchSearch = util.promisify(search.search);
|
|
42
50
|
let pEvaluateSql: (sql: string) => Promise<any>;
|
|
@@ -75,17 +83,37 @@ export async function processLocalTransaction(req: OperationRequest, operationFu
|
|
|
75
83
|
harperLogger.logLevel === terms.LOG_LEVELS.DEBUG ||
|
|
76
84
|
harperLogger.logLevel === terms.LOG_LEVELS.TRACE)
|
|
77
85
|
) {
|
|
78
|
-
// Need to remove auth variables, but we don't want to create
|
|
79
|
-
// the logging is actually going to happen.
|
|
86
|
+
// Need to remove auth variables and secret-bearing fields, but we don't want to create
|
|
87
|
+
// an object unless the logging is actually going to happen. registryAuth carries a
|
|
88
|
+
// transient private registry token on deploy_component; value/values carry .env secrets
|
|
89
|
+
// from set_env_value; value/envelope carry secrets from set_secret — none may reach the
|
|
90
|
+
// operations log.
|
|
80
91
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
81
|
-
const { hdb_user, hdbAuthHeader, password, payload, ...cleanBody } =
|
|
92
|
+
const { hdb_user, hdbAuthHeader, password, payload, registryAuth, value, values, envelope, ...cleanBody } =
|
|
93
|
+
req.body;
|
|
82
94
|
operationLog.info(cleanBody);
|
|
83
95
|
}
|
|
84
96
|
} catch (e) {
|
|
85
97
|
operationLog.error(e);
|
|
86
98
|
}
|
|
87
99
|
|
|
88
|
-
|
|
100
|
+
// Bridge the authenticated user into the ambient async context so static Resource API calls
|
|
101
|
+
// (e.g. table.put) inside operation handlers inherit user attribution for audit records
|
|
102
|
+
// (issue #1591). An explicit context passed by a handler still takes precedence (the
|
|
103
|
+
// transactional wrappers only fall back to contextStorage when no context is provided), and
|
|
104
|
+
// an existing ambient context already carrying this user (e.g. server.operation() called
|
|
105
|
+
// from within a request handler) is preserved rather than shadowed. When the ambient user
|
|
106
|
+
// differs (or is absent), the ambient context is merged rather than replaced: other ambient
|
|
107
|
+
// properties (open transaction, signal, caches) are preserved so atomicity is unaffected and
|
|
108
|
+
// only the user is swapped for attribution; the outer context object itself is never mutated.
|
|
109
|
+
const hdbUser = req.body.hdb_user;
|
|
110
|
+
const currentStore = contextStorage.getStore();
|
|
111
|
+
const callOperationFunction = () =>
|
|
112
|
+
operationFunctionCaller.callOperationFunctionAsAwait(operationFunction, req.body, null);
|
|
113
|
+
let data =
|
|
114
|
+
hdbUser && currentStore?.user !== hdbUser
|
|
115
|
+
? await contextStorage.run({ ...currentStore, user: hdbUser }, callOperationFunction)
|
|
116
|
+
: await callOperationFunction();
|
|
89
117
|
|
|
90
118
|
if (typeof data !== 'object') {
|
|
91
119
|
data = { message: data };
|
|
@@ -113,6 +141,15 @@ export type OperationDefinition = {
|
|
|
113
141
|
httpMethod?: 'DELETE' | 'GET' | 'HEAD' | 'OPTIONS' | 'PATCH' | 'POST' | 'PUT' | 'TRACE'; // method to use for REST
|
|
114
142
|
isJob?: boolean;
|
|
115
143
|
parametersSchema?: any[];
|
|
144
|
+
// When set, the operation declares its authorization requirement to the central verifyPerms
|
|
145
|
+
// system so it participates in the role `operations` allowlist (grantable to a scoped role)
|
|
146
|
+
// instead of a hand-rolled inline check. Tri-state:
|
|
147
|
+
// `true` = super_user by default (grantable to a scoped role via the `operations` allowlist);
|
|
148
|
+
// `false` = grantable AND open to ANY authenticated user with a valid role — it registers an
|
|
149
|
+
// empty-perms entry, which verifyPerms treats as "no specific permission required"
|
|
150
|
+
// and allows; use deliberately, it is NOT a "still locked down" default;
|
|
151
|
+
// omit = pre-existing behavior (no central entry; the caller enforces its own auth, if any).
|
|
152
|
+
requiresSuperUser?: boolean;
|
|
116
153
|
};
|
|
117
154
|
|
|
118
155
|
/**
|
|
@@ -120,7 +157,24 @@ export type OperationDefinition = {
|
|
|
120
157
|
* @param operationDefinition
|
|
121
158
|
*/
|
|
122
159
|
server.registerOperation = (operationDefinition: OperationDefinition) => {
|
|
123
|
-
|
|
160
|
+
const { name, execute, requiresSuperUser } = operationDefinition;
|
|
161
|
+
let handler = execute;
|
|
162
|
+
if (requiresSuperUser !== undefined) {
|
|
163
|
+
// verifyPerms keys requiredPermissions by the handler's function `.name`, but registered ops
|
|
164
|
+
// are typically anonymous arrows (all named "execute") which collide and can't be keyed. Wrap
|
|
165
|
+
// in a FRESH function named after the op so the lookup resolves the right entry. Wrap rather
|
|
166
|
+
// than rename `execute` in place: renaming would mutate a handler shared across two op names,
|
|
167
|
+
// causing the first op to be checked against the second op's permission entry. Forward all
|
|
168
|
+
// args (transparent pass-through) so the wrapper never changes the handler's call contract.
|
|
169
|
+
handler = (...args: any[]) => (execute as any)(...args);
|
|
170
|
+
Object.defineProperty(handler, 'name', { value: name, configurable: true });
|
|
171
|
+
opAuth.registerOperationPermission(name, { requiresSu: requiresSuperUser });
|
|
172
|
+
}
|
|
173
|
+
OPERATION_FUNCTION_MAP.set(name as any, new OperationFunctionObject(handler));
|
|
174
|
+
// Components load per-worker, so a registration made there is invisible to the main-thread
|
|
175
|
+
// ops-API dispatcher (each thread has its own OPERATION_FUNCTION_MAP instance). Announce it
|
|
176
|
+
// so the main thread can forward calls to this worker (#1736).
|
|
177
|
+
if (!isMainThread) announceRegisteredOperation(name);
|
|
124
178
|
};
|
|
125
179
|
|
|
126
180
|
export function chooseOperation(json: OperationRequestBody) {
|
|
@@ -128,6 +182,15 @@ export function chooseOperation(json: OperationRequestBody) {
|
|
|
128
182
|
try {
|
|
129
183
|
getOpResult = getOperationFunction(json);
|
|
130
184
|
} catch (err) {
|
|
185
|
+
// Not in this thread's map — a component may have registered it on a worker thread
|
|
186
|
+
// (components load per-worker). Forward there for execution; the worker runs the
|
|
187
|
+
// full permission check (with the forwarded hdb_user) where the operation function
|
|
188
|
+
// and its metadata actually exist, so no perm check is skipped by returning early
|
|
189
|
+
// here (#1736). Workers never re-forward, so an unknown op can't loop.
|
|
190
|
+
if (isMainThread) {
|
|
191
|
+
const remoteOperationFunction = getRemoteOperationFunction(json.operation);
|
|
192
|
+
if (remoteOperationFunction) return remoteOperationFunction;
|
|
193
|
+
}
|
|
131
194
|
operationLog.error(`Error when selecting operation function - ${err}`);
|
|
132
195
|
throw err;
|
|
133
196
|
}
|
|
@@ -210,6 +273,11 @@ export function getOperationFunction(json: OperationRequestBody): OperationFunct
|
|
|
210
273
|
);
|
|
211
274
|
}
|
|
212
275
|
|
|
276
|
+
// Give the cross-thread bridge (#1736) the worker-side dispatch path for forwarded operations.
|
|
277
|
+
// Injected (rather than imported there) because registeredOperations is imported above — a
|
|
278
|
+
// static import back into this module would be a cycle.
|
|
279
|
+
setLocalOperationDispatch({ chooseOperation, processLocalTransaction });
|
|
280
|
+
|
|
213
281
|
_assignPackageExport('operation', operation);
|
|
214
282
|
/**
|
|
215
283
|
* Standalone function to execute an operation
|
|
@@ -403,6 +471,12 @@ function initializeOperationFunctionMap(): Map<OperationFunctionName, OperationF
|
|
|
403
471
|
terms.OPERATIONS_ENUM.SET_COMPONENT_FILE,
|
|
404
472
|
new OperationFunctionObject(customFunctionOperations.setComponentFile)
|
|
405
473
|
);
|
|
474
|
+
opFuncMap.set(terms.OPERATIONS_ENUM.GET_ENV_KEYS, new OperationFunctionObject(customFunctionOperations.getEnvKeys));
|
|
475
|
+
opFuncMap.set(terms.OPERATIONS_ENUM.SET_ENV_VALUE, new OperationFunctionObject(customFunctionOperations.setEnvValue));
|
|
476
|
+
opFuncMap.set(
|
|
477
|
+
terms.OPERATIONS_ENUM.DELETE_ENV_VALUE,
|
|
478
|
+
new OperationFunctionObject(customFunctionOperations.deleteEnvValue)
|
|
479
|
+
);
|
|
406
480
|
opFuncMap.set(
|
|
407
481
|
terms.OPERATIONS_ENUM.DROP_COMPONENT,
|
|
408
482
|
new OperationFunctionObject(customFunctionOperations.dropComponent)
|
|
@@ -455,6 +529,15 @@ function initializeOperationFunctionMap(): Map<OperationFunctionName, OperationF
|
|
|
455
529
|
terms.OPERATIONS_ENUM.GET_DEPLOYMENT,
|
|
456
530
|
new OperationFunctionObject(deploymentOperations.handleGetDeployment)
|
|
457
531
|
);
|
|
532
|
+
opFuncMap.set(terms.OPERATIONS_ENUM.SET_SECRET, new OperationFunctionObject(secretOperations.setSecret));
|
|
533
|
+
opFuncMap.set(terms.OPERATIONS_ENUM.GRANT_SECRET, new OperationFunctionObject(secretOperations.grantSecret));
|
|
534
|
+
opFuncMap.set(terms.OPERATIONS_ENUM.REVOKE_SECRET, new OperationFunctionObject(secretOperations.revokeSecret));
|
|
535
|
+
opFuncMap.set(terms.OPERATIONS_ENUM.LIST_SECRETS, new OperationFunctionObject(secretOperations.listSecrets));
|
|
536
|
+
opFuncMap.set(terms.OPERATIONS_ENUM.DELETE_SECRET, new OperationFunctionObject(secretOperations.deleteSecret));
|
|
537
|
+
opFuncMap.set(
|
|
538
|
+
terms.OPERATIONS_ENUM.GET_SECRETS_PUBLIC_KEY,
|
|
539
|
+
new OperationFunctionObject(secretOperations.getSecretsPublicKey)
|
|
540
|
+
);
|
|
458
541
|
opFuncMap.set(
|
|
459
542
|
terms.OPERATIONS_ENUM.READ_TRANSACTION_LOG,
|
|
460
543
|
new OperationFunctionObject(transactionLog.readTransactionLog)
|