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
|
@@ -11,6 +11,7 @@ const harperBridge =
|
|
|
11
11
|
require('../../dataLayer/harperBridge/harperBridge.ts').default ||
|
|
12
12
|
require('../../dataLayer/harperBridge/harperBridge.ts');
|
|
13
13
|
const process = require('process');
|
|
14
|
+
const { isMainThread, workerData } = require('worker_threads');
|
|
14
15
|
const { resetDatabases } = require('../../resources/databases.ts');
|
|
15
16
|
|
|
16
17
|
/**
|
|
@@ -22,6 +23,13 @@ const serverItcHandlers = {
|
|
|
22
23
|
[hdbTerms.ITC_EVENT_TYPES.USER]: userHandler,
|
|
23
24
|
[hdbTerms.ITC_EVENT_TYPES.COMPONENT_STATUS_REQUEST]: componentStatusRequestHandler,
|
|
24
25
|
[hdbTerms.ITC_EVENT_TYPES.RESOURCE_OPENAPI_REQUEST]: resourceOpenApiRequestHandler,
|
|
26
|
+
[hdbTerms.ITC_EVENT_TYPES.MIDDLEWARE_CHAINS_REQUEST]: middlewareChainsRequestHandler,
|
|
27
|
+
// #1736 cross-thread registered-operation bridge. Lazy require: this module loads on every
|
|
28
|
+
// thread via itc.js, and registeredOperations pulls in the serverUtilities module graph.
|
|
29
|
+
[hdbTerms.ITC_EVENT_TYPES.OPERATION_REGISTERED]: (event) =>
|
|
30
|
+
require('../serverHelpers/registeredOperations.ts').operationRegisteredHandler(event),
|
|
31
|
+
[hdbTerms.ITC_EVENT_TYPES.OPERATION_EXECUTE_REQUEST]: (event) =>
|
|
32
|
+
require('../serverHelpers/registeredOperations.ts').operationExecuteRequestHandler(event),
|
|
25
33
|
};
|
|
26
34
|
|
|
27
35
|
/**
|
|
@@ -105,6 +113,31 @@ userHandler.addListener = function (listener) {
|
|
|
105
113
|
userListeners.push(listener);
|
|
106
114
|
};
|
|
107
115
|
|
|
116
|
+
const resourceListeners = [];
|
|
117
|
+
/**
|
|
118
|
+
* Local-only fan-out for "JS resources just registered" (resources.js loaded via the jsResource
|
|
119
|
+
* plugin). Unlike schema/user changes this is NOT an ITC event: each worker loads and registers
|
|
120
|
+
* its own JS resources, so a listener (e.g. the MCP application-tool rebuild) only needs to run in
|
|
121
|
+
* the worker where the registration happened. Surfaces author opt-ins (`static mcpTools`/
|
|
122
|
+
* `mcpPrompts`) that land on the resource registry after the MCP component's boot scan (#1448).
|
|
123
|
+
*/
|
|
124
|
+
function resourceHandler() {
|
|
125
|
+
for (const listener of resourceListeners) {
|
|
126
|
+
try {
|
|
127
|
+
listener();
|
|
128
|
+
} catch (err) {
|
|
129
|
+
hdbLogger.error(err);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
resourceHandler.addListener = function (listener) {
|
|
134
|
+
resourceListeners.push(listener);
|
|
135
|
+
};
|
|
136
|
+
// Test seam: drop registered listeners so a unit suite doesn't leak fakes into later suites.
|
|
137
|
+
resourceHandler._resetListenersForTest = function () {
|
|
138
|
+
resourceListeners.length = 0;
|
|
139
|
+
};
|
|
140
|
+
|
|
108
141
|
/**
|
|
109
142
|
* Handles incoming requests for component status from inter-thread communication (ITC).
|
|
110
143
|
* Validates the event, retrieves the current thread's component statuses, and sends a response
|
|
@@ -211,8 +244,45 @@ async function resourceOpenApiRequestHandler(event) {
|
|
|
211
244
|
}
|
|
212
245
|
}
|
|
213
246
|
|
|
247
|
+
/**
|
|
248
|
+
* Handles the main thread's request for the resolved HTTP/upgrade/WebSocket middleware chains (#1573).
|
|
249
|
+
* Only HTTP workers reply: the main thread carries just the operations-API middleware (so it stays
|
|
250
|
+
* silent even though it has responders), and job workers have none. HTTP workers answer even when
|
|
251
|
+
* they have no middleware (empty chains) so get_status doesn't wait out the request timeout. First
|
|
252
|
+
* worker to answer wins — all HTTP workers register identically, so any one is representative.
|
|
253
|
+
*/
|
|
254
|
+
async function middlewareChainsRequestHandler(event) {
|
|
255
|
+
try {
|
|
256
|
+
const validate = validateEvent(event);
|
|
257
|
+
if (validate) {
|
|
258
|
+
hdbLogger.error(validate);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (isMainThread || workerData?.name !== hdbTerms.THREAD_TYPES.HTTP) return;
|
|
263
|
+
const { describeMiddlewareChains } = require('../http.ts');
|
|
264
|
+
|
|
265
|
+
const responseMessage = {
|
|
266
|
+
type: hdbTerms.ITC_EVENT_TYPES.MIDDLEWARE_CHAINS_RESPONSE,
|
|
267
|
+
message: {
|
|
268
|
+
requestId: event.message.requestId,
|
|
269
|
+
chains: describeMiddlewareChains(),
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
if (!threads.sendToThread(event.message.originator, responseMessage)) {
|
|
274
|
+
hdbLogger.trace(
|
|
275
|
+
`Dropping middleware chains response for request ${event.message.requestId}: originator thread ${event.message.originator} is unreachable`
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
} catch (error) {
|
|
279
|
+
hdbLogger.error('Error handling middleware chains request:', error);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
214
283
|
module.exports = serverItcHandlers;
|
|
215
284
|
// Named exports so consumers (e.g., MCP listChanged) can subscribe via
|
|
216
285
|
// `userHandler.addListener(fn)` / `schemaHandler.addListener(fn)`.
|
|
217
286
|
module.exports.userHandler = userHandler;
|
|
218
287
|
module.exports.schemaHandler = schemaHandler;
|
|
288
|
+
module.exports.resourceHandler = resourceHandler;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import hdbLogger from '../utility/logging/harper_logger.ts';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Continuous re-authorization for live subscriptions (#1414).
|
|
5
|
+
*
|
|
6
|
+
* Subscribe-time authorization is a point-in-time check: once a stream (SSE / WebSocket / MQTT)
|
|
7
|
+
* is open, it keeps delivering even if the principal later loses access (drop_user, role/permission
|
|
8
|
+
* change) or its bearer token expires. This registry re-evaluates each live subscription's
|
|
9
|
+
* authorization — at the TABLE/RBAC level, matching how the subscription was granted; there is no
|
|
10
|
+
* per-record evaluation — and terminates any that no longer authorize.
|
|
11
|
+
*
|
|
12
|
+
* Triggers: (1) immediately on the ITC user-change broadcast (the same signal that rebuilds the
|
|
13
|
+
* user/role cache), and (2) on a fixed interval as a backstop and to catch token expiry, which is
|
|
14
|
+
* not event-signaled.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// Backstop interval; also catches token expiry, which is not event-signaled. Overridable for tests.
|
|
18
|
+
const RECHECK_INTERVAL_MS = Number(process.env.HARPER_SUBSCRIPTION_REAUTH_INTERVAL_MS) || 30_000;
|
|
19
|
+
|
|
20
|
+
interface LiveSubscription {
|
|
21
|
+
username: string;
|
|
22
|
+
/** JWT `exp` (seconds since epoch) of the credential the subscription was opened with, if any. */
|
|
23
|
+
authExpiresAt?: number;
|
|
24
|
+
/** Returns true if the principal is still authorized for this subscription. */
|
|
25
|
+
recheck: () => Promise<boolean>;
|
|
26
|
+
/** Stop delivery and tear down the subscription. */
|
|
27
|
+
terminate: () => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const registry = new Set<LiveSubscription>();
|
|
31
|
+
let sweepTimer: any = null;
|
|
32
|
+
let itcListenerInstalled = false;
|
|
33
|
+
let sweeping = false;
|
|
34
|
+
|
|
35
|
+
function ensureStarted(): void {
|
|
36
|
+
if (!sweepTimer) {
|
|
37
|
+
sweepTimer = setInterval(() => void sweep(), RECHECK_INTERVAL_MS);
|
|
38
|
+
// don't keep the worker alive solely for the recheck timer
|
|
39
|
+
sweepTimer.unref?.();
|
|
40
|
+
}
|
|
41
|
+
if (!itcListenerInstalled) {
|
|
42
|
+
try {
|
|
43
|
+
// Fire an immediate sweep when a user/role mutation propagates. serverHandlers rebuilds the
|
|
44
|
+
// user/role cache before invoking listeners, so recheck() observes the new permissions.
|
|
45
|
+
const handlers = require('./itc/serverHandlers.js');
|
|
46
|
+
if (handlers?.userHandler?.addListener) {
|
|
47
|
+
handlers.userHandler.addListener(() => void sweep());
|
|
48
|
+
itcListenerInstalled = true;
|
|
49
|
+
}
|
|
50
|
+
} catch (error) {
|
|
51
|
+
hdbLogger.trace?.(`liveSubscriptionAuth: ITC userHandler unavailable: ${(error as Error).message}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function stopIfIdle(): void {
|
|
57
|
+
if (registry.size === 0 && sweepTimer) {
|
|
58
|
+
clearInterval(sweepTimer);
|
|
59
|
+
sweepTimer = null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Register a live subscription for continuous re-authorization. The returned subscription's normal
|
|
65
|
+
* teardown (end()/close) automatically unregisters it, so callers don't need to.
|
|
66
|
+
*/
|
|
67
|
+
export function registerLiveSubscription(opts: {
|
|
68
|
+
subscription: any;
|
|
69
|
+
username: string;
|
|
70
|
+
authExpiresAt?: number;
|
|
71
|
+
recheck: () => Promise<boolean>;
|
|
72
|
+
}): void {
|
|
73
|
+
const { subscription, username, authExpiresAt, recheck } = opts;
|
|
74
|
+
if (!subscription || typeof subscription !== 'object') return;
|
|
75
|
+
|
|
76
|
+
const entry: LiveSubscription = {
|
|
77
|
+
username,
|
|
78
|
+
authExpiresAt,
|
|
79
|
+
recheck,
|
|
80
|
+
terminate: () => {
|
|
81
|
+
// emit('close') runs the subscription's own teardown wiring; end() removes it from the
|
|
82
|
+
// broadcast notify loop so no further events are delivered. Both are idempotent.
|
|
83
|
+
try {
|
|
84
|
+
subscription.emit?.('close');
|
|
85
|
+
} catch {
|
|
86
|
+
/* ignore */
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
subscription.end?.();
|
|
90
|
+
} catch {
|
|
91
|
+
/* ignore */
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
registry.add(entry);
|
|
96
|
+
|
|
97
|
+
const unregister = () => {
|
|
98
|
+
registry.delete(entry);
|
|
99
|
+
stopIfIdle();
|
|
100
|
+
};
|
|
101
|
+
// Both transports ultimately call end() on normal teardown (MQTT unsubscribe/disconnect; SSE close
|
|
102
|
+
// is wired to end()); wrap it so a closed stream never leaks a registry entry. Also listen for
|
|
103
|
+
// 'close' to cover any iterable that closes without an end().
|
|
104
|
+
const originalEnd = typeof subscription.end === 'function' ? subscription.end.bind(subscription) : null;
|
|
105
|
+
if (originalEnd) {
|
|
106
|
+
subscription.end = function (...args: any[]) {
|
|
107
|
+
unregister();
|
|
108
|
+
return originalEnd(...args);
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
subscription.on?.('close', unregister);
|
|
112
|
+
|
|
113
|
+
ensureStarted();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function sweep(): Promise<void> {
|
|
117
|
+
if (sweeping) return; // a slow recheck must not overlap with the next tick/event
|
|
118
|
+
sweeping = true;
|
|
119
|
+
try {
|
|
120
|
+
const now = Date.now();
|
|
121
|
+
for (const entry of registry) {
|
|
122
|
+
try {
|
|
123
|
+
const expired = entry.authExpiresAt != null && now >= entry.authExpiresAt * 1000;
|
|
124
|
+
const stillAuthorized = expired ? false : await entry.recheck();
|
|
125
|
+
const revoke = expired || !stillAuthorized;
|
|
126
|
+
if (revoke) {
|
|
127
|
+
registry.delete(entry);
|
|
128
|
+
entry.terminate();
|
|
129
|
+
}
|
|
130
|
+
} catch (error) {
|
|
131
|
+
// fail closed: if authorization can't be confirmed, revoke
|
|
132
|
+
registry.delete(entry);
|
|
133
|
+
try {
|
|
134
|
+
entry.terminate();
|
|
135
|
+
} catch {
|
|
136
|
+
/* ignore */
|
|
137
|
+
}
|
|
138
|
+
hdbLogger.warn?.(
|
|
139
|
+
`liveSubscriptionAuth: revoked subscription for ${entry.username} after recheck error: ${(error as Error).message}`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
} finally {
|
|
144
|
+
sweeping = false;
|
|
145
|
+
stopIfIdle();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Test-only: current number of tracked subscriptions. */
|
|
150
|
+
export function _liveSubscriptionCount(): number {
|
|
151
|
+
return registry.size;
|
|
152
|
+
}
|
|
@@ -6,6 +6,7 @@ const configUtils = require('../config/configUtils.ts');
|
|
|
6
6
|
const { dirname } = require('path');
|
|
7
7
|
const { loadCertificates } = require('../security/keys.ts');
|
|
8
8
|
const { installApplications } = require('../components/Application.ts');
|
|
9
|
+
const { errorForLog } = require('../utility/logging/harper_logger.ts');
|
|
9
10
|
|
|
10
11
|
let loadedComponents = new Map();
|
|
11
12
|
/**
|
|
@@ -16,7 +17,7 @@ async function loadRootComponents(isWorkerThread = false) {
|
|
|
16
17
|
try {
|
|
17
18
|
if (isMainThread && !process.env.HARPER_SAFE_MODE) await installApplications();
|
|
18
19
|
} catch (error) {
|
|
19
|
-
console.error(error);
|
|
20
|
+
console.error(errorForLog(error));
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
let resources = resetResources();
|
|
@@ -8,6 +8,18 @@ export type HttpEntry = {
|
|
|
8
8
|
host?: string;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* A single route's resolved middleware order. `order` is the topologically sorted
|
|
13
|
+
* list of entries the chain will invoke, outermost first. `host`/`urlPath` are the
|
|
14
|
+
* route scope (both undefined for the default route). This is the observable form of
|
|
15
|
+
* what `buildRoutedChain`/`buildLinearChain` actually execute — see `describeChains`.
|
|
16
|
+
*/
|
|
17
|
+
export type ResolvedChain = {
|
|
18
|
+
host?: string;
|
|
19
|
+
urlPath?: string;
|
|
20
|
+
order: HttpEntry[];
|
|
21
|
+
};
|
|
22
|
+
|
|
11
23
|
/**
|
|
12
24
|
* Topological sort of middleware entries respecting `before`/`after` constraints.
|
|
13
25
|
* Uses the original registration index as a tiebreaker so config order is preserved
|
|
@@ -92,7 +104,7 @@ export function topoSort(entries: HttpEntry[], onCycle?: () => void): HttpEntry[
|
|
|
92
104
|
*/
|
|
93
105
|
export function buildLinearChain(sorted: HttpEntry[], fallback: Function): Function {
|
|
94
106
|
let next = fallback;
|
|
95
|
-
for (let i = sorted.length; i > 0;
|
|
107
|
+
for (let i = sorted.length; i > 0;) {
|
|
96
108
|
const { listener } = sorted[--i];
|
|
97
109
|
const callback = next;
|
|
98
110
|
next = (...args: any[]) => listener(...args, callback);
|
|
@@ -208,12 +220,16 @@ export function stripPrefix(request: any, prefix: string): any {
|
|
|
208
220
|
* The matched (and prefix-stripped) request is substituted back into the same
|
|
209
221
|
* position before forwarding to the inner chain.
|
|
210
222
|
*/
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
223
|
+
/**
|
|
224
|
+
* Resolves the per-route middleware order for a port that has sub-routes.
|
|
225
|
+
* Returns each sub-route in dispatch priority order (host+path > host-only > path-only;
|
|
226
|
+
* longer paths win ties), followed by the default route last. Each route's `order` is the
|
|
227
|
+
* `after`-dependency-resolved, topologically sorted entry list the chain will invoke.
|
|
228
|
+
*
|
|
229
|
+
* This is the single source of ordering truth: `buildRoutedChain` builds callbacks from it,
|
|
230
|
+
* and `describeChains` reports it, so the observed order can never drift from the served one.
|
|
231
|
+
*/
|
|
232
|
+
export function resolveRoutedChains(portEntries: HttpEntry[], onCycle?: () => void): ResolvedChain[] {
|
|
217
233
|
// Global name registry across all routes (first registration wins)
|
|
218
234
|
const nameToEntry = new Map<string, HttpEntry>();
|
|
219
235
|
for (const entry of portEntries) {
|
|
@@ -233,19 +249,36 @@ export function buildRoutedChain(
|
|
|
233
249
|
const defaultGroup = routeGroups.find((g) => !g.host && !g.urlPath);
|
|
234
250
|
const subRouteGroups = routeGroups.filter((g) => g.host || g.urlPath);
|
|
235
251
|
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
252
|
+
const subRoutes: ResolvedChain[] = subRouteGroups.map((group) => ({
|
|
253
|
+
host: group.host,
|
|
254
|
+
urlPath: group.urlPath,
|
|
255
|
+
order: topoSort(resolveDeps(group.entries, nameToEntry), onCycle),
|
|
256
|
+
}));
|
|
240
257
|
|
|
241
|
-
|
|
258
|
+
subRoutes.sort((a, b) => {
|
|
242
259
|
const aSpec = (a.host ? 2 : 0) + (a.urlPath ? 1 : 0);
|
|
243
260
|
const bSpec = (b.host ? 2 : 0) + (b.urlPath ? 1 : 0);
|
|
244
261
|
if (aSpec !== bSpec) return bSpec - aSpec;
|
|
245
262
|
return (b.urlPath?.length ?? 0) - (a.urlPath?.length ?? 0);
|
|
246
263
|
});
|
|
247
264
|
|
|
248
|
-
|
|
265
|
+
return [...subRoutes, { order: topoSort(defaultGroup?.entries ?? [], onCycle) }];
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function buildRoutedChain(
|
|
269
|
+
portEntries: HttpEntry[],
|
|
270
|
+
fallback: Function,
|
|
271
|
+
onCycle?: () => void,
|
|
272
|
+
requestArgIndex: number = 0
|
|
273
|
+
): Function {
|
|
274
|
+
const resolved = resolveRoutedChains(portEntries, onCycle);
|
|
275
|
+
// resolveRoutedChains returns sub-routes (dispatch order) followed by the default route last.
|
|
276
|
+
const defaultChain = buildLinearChain(resolved[resolved.length - 1].order, fallback);
|
|
277
|
+
const subRouteChains = resolved.slice(0, -1).map((route) => ({
|
|
278
|
+
host: route.host,
|
|
279
|
+
urlPath: route.urlPath,
|
|
280
|
+
chain: buildLinearChain(route.order, fallback),
|
|
281
|
+
}));
|
|
249
282
|
|
|
250
283
|
return function dispatch(...args: any[]) {
|
|
251
284
|
const request = args[requestArgIndex];
|
|
@@ -263,20 +296,77 @@ export function buildRoutedChain(
|
|
|
263
296
|
};
|
|
264
297
|
}
|
|
265
298
|
|
|
299
|
+
export type UnresolvedOrderingRef = { entryName?: string; kind: 'before' | 'after'; target: string };
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Returns the `before`/`after` references among `entries` that name no registered entry.
|
|
303
|
+
* topoSort silently ignores these, so they deserve a diagnostic (see makeCallbackChain).
|
|
304
|
+
*/
|
|
305
|
+
export function findUnresolvedOrderingRefs(entries: HttpEntry[]): UnresolvedOrderingRef[] {
|
|
306
|
+
const names = new Set<string>();
|
|
307
|
+
for (const { name } of entries) {
|
|
308
|
+
if (name) names.add(name);
|
|
309
|
+
}
|
|
310
|
+
const unresolved: UnresolvedOrderingRef[] = [];
|
|
311
|
+
for (const { name, before, after } of entries) {
|
|
312
|
+
if (before && !names.has(before)) unresolved.push({ entryName: name, kind: 'before', target: before });
|
|
313
|
+
if (after && !names.has(after)) unresolved.push({ entryName: name, kind: 'after', target: after });
|
|
314
|
+
}
|
|
315
|
+
return unresolved;
|
|
316
|
+
}
|
|
317
|
+
|
|
266
318
|
/**
|
|
267
319
|
* Builds the complete middleware chain for a given port from the full responders list.
|
|
268
320
|
* Uses a flat linear chain when no sub-routes are present (fast path),
|
|
269
321
|
* or a route-dispatching chain when any entry has urlPath or host.
|
|
322
|
+
*
|
|
323
|
+
* @param onUnresolved - called (once per unresolved reference) when a `before`/`after` names no
|
|
324
|
+
* registered entry, so a typo or legacy config key doesn't silently drop the ordering constraint.
|
|
325
|
+
* Reported on the chain's first dispatch, not at build time: the chain is rebuilt on every
|
|
326
|
+
* registration, so an early build may reference an entry that a later registration resolves.
|
|
270
327
|
*/
|
|
271
328
|
export function makeCallbackChain(
|
|
272
329
|
responders: HttpEntry[],
|
|
273
330
|
portNum: number | string,
|
|
274
331
|
fallback: Function,
|
|
275
332
|
onCycle?: () => void,
|
|
276
|
-
requestArgIndex: number = 0
|
|
333
|
+
requestArgIndex: number = 0,
|
|
334
|
+
onUnresolved?: (ref: UnresolvedOrderingRef) => void
|
|
277
335
|
): Function {
|
|
278
336
|
const portEntries = responders.filter(({ port }) => port === portNum || port === 'all');
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
337
|
+
const chain = portEntries.some((e) => e.urlPath || e.host)
|
|
338
|
+
? buildRoutedChain(portEntries, fallback, onCycle, requestArgIndex)
|
|
339
|
+
: buildLinearChain(topoSort(portEntries, onCycle), fallback);
|
|
340
|
+
if (onUnresolved) {
|
|
341
|
+
const unresolved = findUnresolvedOrderingRefs(portEntries);
|
|
342
|
+
if (unresolved.length > 0) {
|
|
343
|
+
let reported = false;
|
|
344
|
+
return (...args: any[]) => {
|
|
345
|
+
if (!reported) {
|
|
346
|
+
reported = true;
|
|
347
|
+
for (const ref of unresolved) onUnresolved(ref);
|
|
348
|
+
}
|
|
349
|
+
return chain(...args);
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return chain;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Describes the resolved middleware order for a port without building callbacks.
|
|
358
|
+
* Mirrors `makeCallbackChain`'s branch selection and reuses the same resolvers, so the
|
|
359
|
+
* returned order is exactly what a request on that port would traverse. Used for the
|
|
360
|
+
* chain-build debug log and for `get_status` introspection (issue #1573).
|
|
361
|
+
*/
|
|
362
|
+
export function describeChains(
|
|
363
|
+
responders: HttpEntry[],
|
|
364
|
+
portNum: number | string,
|
|
365
|
+
onCycle?: () => void
|
|
366
|
+
): ResolvedChain[] {
|
|
367
|
+
// Must use the exact same port selection and routing branch as makeCallbackChain so that, for a
|
|
368
|
+
// given portNum, the described order equals the order the built chain actually serves.
|
|
369
|
+
const portEntries = responders.filter(({ port }) => port === portNum || port === 'all');
|
|
370
|
+
if (portEntries.some((e) => e.urlPath || e.host)) return resolveRoutedChains(portEntries, onCycle);
|
|
371
|
+
return [{ order: topoSort(portEntries, onCycle) }];
|
|
282
372
|
}
|
package/server/mqtt.ts
CHANGED
|
@@ -463,7 +463,8 @@ function onSocket(socket, send, request, user, mqttSettings) {
|
|
|
463
463
|
break;
|
|
464
464
|
case 'pubcomp':
|
|
465
465
|
case 'puback':
|
|
466
|
-
session.acknowledge(packet.messageId);
|
|
466
|
+
await session.acknowledge(packet.messageId);
|
|
467
|
+
emitEvent('acknowledged', session, packet);
|
|
467
468
|
break;
|
|
468
469
|
case 'pingreq':
|
|
469
470
|
sendPacket({ cmd: 'pingresp' });
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
serverErrorHandler,
|
|
26
26
|
reqBodyValidationHandler,
|
|
27
27
|
} from './serverHelpers/serverHandlers.js';
|
|
28
|
-
import {
|
|
28
|
+
import { registerFastifyInstance } from './http.ts';
|
|
29
29
|
import { registerContentHandlers } from './serverHelpers/contentTypes.ts';
|
|
30
30
|
import { getConfigObj } from '../config/configUtils.ts';
|
|
31
31
|
import { registerMcpProfile } from '../components/mcp/index.ts';
|
|
@@ -94,7 +94,7 @@ async function operationsServer(options: ServerOptions & { resources?: Resources
|
|
|
94
94
|
// On Bun, register the Fastify instance so requests can be delegated via inject()
|
|
95
95
|
if (typeof globalThis.Bun !== 'undefined') {
|
|
96
96
|
const port = options.port || options.securePort || env.get(CONFIG_PARAMS.OPERATIONSAPI_NETWORK_PORT);
|
|
97
|
-
if (port)
|
|
97
|
+
if (port) registerFastifyInstance(port, server);
|
|
98
98
|
}
|
|
99
99
|
if (!server.server.closeIdleConnections) {
|
|
100
100
|
// before Node v18, closeIdleConnections is not available, and we have to setup a listener for fastify
|
|
@@ -60,6 +60,30 @@ export class Headers extends Map<string, [string, string | string[]]> {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Add a field-name token to the `Vary` response header, appending to (and de-duplicating against) any
|
|
65
|
+
* existing value rather than overwriting it. Used to declare cache-partitioning dimensions (Origin,
|
|
66
|
+
* Authorization, Cookie) so a shared cache/CDN keys the response correctly (#1518, #1565).
|
|
67
|
+
*/
|
|
68
|
+
export function addVaryHeader(
|
|
69
|
+
headers: { get(name: string): any; set(name: string, value: string): any },
|
|
70
|
+
token: string
|
|
71
|
+
) {
|
|
72
|
+
const existing = headers.get('Vary');
|
|
73
|
+
if (!existing) {
|
|
74
|
+
headers.set('Vary', token);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const existingString = Array.isArray(existing) ? existing.join(', ') : existing;
|
|
78
|
+
const lowerToken = token.toLowerCase();
|
|
79
|
+
for (const part of existingString.split(',')) {
|
|
80
|
+
const trimmed = part.trim().toLowerCase();
|
|
81
|
+
// a `Vary: *` already covers every dimension, and an exact token match is a no-op
|
|
82
|
+
if (trimmed === lowerToken || trimmed === '*') return;
|
|
83
|
+
}
|
|
84
|
+
headers.set('Vary', existingString + ', ' + token);
|
|
85
|
+
}
|
|
86
|
+
|
|
63
87
|
export function appendHeader(headers, name, value, commaDelimited) {
|
|
64
88
|
if (headers.append) {
|
|
65
89
|
headers.append(name, value, commaDelimited);
|
|
@@ -129,7 +129,7 @@ class JSONStream extends Readable {
|
|
|
129
129
|
}
|
|
130
130
|
},
|
|
131
131
|
(error) => {
|
|
132
|
-
console.error(error);
|
|
132
|
+
console.error(harperLogger.errorForLog(error));
|
|
133
133
|
this.done = true;
|
|
134
134
|
this.push(errorToString(error));
|
|
135
135
|
this.push(null);
|
|
@@ -201,7 +201,7 @@ class JSONStream extends Readable {
|
|
|
201
201
|
}
|
|
202
202
|
} while (this.push(nextString));
|
|
203
203
|
} catch (error) {
|
|
204
|
-
console.error(error);
|
|
204
|
+
console.error(harperLogger.errorForLog(error));
|
|
205
205
|
this.push(errorToString(error));
|
|
206
206
|
this.push(null);
|
|
207
207
|
return true;
|
|
@@ -218,7 +218,7 @@ class JSONStream extends Readable {
|
|
|
218
218
|
}
|
|
219
219
|
|
|
220
220
|
function handleError(error) {
|
|
221
|
-
console.error(error);
|
|
221
|
+
console.error(harperLogger.errorForLog(error));
|
|
222
222
|
return JSON.stringify(errorToString(error));
|
|
223
223
|
}
|
|
224
224
|
|
|
@@ -436,6 +436,133 @@ export class BunRequest {
|
|
|
436
436
|
}
|
|
437
437
|
}
|
|
438
438
|
|
|
439
|
+
/**
|
|
440
|
+
* uWebSockets.js Request adapter. Wraps the data extracted from a uWS HttpRequest/HttpResponse
|
|
441
|
+
* (which is only valid synchronously inside the route handler) into Harper's request interface,
|
|
442
|
+
* matching BunRequest. Intended for the plaintext-UDS-behind-symphony path: TLS/mTLS are
|
|
443
|
+
* terminated upstream, so peerCertificate/authorized are not available here and the real client
|
|
444
|
+
* IP arrives via the X-Forwarded-For header.
|
|
445
|
+
*/
|
|
446
|
+
export class UwsRequest {
|
|
447
|
+
#body: UwsRequestBody | undefined;
|
|
448
|
+
#isSecure: boolean;
|
|
449
|
+
#ip: string | undefined;
|
|
450
|
+
#signal: AbortSignal | undefined;
|
|
451
|
+
public _nodeRequest: any = null;
|
|
452
|
+
public _nodeResponse: any = null;
|
|
453
|
+
public method: string;
|
|
454
|
+
public url: string;
|
|
455
|
+
public headers: RequestHeaders;
|
|
456
|
+
public isWebSocket?: boolean;
|
|
457
|
+
public user?: any;
|
|
458
|
+
public response: {
|
|
459
|
+
status?: number;
|
|
460
|
+
headers: ResponseHeaders;
|
|
461
|
+
};
|
|
462
|
+
public __harperRequestUpgraded: boolean;
|
|
463
|
+
|
|
464
|
+
constructor(source: {
|
|
465
|
+
method: string;
|
|
466
|
+
url: string;
|
|
467
|
+
headers: Record<string, string | string[]>;
|
|
468
|
+
ip?: string;
|
|
469
|
+
secure?: boolean;
|
|
470
|
+
body?: UwsRequestBody;
|
|
471
|
+
signal?: AbortSignal;
|
|
472
|
+
}) {
|
|
473
|
+
this.method = source.method;
|
|
474
|
+
this.url = source.url;
|
|
475
|
+
this.headers = new RequestHeaders(source.headers);
|
|
476
|
+
this.#isSecure = !!source.secure;
|
|
477
|
+
this.#ip = source.ip;
|
|
478
|
+
this.#body = source.body;
|
|
479
|
+
this.#signal = source.signal;
|
|
480
|
+
this.__harperRequestUpgraded = false;
|
|
481
|
+
}
|
|
482
|
+
get absoluteURL() {
|
|
483
|
+
return this.protocol + '://' + this.host + this.url;
|
|
484
|
+
}
|
|
485
|
+
get pathname() {
|
|
486
|
+
const queryStart = this.url.indexOf('?');
|
|
487
|
+
if (queryStart > -1) return this.url.slice(0, queryStart);
|
|
488
|
+
return this.url;
|
|
489
|
+
}
|
|
490
|
+
set pathname(pathname) {
|
|
491
|
+
const queryStart = this.url.indexOf('?');
|
|
492
|
+
if (queryStart > -1) this.url = pathname + this.url.slice(queryStart);
|
|
493
|
+
else this.url = pathname;
|
|
494
|
+
}
|
|
495
|
+
get protocol() {
|
|
496
|
+
// Behind symphony the on-the-wire hop is plaintext; the original scheme is conveyed
|
|
497
|
+
// by the proxy (X-Forwarded-Proto) or assumed from the listener's secure flag.
|
|
498
|
+
const xfp = this.headers.get('x-forwarded-proto');
|
|
499
|
+
if (xfp) return Array.isArray(xfp) ? xfp[0] : xfp;
|
|
500
|
+
return this.#isSecure ? 'https' : 'http';
|
|
501
|
+
}
|
|
502
|
+
get ip() {
|
|
503
|
+
// A real peer address (direct-TCP path) is authoritative and unspoofable, so it wins. Only the
|
|
504
|
+
// symphony-UDS path — where the socket has no client address (#ip is left unset) and symphony is
|
|
505
|
+
// the trusted setter/stripper of X-Forwarded-For — falls back to XFF. This prevents a direct
|
|
506
|
+
// client from spoofing `X-Forwarded-For: 127.0.0.1` to satisfy local auth (security/auth.ts).
|
|
507
|
+
if (this.#ip) return this.#ip;
|
|
508
|
+
const xff = this.headers.get('x-forwarded-for');
|
|
509
|
+
if (xff) return (Array.isArray(xff) ? xff[0] : xff).split(',')[0].trim();
|
|
510
|
+
return '';
|
|
511
|
+
}
|
|
512
|
+
get authorized() {
|
|
513
|
+
// TLS terminated upstream — client authorization not available at this hop.
|
|
514
|
+
return undefined;
|
|
515
|
+
}
|
|
516
|
+
get peerCertificate() {
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
get mtlsConfig() {
|
|
520
|
+
return undefined;
|
|
521
|
+
}
|
|
522
|
+
get body() {
|
|
523
|
+
// Bodyless requests get an already-ended empty stream so consumers can still read it uniformly.
|
|
524
|
+
if (!this.#body) {
|
|
525
|
+
this.#body = new UwsRequestBody();
|
|
526
|
+
this.#body.push(null);
|
|
527
|
+
}
|
|
528
|
+
return this.#body;
|
|
529
|
+
}
|
|
530
|
+
get host() {
|
|
531
|
+
return this.headers.get('host') as string;
|
|
532
|
+
}
|
|
533
|
+
get hostname() {
|
|
534
|
+
return this.headers.get('host') as string;
|
|
535
|
+
}
|
|
536
|
+
get httpVersion() {
|
|
537
|
+
return '1.1';
|
|
538
|
+
}
|
|
539
|
+
get isAborted() {
|
|
540
|
+
return this.#signal?.aborted ?? false;
|
|
541
|
+
}
|
|
542
|
+
get signal(): AbortSignal {
|
|
543
|
+
return this.#signal ?? new AbortController().signal;
|
|
544
|
+
}
|
|
545
|
+
_abort(): void {
|
|
546
|
+
// Abort is driven by the uWS res.onAborted handler wired into the provided signal.
|
|
547
|
+
}
|
|
548
|
+
get nodeRequest() {
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
sendEarlyHints(_link: string, _headers: Record<string, any> = {}) {
|
|
552
|
+
// Early hints not wired for the uWS path
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// A push-based Readable: the uWS server pushes each request-body chunk (and the terminating null) in
|
|
557
|
+
// as it arrives off the socket, so consumers (streamToBuffer, future streaming deserializers) see a
|
|
558
|
+
// real stream instead of a pre-buffered blob. Extends Readable so it matches the RequestBody/
|
|
559
|
+
// BunRequestBody contract — on/pipe, `for await` async iteration, destroy() for abort handling.
|
|
560
|
+
// _read() is a no-op because flow is driven by push() from the uWS onData callback; uWS has no inbound
|
|
561
|
+
// backpressure, so pushed-but-unread chunks queue here (bounded by the server's maxBodyBytes ceiling).
|
|
562
|
+
export class UwsRequestBody extends Readable {
|
|
563
|
+
_read() {}
|
|
564
|
+
}
|
|
565
|
+
|
|
439
566
|
class RequestBody {
|
|
440
567
|
#nodeRequest: IncomingMessage;
|
|
441
568
|
constructor(nodeRequest: IncomingMessage) {
|
|
@@ -623,15 +623,10 @@ function transformIterable(iterable, transform) {
|
|
|
623
623
|
next() {
|
|
624
624
|
const step = iterator.next();
|
|
625
625
|
if (step.then) {
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
done: step.done,
|
|
629
|
-
}));
|
|
626
|
+
// don't transform the terminal sentinel step, whose value is undefined
|
|
627
|
+
return step.then((result) => (result.done ? result : { value: transform(result.value), done: false }));
|
|
630
628
|
}
|
|
631
|
-
return {
|
|
632
|
-
value: transform(step.value),
|
|
633
|
-
done: step.done,
|
|
634
|
-
};
|
|
629
|
+
return step.done ? step : { value: transform(step.value), done: false };
|
|
635
630
|
},
|
|
636
631
|
return(value) {
|
|
637
632
|
return iterator.return(value);
|