@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
|
@@ -13,7 +13,12 @@ const { ENV_ENCRYPTED_PREFIX } = require('../utility/envFile.ts');
|
|
|
13
13
|
// File and project names can only be alphanumeric, dash and underscores. Both patterns are shared
|
|
14
14
|
// with the CLI (utility/componentNames.ts): `harper deploy setup=true` resolves a project name and a
|
|
15
15
|
// credential host client-side, and has to reject exactly what these schemas would.
|
|
16
|
-
const {
|
|
16
|
+
const {
|
|
17
|
+
PROJECT_NAME_PATTERN: PROJECT_FILE_NAME_REGEX,
|
|
18
|
+
GIT_HOST_PATTERN,
|
|
19
|
+
isReservedComponentName,
|
|
20
|
+
} = require('../utility/componentNames.ts');
|
|
21
|
+
const { assertBranchedDatabases } = require('./Application.ts');
|
|
17
22
|
|
|
18
23
|
// dotenv's accepted key character set. Restricting keys to this prevents a crafted key (e.g. one
|
|
19
24
|
// containing `=` or a newline) from injecting extra assignments into a .env file.
|
|
@@ -24,6 +29,12 @@ const ENV_KEY_REGEX = /^[\w.-]+$/;
|
|
|
24
29
|
const HOSTNAME_SCHEMA = Joi.string().hostname();
|
|
25
30
|
const IPV6_SCHEMA = Joi.string().ip({ version: 'ipv6' });
|
|
26
31
|
|
|
32
|
+
// A deployment id names both an hdb_deployment row and a staging directory, so it is held to the shape
|
|
33
|
+
// the recorder mints — `randomUUID()`, which is lowercase. LOWERCASE ONLY, because a case-insensitive
|
|
34
|
+
// filesystem resolves an uppercase id to the real directory while every string compare against it fails:
|
|
35
|
+
// the retention pin would not recognise the artifact the request is about to activate, and would evict it.
|
|
36
|
+
const DEPLOYMENT_ID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
37
|
+
|
|
27
38
|
module.exports = {
|
|
28
39
|
getDropCustomFunctionValidator,
|
|
29
40
|
setCustomFunctionValidator,
|
|
@@ -73,6 +84,31 @@ function checkProjectExists(checkExists, project, helpers) {
|
|
|
73
84
|
}
|
|
74
85
|
}
|
|
75
86
|
|
|
87
|
+
// Drop/package/file operations deliberately don't call this: an application deployed under the
|
|
88
|
+
// name before it was reserved has to stay removable.
|
|
89
|
+
function checkReservedProjectName(project, helpers) {
|
|
90
|
+
if (isReservedComponentName(project)) return helpers.message(HDB_ERROR_MSGS.RESERVED_PROJECT_NAME(project));
|
|
91
|
+
return project;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The file writers create the project directory as a side effect of writing into it, so they refuse
|
|
96
|
+
* only the creation — an application that already holds the name stays editable until it is
|
|
97
|
+
* migrated off it.
|
|
98
|
+
*/
|
|
99
|
+
function checkReservedProjectCreation(project, helpers) {
|
|
100
|
+
if (!isReservedComponentName(project)) return project;
|
|
101
|
+
let projectDir;
|
|
102
|
+
try {
|
|
103
|
+
const componentsRoot = configUtils.getConfigPath(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT);
|
|
104
|
+
if (componentsRoot) projectDir = path.join(componentsRoot, project);
|
|
105
|
+
} catch (err) {
|
|
106
|
+
hdbLogger.error(err);
|
|
107
|
+
}
|
|
108
|
+
if (projectDir && fs.existsSync(projectDir)) return project;
|
|
109
|
+
return helpers.message(HDB_ERROR_MSGS.RESERVED_PROJECT_NAME(project));
|
|
110
|
+
}
|
|
111
|
+
|
|
76
112
|
function checkFilePath(path, helpers) {
|
|
77
113
|
if (path.includes('..')) return helpers.message('Invalid file path');
|
|
78
114
|
return path;
|
|
@@ -154,6 +190,7 @@ function setComponentFileValidator(req) {
|
|
|
154
190
|
const setCompSchema = Joi.object({
|
|
155
191
|
project: Joi.string()
|
|
156
192
|
.pattern(PROJECT_FILE_NAME_REGEX)
|
|
193
|
+
.custom(checkReservedProjectCreation)
|
|
157
194
|
.required()
|
|
158
195
|
.messages({ 'string.pattern.base': HDB_ERROR_MSGS.BAD_PROJECT_NAME }),
|
|
159
196
|
file: Joi.string().custom(checkFilePath).required(),
|
|
@@ -214,6 +251,7 @@ function setEnvValueValidator(req) {
|
|
|
214
251
|
const schema = Joi.object({
|
|
215
252
|
project: Joi.string()
|
|
216
253
|
.pattern(PROJECT_FILE_NAME_REGEX)
|
|
254
|
+
.custom(checkReservedProjectCreation)
|
|
217
255
|
.required()
|
|
218
256
|
.messages({ 'string.pattern.base': HDB_ERROR_MSGS.BAD_PROJECT_NAME }),
|
|
219
257
|
file: Joi.string().custom(checkFilePath).optional(),
|
|
@@ -330,6 +368,7 @@ function addComponentValidator(req) {
|
|
|
330
368
|
const addFuncSchema = Joi.object({
|
|
331
369
|
project: Joi.string()
|
|
332
370
|
.pattern(PROJECT_FILE_NAME_REGEX)
|
|
371
|
+
.custom(checkReservedProjectName)
|
|
333
372
|
.custom(checkProjectExists.bind(null, false))
|
|
334
373
|
.required()
|
|
335
374
|
.messages({ 'string.pattern.base': HDB_ERROR_MSGS.BAD_PROJECT_NAME }),
|
|
@@ -455,10 +494,36 @@ function deployComponentValidator(req) {
|
|
|
455
494
|
const deployProjSchema = Joi.object({
|
|
456
495
|
project: Joi.string()
|
|
457
496
|
.pattern(PROJECT_FILE_NAME_REGEX)
|
|
497
|
+
.custom(checkReservedProjectName)
|
|
458
498
|
.required()
|
|
459
499
|
.messages({ 'string.pattern.base': HDB_ERROR_MSGS.BAD_PROJECT_NAME }),
|
|
460
500
|
package: Joi.string().optional(),
|
|
461
|
-
|
|
501
|
+
// `activate: false` builds and certifies the component, then stops, leaving an artifact a later
|
|
502
|
+
// request activates by `deployment_id`. STRICT: `validateBySchema` throws away Joi's converted
|
|
503
|
+
// value, so a coercing boolean would let `activate: "false"` validate here and then read as truthy
|
|
504
|
+
// in the handler — deploying the very release the caller asked not to make live.
|
|
505
|
+
activate: Joi.boolean().strict().optional(),
|
|
506
|
+
// Activate a build staged earlier by `activate: false`. It becomes a path segment
|
|
507
|
+
// (`.deploy-staging/<id>/<component>`), so the charset is pinned as tightly as the value it names.
|
|
508
|
+
deployment_id: Joi.string().pattern(DEPLOYMENT_ID_REGEX).optional().messages({
|
|
509
|
+
'string.pattern.base': `'deployment_id' must be a UUID`,
|
|
510
|
+
}),
|
|
511
|
+
// Set by deployComponent from the origin's deployment row and carried to peers by replication.
|
|
512
|
+
// Validated because it is a path segment on EVERY deploy and unknown keys are admitted, so an
|
|
513
|
+
// operator-supplied one would otherwise reach the filesystem unchecked.
|
|
514
|
+
_deploymentId: Joi.string().pattern(DEPLOYMENT_ID_REGEX).optional().messages({
|
|
515
|
+
'string.pattern.base': `'_deploymentId' must be a UUID`,
|
|
516
|
+
}),
|
|
517
|
+
restart: Joi.alternatives()
|
|
518
|
+
.try(Joi.boolean(), Joi.string().valid('rolling'))
|
|
519
|
+
.optional()
|
|
520
|
+
// Nothing goes live, so there is nothing to restart into.
|
|
521
|
+
.when('activate', {
|
|
522
|
+
is: false,
|
|
523
|
+
then: Joi.forbidden().messages({
|
|
524
|
+
'any.unknown': `'restart' cannot be combined with 'activate: false': staging does not change the running component`,
|
|
525
|
+
}),
|
|
526
|
+
}),
|
|
462
527
|
install_command: Joi.string().optional(),
|
|
463
528
|
install_timeout: Joi.number().optional(),
|
|
464
529
|
install_allow_scripts: Joi.boolean().optional(),
|
|
@@ -496,6 +561,21 @@ function deployComponentValidator(req) {
|
|
|
496
561
|
})
|
|
497
562
|
.optional()
|
|
498
563
|
.messages({ 'string.bracketedHost': '{#label} must not be bracketed; use the bare IPv6 literal' }),
|
|
564
|
+
// Databases this application forks a private copy of (harper#642/#643). Deployment routing
|
|
565
|
+
// like urlPath/host above, not component config: reusing assertBranchedDatabases here (rather
|
|
566
|
+
// than re-deriving its rules in Joi) gives a synchronous 400 at deploy time instead of a load
|
|
567
|
+
// failure discovered much later and disconnected from the request that caused it.
|
|
568
|
+
isolated: Joi.boolean().optional(),
|
|
569
|
+
branchedDatabases: Joi.custom((value, helpers) => {
|
|
570
|
+
try {
|
|
571
|
+
assertBranchedDatabases(req.project, value);
|
|
572
|
+
} catch (error) {
|
|
573
|
+
return helpers.error('any.invalid', { reason: error.message });
|
|
574
|
+
}
|
|
575
|
+
return value;
|
|
576
|
+
}, 'branchedDatabases')
|
|
577
|
+
.optional()
|
|
578
|
+
.messages({ 'any.invalid': '{{#reason}}' }),
|
|
499
579
|
// Deploy credentials. The array is kind-heterogeneous: an entry's kind is implied by its
|
|
500
580
|
// identifying key rather than a separate discriminator field, so a new kind is added as
|
|
501
581
|
// another item alternative here without reshaping the field. Today: npm registry auth
|
|
@@ -527,7 +607,22 @@ function deployComponentValidator(req) {
|
|
|
527
607
|
}),
|
|
528
608
|
})
|
|
529
609
|
.with('urlPath', 'package')
|
|
530
|
-
.with('host', 'package')
|
|
610
|
+
.with('host', 'package')
|
|
611
|
+
// An activation has no build inputs and takes no routing or packaging opinions: the artifact's own
|
|
612
|
+
// descriptor is the authority for what it publishes, and silently ignoring these would hide that.
|
|
613
|
+
.without('deployment_id', [
|
|
614
|
+
'package',
|
|
615
|
+
'payload',
|
|
616
|
+
'activate',
|
|
617
|
+
'install_command',
|
|
618
|
+
'install_timeout',
|
|
619
|
+
'install_allow_scripts',
|
|
620
|
+
'credentials',
|
|
621
|
+
'urlPath',
|
|
622
|
+
'host',
|
|
623
|
+
'branchedDatabases',
|
|
624
|
+
'isolated',
|
|
625
|
+
]);
|
|
531
626
|
|
|
532
627
|
return validator.validateBySchema(req, deployProjSchema);
|
|
533
628
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { join, relative, sep } from 'node:path';
|
|
1
|
+
import { basename, join, relative, sep } from 'node:path';
|
|
2
2
|
import { stat, readdir } from 'node:fs/promises';
|
|
3
3
|
import { Readable, pipeline } from 'node:stream';
|
|
4
4
|
import tar from 'tar-fs';
|
|
@@ -13,6 +13,27 @@ const DEFAULT_OPTIONS: PackageOptions = { skip_node_modules: false, skip_symlink
|
|
|
13
13
|
|
|
14
14
|
const WEBPACK_CACHE_SEGMENT = join('cache', 'webpack');
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* `node_modules/harper` is Harper's, whatever is on disk there.
|
|
18
|
+
*
|
|
19
|
+
* Every non-root component load runs `symlinkHarperModule`, which does `rmSync(harperModule, {
|
|
20
|
+
* recursive: true, force: true })` and then links the running install (`components/componentLoader.ts`).
|
|
21
|
+
* It does that whether or not the path already existed, so component content there survives no load and
|
|
22
|
+
* packaging it cannot be preserving component source — while packaging it walks the whole install, since
|
|
23
|
+
* the packer dereferences symlinks and recurses into linked directories.
|
|
24
|
+
*
|
|
25
|
+
* `harperdb` is deliberately NOT here, and the asymmetry is the reason: the loader only *repairs*
|
|
26
|
+
* `node_modules/harperdb` when that path already exists. A legacy component shipping a real
|
|
27
|
+
* npm-installed `harperdb` that has never been loaded on this host therefore owns it — and excluding it
|
|
28
|
+
* would strip it from the tarball, after which `installApplication` skips installing (it returns early
|
|
29
|
+
* when `node_modules` exists), the loader's present-gate never fires, and `import from 'harperdb'`
|
|
30
|
+
* fails on the target — and that holds whether the path is skipped by name or by resolved identity,
|
|
31
|
+
* because an archive that simply OMITS it cannot restore it. So a `harperdb` link to the install is
|
|
32
|
+
* still followed. Closing that means packing a placeholder at the path so the loader's present-gate can
|
|
33
|
+
* fire on the target, not skipping it; tracked in harper#2577.
|
|
34
|
+
*/
|
|
35
|
+
const INSTALL_OWNED_PATH = join('node_modules', 'harper');
|
|
36
|
+
|
|
16
37
|
/**
|
|
17
38
|
* Whether `fullPath` (an absolute path under `directory`) should be excluded from the package when
|
|
18
39
|
* `skip_node_modules` is set. The path is first made relative to `directory`, so packaging a component
|
|
@@ -21,6 +42,9 @@ const WEBPACK_CACHE_SEGMENT = join('cache', 'webpack');
|
|
|
21
42
|
* excluded the whole tree. Shared by the stream packer and the directory walk so they cannot diverge.
|
|
22
43
|
*/
|
|
23
44
|
function isExcluded(directory: string, fullPath: string, options: PackageOptions): boolean {
|
|
45
|
+
// Unconditional, including under `skip_symlinks`: packed literally, the link ships an absolute path to
|
|
46
|
+
// the packaging host's install root, which arrives dangling.
|
|
47
|
+
if (basename(fullPath) === 'harper' && relative(directory, fullPath) === INSTALL_OWNED_PATH) return true;
|
|
24
48
|
if (!options.skip_node_modules) return false;
|
|
25
49
|
const rel = relative(directory, fullPath);
|
|
26
50
|
return rel.split(sep).includes('node_modules') || rel.includes(WEBPACK_CACHE_SEGMENT);
|
|
@@ -24,6 +24,17 @@ export function requestRestart() {
|
|
|
24
24
|
restartArrayBuffer.notify();
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
export function requestRestartAfterDeploy(
|
|
28
|
+
isNewComponent: boolean,
|
|
29
|
+
packageMetadataChanged: boolean,
|
|
30
|
+
wasIsolated: boolean,
|
|
31
|
+
isIsolated: boolean
|
|
32
|
+
): boolean {
|
|
33
|
+
if (!isNewComponent && !packageMetadataChanged && wasIsolated === isIsolated) return false;
|
|
34
|
+
requestRestart();
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
|
|
27
38
|
export function restartNeeded() {
|
|
28
39
|
ensureInitialized();
|
|
29
40
|
return restartNeededArray[0] === 1;
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* centralized management of component health status.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { deployValidationStatusSink } from '../../server/serverHelpers/deployValidationState.ts';
|
|
8
9
|
import { ComponentStatus } from './ComponentStatus.ts';
|
|
9
10
|
import {
|
|
10
11
|
type ComponentStatusLevel,
|
|
@@ -24,6 +25,37 @@ export type ComponentStatusMap = Map<string, ComponentStatus>;
|
|
|
24
25
|
* Component Status Registry Class
|
|
25
26
|
* Provides a centralized registry for managing component health status
|
|
26
27
|
*/
|
|
28
|
+
/**
|
|
29
|
+
* A status copy handed to a validation must share nothing mutable with the live entry. `Error` is reachable
|
|
30
|
+
* through the public `statusForComponent(...).get()` path, so candidate code could otherwise edit
|
|
31
|
+
* `message`, `cause` or a custom field on the serving component's own error object. Own enumerable
|
|
32
|
+
* properties are carried across; `cause` is flattened to a message, because an error graph of unknown depth
|
|
33
|
+
* is not worth cloning to hand a throwaway load its own read.
|
|
34
|
+
*/
|
|
35
|
+
function detachError(error: Error | string | undefined): Error | string | undefined {
|
|
36
|
+
if (!(error instanceof Error)) return error;
|
|
37
|
+
const detached = new Error(error.message);
|
|
38
|
+
detached.name = error.name;
|
|
39
|
+
detached.stack = error.stack;
|
|
40
|
+
for (const [key, value] of Object.entries(error)) {
|
|
41
|
+
if (key === 'cause') continue;
|
|
42
|
+
// Detached too where it can be: copying an object property by reference would leave the candidate a
|
|
43
|
+
// mutable handle into the live error after all. Anything unclonable (a function, a class instance) is
|
|
44
|
+
// replaced by its string form rather than shared.
|
|
45
|
+
let detachedValue: unknown;
|
|
46
|
+
try {
|
|
47
|
+
detachedValue = structuredClone(value);
|
|
48
|
+
} catch {
|
|
49
|
+
detachedValue = typeof value === 'object' && value !== null ? String(value) : value;
|
|
50
|
+
}
|
|
51
|
+
(detached as unknown as Record<string, unknown>)[key] = detachedValue;
|
|
52
|
+
}
|
|
53
|
+
if (error.cause !== undefined) {
|
|
54
|
+
detached.cause = error.cause instanceof Error ? `${error.cause.name}: ${error.cause.message}` : error.cause;
|
|
55
|
+
}
|
|
56
|
+
return detached;
|
|
57
|
+
}
|
|
58
|
+
|
|
27
59
|
export class ComponentStatusRegistry {
|
|
28
60
|
private statusMap: ComponentStatusMap = new Map();
|
|
29
61
|
|
|
@@ -61,6 +93,16 @@ export class ComponentStatusRegistry {
|
|
|
61
93
|
);
|
|
62
94
|
}
|
|
63
95
|
|
|
96
|
+
// A deploy's pre-flight validation loads the CANDIDATE's code under the real component's name, so its
|
|
97
|
+
// writes would land on the live component. They go to the validation's own throwaway map instead —
|
|
98
|
+
// which is why this cannot simply be reverted afterwards: the live component keeps serving through
|
|
99
|
+
// that window, and `statusForComponent()` is a public API its own code may call at any time, so
|
|
100
|
+
// reverting would silently discard a genuine report from it.
|
|
101
|
+
const sink = deployValidationStatusSink();
|
|
102
|
+
if (sink) {
|
|
103
|
+
sink.set(componentName, new ComponentStatus(status, message, error));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
64
106
|
this.statusMap.set(componentName, new ComponentStatus(status, message, error));
|
|
65
107
|
}
|
|
66
108
|
|
|
@@ -68,6 +110,23 @@ export class ComponentStatusRegistry {
|
|
|
68
110
|
* Get the current status of a component
|
|
69
111
|
*/
|
|
70
112
|
public getStatus(componentName: string): ComponentStatus | undefined {
|
|
113
|
+
// Inside a validation the candidate reads its OWN writes, and never the live object: the returned
|
|
114
|
+
// `ComponentStatus` is mutable, so handing back the live one would let candidate code edit the
|
|
115
|
+
// serving component's status in place and bypass the diversion entirely. A component the candidate
|
|
116
|
+
// has not written yet is copied into the sink on first read for the same reason.
|
|
117
|
+
const sink = deployValidationStatusSink();
|
|
118
|
+
if (sink) {
|
|
119
|
+
const diverted = sink.get(componentName);
|
|
120
|
+
if (diverted) return diverted;
|
|
121
|
+
const live = this.statusMap.get(componentName);
|
|
122
|
+
if (!live) return undefined;
|
|
123
|
+
const copy = new ComponentStatus(live.status, live.message, detachError(live.error));
|
|
124
|
+
// A new Date, not the live one: `Date` is mutable, so sharing it would let candidate code reach
|
|
125
|
+
// back into the serving component's status through the copy.
|
|
126
|
+
copy.lastChecked = new Date(live.lastChecked);
|
|
127
|
+
sink.set(componentName, copy);
|
|
128
|
+
return copy;
|
|
129
|
+
}
|
|
71
130
|
return this.statusMap.get(componentName);
|
|
72
131
|
}
|
|
73
132
|
|
|
@@ -1,38 +1,124 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { readFile } from 'node:fs/promises';
|
|
1
|
+
import { FSWatcher } from 'chokidar';
|
|
3
2
|
import { getConfigFilePath } from './configUtils.ts';
|
|
3
|
+
import { readConfigFileSync } from './readConfigFileSync.ts';
|
|
4
4
|
import { EventEmitter, once } from 'node:events';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { parseConfigFile } from './parseConfigFile.ts';
|
|
6
|
+
import {
|
|
7
|
+
POLLING_FALLBACK_OPTIONS,
|
|
8
|
+
claimLostNativeWatchError,
|
|
9
|
+
guardedWatch,
|
|
10
|
+
isWatcherExhaustionError,
|
|
11
|
+
warnWatcherFallback,
|
|
12
|
+
} from '../utility/watcherFallback.ts';
|
|
13
|
+
import { resolveWatchTarget } from '../utility/watchPath.ts';
|
|
14
|
+
import { errorForLog, loggerWithTag } from '../utility/logging/harper_logger.ts';
|
|
15
|
+
import { ConfigReadRetry } from './configReadRetry.ts';
|
|
16
|
+
import { ArmGate } from './watcherArming.ts';
|
|
17
|
+
|
|
18
|
+
function isMissingFile(error: unknown): boolean {
|
|
19
|
+
return !!error && typeof error === 'object' && (error as NodeJS.ErrnoException).code === 'ENOENT';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// `harper_logger` imports this module at its own bottom to break their cycle, so a tagged logger
|
|
23
|
+
// built at module scope would run `loggerWithTag()` before `mainLogger` is initialized.
|
|
24
|
+
let taggedLogger: ReturnType<typeof loggerWithTag> | undefined;
|
|
25
|
+
function logger() {
|
|
26
|
+
return (taggedLogger ??= loggerWithTag('config-watcher'));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let sharedWatcher: RootConfigWatcher | undefined;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The isolate-wide watcher for the root config file. Consumers (logging, models) subscribe to this
|
|
33
|
+
* one instance instead of each opening their own native watcher — per-worker duplicates double the
|
|
34
|
+
* inotify/FD footprint and the synchronous read+parse work on every config write.
|
|
35
|
+
*/
|
|
36
|
+
export function getSharedRootConfigWatcher(): RootConfigWatcher {
|
|
37
|
+
return (sharedWatcher ??= new RootConfigWatcher());
|
|
38
|
+
}
|
|
7
39
|
|
|
8
40
|
export class RootConfigWatcher extends EventEmitter {
|
|
9
41
|
#configFilePath: string;
|
|
42
|
+
#watchPath: string;
|
|
10
43
|
#watcher!: FSWatcher;
|
|
11
44
|
#config: any;
|
|
12
45
|
#usingPolling: boolean;
|
|
13
46
|
#closed: boolean;
|
|
14
47
|
#openCount: number = 0;
|
|
48
|
+
#readCount: number = 0;
|
|
49
|
+
#readRetry: ConfigReadRetry = new ConfigReadRetry();
|
|
50
|
+
#armGate: ArmGate = new ArmGate();
|
|
51
|
+
// The gate above is chokidar's scan finishing; this is the barrier's gate. A terminal outcome
|
|
52
|
+
// opens it without claiming the watch is armed, so the arming re-read still runs afterwards.
|
|
53
|
+
#barrierOpen: boolean = false;
|
|
54
|
+
#configLoaded: boolean = false;
|
|
55
|
+
#readyStaged: boolean = false;
|
|
56
|
+
#readyEmitted: boolean = false;
|
|
15
57
|
ready: Promise<any[]>;
|
|
16
58
|
|
|
17
|
-
constructor() {
|
|
59
|
+
constructor(configFilePath: string = getConfigFilePath()) {
|
|
18
60
|
super();
|
|
19
|
-
this.#configFilePath =
|
|
20
|
-
|
|
61
|
+
this.#configFilePath = configFilePath;
|
|
62
|
+
const watchTarget = resolveWatchTarget(this.#configFilePath);
|
|
63
|
+
this.#watchPath = watchTarget.path;
|
|
64
|
+
this.#usingPolling = watchTarget.mustPoll;
|
|
21
65
|
this.#closed = false;
|
|
22
66
|
this.ready = once(this, 'ready');
|
|
23
67
|
this.#openWatcher();
|
|
24
68
|
}
|
|
25
69
|
|
|
26
70
|
#openWatcher() {
|
|
27
|
-
this.#openCount
|
|
28
|
-
this.#watcher =
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
})
|
|
71
|
+
const generation = ++this.#openCount;
|
|
72
|
+
this.#watcher = guardedWatch(this.#watchPath, {
|
|
73
|
+
persistent: false,
|
|
74
|
+
...(this.#usingPolling ? POLLING_FALLBACK_OPTIONS : {}),
|
|
75
|
+
})
|
|
33
76
|
.on('add', this.handleChange.bind(this))
|
|
34
77
|
.on('change', this.handleChange.bind(this))
|
|
35
|
-
.on('error', this.handleError.bind(this))
|
|
78
|
+
.on('error', this.handleError.bind(this))
|
|
79
|
+
// Generation-bound: `#armGate.reset()` runs before the failed watcher is closed, so a
|
|
80
|
+
// `ready` still queued on it would arm the gate on the replacement's behalf and the
|
|
81
|
+
// replacement's own `ready` would then be a no-op — leaving its scan window unre-read.
|
|
82
|
+
.on('ready', () => this.#handleArmed(generation));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
#handleArmed(generation: number) {
|
|
86
|
+
if (this.#closed || generation !== this.#openCount) return;
|
|
87
|
+
this.#armGate.arm(() => this.#markArmed());
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#markArmed() {
|
|
91
|
+
this.#barrierOpen = true;
|
|
92
|
+
// A write that landed while the watch was unarmed was never reported, and a scan that found
|
|
93
|
+
// no file at all reported nothing either, so arming always re-reads rather than publishing
|
|
94
|
+
// what an earlier read staged — or, with no file, staying pending forever.
|
|
95
|
+
this.#read(true);
|
|
96
|
+
// A read that armed the ladder settles `ready` itself, with the newer config.
|
|
97
|
+
if (!this.#readRetry.pending) this.#emitReady();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
#emitReady() {
|
|
101
|
+
if (this.#readyEmitted || !this.#barrierOpen || !this.#readyStaged || this.#closed) return;
|
|
102
|
+
this.#readyEmitted = true;
|
|
103
|
+
try {
|
|
104
|
+
this.emit('ready', this.#config);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
logger().warn('A Harper configuration listener failed', errorForLog(error));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// `harper_logger.start()` awaits `ready` with no timeout, so every terminal outcome has to
|
|
111
|
+
// settle the barrier. A failed read discards an earlier staged value, but a watcher error keeps
|
|
112
|
+
// it because no later read superseded it. No config is represented by `undefined`, not `{}`:
|
|
113
|
+
// an empty object is a configuration that turns logging off.
|
|
114
|
+
#stageBootFallback(discardStaged = true) {
|
|
115
|
+
if (this.#readyEmitted) return;
|
|
116
|
+
if (discardStaged) {
|
|
117
|
+
this.#config = undefined;
|
|
118
|
+
this.#configLoaded = false;
|
|
119
|
+
}
|
|
120
|
+
this.#readyStaged = true;
|
|
121
|
+
this.#emitReady();
|
|
36
122
|
}
|
|
37
123
|
|
|
38
124
|
// Test-only: simulate the underlying chokidar watcher emitting an error.
|
|
@@ -42,17 +128,29 @@ export class RootConfigWatcher extends EventEmitter {
|
|
|
42
128
|
this.handleError(error);
|
|
43
129
|
}
|
|
44
130
|
|
|
45
|
-
// Test-only: whether the watcher has fallen back to polling.
|
|
46
131
|
get _usingPollingForTests(): boolean {
|
|
47
132
|
return this.#usingPolling;
|
|
48
133
|
}
|
|
49
134
|
|
|
50
|
-
// Test-only: number of times the underlying watcher has been (re)opened.
|
|
51
135
|
get _openCountForTests(): number {
|
|
52
136
|
return this.#openCount;
|
|
53
137
|
}
|
|
54
138
|
|
|
139
|
+
// Distinguishes a ladder rung from a watcher event.
|
|
140
|
+
get _readCountForTests(): number {
|
|
141
|
+
return this.#readCount;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
get _armedForTests(): boolean {
|
|
145
|
+
return this.#armGate.armed;
|
|
146
|
+
}
|
|
147
|
+
|
|
55
148
|
handleError(error: unknown) {
|
|
149
|
+
// A queued chokidar error can land after close(), which has dropped every listener.
|
|
150
|
+
if (this.#closed) return;
|
|
151
|
+
// See EntryHandler.#handleWatcherError: a lost native watch handle is benign
|
|
152
|
+
// and must not be surfaced to consumers as a config-watch failure.
|
|
153
|
+
if (claimLostNativeWatchError(error)) return;
|
|
56
154
|
if (isWatcherExhaustionError(error)) {
|
|
57
155
|
// Swallow every exhaustion error — chokidar can emit several before the
|
|
58
156
|
// failed native watcher closes, and we don't want a flurry of ENOSPC to
|
|
@@ -60,47 +158,149 @@ export class RootConfigWatcher extends EventEmitter {
|
|
|
60
158
|
if (!this.#usingPolling) {
|
|
61
159
|
warnWatcherFallback(this.#configFilePath);
|
|
62
160
|
this.#usingPolling = true;
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
//
|
|
67
|
-
this
|
|
68
|
-
|
|
161
|
+
// The generation that just failed no longer speaks for the watch; the replacement
|
|
162
|
+
// arms on its own scan, and re-reads then as the first one did.
|
|
163
|
+
this.#armGate.reset();
|
|
164
|
+
// Start close() from a microtask, not directly here, so a synchronous throw
|
|
165
|
+
// can't escape this 'error' listener as an uncaught exception.
|
|
166
|
+
Promise.resolve()
|
|
167
|
+
.then(() => this.#watcher.close())
|
|
69
168
|
.catch(() => {
|
|
70
169
|
// Teardown errors on an already-failed watcher are not actionable.
|
|
71
170
|
})
|
|
72
|
-
.
|
|
171
|
+
.then(() => {
|
|
73
172
|
if (!this.#closed) this.#openWatcher();
|
|
74
|
-
})
|
|
173
|
+
})
|
|
174
|
+
.catch((error) =>
|
|
175
|
+
logger().warn(`Could not reopen the ${this.#configFilePath} watch on polling`, errorForLog(error))
|
|
176
|
+
);
|
|
177
|
+
} else {
|
|
178
|
+
// Already polling — the replacement failed too, or the watch was polling from
|
|
179
|
+
// construction (`mustPoll`) and never had a fallback to take. Either way the branch
|
|
180
|
+
// above reopens only once, so this is the watch's terminal outcome and the barrier
|
|
181
|
+
// has to settle or `harper_logger.start()` awaits it forever.
|
|
182
|
+
this.#barrierOpen = true;
|
|
183
|
+
this.#stageBootFallback(false);
|
|
75
184
|
}
|
|
76
185
|
return;
|
|
77
186
|
}
|
|
78
|
-
|
|
187
|
+
// chokidar may never reach its own `ready` after a scan error, and nothing else would
|
|
188
|
+
// settle the barrier: the error is this read's terminal outcome. The scan is not over
|
|
189
|
+
// though, so the arm gate stays closed and a later `ready` still takes the arming re-read.
|
|
190
|
+
this.#barrierOpen = true;
|
|
191
|
+
this.#stageBootFallback(false);
|
|
192
|
+
// Settling the barrier removed the `error` listener `once(this, 'ready')` attached, and an
|
|
193
|
+
// emit with none left throws the error back into chokidar's dispatch — as does a consumer
|
|
194
|
+
// that throws from its own handler.
|
|
195
|
+
if (this.listenerCount('error') === 0) {
|
|
196
|
+
logger().warn(`The Harper configuration watcher at ${this.#configFilePath} failed`, errorForLog(error));
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
this.emit('error', error);
|
|
201
|
+
} catch (listenerError) {
|
|
202
|
+
logger().warn('A Harper configuration error listener failed', errorForLog(listenerError));
|
|
203
|
+
}
|
|
79
204
|
}
|
|
80
205
|
|
|
81
206
|
handleChange() {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (!data) return;
|
|
207
|
+
this.#read(true);
|
|
208
|
+
}
|
|
85
209
|
|
|
86
|
-
|
|
210
|
+
// `harper_logger.start()` awaits `ready` with no timeout and the ladder may be the only thing
|
|
211
|
+
// left to settle it, so until then its timer keeps the thread alive rather than letting it
|
|
212
|
+
// drain and exit mid-boot.
|
|
213
|
+
#schedule(): boolean {
|
|
214
|
+
return this.#readRetry.schedule(() => this.#read(false), !this.#readyEmitted);
|
|
215
|
+
}
|
|
87
216
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
217
|
+
#read(waitForLock: boolean) {
|
|
218
|
+
// A queued chokidar callback can still land after close(), which has already discarded the
|
|
219
|
+
// config and dropped every listener.
|
|
220
|
+
if (this.#closed) return;
|
|
221
|
+
this.#readCount++;
|
|
222
|
+
let data: string;
|
|
223
|
+
try {
|
|
224
|
+
data = readConfigFileSync(this.#configFilePath, waitForLock);
|
|
225
|
+
} catch (error) {
|
|
226
|
+
// A missing file is not a lock — `readConfigFileSync` does not retry it either, and
|
|
227
|
+
// `OptionsWatcher` settles it immediately as the install window. Taking the ladder here
|
|
228
|
+
// would disagree with that and cost `harper_logger.start()` the whole budget on every
|
|
229
|
+
// boot that has no config file (an env-var-only deployment, an empty mounted rootPath).
|
|
230
|
+
if (!isMissingFile(error) && this.#schedule()) return;
|
|
231
|
+
// A ladder armed by an earlier empty read is spent by the time a rung lands on ENOENT,
|
|
232
|
+
// and every other terminal path clears its deadline.
|
|
233
|
+
this.#readRetry.reset();
|
|
234
|
+
logger().warn(
|
|
235
|
+
`Unable to read the Harper configuration file at ${this.#configFilePath}` +
|
|
236
|
+
(this.#configLoaded ? ', continuing with the previously loaded configuration' : '; none has been loaded yet'),
|
|
237
|
+
errorForLog(error)
|
|
238
|
+
);
|
|
239
|
+
this.#stageBootFallback();
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
// See DESIGN.md, "An empty read is a writer mid-write, not an empty config".
|
|
243
|
+
if (!data) {
|
|
244
|
+
if (this.#schedule()) return;
|
|
245
|
+
logger().warn(`The Harper configuration file at ${this.#configFilePath} is empty`);
|
|
246
|
+
this.#stageBootFallback();
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
let config;
|
|
250
|
+
try {
|
|
251
|
+
config = parseConfigFile(data, this.#configFilePath);
|
|
252
|
+
} catch (error) {
|
|
253
|
+
// A read taken mid-write is untrustworthy, not only an empty one: the writer's first
|
|
254
|
+
// `write(2)` can land a prefix of the document, and the event carrying the rest is the one
|
|
255
|
+
// chokidar throttles away. So an unparseable read rides out the same ladder as an empty
|
|
256
|
+
// one, and only a read that parses releases it.
|
|
257
|
+
if (this.#schedule()) return;
|
|
258
|
+
logger().warn((error as Error).message);
|
|
259
|
+
this.#stageBootFallback();
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
// The third shape a mid-write read takes, and the only one that parses: a truncated
|
|
263
|
+
// document, a lone `\n`, a file that is nothing but comments all yield `null` rather than
|
|
264
|
+
// throwing, and adopting one hands every consumer a config with nothing in it. Same ladder
|
|
265
|
+
// as the two above, and past it the file is empty rather than mid-write.
|
|
266
|
+
if (!config || typeof config !== 'object') {
|
|
267
|
+
if (this.#schedule()) return;
|
|
268
|
+
logger().warn(`The Harper configuration file at ${this.#configFilePath} is empty`);
|
|
269
|
+
this.#stageBootFallback();
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
this.#readRetry.reset();
|
|
93
273
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
274
|
+
// Before `ready` goes out there is no prior state to have changed *since*.
|
|
275
|
+
this.#configLoaded = true;
|
|
276
|
+
this.#readyStaged = true;
|
|
277
|
+
if (!this.#readyEmitted) {
|
|
278
|
+
this.#config = config;
|
|
279
|
+
this.#emitReady();
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
this.emit('change', (this.#config = config));
|
|
285
|
+
} catch (error) {
|
|
286
|
+
logger().warn('A Harper configuration change listener failed', errorForLog(error));
|
|
287
|
+
}
|
|
99
288
|
}
|
|
100
289
|
|
|
101
290
|
close() {
|
|
291
|
+
// Closing is a terminal outcome too: leaving `ready` pending would hang anything still
|
|
292
|
+
// awaiting the barrier. Through `#emitReady`, so a listener that throws cannot skip the
|
|
293
|
+
// teardown below it and leave the watcher and its arm timer running.
|
|
294
|
+
this.#barrierOpen = true;
|
|
295
|
+
this.#readyStaged = true;
|
|
296
|
+
this.#emitReady();
|
|
102
297
|
this.#closed = true;
|
|
103
|
-
this.#
|
|
298
|
+
this.#readRetry.cancel();
|
|
299
|
+
this.#armGate.cancel();
|
|
300
|
+
// chokidar's close() is a promise; an unhandled teardown rejection would reach Node as one,
|
|
301
|
+
// on the path whose whole job is to stop caring about this watcher. Same shape as the
|
|
302
|
+
// exhaustion-recovery close above, and as `OptionsWatcher.close`.
|
|
303
|
+
Promise.resolve(this.#watcher.close()).catch(() => {});
|
|
104
304
|
this.#config = undefined;
|
|
105
305
|
this.emit('close');
|
|
106
306
|
this.removeAllListeners();
|