@harperfast/harper 5.2.13 → 5.3.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent/mcpTools.ts +1 -1
- package/agent/session.ts +25 -14
- package/bin/cliOperations.ts +58 -9
- package/bin/copyDb.ts +282 -59
- package/bin/deploySetup.ts +16 -5
- package/bin/harper.ts +1 -1
- package/bin/help.ts +4 -1
- package/bin/lite.ts +4 -1
- package/bin/restart.ts +120 -4
- package/bin/run.ts +6 -11
- package/bin/upgrade.js +7 -3
- package/bin/workloadIdentity.ts +119 -0
- package/components/Application.ts +3436 -242
- package/components/ApplicationScope.ts +8 -0
- package/components/EntryHandler.ts +59 -39
- package/components/OptionsWatcher.ts +440 -98
- package/components/RuntimeModuleTracker.ts +38 -7
- package/components/Scope.ts +56 -15
- package/components/awaitRestart.ts +84 -0
- package/components/componentLoader.ts +381 -32
- package/components/componentPreparationLock.ts +16 -5
- package/components/deploymentOperations.ts +4 -1
- package/components/deploymentRecorder.ts +9 -2
- package/components/mcp/adapters/harperHttp.ts +4 -0
- package/components/mcp/listChanged.ts +4 -0
- package/components/mcp/toolRegistry.ts +2 -0
- package/components/mcp/tools/operations.ts +9 -0
- package/components/mcp/tools/schemas/operationDescriptions.ts +2 -2
- package/components/operations.js +537 -113
- package/components/operationsValidation.js +98 -3
- package/components/packageComponent.ts +25 -1
- package/components/requestRestart.ts +11 -0
- package/components/status/ComponentStatusRegistry.ts +59 -0
- package/config/RootConfigWatcher.ts +240 -40
- package/config/configReadRetry.ts +62 -0
- package/config/configUtils.ts +357 -48
- package/config/harperConfigEnvVars.ts +170 -27
- package/config/parseConfigFile.ts +34 -0
- package/config/readConfigFileSync.ts +44 -0
- package/config/watcherArming.ts +59 -0
- package/config-root.schema.json +33 -0
- package/dataLayer/blobBackup.ts +160 -50
- package/dataLayer/delete.ts +6 -1
- package/dataLayer/harperBridge/ResourceBridge.ts +80 -10
- package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.js +3 -1
- package/dataLayer/hdbInfoController.ts +34 -1
- package/dataLayer/insert.ts +44 -1
- package/dataLayer/rocksdbBackup.ts +53 -10
- package/dataLayer/schema.ts +11 -1
- package/dataLayer/schemaDescribe.ts +8 -1
- package/dist/agent/mcpTools.js +1 -1
- package/dist/agent/mcpTools.js.map +1 -1
- package/dist/agent/session.d.ts +22 -0
- package/dist/agent/session.js +26 -15
- package/dist/agent/session.js.map +1 -1
- package/dist/bin/cliOperations.js +61 -9
- package/dist/bin/cliOperations.js.map +1 -1
- package/dist/bin/copyDb.d.ts +12 -1
- package/dist/bin/copyDb.js +248 -60
- package/dist/bin/copyDb.js.map +1 -1
- package/dist/bin/deploySetup.d.ts +2 -0
- package/dist/bin/deploySetup.js +11 -3
- package/dist/bin/deploySetup.js.map +1 -1
- package/dist/bin/harper.js +1 -1
- package/dist/bin/harper.js.map +1 -1
- package/dist/bin/help.js +4 -1
- package/dist/bin/help.js.map +1 -1
- package/dist/bin/lite.js +4 -1
- package/dist/bin/lite.js.map +1 -1
- package/dist/bin/restart.js +91 -6
- package/dist/bin/restart.js.map +1 -1
- package/dist/bin/run.js +4 -10
- package/dist/bin/run.js.map +1 -1
- package/dist/bin/upgrade.js +4 -3
- package/dist/bin/upgrade.js.map +1 -1
- package/dist/bin/workloadIdentity.d.ts +18 -0
- package/dist/bin/workloadIdentity.js +100 -0
- package/dist/bin/workloadIdentity.js.map +1 -0
- package/dist/components/Application.d.ts +235 -17
- package/dist/components/Application.js +3038 -238
- package/dist/components/Application.js.map +1 -1
- package/dist/components/ApplicationScope.d.ts +8 -0
- package/dist/components/ApplicationScope.js +7 -0
- package/dist/components/ApplicationScope.js.map +1 -1
- package/dist/components/EntryHandler.js +26 -10
- package/dist/components/EntryHandler.js.map +1 -1
- package/dist/components/OptionsWatcher.d.ts +4 -0
- package/dist/components/OptionsWatcher.js +440 -99
- package/dist/components/OptionsWatcher.js.map +1 -1
- package/dist/components/RuntimeModuleTracker.js +40 -6
- package/dist/components/RuntimeModuleTracker.js.map +1 -1
- package/dist/components/Scope.js +52 -13
- package/dist/components/Scope.js.map +1 -1
- package/dist/components/awaitRestart.d.ts +33 -0
- package/dist/components/awaitRestart.js +61 -0
- package/dist/components/awaitRestart.js.map +1 -0
- package/dist/components/componentLoader.d.ts +38 -1
- package/dist/components/componentLoader.js +313 -24
- package/dist/components/componentLoader.js.map +1 -1
- package/dist/components/componentPreparationLock.d.ts +5 -0
- package/dist/components/componentPreparationLock.js +14 -6
- package/dist/components/componentPreparationLock.js.map +1 -1
- package/dist/components/deploymentOperations.js +4 -1
- package/dist/components/deploymentOperations.js.map +1 -1
- package/dist/components/deploymentRecorder.d.ts +4 -2
- package/dist/components/deploymentRecorder.js +1 -0
- package/dist/components/deploymentRecorder.js.map +1 -1
- package/dist/components/mcp/adapters/harperHttp.js +4 -0
- package/dist/components/mcp/adapters/harperHttp.js.map +1 -1
- package/dist/components/mcp/listChanged.js +5 -0
- package/dist/components/mcp/listChanged.js.map +1 -1
- package/dist/components/mcp/toolRegistry.d.ts +1 -0
- package/dist/components/mcp/toolRegistry.js.map +1 -1
- package/dist/components/mcp/tools/operations.d.ts +5 -0
- package/dist/components/mcp/tools/operations.js +9 -0
- package/dist/components/mcp/tools/operations.js.map +1 -1
- package/dist/components/mcp/tools/schemas/operationDescriptions.js +2 -2
- package/dist/components/mcp/tools/schemas/operationDescriptions.js.map +1 -1
- package/dist/components/operations.d.ts +28 -0
- package/dist/components/operations.js +476 -113
- package/dist/components/operations.js.map +1 -1
- package/dist/components/operationsValidation.js +97 -3
- package/dist/components/operationsValidation.js.map +1 -1
- package/dist/components/packageComponent.js +24 -0
- package/dist/components/packageComponent.js.map +1 -1
- package/dist/components/requestRestart.d.ts +1 -0
- package/dist/components/requestRestart.js +7 -0
- package/dist/components/requestRestart.js.map +1 -1
- package/dist/components/status/ComponentStatusRegistry.d.ts +0 -4
- package/dist/components/status/ComponentStatusRegistry.js +63 -0
- package/dist/components/status/ComponentStatusRegistry.js.map +1 -1
- package/dist/config/RootConfigWatcher.d.ts +9 -1
- package/dist/config/RootConfigWatcher.js +228 -37
- package/dist/config/RootConfigWatcher.js.map +1 -1
- package/dist/config/configReadRetry.d.ts +8 -0
- package/dist/config/configReadRetry.js +62 -0
- package/dist/config/configReadRetry.js.map +1 -0
- package/dist/config/configUtils.d.ts +11 -2
- package/dist/config/configUtils.js +304 -47
- package/dist/config/configUtils.js.map +1 -1
- package/dist/config/harperConfigEnvVars.d.ts +16 -0
- package/dist/config/harperConfigEnvVars.js +162 -25
- package/dist/config/harperConfigEnvVars.js.map +1 -1
- package/dist/config/parseConfigFile.d.ts +4 -0
- package/dist/config/parseConfigFile.js +35 -0
- package/dist/config/parseConfigFile.js.map +1 -0
- package/dist/config/readConfigFileSync.d.ts +1 -0
- package/dist/config/readConfigFileSync.js +47 -0
- package/dist/config/readConfigFileSync.js.map +1 -0
- package/dist/config/watcherArming.d.ts +15 -0
- package/dist/config/watcherArming.js +59 -0
- package/dist/config/watcherArming.js.map +1 -0
- package/dist/dataLayer/blobBackup.d.ts +49 -20
- package/dist/dataLayer/blobBackup.js +139 -50
- package/dist/dataLayer/blobBackup.js.map +1 -1
- package/dist/dataLayer/delete.js +1 -1
- package/dist/dataLayer/delete.js.map +1 -1
- package/dist/dataLayer/harperBridge/ResourceBridge.d.ts +14 -1
- package/dist/dataLayer/harperBridge/ResourceBridge.js +72 -12
- package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
- package/dist/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.d.ts +3 -1
- package/dist/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.js +3 -1
- package/dist/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.js.map +1 -1
- package/dist/dataLayer/hdbInfoController.d.ts +10 -0
- package/dist/dataLayer/hdbInfoController.js +30 -1
- package/dist/dataLayer/hdbInfoController.js.map +1 -1
- package/dist/dataLayer/insert.d.ts +9 -1
- package/dist/dataLayer/insert.js +30 -0
- package/dist/dataLayer/insert.js.map +1 -1
- package/dist/dataLayer/rocksdbBackup.d.ts +2 -2
- package/dist/dataLayer/rocksdbBackup.js +45 -8
- package/dist/dataLayer/rocksdbBackup.js.map +1 -1
- package/dist/dataLayer/schema.js +8 -0
- package/dist/dataLayer/schema.js.map +1 -1
- package/dist/dataLayer/schemaDescribe.js +8 -1
- package/dist/dataLayer/schemaDescribe.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/json/systemSchema.json +55 -0
- package/dist/resources/DatabaseTransaction.d.ts +77 -0
- package/dist/resources/DatabaseTransaction.js +586 -54
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/LMDBTransaction.d.ts +2 -1
- package/dist/resources/LMDBTransaction.js +43 -6
- package/dist/resources/LMDBTransaction.js.map +1 -1
- package/dist/resources/PrimaryRocksDatabase.d.ts +1 -0
- package/dist/resources/PrimaryRocksDatabase.js +49 -5
- package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
- package/dist/resources/RecordEncoder.d.ts +20 -0
- package/dist/resources/RecordEncoder.js +110 -9
- package/dist/resources/RecordEncoder.js.map +1 -1
- package/dist/resources/RequestTarget.d.ts +2 -0
- package/dist/resources/RequestTarget.js.map +1 -1
- package/dist/resources/Resource.js +117 -24
- package/dist/resources/Resource.js.map +1 -1
- package/dist/resources/ResourceInterface.d.ts +28 -1
- package/dist/resources/ResourceInterface.js.map +1 -1
- package/dist/resources/RocksIndexStore.d.ts +6 -1
- package/dist/resources/RocksIndexStore.js +24 -9
- package/dist/resources/RocksIndexStore.js.map +1 -1
- package/dist/resources/RocksTransactionLogStore.d.ts +24 -1
- package/dist/resources/RocksTransactionLogStore.js +160 -49
- package/dist/resources/RocksTransactionLogStore.js.map +1 -1
- package/dist/resources/Table.d.ts +165 -10
- package/dist/resources/Table.js +1805 -303
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/analytics/write.js +10 -3
- package/dist/resources/analytics/write.js.map +1 -1
- package/dist/resources/auditStore.d.ts +181 -2
- package/dist/resources/auditStore.js +640 -29
- package/dist/resources/auditStore.js.map +1 -1
- package/dist/resources/blob.d.ts +129 -9
- package/dist/resources/blob.js +992 -114
- package/dist/resources/blob.js.map +1 -1
- package/dist/resources/branchDatabase.d.ts +48 -0
- package/dist/resources/branchDatabase.js +892 -0
- package/dist/resources/branchDatabase.js.map +1 -0
- package/dist/resources/crdt.js +50 -12
- package/dist/resources/crdt.js.map +1 -1
- package/dist/resources/dataLoader.js +3 -4
- package/dist/resources/dataLoader.js.map +1 -1
- package/dist/resources/databases.d.ts +157 -14
- package/dist/resources/databases.js +1462 -325
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/defineTable.d.ts +10 -2
- package/dist/resources/defineTable.js +9 -1
- package/dist/resources/defineTable.js.map +1 -1
- package/dist/resources/derivedIndexRegistry.d.ts +5 -0
- package/dist/resources/derivedIndexRegistry.js +68 -0
- package/dist/resources/derivedIndexRegistry.js.map +1 -0
- package/dist/resources/derivedIndexRuntime.d.ts +215 -0
- package/dist/resources/derivedIndexRuntime.js +2027 -0
- package/dist/resources/derivedIndexRuntime.js.map +1 -0
- package/dist/resources/graphql.d.ts +1 -1
- package/dist/resources/graphql.js +52 -16
- package/dist/resources/graphql.js.map +1 -1
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +103 -9
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +854 -41
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
- package/dist/resources/indexes/hnswDerivedIndex.d.ts +67 -0
- package/dist/resources/indexes/hnswDerivedIndex.js +464 -0
- package/dist/resources/indexes/hnswDerivedIndex.js.map +1 -0
- package/dist/resources/indexes/hnswPlaneBinding.d.ts +65 -0
- package/dist/resources/indexes/hnswPlaneBinding.js +91 -0
- package/dist/resources/indexes/hnswPlaneBinding.js.map +1 -0
- package/dist/resources/longLivedTransactions.d.ts +71 -0
- package/dist/resources/longLivedTransactions.js +358 -0
- package/dist/resources/longLivedTransactions.js.map +1 -0
- package/dist/resources/models/backendRegistry.d.ts +26 -0
- package/dist/resources/models/backendRegistry.js +60 -2
- package/dist/resources/models/backendRegistry.js.map +1 -1
- package/dist/resources/models/bootstrap.d.ts +33 -1
- package/dist/resources/models/bootstrap.js +416 -31
- package/dist/resources/models/bootstrap.js.map +1 -1
- package/dist/resources/nodeIdMapping.d.ts +5 -0
- package/dist/resources/nodeIdMapping.js +49 -0
- package/dist/resources/nodeIdMapping.js.map +1 -1
- package/dist/resources/recordLock.d.ts +123 -0
- package/dist/resources/recordLock.js +315 -0
- package/dist/resources/recordLock.js.map +1 -0
- package/dist/resources/recordLockCoordinator.d.ts +557 -0
- package/dist/resources/recordLockCoordinator.js +2565 -0
- package/dist/resources/recordLockCoordinator.js.map +1 -0
- package/dist/resources/replayLogs.d.ts +14 -1
- package/dist/resources/replayLogs.js +157 -24
- package/dist/resources/replayLogs.js.map +1 -1
- package/dist/resources/replayLogsGuards.d.ts +94 -7
- package/dist/resources/replayLogsGuards.js +111 -7
- package/dist/resources/replayLogsGuards.js.map +1 -1
- package/dist/resources/replicatedApplyFailure.d.ts +16 -0
- package/dist/resources/replicatedApplyFailure.js +63 -0
- package/dist/resources/replicatedApplyFailure.js.map +1 -0
- package/dist/resources/scheduler/scheduler.js +3 -3
- package/dist/resources/scheduler/scheduler.js.map +1 -1
- package/dist/resources/search.d.ts +10 -4
- package/dist/resources/search.js +283 -37
- package/dist/resources/search.js.map +1 -1
- package/dist/resources/tracked.d.ts +5 -1
- package/dist/resources/tracked.js +74 -23
- package/dist/resources/tracked.js.map +1 -1
- package/dist/resources/transactionBroadcast.d.ts +1 -1
- package/dist/resources/transactionBroadcast.js +2 -2
- package/dist/resources/transactionBroadcast.js.map +1 -1
- package/dist/security/auth.js +61 -30
- package/dist/security/auth.js.map +1 -1
- package/dist/security/authn/oidc/claims.d.ts +22 -0
- package/dist/security/authn/oidc/claims.js +71 -0
- package/dist/security/authn/oidc/claims.js.map +1 -0
- package/dist/security/authn/oidc/identityToken.d.ts +27 -0
- package/dist/security/authn/oidc/identityToken.js +111 -0
- package/dist/security/authn/oidc/identityToken.js.map +1 -0
- package/dist/security/authn/oidc/jwks.d.ts +25 -0
- package/dist/security/authn/oidc/jwks.js +261 -0
- package/dist/security/authn/oidc/jwks.js.map +1 -0
- package/dist/security/authn/oidc/providers/generic.d.ts +13 -0
- package/dist/security/authn/oidc/providers/generic.js +34 -0
- package/dist/security/authn/oidc/providers/generic.js.map +1 -0
- package/dist/security/authn/oidc/providers/githubActions.d.ts +11 -0
- package/dist/security/authn/oidc/providers/githubActions.js +129 -0
- package/dist/security/authn/oidc/providers/githubActions.js.map +1 -0
- package/dist/security/authn/oidc/providers/index.d.ts +37 -0
- package/dist/security/authn/oidc/providers/index.js +24 -0
- package/dist/security/authn/oidc/providers/index.js.map +1 -0
- package/dist/security/authn/oidc/tokenExchange.d.ts +12 -0
- package/dist/security/authn/oidc/tokenExchange.js +306 -0
- package/dist/security/authn/oidc/tokenExchange.js.map +1 -0
- package/dist/security/authn/oidc/trustPolicyOperations.d.ts +49 -0
- package/dist/security/authn/oidc/trustPolicyOperations.js +358 -0
- package/dist/security/authn/oidc/trustPolicyOperations.js.map +1 -0
- package/dist/security/authn/oidc/types.d.ts +38 -0
- package/dist/security/authn/oidc/types.js +6 -0
- package/dist/security/authn/oidc/types.js.map +1 -0
- package/dist/security/certificateVerification/index.js +40 -11
- package/dist/security/certificateVerification/index.js.map +1 -1
- package/dist/security/certificateVerification/trustedIssuers.d.ts +24 -0
- package/dist/security/certificateVerification/trustedIssuers.js +79 -0
- package/dist/security/certificateVerification/trustedIssuers.js.map +1 -0
- package/dist/security/certificateVerification/types.d.ts +1 -0
- package/dist/security/credentialProvenance.d.ts +35 -0
- package/dist/security/credentialProvenance.js +51 -0
- package/dist/security/credentialProvenance.js.map +1 -0
- package/dist/security/credentialRejection.d.ts +4 -0
- package/dist/security/credentialRejection.js +24 -0
- package/dist/security/credentialRejection.js.map +1 -0
- package/dist/security/deferredAuthentication.d.ts +36 -0
- package/dist/security/deferredAuthentication.js +70 -0
- package/dist/security/deferredAuthentication.js.map +1 -0
- package/dist/security/impersonation.d.ts +21 -0
- package/dist/security/impersonation.js +108 -9
- package/dist/security/impersonation.js.map +1 -1
- package/dist/security/jsLoader.d.ts +6 -0
- package/dist/security/jsLoader.js +77 -15
- package/dist/security/jsLoader.js.map +1 -1
- package/dist/security/keys.js +301 -71
- package/dist/security/keys.js.map +1 -1
- package/dist/security/operationScope.d.ts +21 -0
- package/dist/security/operationScope.js +36 -0
- package/dist/security/operationScope.js.map +1 -0
- package/dist/security/permissionsTranslator.js +21 -0
- package/dist/security/permissionsTranslator.js.map +1 -1
- package/dist/security/tokenAuthentication.d.ts +19 -1
- package/dist/security/tokenAuthentication.js +191 -10
- package/dist/security/tokenAuthentication.js.map +1 -1
- package/dist/security/user.js +4 -3
- package/dist/security/user.js.map +1 -1
- package/dist/server/DurableSubscriptionsSession.d.ts +2 -2
- package/dist/server/DurableSubscriptionsSession.js +67 -10
- package/dist/server/DurableSubscriptionsSession.js.map +1 -1
- package/dist/server/REST.js +106 -2
- package/dist/server/REST.js.map +1 -1
- package/dist/server/graphqlQuerying.js +4 -0
- package/dist/server/graphqlQuerying.js.map +1 -1
- package/dist/server/http.d.ts +16 -1
- package/dist/server/http.js +122 -17
- package/dist/server/http.js.map +1 -1
- package/dist/server/itc/serverHandlers.js +8 -1
- package/dist/server/itc/serverHandlers.js.map +1 -1
- package/dist/server/jobs/jobProcess.js +6 -2
- package/dist/server/jobs/jobProcess.js.map +1 -1
- package/dist/server/jobs/jobs.js +4 -1
- package/dist/server/jobs/jobs.js.map +1 -1
- package/dist/server/liveSubscriptionAuth.d.ts +26 -4
- package/dist/server/liveSubscriptionAuth.js +105 -39
- package/dist/server/liveSubscriptionAuth.js.map +1 -1
- package/dist/server/loadRootComponents.js +49 -10
- package/dist/server/loadRootComponents.js.map +1 -1
- package/dist/server/mqtt.d.ts +2 -0
- package/dist/server/mqtt.js +165 -30
- package/dist/server/mqtt.js.map +1 -1
- package/dist/server/nodeName.d.ts +2 -0
- package/dist/server/nodeName.js +107 -23
- package/dist/server/nodeName.js.map +1 -1
- package/dist/server/serverHelpers/Headers.d.ts +27 -0
- package/dist/server/serverHelpers/Headers.js +145 -1
- package/dist/server/serverHelpers/Headers.js.map +1 -1
- package/dist/server/serverHelpers/NodeAdapterResponse.d.ts +48 -0
- package/dist/server/serverHelpers/NodeAdapterResponse.js +220 -0
- package/dist/server/serverHelpers/NodeAdapterResponse.js.map +1 -0
- package/dist/server/serverHelpers/Request.d.ts +5 -10
- package/dist/server/serverHelpers/Request.js +38 -136
- package/dist/server/serverHelpers/Request.js.map +1 -1
- package/dist/server/serverHelpers/contentTypes.d.ts +11 -0
- package/dist/server/serverHelpers/contentTypes.js +221 -40
- package/dist/server/serverHelpers/contentTypes.js.map +1 -1
- package/dist/server/serverHelpers/deployValidationState.d.ts +3 -0
- package/dist/server/serverHelpers/deployValidationState.js +9 -19
- package/dist/server/serverHelpers/deployValidationState.js.map +1 -1
- package/dist/server/serverHelpers/operationAuthorizationState.d.ts +12 -0
- package/dist/server/serverHelpers/operationAuthorizationState.js +24 -2
- package/dist/server/serverHelpers/operationAuthorizationState.js.map +1 -1
- package/dist/server/serverHelpers/registeredOperations.d.ts +5 -4
- package/dist/server/serverHelpers/registeredOperations.js +74 -21
- package/dist/server/serverHelpers/registeredOperations.js.map +1 -1
- package/dist/server/serverHelpers/requestSanitization.d.ts +11 -0
- package/dist/server/serverHelpers/requestSanitization.js +20 -0
- package/dist/server/serverHelpers/requestSanitization.js.map +1 -0
- package/dist/server/serverHelpers/serverHandlers.js +6 -3
- package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
- package/dist/server/serverHelpers/serverUtilities.d.ts +18 -0
- package/dist/server/serverHelpers/serverUtilities.js +178 -29
- package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
- package/dist/server/serverHelpers/sharedMessageEncoding.d.ts +67 -0
- package/dist/server/serverHelpers/sharedMessageEncoding.js +280 -0
- package/dist/server/serverHelpers/sharedMessageEncoding.js.map +1 -0
- package/dist/server/serverHelpers/uwsServer.js +19 -1
- package/dist/server/serverHelpers/uwsServer.js.map +1 -1
- package/dist/server/static.js +24 -28
- package/dist/server/static.js.map +1 -1
- package/dist/server/storageReclamation.d.ts +5 -0
- package/dist/server/storageReclamation.js +17 -1
- package/dist/server/storageReclamation.js.map +1 -1
- package/dist/server/threads/isolatedApplications.d.ts +47 -0
- package/dist/server/threads/isolatedApplications.js +171 -0
- package/dist/server/threads/isolatedApplications.js.map +1 -0
- package/dist/server/threads/itc.d.ts +7 -2
- package/dist/server/threads/itc.js +5 -1
- package/dist/server/threads/itc.js.map +1 -1
- package/dist/server/threads/logRotationTransport.d.ts +1 -0
- package/dist/server/threads/logRotationTransport.js +33 -0
- package/dist/server/threads/logRotationTransport.js.map +1 -0
- package/dist/server/threads/manageThreads.d.ts +83 -3
- package/dist/server/threads/manageThreads.js +769 -75
- package/dist/server/threads/manageThreads.js.map +1 -1
- package/dist/server/threads/socketRouter.d.ts +1 -0
- package/dist/server/threads/socketRouter.js +277 -31
- package/dist/server/threads/socketRouter.js.map +1 -1
- package/dist/server/threads/threadHeapMemory.d.ts +2 -0
- package/dist/server/threads/threadHeapMemory.js +31 -0
- package/dist/server/threads/threadHeapMemory.js.map +1 -0
- package/dist/server/threads/threadServer.js +76 -22
- package/dist/server/threads/threadServer.js.map +1 -1
- package/dist/sqlEngine/config.d.ts +1 -3
- package/dist/sqlEngine/config.js +19 -16
- package/dist/sqlEngine/config.js.map +1 -1
- package/dist/sqlTranslator/index.d.ts +1 -1
- package/dist/sqlTranslator/index.js +30 -7
- package/dist/sqlTranslator/index.js.map +1 -1
- package/dist/upgrade/directives/5-3-0.d.ts +7 -0
- package/dist/upgrade/directives/5-3-0.js +148 -0
- package/dist/upgrade/directives/5-3-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/OperationFunctionCaller.js +2 -1
- package/dist/utility/OperationFunctionCaller.js.map +1 -1
- package/dist/utility/common_utils.d.ts +16 -0
- package/dist/utility/common_utils.js +32 -6
- package/dist/utility/common_utils.js.map +1 -1
- package/dist/utility/componentNames.d.ts +8 -0
- package/dist/utility/componentNames.js +12 -1
- package/dist/utility/componentNames.js.map +1 -1
- package/dist/utility/environment/environmentManager.js +3 -6
- package/dist/utility/environment/environmentManager.js.map +1 -1
- package/dist/utility/environment/systemInformation.d.ts +1 -0
- package/dist/utility/environment/systemInformation.js +1 -0
- package/dist/utility/environment/systemInformation.js.map +1 -1
- package/dist/utility/errors/commonErrors.d.ts +2 -0
- package/dist/utility/errors/commonErrors.js +2 -0
- package/dist/utility/errors/commonErrors.js.map +1 -1
- package/dist/utility/errors/hdbError.d.ts +33 -0
- package/dist/utility/errors/hdbError.js +58 -1
- package/dist/utility/errors/hdbError.js.map +1 -1
- package/dist/utility/globalSchema.d.ts +18 -0
- package/dist/utility/hdbTerms.d.ts +18 -0
- package/dist/utility/hdbTerms.js +20 -2
- package/dist/utility/hdbTerms.js.map +1 -1
- package/dist/utility/logging/harper_logger.d.ts +2 -0
- package/dist/utility/logging/harper_logger.js +286 -30
- package/dist/utility/logging/harper_logger.js.map +1 -1
- package/dist/utility/logging/logGenerationCoordinator.d.ts +35 -0
- package/dist/utility/logging/logGenerationCoordinator.js +184 -0
- package/dist/utility/logging/logGenerationCoordinator.js.map +1 -0
- package/dist/utility/logging/logRotation.d.ts +46 -0
- package/dist/utility/logging/logRotation.js +365 -0
- package/dist/utility/logging/logRotation.js.map +1 -0
- package/dist/utility/logging/logRotator.d.ts +1 -1
- package/dist/utility/logging/logRotator.js +192 -85
- package/dist/utility/logging/logRotator.js.map +1 -1
- package/dist/utility/nodeIdentity.d.ts +9 -0
- package/dist/utility/nodeIdentity.js +58 -0
- package/dist/utility/nodeIdentity.js.map +1 -0
- package/dist/utility/npmUtilities.js +11 -7
- package/dist/utility/npmUtilities.js.map +1 -1
- package/dist/utility/operationPermissions.d.ts +3 -1
- package/dist/utility/operationPermissions.js +16 -1
- package/dist/utility/operationPermissions.js.map +1 -1
- package/dist/utility/operation_authorization.d.ts +10 -7
- package/dist/utility/operation_authorization.js +212 -41
- package/dist/utility/operation_authorization.js.map +1 -1
- package/dist/utility/watchPath.d.ts +29 -0
- package/dist/utility/watchPath.js +68 -0
- package/dist/utility/watchPath.js.map +1 -0
- package/dist/utility/watcherFallback.d.ts +41 -0
- package/dist/utility/watcherFallback.js +153 -0
- package/dist/utility/watcherFallback.js.map +1 -1
- package/dist/validation/configValidator.d.ts +12 -0
- package/dist/validation/configValidator.js +205 -75
- package/dist/validation/configValidator.js.map +1 -1
- package/dist/validation/installValidator.js +12 -0
- package/dist/validation/installValidator.js.map +1 -1
- package/dist/validation/validationWrapper.d.ts +11 -0
- package/dist/validation/validationWrapper.js +16 -3
- package/dist/validation/validationWrapper.js.map +1 -1
- package/index.ts +8 -0
- package/json/systemSchema.json +55 -0
- package/npm-shrinkwrap.json +286 -194
- package/package.json +15 -7
- package/resources/DESIGN.md +219 -52
- package/resources/DatabaseTransaction.ts +657 -52
- package/resources/LMDBTransaction.ts +46 -6
- package/resources/PrimaryRocksDatabase.ts +46 -7
- package/resources/RecordEncoder.ts +124 -9
- package/resources/RequestTarget.ts +2 -0
- package/resources/Resource.ts +114 -22
- package/resources/ResourceInterface.ts +31 -0
- package/resources/RocksIndexStore.ts +30 -9
- package/resources/RocksTransactionLogStore.ts +188 -51
- package/resources/Table.ts +1960 -301
- package/resources/analytics/write.ts +10 -3
- package/resources/auditStore.ts +647 -32
- package/resources/blob.ts +1029 -110
- package/resources/branchDatabase.ts +941 -0
- package/resources/crdt.ts +76 -12
- package/resources/dataLoader.ts +3 -4
- package/resources/databases.ts +1584 -320
- package/resources/defineTable.ts +18 -2
- package/resources/derivedIndexRegistry.ts +56 -0
- package/resources/derivedIndexRuntime.ts +2292 -0
- package/resources/graphql.ts +72 -17
- package/resources/indexes/HierarchicalNavigableSmallWorld.ts +911 -48
- package/resources/indexes/hnswDerivedIndex.ts +531 -0
- package/resources/indexes/hnswPlaneBinding.ts +174 -0
- package/resources/longLivedTransactions.ts +360 -0
- package/resources/models/backendRegistry.ts +84 -2
- package/resources/models/bootstrap.ts +473 -28
- package/resources/nodeIdMapping.ts +50 -0
- package/resources/recordLock.ts +419 -0
- package/resources/recordLockCoordinator.ts +3043 -0
- package/resources/replayLogs.ts +152 -26
- package/resources/replayLogsGuards.ts +171 -8
- package/resources/replicatedApplyFailure.ts +77 -0
- package/resources/scheduler/scheduler.ts +4 -4
- package/resources/search.ts +288 -46
- package/resources/tracked.ts +73 -22
- package/resources/transactionBroadcast.ts +3 -3
- package/security/auth.ts +68 -29
- package/security/authn/oidc/claims.ts +72 -0
- package/security/authn/oidc/identityToken.ts +129 -0
- package/security/authn/oidc/jwks.ts +260 -0
- package/security/authn/oidc/providers/generic.ts +40 -0
- package/security/authn/oidc/providers/githubActions.ts +137 -0
- package/security/authn/oidc/providers/index.ts +52 -0
- package/security/authn/oidc/tokenExchange.ts +300 -0
- package/security/authn/oidc/trustPolicyOperations.ts +343 -0
- package/security/authn/oidc/types.ts +41 -0
- package/security/certificateVerification/index.ts +54 -13
- package/security/certificateVerification/trustedIssuers.ts +76 -0
- package/security/certificateVerification/types.ts +1 -0
- package/security/credentialProvenance.ts +47 -0
- package/security/credentialRejection.ts +22 -0
- package/security/deferredAuthentication.ts +71 -0
- package/security/impersonation.ts +117 -12
- package/security/jsLoader.ts +83 -18
- package/security/keys.ts +298 -72
- package/security/operationScope.ts +33 -0
- package/security/permissionsTranslator.js +23 -0
- package/security/tokenAuthentication.ts +233 -12
- package/security/user.ts +4 -3
- package/server/DESIGN.md +194 -16
- package/server/DurableSubscriptionsSession.ts +71 -11
- package/server/REST.ts +115 -4
- package/server/graphqlQuerying.ts +4 -0
- package/server/http.ts +133 -20
- package/server/itc/serverHandlers.js +8 -1
- package/server/jobs/jobProcess.ts +8 -2
- package/server/jobs/jobs.ts +4 -1
- package/server/liveSubscriptionAuth.ts +129 -46
- package/server/loadRootComponents.js +50 -8
- package/server/mqtt.ts +179 -38
- package/server/nodeName.ts +103 -21
- package/server/serverHelpers/Headers.ts +135 -0
- package/server/serverHelpers/NodeAdapterResponse.ts +221 -0
- package/server/serverHelpers/Request.ts +33 -131
- package/server/serverHelpers/contentTypes.ts +217 -36
- package/server/serverHelpers/deployValidationState.ts +24 -13
- package/server/serverHelpers/operationAuthorizationState.ts +34 -3
- package/server/serverHelpers/registeredOperations.ts +79 -22
- package/server/serverHelpers/requestSanitization.ts +15 -0
- package/server/serverHelpers/serverHandlers.js +6 -3
- package/server/serverHelpers/serverUtilities.ts +232 -40
- package/server/serverHelpers/sharedMessageEncoding.ts +307 -0
- package/server/serverHelpers/uwsServer.ts +17 -2
- package/server/static.ts +23 -29
- package/server/storageReclamation.ts +15 -2
- package/server/threads/isolatedApplications.ts +157 -0
- package/server/threads/itc.js +11 -1
- package/server/threads/logRotationTransport.ts +40 -0
- package/server/threads/manageThreads.js +771 -66
- package/server/threads/socketRouter.ts +291 -30
- package/server/threads/threadHeapMemory.ts +26 -0
- package/server/threads/threadServer.js +73 -22
- package/sqlTranslator/index.ts +31 -8
- package/studio/web/assets/{Chat-4RrB5134.js → Chat-D3j-1yY1.js} +1 -1
- package/studio/web/assets/{FloatingChat-omlNMDcJ.js → FloatingChat-BxJGYcfB.js} +3 -3
- package/studio/web/assets/{apiToken-Bke3wvfZ.js → apiToken-CT55oWOe.js} +1 -1
- package/studio/web/assets/{applications-DvFDYJgK.js → applications-D9Ct9_vm.js} +1 -1
- package/studio/web/assets/{cssMode-C1vRa7zh.js → cssMode-DV8H7VwA.js} +1 -1
- package/studio/web/assets/{editor-3XRWEDWX.js → editor-uatc0unt.js} +1 -1
- package/studio/web/assets/{html-CTY5tdMr.js → html-Bm6D6paN.js} +1 -1
- package/studio/web/assets/{htmlMode-CG1vSD9t.js → htmlMode-CEn7tpLG.js} +1 -1
- package/studio/web/assets/{index-Dfpeofdu.js → index-BIXW6Pu4.js} +5 -5
- package/studio/web/assets/{index.lazy-B1VOIv-t.js → index.lazy-UI7L-Vrk.js} +1 -1
- package/studio/web/assets/{javascript-CJeJzGnI.js → javascript-CJ0G3AFZ.js} +1 -1
- package/studio/web/assets/{jsonMode-CYPBwM82.js → jsonMode-DQADAYEa.js} +1 -1
- package/studio/web/assets/{languageServices-IKH4GUHl.js → languageServices-CAQJXWcI.js} +1 -1
- package/studio/web/assets/{lspLanguageFeatures-BC8_gxKG.js → lspLanguageFeatures-CCQ8P5sY.js} +1 -1
- package/studio/web/assets/{notifications-BfKBpYcq.js → notifications-BbxTU6Aw.js} +1 -1
- package/studio/web/assets/{notifications-DFbArTfC.js → notifications-Cvb3P1lB.js} +1 -1
- package/studio/web/assets/{profile-Q4-T6c-S.js → profile-Yyb7gsvL.js} +1 -1
- package/studio/web/assets/{regions-CtkV0xje.js → regions-OgjGHlU5.js} +1 -1
- package/studio/web/assets/{register-1ZZuMsiA.js → register-6qwNEOY3.js} +2 -2
- package/studio/web/assets/{setComponentFile-ZWLcWv5X.js → setComponentFile-BilDMtgB.js} +1 -1
- package/studio/web/assets/{setup-DWprJyJy.js → setup-J6qJ7OIU.js} +2 -2
- package/studio/web/assets/{status--aNm8isn.js → status-0RWGcfyD.js} +1 -1
- package/studio/web/assets/{toggleHighContrast-Cq_lt3XD.js → toggleHighContrast-BIn-vErT.js} +1 -1
- package/studio/web/assets/{tsMode-pjgytARx.js → tsMode-DgUXku4d.js} +1 -1
- package/studio/web/assets/{typescript-Cdg0mqUh.js → typescript-C9orXcsM.js} +1 -1
- package/studio/web/assets/{useEntityRestURL-RZhaY8Rn.js → useEntityRestURL-BEoXXbUB.js} +1 -1
- package/studio/web/assets/{workers-BgoXIqQe.js → workers-JVzSDmgx.js} +1 -1
- package/studio/web/assets/{xml-BwSeDMiP.js → xml-Cq-S8S4X.js} +1 -1
- package/studio/web/assets/{yaml-DotCUG5l.js → yaml-sfoRdh1M.js} +1 -1
- package/studio/web/index.html +1 -1
- package/upgrade/directives/5-3-0.ts +132 -0
- package/upgrade/directives/directivesController.ts +2 -1
- package/utility/OperationFunctionCaller.ts +2 -1
- package/utility/common_utils.ts +30 -5
- package/utility/componentNames.ts +12 -0
- package/utility/environment/environmentManager.ts +3 -7
- package/utility/environment/systemInformation.ts +7 -0
- package/utility/errors/commonErrors.ts +4 -0
- package/utility/errors/hdbError.ts +57 -0
- package/utility/hdbTerms.ts +19 -0
- package/utility/logging/harper_logger.ts +278 -26
- package/utility/logging/logGenerationCoordinator.ts +196 -0
- package/utility/logging/logRotation.ts +367 -0
- package/utility/logging/logRotator.ts +213 -81
- package/utility/nodeIdentity.ts +45 -0
- package/utility/npmUtilities.ts +12 -8
- package/utility/operationPermissions.ts +18 -1
- package/utility/operation_authorization.ts +231 -42
- package/utility/watchPath.ts +63 -0
- package/utility/watcherFallback.ts +148 -0
- package/validation/configValidator.ts +215 -75
- package/validation/installValidator.ts +15 -0
- package/validation/validationWrapper.ts +18 -4
package/server/DESIGN.md
CHANGED
|
@@ -47,7 +47,8 @@ A request entering `http.ts` does **not** go through Fastify. The two `handleApp
|
|
|
47
47
|
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
48
48
|
| `serverHelpers/Request.ts` | Wraps `IncomingMessage` with Harper-specific fields (user, response, headers). |
|
|
49
49
|
| `serverHelpers/Headers.ts` | Header mutation/merge utilities. |
|
|
50
|
-
| `serverHelpers/contentTypes.ts` | (de)serialization registry; `serialize`, `serializeMessage`, `getDeserializer`.
|
|
50
|
+
| `serverHelpers/contentTypes.ts` | (de)serialization registry; `serialize`, `serializeMessage`, `getMessageSerializer`, `getDeserializer`. |
|
|
51
|
+
| `serverHelpers/sharedMessageEncoding.ts` | Per-message encoding shared across the subscribers of a topic — one serialization per (message, content type) and, for MQTT QoS 0, one PUBLISH packet per (payload, topic, protocol version). Keyed on the message object, which `transactionBroadcast` dispatches by identity to every subscription of a key. **Identity contract:** see below. |
|
|
51
52
|
| `serverHelpers/serverUtilities.ts` | `OperationDefinition` and shared helpers. |
|
|
52
53
|
| `serverHelpers/OperationFunctionObject.ts` | Wraps an operation handler with metadata. |
|
|
53
54
|
| `serverHelpers/JSONStream.ts` | Streaming JSON output for large responses. |
|
|
@@ -58,6 +59,20 @@ A request entering `http.ts` does **not** go through Fastify. The two `handleApp
|
|
|
58
59
|
| `serverRegistry.ts` | Trivial registry export. |
|
|
59
60
|
| `status/` | Server status reporting (cluster status, per-port info). |
|
|
60
61
|
|
|
62
|
+
> **Subscription message identity contract.** `sharedMessageEncoding.ts` encodes a message once and
|
|
63
|
+
> reuses the bytes for every subscriber of the topic, keyed on the message object's identity. That is
|
|
64
|
+
> sound for every internal producer because each yields a fresh object per version — `transactionBroadcast`
|
|
65
|
+
> hands one `auditRecord` to every subscription, `auditStore`'s `getValue` memoizes the decode in its
|
|
66
|
+
> closure, and `primaryStore.getEntry` is version-guarded — so identity implies equal content. Pinned by
|
|
67
|
+
> `unitTests/resources/subscriptionValueIdentity.test.js`.
|
|
68
|
+
>
|
|
69
|
+
> Rather than leave that as a rule custom Resources have to know, the event's record `version` is part
|
|
70
|
+
> of the cache key: `DurableSubscriptionsSession` forwards it to the delivery listener, and an entry
|
|
71
|
+
> whose version does not match is re-encoded. A Resource that reuses one envelope but advances the
|
|
72
|
+
> version is therefore correct, not merely disallowed; one that supplies no version is not shared at
|
|
73
|
+
> all. Only mutating an object _without_ changing its version can serve stale bytes, and that is
|
|
74
|
+
> indistinguishable from re-sending the same message.
|
|
75
|
+
|
|
61
76
|
### Threads
|
|
62
77
|
|
|
63
78
|
| File | Purpose |
|
|
@@ -74,6 +89,17 @@ calls fail with `ERR_HARPER_PROCESS_SHUTTING_DOWN`; scoped worker-type restarts
|
|
|
74
89
|
`shutdownWorkersNow()` remains an immediate teardown: its worker shutdown messages are best-effort,
|
|
75
90
|
and it force-terminates the remaining worker set rather than waiting for application drain hooks.
|
|
76
91
|
|
|
92
|
+
A rolling restart serves the _old_ code until each worker is replaced, and where the OS grants
|
|
93
|
+
`SO_REUSEPORT` the not-yet-replaced workers keep accepting connections for the whole restart — so a
|
|
94
|
+
component deploy is not live pool-wide until `restartWorkers()` resolves. For the overlapping types
|
|
95
|
+
(HTTP) it waits for each replacement to report `CHILD_STARTED` — including the ones that can only be
|
|
96
|
+
started after their predecessor releases its exclusive ports (Windows/macOS/Bun) — and reports how
|
|
97
|
+
many workers it left on the old code because a replacement never came up, versus how many
|
|
98
|
+
replacements never started after their predecessor was already gone. Other thread types start their
|
|
99
|
+
replacement without being awaited. Each wait is bounded by a per-worker startup backstop, so
|
|
100
|
+
resolution means "the restart finished", not "every worker is new". A caller that treats its own success as
|
|
101
|
+
"the component is live" must await it (see `deployComponent` in `components/operations.js`).
|
|
102
|
+
|
|
77
103
|
> Workers receive `workerData.noServerStart = true` — never start the server inside a worker.
|
|
78
104
|
>
|
|
79
105
|
> `threadServer.listenOnDomainSocket()` skips a listener only when its path exceeds the platform's
|
|
@@ -105,7 +131,8 @@ Every entry is a top-level function or named const. Jump via go-to-symbol or `gr
|
|
|
105
131
|
| `getPorts()` | Resolves listener options → list of `{port, secure}`. |
|
|
106
132
|
| `httpServer()` | Main listener registration entry point. |
|
|
107
133
|
| `getHTTPServer(port, secure, options)` | **The largest function in the file.** Creates/retrieves the underlying Node HTTP/HTTPS server. Wires `request`, `upgrade`, error handlers, TLS context, and the per-port middleware chain. |
|
|
108
|
-
| `makeCallbackChain()` | Builds the per-port handler chain via `middlewareChain.topoSort`.
|
|
134
|
+
| `makeCallbackChain()` | Builds the per-port handler chain via `middlewareChain.topoSort`, and records its resolved order for `get_status`. |
|
|
135
|
+
| `buildChains()` | Stores a built chain, and rebuilds every other already-built chain of that kind when the registration is on the `'all'` pseudo-port. See "Middleware ordering" below. |
|
|
109
136
|
| `unhandled()` | Terminal 404 handler. |
|
|
110
137
|
| `onRequest()` | Thin alias of `httpServer({requestOnly: true})`. |
|
|
111
138
|
| `onUpgrade()` / `upgradeListeners` (const) | Register HTTP upgrade listener; underlying list. |
|
|
@@ -125,6 +152,19 @@ Components register listeners with optional `before: 'name'` / `after: 'name'` o
|
|
|
125
152
|
|
|
126
153
|
The default WebSocket upgrade handler is registered automatically inside `onWebSocket()` the first time it runs for a given port.
|
|
127
154
|
|
|
155
|
+
**`port: 'all'` is a pseudo-port, and its chain is not the one that serves traffic.** Chain building
|
|
156
|
+
folds every `'all'` entry into each concrete port's chain, so `chains.all` exists but nothing
|
|
157
|
+
dispatches through it — `httpChain[port]`/`upgradeChains[port]`/`websocketChains[port]` are looked up
|
|
158
|
+
by the bound port on every request (Node, Bun, and uWS alike). A registration therefore has to
|
|
159
|
+
rebuild the chains it affects, not just its own key: `http.ts → buildChains()` rebuilds every
|
|
160
|
+
already-built chain of that kind whenever the registration is on `'all'`. Without that, an entry
|
|
161
|
+
registered on `'all'` after the concrete port's chain was built — the shape an application catch-all
|
|
162
|
+
mounted `after: 'rest'` takes, since `rest` registers first — updates only `chains.all` and never
|
|
163
|
+
reaches a request (#2418). Rebuilding is a pure function of the listener list and the port, so the
|
|
164
|
+
extra passes can only reproduce a port's order or extend it. `buildChains()` also writes the
|
|
165
|
+
`get_status` chain description in the same pass, which is what keeps that report from ever
|
|
166
|
+
describing an order a port isn't running (#1573).
|
|
167
|
+
|
|
128
168
|
### Application mounts (`host` / `urlPath` in the root config)
|
|
129
169
|
|
|
130
170
|
An operator mounts an application by putting `host`/`urlPath` on its entry in the **root** config; `components/scopeMount.ts` models it and the loader threads it into every `Scope` for that application (both load paths — the root-config `package` recursion and the components-root directory scan).
|
|
@@ -154,16 +194,152 @@ must run on a worker, `registeredOperations.ts` carries that state in the same-p
|
|
|
154
194
|
separately from the structured-cloned body. Never attach trusted dispatch state to an operation
|
|
155
195
|
payload.
|
|
156
196
|
|
|
197
|
+
`server.registerOperation()` runs per-worker, so anything the **main** thread must later know about a
|
|
198
|
+
registered op has to ride the OPERATION_REGISTERED announcement — a module-local registry populated
|
|
199
|
+
during registration exists only in the worker that registered. The bridge carries two such facts
|
|
200
|
+
today: name→thread routing (for execution forwarding) and `grantable` (so `validateOperations` on
|
|
201
|
+
main will accept the name in a role's `operations` allowlist, for add_role/alter_role, impersonation,
|
|
202
|
+
and OIDC trust policies). Adding a third main-thread consumer of a worker-registered fact means
|
|
203
|
+
extending that message, not reading a registry that main never populated. Grantability is safe to
|
|
204
|
+
mirror because it only widens what an allowlist may _name_; enforcement stays on the worker's
|
|
205
|
+
`chooseOperation`.
|
|
206
|
+
|
|
157
207
|
## Resource ↔ HTTP boundary
|
|
158
208
|
|
|
159
209
|
`REST.ts → http(request, nextHandler)` is the chief integration point: it takes a `Request`, asks the `Resources` registry for a match, builds a `RequestTarget`, and dispatches into the Resource class's static method. Cache headers are translated to `request.expiresAt` / `onlyIfCached` / `noCache` flags within the same function.
|
|
160
210
|
|
|
211
|
+
### Streaming startup errors
|
|
212
|
+
|
|
213
|
+
SSE and NDJSON serializers eagerly take and hold their first iterator step for GET requests. `REST.ts` waits through the next event-loop turn for that step: an immediate rejection remains an HTTP error rendered as Problem Details, while a first item or the cutoff commits the stream. Mutating requests do not use the startup-status gate because their transaction has already committed by this point; all of their stream failures use the in-band form. Later GET failures are terminal, format-valid records (`event: harper-error` for SSE and a reserved control record for NDJSON). Keep the decision in the serializer/REST boundary so Node, uWS, Bun, compression, and injection share one contract; transports must not independently prefetch the iterator.
|
|
214
|
+
|
|
215
|
+
SSE terminal event data uses `{ error: <code-or-class>, message: <message>, status?: <status> }`; NDJSON wraps the same object as `{ "$harperStreamError": { ... } }` so it cannot be mistaken for an ordinary row with an `error` field.
|
|
216
|
+
Sources with `mapError` remain unmapped for these two formats so their failures reach this single contract instead of becoming legacy data records.
|
|
217
|
+
generic JSON-array streaming retains its older `{ error: "<name>: <message>" }` element shape.
|
|
218
|
+
The `error` value is the stable programmatic discriminator; `message` is diagnostic and follows the same thrown-message exposure policy as pre-commit Problem Details.
|
|
219
|
+
|
|
220
|
+
Clean stream completion does not prove completeness; clients must inspect SSE for the `harper-error` event and NDJSON for the `$harperStreamError` control record.
|
|
221
|
+
|
|
222
|
+
### Deferred credential rejection (#2418)
|
|
223
|
+
|
|
224
|
+
`authentication` runs before route matching, so when it meets an `Authorization` header it cannot
|
|
225
|
+
resolve it does not yet know whether Harper or an application owns the URL. Rejecting there forces
|
|
226
|
+
an application to choose between two things it needs: Harper owning its own routes (status, REST
|
|
227
|
+
resources) and its own routes receiving their own credential scheme untouched.
|
|
228
|
+
|
|
229
|
+
So a rejection is recorded rather than answered:
|
|
230
|
+
|
|
231
|
+
- **Valid Harper credentials** authenticate normally and populate `request.user`. Unchanged.
|
|
232
|
+
- **No credentials** continue anonymously. Unchanged.
|
|
233
|
+
- **A syntactically valid credential Harper does not recognize** leaves `request.user` unset, leaves
|
|
234
|
+
the inbound `Authorization` header byte-for-byte intact, and records request-local state through
|
|
235
|
+
`security/deferredAuthentication.ts`. The state lives behind a module-private `Symbol`. Its
|
|
236
|
+
descriptor and value are immutable and non-enumerable, so downstream middleware cannot clear it
|
|
237
|
+
and it does not leak through request copies or serialization.
|
|
238
|
+
- **An internal authentication fault** — unreadable or malformed JWT key material, a storage failure,
|
|
239
|
+
an unexpected error type — is never deferred, and fails closed with the in-line 401.
|
|
240
|
+
- **The operations API** never defers: `request.isOperationsServer` short-circuits to the in-line
|
|
241
|
+
401, because every operations route is Harper-owned and there is nothing to defer to.
|
|
242
|
+
|
|
243
|
+
**Rejection provenance is asserted, never inferred.** `security/credentialRejection.ts` holds a
|
|
244
|
+
module-private `Symbol` tag; only the code that actually concludes "this credential is unacceptable"
|
|
245
|
+
sets it, and `isCredentialRejection()` reads nothing else. Status ranges cannot carry that meaning:
|
|
246
|
+
`findAndValidateUser()` lazily loads the user cache, whose system-table searches raise a
|
|
247
|
+
default-status-400 `ClientError` when `system.hdb_role`/`system.hdb_user` is unavailable, so a 4xx
|
|
248
|
+
test would classify a storage outage as an unknown credential and hand it to application
|
|
249
|
+
authorization. The tag is set at exactly these points:
|
|
250
|
+
|
|
251
|
+
| Tagged rejection | Where |
|
|
252
|
+
| ------------------------------------------------------------------ | ------------------------------------------ |
|
|
253
|
+
| unknown user, inactive user, bad password | `security/user.ts → findAndValidateUser()` |
|
|
254
|
+
| JWT syntax, signature, expiry, not-before, subject/claim rejection | `tokenAuthentication.ts → validateToken()` |
|
|
255
|
+
| refresh-token hash mismatch, malformed scoped-token claims | `tokenAuthentication.ts` |
|
|
256
|
+
| an `Authorization` scheme Harper does not implement | `security/auth.ts` |
|
|
257
|
+
|
|
258
|
+
`validateToken()` separates the two in the same catch: `jsonwebtoken` reports unusable key material
|
|
259
|
+
through the very same `JsonWebTokenError` type it uses for a forged token, so the public key is
|
|
260
|
+
validated as key material before `jwt.verify()` runs and a residual key-material message is treated
|
|
261
|
+
as a fault. An untagged error propagates unmasked.
|
|
262
|
+
|
|
263
|
+
The Bearer path's refresh-token probe follows the same rule. When operation-token validation says
|
|
264
|
+
`invalid token`, authentication retries the credential as a refresh token; a fault raised by that
|
|
265
|
+
retry propagates, and only an ordinary _tagged_ refresh rejection restores the original
|
|
266
|
+
operation-token rejection for deferral. An in-line fail-closed response logs the original fault
|
|
267
|
+
server-side and returns the same generic authentication failure a rejected credential gets, so
|
|
268
|
+
internal detail never reaches an unauthenticated client.
|
|
269
|
+
|
|
270
|
+
Any layer that establishes Harper owns the route then settles the deferred state before doing work:
|
|
271
|
+
|
|
272
|
+
| Layer | Where |
|
|
273
|
+
| --------------------------------------- | ------------------------------------------------------------------ |
|
|
274
|
+
| `REST.ts → http()` | after `resources.getMatch` succeeds (and for the OpenAPI document) |
|
|
275
|
+
| `REST.ts` WebSocket handler | after `resources.getMatch(url, 'ws')` succeeds |
|
|
276
|
+
| `graphqlQuerying.ts` | after the `/graphql` prefix match, ahead of its error mapping |
|
|
277
|
+
| `static.ts` | after a static file entry matches |
|
|
278
|
+
| `mqtt.ts` WebSocket handler | once the pending HTTP chain settles, before the first packet |
|
|
279
|
+
| `components/mcp/adapters/harperHttp.ts` | after the WebSocket hand-off, before the body is read |
|
|
280
|
+
|
|
281
|
+
**Every Harper-owned handler registered `after: 'authentication'` owes this settlement**, because
|
|
282
|
+
declining to call `nextHandler` is precisely the moment ownership is settled. A handler that skips
|
|
283
|
+
it serves an unrecognized credential as anonymous — the MCP application mount did exactly that, and
|
|
284
|
+
returned 200 for an `initialize` the base revision answered with 401.
|
|
285
|
+
|
|
286
|
+
Settlement goes through `settleDeferredCredentialRejection()`, which returns the response descriptor
|
|
287
|
+
`security/auth.ts` used to return in line: status 401 and `serializeMessage({error: message}, request)`
|
|
288
|
+
in the request's negotiated content type. That matters because an owner's own error mapping is not
|
|
289
|
+
that contract — REST renders a thrown error as an RFC 9457 Problem Details document and GraphQL as
|
|
290
|
+
`{errors:[{message}]}` — and a rejected credential never reached either before deferral existed.
|
|
291
|
+
`assertNoDeferredCredentialRejection()` is the throwing form, for a WebSocket upgrade that has no
|
|
292
|
+
descriptor to return. A WebSocket owner cannot read the state synchronously: `server/http.ts` starts
|
|
293
|
+
`httpChain[port](request)` and invokes the WebSocket chain with the still-pending completion, so
|
|
294
|
+
authentication has not yet classified the credential. `mqtt.ts` therefore settles on that promise —
|
|
295
|
+
the same one the session's principal resolves from — and closes the socket from its rejection, while
|
|
296
|
+
its frame handlers still attach synchronously. The deferred status is pinned to 401 regardless of the underlying error's own
|
|
297
|
+
status, so a 403 `token expired` reads exactly as it did before. A Harper-owned route therefore behaves identically to the pre-deferral
|
|
298
|
+
build, protected or public: an unknown credential can never buy access an anonymous caller would
|
|
299
|
+
have received, and can never reach an application catch-all. Only a URL that reached
|
|
300
|
+
`nextHandler` — one no Harper route owns — carries the original header onward.
|
|
301
|
+
|
|
302
|
+
The contract is route-ownership-based, not path-based. There is no exemption list, no carrier
|
|
303
|
+
header, no credential rename, and no pre-auth stripping shim.
|
|
304
|
+
|
|
305
|
+
**Authentication does not re-decorate a 401 it did not raise.** `security/auth.ts` post-processes any
|
|
306
|
+
401 coming back up the chain — overwriting `WWW-Authenticate` with `Basic`, or rewriting the status to
|
|
307
|
+
a 302 at `resources.loginPath` for a browser. The in-line rejection deferral replaced returned before
|
|
308
|
+
that code, so a settled rejection must skip it to stay wire-identical; and a 401 an application
|
|
309
|
+
catch-all raised for its own scheme (a WooCommerce or Bearer challenge) is that application's to make.
|
|
310
|
+
Both cases are keyed on the deferred state, so a request that deferred nothing keeps the existing
|
|
311
|
+
behavior exactly. The #1565 identity floor still applies either way — it is stamped in
|
|
312
|
+
`applyResponseHeaders`, not in the challenge rewriting.
|
|
313
|
+
|
|
314
|
+
**The identity cache floor survives the legacy Fastify fallbacks.** A response produced under a
|
|
315
|
+
deferred credential is credential-dependent (#1565), so `authentication` stamps
|
|
316
|
+
`Cache-Control: private, no-cache` and `Vary: Authorization, Cookie` on it. Before deferral an
|
|
317
|
+
unrecognized credential could not reach a fallback at all, so this was unreachable. All three adapters
|
|
318
|
+
now reconcile through one policy, `Headers.ts → mergeChainHeadersIntoFallback()`: Fastify wins every
|
|
319
|
+
single-valued header it set, `Vary` is unioned, and the private scope is re-applied unless the final
|
|
320
|
+
response explicitly opts into shared caching (`public`/`s-maxage`).
|
|
321
|
+
|
|
322
|
+
`Set-Cookie` is the deliberate exception to Fastify-wins. It is a list-valued field, so the chain's
|
|
323
|
+
cookies are appended beside Fastify's and de-duplicated by exact value, never by cookie name. A cookie
|
|
324
|
+
is identified by name _plus_ `Domain` _plus_ `Path` (RFC 6265 §5.3), so collapsing by name drops
|
|
325
|
+
legitimately distinct cookies — a same-name pair scoped to `/` and `/wp-admin`, or a `Max-Age=0`
|
|
326
|
+
deletion paired with a set. Exact value is also what makes Node's `writeHead` re-merge idempotent,
|
|
327
|
+
because that path sees the chain's own cookie already on the response. Two cookies that do share a
|
|
328
|
+
full identity both reach the client, chain last, and the user agent resolves them last-wins.
|
|
329
|
+
|
|
330
|
+
Bun and uWS rebuild their headers from Fastify's reply and merge once. Node hands Fastify the same
|
|
331
|
+
`ServerResponse` the chain's headers were copied onto, so copying is not enough — a route calling
|
|
332
|
+
`reply.header('Cache-Control', …)` replaces the floor outright and can make a credential-dependent
|
|
333
|
+
response shared-cacheable. `bridgeChainHeadersToNodeResponse()` therefore runs the same merge from a
|
|
334
|
+
`writeHead` interception, the last point the header set is still mutable and the one Node also routes
|
|
335
|
+
implicit headers through.
|
|
336
|
+
|
|
161
337
|
### Response Cache-Control / Vary policy (#1518, #1565)
|
|
162
338
|
|
|
163
339
|
Three tiers, applied in two places:
|
|
164
340
|
|
|
165
341
|
1. **App/resource explicit** — a `Cache-Control` set by the resource (or `@table(cacheControl: "...")` for anonymous reads, emitted in `REST.ts → http()`) always wins. The declaration is required: anonymous readability alone never emits shared-cache headers, because a request-attribute-gated `allowRead` (IP, headers) would make inferred `public` unsound.
|
|
166
|
-
2. **Identity floor** — `security/auth.ts → applyResponseHeaders` stamps `Cache-Control: private, no-cache` + `Vary: Authorization` (+ `Cookie` when sessions are on) on any response where a principal was resolved
|
|
342
|
+
2. **Identity floor** — `security/auth.ts → applyResponseHeaders` stamps `Cache-Control: private, no-cache` + `Vary: Authorization` (+ `Cookie` when sessions are on) on any response where a principal was resolved, credentials were rejected (401), or a credential rejection was deferred (#2418 — the application answered using the header Harper passed through, so the response is credential-dependent at a plain 200), _unless_ the app opted into shared caching with `public`/`s-maxage` (the RFC 9111 opt-in).
|
|
167
343
|
3. **CORS partitioning** — when CORS is enabled, every response gets `Vary: Origin` (the ACAO header is reflected per-origin, and its absence on no-Origin requests is origin-dependent too).
|
|
168
344
|
|
|
169
345
|
The `@table(cacheControl:)` value is persisted on the primary-key attribute (like `expiration`), so all threads and future boots see it; `resources/databases.ts → table()` treats `null` as "schema explicitly has none" (clears on reload) and `undefined` as "caller is not schema-defining" (no clobber from `add_attribute`/cluster schema events).
|
|
@@ -172,19 +348,21 @@ The `@table(cacheControl:)` value is persisted on the primary-key attribute (lik
|
|
|
172
348
|
|
|
173
349
|
## "Where is X" cheat sheet
|
|
174
350
|
|
|
175
|
-
| Question
|
|
176
|
-
|
|
|
177
|
-
| Where do I register a new HTTP handler?
|
|
178
|
-
| Where do I register a WebSocket handler?
|
|
179
|
-
| How does `before`/`after` middleware ordering work?
|
|
180
|
-
| Where does PROXY protocol get parsed?
|
|
181
|
-
| How does an app read forwarded TLS facts (JA3/JA4, ALPN, SNI, mTLS cert)?
|
|
182
|
-
|
|
|
183
|
-
| Where is the
|
|
184
|
-
|
|
|
185
|
-
|
|
|
186
|
-
|
|
|
187
|
-
| Where
|
|
351
|
+
| Question | Where |
|
|
352
|
+
| -------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
353
|
+
| Where do I register a new HTTP handler? | `http.ts → httpServer()` (or `onRequest()` for the request-only form) |
|
|
354
|
+
| Where do I register a WebSocket handler? | `http.ts → onWebSocket()` |
|
|
355
|
+
| How does `before`/`after` middleware ordering work? | `middlewareChain.ts → topoSort` |
|
|
356
|
+
| Where does PROXY protocol get parsed? | `serverHelpers/proxyProtocol.ts` (applied by `http.ts → enableProxyProtocol` / `createH2CProxyFront`) |
|
|
357
|
+
| How does an app read forwarded TLS facts (JA3/JA4, ALPN, SNI, mTLS cert)? | `request.connectionInfo` (`ConnectionInfo` from `serverHelpers/proxyProtocol.ts`); only set from a trusted PROXY v2 header on the UDS mirror, never from a request header |
|
|
358
|
+
| Why does mTLS revocation checking look up the client cert's issuer itself? | `security/certificateVerification/trustedIssuers.ts` — Node only completes a peer chain from the _listener_ context's store, but Harper's client CAs live on SNI contexts (`security/keys.ts → createTLSSelector`), so a resumed TLS session (and Node 26.8.0/26.8.1, nodejs/node#65579) exposes the leaf alone; `verifyCertificate` recovers the issuer from the published CA set and otherwise applies `failureMode` |
|
|
359
|
+
| Where is the REST request → Resource dispatch? | `REST.ts → http()` |
|
|
360
|
+
| Where is the operations API request handled? | `operationsServer.ts → handler` |
|
|
361
|
+
| How are content types (de)serialized? | `serverHelpers/contentTypes.ts` |
|
|
362
|
+
| Why doesn't every MQTT subscriber re-serialize the message it receives? | `serverHelpers/sharedMessageEncoding.ts` (memoized on the message object the fan-out shares); consumed by the outbound listener in `mqtt.ts` |
|
|
363
|
+
| Where do durable subscriptions live? | `DurableSubscriptionsSession.ts` |
|
|
364
|
+
| How are sockets dispatched to worker threads? | `threads/socketRouter.ts` |
|
|
365
|
+
| Where is the Operations API wired into Fastify? | `operationsServer.ts → buildServer` |
|
|
188
366
|
|
|
189
367
|
---
|
|
190
368
|
|
|
@@ -32,6 +32,24 @@ function getDurableSession() {
|
|
|
32
32
|
return _DurableSession;
|
|
33
33
|
}
|
|
34
34
|
let _LastWill: any;
|
|
35
|
+
/**
|
|
36
|
+
* A scoped token's only revocation is expiry, so its will must not publish past it. Keyed off the
|
|
37
|
+
* persisted will principal (not any live session user) so both will paths agree on one source.
|
|
38
|
+
* Fails closed: a scoped will with no recorded expiry is treated as expired rather than published.
|
|
39
|
+
*/
|
|
40
|
+
function isWillFromExpiredScopedToken(will: any): boolean {
|
|
41
|
+
const user = will?.user;
|
|
42
|
+
if (!user?._scopedToken) return false;
|
|
43
|
+
return !user.authExpiresAt || user.authExpiresAt * 1000 <= Date.now();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Drops the runtime-only pre-expanded operations Set so the permission set is storage-safe. */
|
|
47
|
+
function stripRuntimePermissionState(permission: any): any {
|
|
48
|
+
if (!permission || typeof permission !== 'object') return permission;
|
|
49
|
+
const { _expandedOperations, ...durable } = permission;
|
|
50
|
+
return durable;
|
|
51
|
+
}
|
|
52
|
+
|
|
35
53
|
function getLastWill() {
|
|
36
54
|
if (!_LastWill) {
|
|
37
55
|
_LastWill = table({
|
|
@@ -56,13 +74,23 @@ if (getWorkerIndex() === 0) {
|
|
|
56
74
|
for await (const will of getLastWill().search({})) {
|
|
57
75
|
const data = will.data;
|
|
58
76
|
const message = { ...will };
|
|
59
|
-
if (message.user?.username) message.user = await (server as any).getUser(message.user.username);
|
|
60
77
|
try {
|
|
61
|
-
|
|
78
|
+
if (message.user?._scopedToken) {
|
|
79
|
+
// A scoped token's username is attribution only; never rehydrate it by name (that could
|
|
80
|
+
// substitute a real principal). The will carries the token's own downgraded role.
|
|
81
|
+
if (!isWillFromExpiredScopedToken(message)) {
|
|
82
|
+
await publishMessage(message, data, message);
|
|
83
|
+
} else warn('Dropping will from an expired scoped token', data);
|
|
84
|
+
} else {
|
|
85
|
+
if (message.user?.username) message.user = await (server as any).getUser(message.user.username);
|
|
86
|
+
await publishMessage(message, data, message);
|
|
87
|
+
}
|
|
88
|
+
await getLastWill().delete(will.id);
|
|
62
89
|
} catch {
|
|
90
|
+
// One will's publish/delete failure must not abort replay of the rest; the row stays and
|
|
91
|
+
// is retried on the next restart.
|
|
63
92
|
warn('Failed to publish will', data);
|
|
64
93
|
}
|
|
65
|
-
getLastWill().delete(will.id);
|
|
66
94
|
}
|
|
67
95
|
})();
|
|
68
96
|
}
|
|
@@ -125,7 +153,22 @@ export async function getSession({
|
|
|
125
153
|
}
|
|
126
154
|
if (will) {
|
|
127
155
|
will.id = sessionId;
|
|
128
|
-
will
|
|
156
|
+
// A scoped-token bearer's will must carry the token's own role and expiry: its username is
|
|
157
|
+
// attribution only and cannot be rehydrated from hdb_user at replay time. Persist only the
|
|
158
|
+
// durable permission fields — not the runtime-only _expandedOperations Set, which is rebuilt
|
|
159
|
+
// on read and would not round-trip through storage.
|
|
160
|
+
will.user = user?._scopedToken
|
|
161
|
+
? {
|
|
162
|
+
username: user.username,
|
|
163
|
+
_scopedToken: true,
|
|
164
|
+
authExpiresAt: user.authExpiresAt,
|
|
165
|
+
role: user.role && {
|
|
166
|
+
role: user.role.role,
|
|
167
|
+
id: user.role.id,
|
|
168
|
+
permission: stripRuntimePermissionState(user.role.permission),
|
|
169
|
+
},
|
|
170
|
+
}
|
|
171
|
+
: { username: user?.username };
|
|
129
172
|
// Must be durably persisted before CONNACK is sent (getSession() resolving is what lets
|
|
130
173
|
// mqtt.ts send CONNACK). Otherwise a client that connects and then disconnects abruptly
|
|
131
174
|
// (no DISCONNECT packet) can race ahead of this write: SubscriptionsSession.disconnect()
|
|
@@ -155,7 +198,7 @@ type Acknowledgement = {
|
|
|
155
198
|
};
|
|
156
199
|
|
|
157
200
|
class SubscriptionsSession {
|
|
158
|
-
listener: (message,
|
|
201
|
+
listener: (topic, message, messageId, subscription, version?) => any;
|
|
159
202
|
sessionId: any;
|
|
160
203
|
user: any;
|
|
161
204
|
request: any;
|
|
@@ -303,7 +346,16 @@ class SubscriptionsSession {
|
|
|
303
346
|
let path = update.id;
|
|
304
347
|
if (Array.isArray(path)) path = keyArrayToString(path);
|
|
305
348
|
if (path == null) path = '';
|
|
306
|
-
|
|
349
|
+
// the version is forwarded so the delivery side can tell a store-sourced event (whose
|
|
350
|
+
// value is a fresh object per version) from an app-yielded one that may be a reused
|
|
351
|
+
// mutable envelope — only the former is safe to encode once and share
|
|
352
|
+
const result = await this.listener(
|
|
353
|
+
resourcePath + '/' + path,
|
|
354
|
+
update.value,
|
|
355
|
+
messageId,
|
|
356
|
+
subscriptionRequest,
|
|
357
|
+
update.version
|
|
358
|
+
);
|
|
307
359
|
if (result === false) break;
|
|
308
360
|
if (this.awaitingAcks?.size > AWAITING_ACKS_HIGH_WATER_MARK) {
|
|
309
361
|
// slow it down if we are getting too far ahead in acks
|
|
@@ -372,7 +424,7 @@ class SubscriptionsSession {
|
|
|
372
424
|
}
|
|
373
425
|
return context;
|
|
374
426
|
}
|
|
375
|
-
setListener(listener: (message) => any) {
|
|
427
|
+
setListener(listener: (topic, message, messageId, subscription, version?) => any) {
|
|
376
428
|
this.listener = listener;
|
|
377
429
|
}
|
|
378
430
|
disconnect(clientTerminated) {
|
|
@@ -382,8 +434,11 @@ class SubscriptionsSession {
|
|
|
382
434
|
try {
|
|
383
435
|
if (!clientTerminated) {
|
|
384
436
|
const will = await getLastWill().get(this.sessionId);
|
|
385
|
-
if (will) {
|
|
386
|
-
|
|
437
|
+
if (will && !isWillFromExpiredScopedToken(will)) {
|
|
438
|
+
// A scoped will authorizes under its own embedded role, never the disconnecting
|
|
439
|
+
// session's user (which may be a later same-clientId reconnect).
|
|
440
|
+
const willContext = will.user?._scopedToken ? { ...context, user: will.user } : context;
|
|
441
|
+
await publishMessage(will, will.data, willContext);
|
|
387
442
|
}
|
|
388
443
|
}
|
|
389
444
|
} finally {
|
|
@@ -413,10 +468,15 @@ async function publishMessage(message: any, data: any, context: any) {
|
|
|
413
468
|
message = { ...message, data, async: true };
|
|
414
469
|
context.authorize = true;
|
|
415
470
|
const entry = resources.getMatch(topic, 'mqtt');
|
|
416
|
-
if (!entry)
|
|
417
|
-
|
|
471
|
+
if (!entry) {
|
|
472
|
+
// Typed like addSubscription's identical miss, so a protocol layer can map it to a specific
|
|
473
|
+
// code rather than a generic failure.
|
|
474
|
+
const notFoundError: any = new Error(
|
|
418
475
|
`Can not publish to topic ${topic} as it does not exist, no resource has been defined to handle this topic`
|
|
419
476
|
);
|
|
477
|
+
notFoundError.statusCode = 404;
|
|
478
|
+
throw notFoundError;
|
|
479
|
+
}
|
|
420
480
|
message.url = entry.relativeURL;
|
|
421
481
|
const target = new RequestTarget(entry.relativeURL);
|
|
422
482
|
if (entry.params) Object.assign(target, entry.params); // bind parameterised path segments (e.g. :id, *rest)
|
package/server/REST.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { stat } from 'node:fs/promises';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
serialize,
|
|
5
|
+
serializeMessage,
|
|
6
|
+
getDeserializer,
|
|
7
|
+
waitForStreamStartup,
|
|
8
|
+
discardSerializedStream,
|
|
9
|
+
} from '../server/serverHelpers/contentTypes.ts';
|
|
4
10
|
import { addAnalyticsListener, recordAction, recordActionBinary } from '../resources/analytics/write.ts';
|
|
5
11
|
import * as harperLogger from '../utility/logging/harper_logger.ts';
|
|
6
12
|
import { ServerError, ClientError } from '../utility/errors/hdbError.ts';
|
|
@@ -8,13 +14,17 @@ import { Resources } from '../resources/Resources.ts';
|
|
|
8
14
|
import { Resource, missingMethod, allowedMethods } from '../resources/Resource.ts';
|
|
9
15
|
import { IterableEventQueue } from '../resources/IterableEventQueue.ts';
|
|
10
16
|
import { transaction } from '../resources/transaction.ts';
|
|
11
|
-
import { Headers, mergeHeaders } from '../server/serverHelpers/Headers.ts';
|
|
17
|
+
import { Headers, mergeHeaders, addVaryHeader } from '../server/serverHelpers/Headers.ts';
|
|
12
18
|
import { generateJsonApi } from '../resources/openApi.ts';
|
|
13
19
|
import { getConfigPath } from '../config/configUtils.ts';
|
|
14
20
|
import { CONFIG_PARAMS } from '../utility/hdbTerms.ts';
|
|
15
21
|
import { ASIDE_STAGING_DIR } from '../components/Application.ts';
|
|
16
22
|
import { COMPONENT_PREPARATION_LOCK_DIR } from '../components/componentPreparationLock.ts';
|
|
17
23
|
import { restartNeeded } from '../components/requestRestart.ts';
|
|
24
|
+
import {
|
|
25
|
+
assertNoDeferredCredentialRejection,
|
|
26
|
+
settleDeferredCredentialRejection,
|
|
27
|
+
} from '../security/deferredAuthentication.ts';
|
|
18
28
|
|
|
19
29
|
import { Request } from '../server/serverHelpers/Request.ts';
|
|
20
30
|
import { RequestTarget } from '../resources/RequestTarget';
|
|
@@ -117,6 +127,39 @@ async function findInactiveComponent(url: string): Promise<string | undefined> {
|
|
|
117
127
|
}
|
|
118
128
|
}
|
|
119
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Emit RFC 7233-style pagination headers for a `Prefer: count=` request. Table.search returns the page
|
|
132
|
+
* with a `recordCount` (total matching records, or null when an exact scan hit its guardrail or an
|
|
133
|
+
* estimate was suppressed by an opaque filter). `Content-Range: items <start>-<end>/<total>` lets a
|
|
134
|
+
* client paginate — `<total>` is `*` when unavailable. `Preference-Applied` echoes the count mode the
|
|
135
|
+
* server actually applied (`exact` or `estimated`, after any per-mount downgrade), so a `.../*` total
|
|
136
|
+
* reads as "that mode was applied but the total is unavailable" rather than "no count was requested".
|
|
137
|
+
* All three headers are added to `Access-Control-Expose-Headers` so a browser can read them cross-origin
|
|
138
|
+
* (they aren't safelisted).
|
|
139
|
+
*/
|
|
140
|
+
function setCountHeaders(headers: Headers, offset: number, mode: string, page: any) {
|
|
141
|
+
const total = page.recordCount;
|
|
142
|
+
const len = Array.isArray(page) ? page.length : 0;
|
|
143
|
+
const range = len > 0 ? `${offset}-${offset + len - 1}` : '*';
|
|
144
|
+
const totalStr = typeof total === 'number' ? String(total) : '*';
|
|
145
|
+
headers.set('Range-Unit', 'items');
|
|
146
|
+
headers.set('Content-Range', `items ${range}/${totalStr}`);
|
|
147
|
+
headers.set('Preference-Applied', `count=${mode}`);
|
|
148
|
+
// Append (don't overwrite) so a resource that already exposed its own headers keeps them. Compare
|
|
149
|
+
// case-insensitive comma tokens, not substrings, so an unrelated existing token (e.g.
|
|
150
|
+
// `X-Content-Range-Metadata`) doesn't suppress the real `Content-Range` token.
|
|
151
|
+
const exposed = headers.get('Access-Control-Expose-Headers');
|
|
152
|
+
const existing = new Set(
|
|
153
|
+
(Array.isArray(exposed) ? exposed.join(',') : exposed || '')
|
|
154
|
+
.split(',')
|
|
155
|
+
.map((token) => token.trim().toLowerCase())
|
|
156
|
+
.filter(Boolean)
|
|
157
|
+
);
|
|
158
|
+
for (const name of ['Content-Range', 'Range-Unit', 'Preference-Applied']) {
|
|
159
|
+
if (!existing.has(name.toLowerCase())) headers.append('Access-Control-Expose-Headers', name, true);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
120
163
|
async function http(request: Request, nextHandler, resources: Resources, httpOptions: any) {
|
|
121
164
|
const headersObject = request.headers.asObject;
|
|
122
165
|
const isSse = headersObject.accept === 'text/event-stream';
|
|
@@ -165,7 +208,32 @@ async function http(request: Request, nextHandler, resources: Resources, httpOpt
|
|
|
165
208
|
|
|
166
209
|
(target as any).async = true;
|
|
167
210
|
resource = entry.Resource;
|
|
211
|
+
// Pagination total-count opt-in (no default): `Prefer: count=exact|estimated`. Table.search
|
|
212
|
+
// reads target.count to compute the total emitted as Content-Range below. Only honored on
|
|
213
|
+
// GET/HEAD reads: setting it for other methods would hand their `search()` a materialized array
|
|
214
|
+
// instead of the AsyncIterable they iterate (e.g. a collection DELETE at Table.ts).
|
|
215
|
+
const prefer = headersObject['prefer'];
|
|
216
|
+
if (prefer && (method === 'GET' || method === 'HEAD')) {
|
|
217
|
+
// Exact counting scans the full matched set, so it is opt-in per mount via
|
|
218
|
+
// `rest: { exactCount: true }` (default off); count=exact is otherwise served as a cheap
|
|
219
|
+
// estimate. Estimated is always available. Accept a string `"true"` too, since not every
|
|
220
|
+
// config source coerces to a boolean.
|
|
221
|
+
const exactEnabled = (httpOptions as any)?.exactCount === true || (httpOptions as any)?.exactCount === 'true';
|
|
222
|
+
for (const pref of parseHeaderValue(prefer as any)) {
|
|
223
|
+
// The header parser can hand back a non-string value (e.g. a bare `Prefer: count` with no
|
|
224
|
+
// `=`), so coerce before lower-casing rather than calling `.toLowerCase()` on a boolean.
|
|
225
|
+
const mode = String(pref?.value ?? '').toLowerCase();
|
|
226
|
+
if (pref?.name === 'count' && (mode === 'exact' || mode === 'estimated')) {
|
|
227
|
+
// A count=exact request on a mount that hasn't opted in is downgraded to estimated,
|
|
228
|
+
// signaled back to the client via Preference-Applied.
|
|
229
|
+
(target as any).count = mode === 'exact' && !exactEnabled ? 'estimated' : mode;
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
168
234
|
}
|
|
235
|
+
const settledCredentialRejection = settleDeferredCredentialRejection(request);
|
|
236
|
+
if (settledCredentialRejection) return settledCredentialRejection;
|
|
169
237
|
if ((resource as any)?.isCaching) {
|
|
170
238
|
const cacheControl = headersObject['cache-control'];
|
|
171
239
|
if (cacheControl) {
|
|
@@ -286,7 +354,15 @@ async function http(request: Request, nextHandler, resources: Resources, httpOpt
|
|
|
286
354
|
headers.setIfNone('Last-Modified', new Date(lastModification).toUTCString());
|
|
287
355
|
} else if (responseData.headers) {
|
|
288
356
|
// if response is a Response object (or response-like envelope with headers), use it as the response
|
|
289
|
-
|
|
357
|
+
const response = finalizeResponse(responseData, headers, status, request);
|
|
358
|
+
if (request.method === 'HEAD') {
|
|
359
|
+
discardSerializedStream(response.body);
|
|
360
|
+
if (!(response instanceof Response)) response.body = undefined;
|
|
361
|
+
} else if (request.method === 'GET') {
|
|
362
|
+
const startup = waitForStreamStartup(response.body);
|
|
363
|
+
if (startup) await startup;
|
|
364
|
+
}
|
|
365
|
+
return response;
|
|
290
366
|
} else if (isFinite(lastModification)) {
|
|
291
367
|
etagFloat[0] = lastModification;
|
|
292
368
|
// base64 encoding of the 64-bit float encoding of the date in ms (with quotes)
|
|
@@ -348,9 +424,30 @@ async function http(request: Request, nextHandler, resources: Resources, httpOpt
|
|
|
348
424
|
}
|
|
349
425
|
// TODO: Handle 201 Created
|
|
350
426
|
if (responseData !== undefined) {
|
|
427
|
+
if (
|
|
428
|
+
(target as any)?.count &&
|
|
429
|
+
(method === 'GET' || method === 'HEAD') &&
|
|
430
|
+
Array.isArray(responseData) &&
|
|
431
|
+
(responseData as any).recordCount !== undefined
|
|
432
|
+
) {
|
|
433
|
+
// Array.isArray guards the single-record path: a record that happens to carry a
|
|
434
|
+
// `recordCount` attribute must not be mistaken for a count page (Table.search only ever
|
|
435
|
+
// returns the count as an array).
|
|
436
|
+
setCountHeaders(headers, (target as any).offset || 0, (target as any).count, responseData);
|
|
437
|
+
}
|
|
351
438
|
responseObject.body = serialize(responseData, request, responseObject);
|
|
352
|
-
if (method === 'HEAD')
|
|
439
|
+
if (request.method === 'HEAD') {
|
|
440
|
+
discardSerializedStream(responseObject.body);
|
|
441
|
+
responseObject.body = undefined; // we want everything else to be the same as GET, but then omit the body
|
|
442
|
+
} else if (request.method === 'GET') {
|
|
443
|
+
const startup = waitForStreamStartup(responseObject.body);
|
|
444
|
+
if (startup) await startup;
|
|
445
|
+
}
|
|
353
446
|
}
|
|
447
|
+
// A collection read's count headers vary by the request's `Prefer` value; serialize() just reset
|
|
448
|
+
// `Vary`, so declare it here (after serialization) — otherwise a shared cache could serve count
|
|
449
|
+
// headers to a request that didn't ask, or a cached non-count response to one that did.
|
|
450
|
+
if ((method === 'GET' || method === 'HEAD') && (target as any)?.isCollection) addVaryHeader(headers, 'Prefer');
|
|
354
451
|
return responseObject;
|
|
355
452
|
} catch (error) {
|
|
356
453
|
error ??= new Error('Unknown error occurred');
|
|
@@ -389,6 +486,19 @@ async function http(request: Request, nextHandler, resources: Resources, httpOpt
|
|
|
389
486
|
headers,
|
|
390
487
|
body: undefined,
|
|
391
488
|
};
|
|
489
|
+
// The error body is a different representation, so success representation and caching headers
|
|
490
|
+
// accumulated before the startup failure must not survive its serialization.
|
|
491
|
+
for (const header of [
|
|
492
|
+
'Content-Encoding',
|
|
493
|
+
'Cache-Control',
|
|
494
|
+
'ETag',
|
|
495
|
+
'Last-Modified',
|
|
496
|
+
'Content-Range',
|
|
497
|
+
'Range-Unit',
|
|
498
|
+
'Preference-Applied',
|
|
499
|
+
'Age',
|
|
500
|
+
])
|
|
501
|
+
headers.delete(header);
|
|
392
502
|
responseObject.body = serialize(problemDetail, request, responseObject);
|
|
393
503
|
return responseObject;
|
|
394
504
|
}
|
|
@@ -482,6 +592,7 @@ export function handleApplication(scope: import('../components/Scope.ts').Scope)
|
|
|
482
592
|
// TODO: Ideally we would like to have a 404 response before upgrading to WebSocket protocol, probably
|
|
483
593
|
return ws.close(1011, `No resource was found to handle ${request.pathname}`);
|
|
484
594
|
} else {
|
|
595
|
+
assertNoDeferredCredentialRejection(request);
|
|
485
596
|
request.handlerPath = entry.path;
|
|
486
597
|
recordAction(
|
|
487
598
|
(action) => ({
|