@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
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
import { type Logger } from '../utility/logging/logger.ts';
|
|
2
2
|
import { loggerWithTag } from '../utility/logging/harper_logger.ts';
|
|
3
3
|
import { EventEmitter, once } from 'events';
|
|
4
|
-
import
|
|
5
|
-
import chokidar, { type FSWatcher } from 'chokidar';
|
|
4
|
+
import { type FSWatcher } from 'chokidar';
|
|
6
5
|
import { readFile } from 'node:fs/promises';
|
|
7
6
|
import { isDeepStrictEqual } from 'util';
|
|
8
7
|
import { DEFAULT_CONFIG } from './DEFAULT_CONFIG.ts';
|
|
9
8
|
import { cloneDeep } from 'lodash';
|
|
10
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
POLLING_FALLBACK_OPTIONS,
|
|
11
|
+
claimLostNativeWatchError,
|
|
12
|
+
guardedWatch,
|
|
13
|
+
isWatcherExhaustionError,
|
|
14
|
+
warnWatcherFallback,
|
|
15
|
+
} from '../utility/watcherFallback.ts';
|
|
16
|
+
import { resolveWatchTarget } from '../utility/watchPath.ts';
|
|
11
17
|
import { overlayRootEnvConfig, isRootConfigFilename } from '../config/harperConfigEnvVars.ts';
|
|
18
|
+
import { readConfigFileSync } from '../config/readConfigFileSync.ts';
|
|
19
|
+
import { parseConfigFile } from '../config/parseConfigFile.ts';
|
|
20
|
+
import { ConfigReadRetry } from '../config/configReadRetry.ts';
|
|
21
|
+
import { ArmGate } from '../config/watcherArming.ts';
|
|
12
22
|
|
|
13
23
|
export interface Config {
|
|
14
24
|
[key: string]: ConfigValue;
|
|
@@ -84,15 +94,26 @@ export class CannotSetPropertyError extends Error {
|
|
|
84
94
|
*/
|
|
85
95
|
export class OptionsWatcher extends EventEmitter<OptionsWatcherEventMap> {
|
|
86
96
|
#filePath: string;
|
|
97
|
+
#watchPath: string;
|
|
87
98
|
#watcher!: FSWatcher;
|
|
88
99
|
#scopedConfig?: ConfigValue;
|
|
89
100
|
#rootConfig?: Config;
|
|
90
101
|
#isRootConfig: boolean;
|
|
102
|
+
#synchronousRead: boolean;
|
|
103
|
+
#readRetry: ConfigReadRetry = new ConfigReadRetry();
|
|
104
|
+
#armGate: ArmGate = new ArmGate();
|
|
91
105
|
#name: string;
|
|
92
106
|
#logger: Logger;
|
|
93
107
|
#usingPolling: boolean;
|
|
94
108
|
#closed: boolean;
|
|
95
109
|
#openCount: number = 0;
|
|
110
|
+
#readCount: number = 0;
|
|
111
|
+
#readSequence: number = 0;
|
|
112
|
+
#appliedSequence: number = 0;
|
|
113
|
+
#scopeConfigured: boolean = false;
|
|
114
|
+
#armAbsence?: NodeJS.Immediate;
|
|
115
|
+
#readyEmitted: boolean = false;
|
|
116
|
+
#envComposeError: unknown;
|
|
96
117
|
#pendingReads: Set<Promise<void>> = new Set();
|
|
97
118
|
ready: Promise<any[]>;
|
|
98
119
|
|
|
@@ -100,90 +121,103 @@ export class OptionsWatcher extends EventEmitter<OptionsWatcherEventMap> {
|
|
|
100
121
|
super();
|
|
101
122
|
this.#name = name;
|
|
102
123
|
this.#filePath = filePath;
|
|
124
|
+
const watchTarget = resolveWatchTarget(filePath);
|
|
125
|
+
this.#watchPath = watchTarget.path;
|
|
103
126
|
// Root-config watchers must see runtime env config (HARPER_SET_CONFIG et al.)
|
|
104
127
|
// even when it hasn't been flushed to disk yet — see #handleChange (#1618).
|
|
105
128
|
// Application scopes watch their own config.yaml and are never overlaid.
|
|
106
|
-
|
|
129
|
+
const rootConfigFile = isRootConfigFilename(filePath);
|
|
130
|
+
this.#isRootConfig = isRootConfig ?? rootConfigFile;
|
|
131
|
+
this.#synchronousRead = this.#isRootConfig;
|
|
107
132
|
this.#logger = logger || loggerWithTag(name);
|
|
108
|
-
this.#usingPolling =
|
|
133
|
+
this.#usingPolling = watchTarget.mustPoll;
|
|
109
134
|
this.#closed = false;
|
|
110
135
|
this.ready = once(this, 'ready');
|
|
111
136
|
this.#openWatcher();
|
|
112
137
|
}
|
|
113
138
|
|
|
114
139
|
#openWatcher() {
|
|
115
|
-
this.#openCount
|
|
116
|
-
this.#watcher =
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
})
|
|
140
|
+
const generation = ++this.#openCount;
|
|
141
|
+
this.#watcher = guardedWatch(this.#watchPath, {
|
|
142
|
+
persistent: false,
|
|
143
|
+
...(this.#usingPolling ? POLLING_FALLBACK_OPTIONS : {}),
|
|
144
|
+
})
|
|
121
145
|
.on('add', this.#handleChange.bind(this))
|
|
122
146
|
.on('change', this.#handleChange.bind(this))
|
|
123
147
|
.on('error', this.#handleError.bind(this))
|
|
124
148
|
.on('unlink', this.#handleUnlink.bind(this))
|
|
125
|
-
.
|
|
149
|
+
// Generation-bound: `#armGate.reset()` runs before the failed watcher is closed, so a
|
|
150
|
+
// `ready` still queued on it would arm the gate on the replacement's behalf and the
|
|
151
|
+
// replacement's own `ready` would then be a no-op — leaving its scan window unre-read.
|
|
152
|
+
.on('ready', () => this.#handleArmed(generation));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Every root-declared plugin gets its own root-config watcher and each reads synchronously, so
|
|
156
|
+
// each has the unarmed window DESIGN.md's "`ready` means the watcher is armed" describes. The
|
|
157
|
+
// write that lands in it is reported by no event, so only this re-read can deliver it.
|
|
158
|
+
#handleArmed(generation: number) {
|
|
159
|
+
if (this.#closed || generation !== this.#openCount) return;
|
|
160
|
+
this.#armGate.arm(() => this.#read(true, true));
|
|
126
161
|
}
|
|
127
162
|
|
|
128
163
|
#handleChange() {
|
|
164
|
+
this.#read(true);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// While `ready` is outstanding the ladder is the only thing that can settle it, so its timer has
|
|
168
|
+
// to keep the thread alive; afterwards it must not, or a config file nobody is reading would
|
|
169
|
+
// hold a worker open.
|
|
170
|
+
#schedule(): boolean {
|
|
171
|
+
return this.#readRetry.schedule(() => this.#read(false), !this.#readyEmitted);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
#read(waitForLock: boolean, arming: boolean = false) {
|
|
175
|
+
// A queued chokidar callback can still land after close(), and by then removeAllListeners()
|
|
176
|
+
// has run — emitting into an EventEmitter with no 'error' listener would throw out of it.
|
|
177
|
+
if (this.#closed) return;
|
|
178
|
+
this.#readCount++;
|
|
179
|
+
if (this.#synchronousRead) {
|
|
180
|
+
try {
|
|
181
|
+
let contents: string;
|
|
182
|
+
try {
|
|
183
|
+
contents = readConfigFileSync(this.#filePath, waitForLock);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
this.#handleReadError(error, arming);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
this.#applyContents(contents);
|
|
189
|
+
} catch (error) {
|
|
190
|
+
// A listener of what `#applyContents` emitted may have closed the watcher, after
|
|
191
|
+
// which `emit('error')` has no listener left and would throw out of here.
|
|
192
|
+
if (!this.#closed) this.#surfaceFailure(error);
|
|
193
|
+
}
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// The `#closed` check above cannot cover the completion: close() may land while this read is
|
|
198
|
+
// in flight, and by then its retry state is cancelled and its listeners are gone. Nothing
|
|
199
|
+
// orders these against each other either — chokidar does not await one read before starting
|
|
200
|
+
// the next — so an older one completing last would put the file's previous contents back
|
|
201
|
+
// with no event left to correct it.
|
|
202
|
+
const sequence = ++this.#readSequence;
|
|
203
|
+
// `<=`, not `<`: a deletion supersedes the reads already in flight without being a read of
|
|
204
|
+
// its own, and says so by claiming the sequence they were issued under.
|
|
205
|
+
const outranked = () => this.#closed || sequence <= this.#appliedSequence;
|
|
129
206
|
const read: Promise<void> = readFile(this.#filePath, 'utf-8')
|
|
130
|
-
.then(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
this.#rootConfig = parsed && typeof parsed === 'object' ? parsed : undefined;
|
|
141
|
-
// If the extension is in the config file
|
|
142
|
-
if (this.#rootConfig && this.#name in this.#rootConfig) {
|
|
143
|
-
// If a config object does not exist
|
|
144
|
-
if (!this.#scopedConfig) {
|
|
145
|
-
// set it
|
|
146
|
-
this.#scopedConfig = this.#rootConfig[this.#name];
|
|
147
|
-
// and emit a ready event
|
|
148
|
-
this.emit('ready', this.#scopedConfig);
|
|
149
|
-
} else {
|
|
150
|
-
// Otherwise, merge the new config with the old config
|
|
151
|
-
this.#merge(this.#rootConfig[this.#name], this.#scopedConfig);
|
|
152
|
-
}
|
|
153
|
-
} else {
|
|
154
|
-
// Otherwise, if the extension is not in the config file
|
|
155
|
-
// This means the plugin was removed from the config file
|
|
156
|
-
if (this.#scopedConfig) {
|
|
157
|
-
// and a config exists, remove it
|
|
158
|
-
this.#scopedConfig = undefined;
|
|
159
|
-
this.emit('remove');
|
|
160
|
-
}
|
|
161
|
-
// Otherwise do nothing - the user may add the config back in later
|
|
207
|
+
.then(
|
|
208
|
+
(contents) => {
|
|
209
|
+
if (outranked()) return;
|
|
210
|
+
this.#appliedSequence = sequence;
|
|
211
|
+
this.#applyContents(contents);
|
|
212
|
+
},
|
|
213
|
+
(error) => {
|
|
214
|
+
if (outranked()) return;
|
|
215
|
+
this.#appliedSequence = sequence;
|
|
216
|
+
this.#handleReadError(error, arming);
|
|
162
217
|
}
|
|
163
|
-
|
|
218
|
+
)
|
|
164
219
|
.catch((error) => {
|
|
165
|
-
|
|
166
|
-
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
167
|
-
// A readFile ENOENT here is the install window (file not written yet) or a
|
|
168
|
-
// transient read race — NOT a real deletion, which chokidar routes to
|
|
169
|
-
// `#handleUnlink`. Env config is file-independent, so when it provides this
|
|
170
|
-
// scope the missing file must not discard it (#1618). When it does not, fall
|
|
171
|
-
// through to the original ENOENT handling with #rootConfig untouched, so a
|
|
172
|
-
// first boot still emits `ready` (not `remove`, which nothing consumes at
|
|
173
|
-
// boot → `ready` would hang forever).
|
|
174
|
-
if (this.#applyEnvOnlyConfig()) return;
|
|
175
|
-
// And a config already exists, reset it to the default
|
|
176
|
-
if (this.#rootConfig) {
|
|
177
|
-
this.#resetConfig();
|
|
178
|
-
this.emit('remove');
|
|
179
|
-
} else {
|
|
180
|
-
// Otherwise, if no config exists, then just set to default and emit ready
|
|
181
|
-
this.#resetConfig();
|
|
182
|
-
this.emit('ready');
|
|
183
|
-
}
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
this.emit('error', error);
|
|
220
|
+
if (!this.#closed) this.#surfaceFailure(error);
|
|
187
221
|
})
|
|
188
222
|
.finally(() => {
|
|
189
223
|
this.#pendingReads.delete(read);
|
|
@@ -191,7 +225,202 @@ export class OptionsWatcher extends EventEmitter<OptionsWatcherEventMap> {
|
|
|
191
225
|
this.#pendingReads.add(read);
|
|
192
226
|
}
|
|
193
227
|
|
|
228
|
+
#applyContents(contents: string) {
|
|
229
|
+
// An empty read is a writer's truncate window, not an emptied config, and falling through
|
|
230
|
+
// would emit `remove` — see DESIGN.md, "An empty read is a writer mid-write, not an empty
|
|
231
|
+
// config". Both read paths can land in that window; only the synchronous one is likely to.
|
|
232
|
+
if (!contents) {
|
|
233
|
+
if (this.#schedule()) return;
|
|
234
|
+
// Past the ladder the file is empty rather than mid-write, and an empty file carries no
|
|
235
|
+
// scope: keep what is already applied instead of reading it as a removal, and settle the
|
|
236
|
+
// boot barrier on the defaults rather than leaving `Scope.ready` pending forever.
|
|
237
|
+
this.#logger.warn?.(`Configuration file ${this.#filePath} is empty.`);
|
|
238
|
+
this.#settleUnconfigured();
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
let parsed;
|
|
242
|
+
try {
|
|
243
|
+
parsed = parseConfigFile(contents, this.#filePath);
|
|
244
|
+
} catch (error) {
|
|
245
|
+
// A prefix of the document is as much a mid-write read as an empty one, and the event
|
|
246
|
+
// carrying the rest is the one chokidar throttles away — same ladder, same reason. Past it
|
|
247
|
+
// the file really is malformed, which `#read` routes to `#surfaceFailure`.
|
|
248
|
+
if (!this.#closed && this.#schedule()) return;
|
|
249
|
+
throw error;
|
|
250
|
+
}
|
|
251
|
+
// Judged on the file's own parse, before the overlay below: a truncated document, a lone
|
|
252
|
+
// `\n` and a file of nothing but comments all parse to `null` rather than throwing, and
|
|
253
|
+
// `overlayRootEnvConfig` turns any of them into a non-null object whenever a config env var
|
|
254
|
+
// is set — the norm in containers — so overlaying first would launder a half-written file
|
|
255
|
+
// into a valid-looking env-only config and wipe the file's own options. Past the ladder it
|
|
256
|
+
// is an empty file, which `#applyContents` already keeps rather than reads as a removal.
|
|
257
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
258
|
+
if (this.#schedule()) return;
|
|
259
|
+
this.#logger.warn?.(`Configuration file ${this.#filePath} is empty.`);
|
|
260
|
+
this.#settleUnconfigured();
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
this.#readRetry.reset();
|
|
264
|
+
// The on-disk root config is not guaranteed to include runtime env config at
|
|
265
|
+
// boot: the file flush races component loading, so a scope's boot-time reads
|
|
266
|
+
// (e.g. an `enabled` gate in handleApplication) could observe pre-env values
|
|
267
|
+
// the componentLoader itself never saw. Ask the config layer to overlay env
|
|
268
|
+
// config onto EVERY root-config read so scope.options matches the resolved
|
|
269
|
+
// view (#1618). Non-root scopes and the no-env-vars case are untouched
|
|
270
|
+
// (overlayRootEnvConfig is a no-op there).
|
|
271
|
+
if (this.#isRootConfig) {
|
|
272
|
+
try {
|
|
273
|
+
parsed = overlayRootEnvConfig(parsed);
|
|
274
|
+
} catch (error) {
|
|
275
|
+
this.#envComposeError = error;
|
|
276
|
+
if (this.#readyEmitted) this.#reportEnvComposeFailure();
|
|
277
|
+
else this.#settleUnconfigured();
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
this.#rootConfig = parsed && typeof parsed === 'object' ? parsed : undefined;
|
|
282
|
+
// If the extension is in the config file
|
|
283
|
+
if (this.#rootConfig && this.#name in this.#rootConfig) {
|
|
284
|
+
this.#applyScopedConfig(this.#rootConfig[this.#name]);
|
|
285
|
+
} else {
|
|
286
|
+
// Otherwise, if the extension is not in the config file
|
|
287
|
+
// This means the plugin was removed from the config file
|
|
288
|
+
// Presence, not truthiness: `myPlugin:` with nothing under it is a configured scope, and
|
|
289
|
+
// deleting that block has to reach `Scope` as a removal like any other.
|
|
290
|
+
if (this.#scopeConfigured) {
|
|
291
|
+
this.#scopeConfigured = false;
|
|
292
|
+
this.#scopedConfig = undefined;
|
|
293
|
+
this.#emitRemove();
|
|
294
|
+
}
|
|
295
|
+
// The scope may be added back later, but this read is still a terminal outcome: with
|
|
296
|
+
// nothing ever applied, neither branch above emits, and `Scope.ready` would stay pending
|
|
297
|
+
// for a config that read perfectly well. The read succeeded, so only the scope falls back
|
|
298
|
+
// to its default — `#settleUnconfigured`'s full reset would discard the root config this
|
|
299
|
+
// very read produced.
|
|
300
|
+
if (!this.#readyEmitted) {
|
|
301
|
+
this.#scopedConfig = cloneDeep(DEFAULT_CONFIG[this.#name]);
|
|
302
|
+
this.#emitReady(this.#scopedConfig);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
#handleReadError(error: unknown, arming: boolean = false) {
|
|
308
|
+
// If the config file does not exist
|
|
309
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
310
|
+
// A readFile ENOENT here is the install window (file not written yet) or a
|
|
311
|
+
// transient read race — NOT a real deletion, which chokidar routes to
|
|
312
|
+
// `#handleUnlink`. Env config is file-independent, so when it provides this
|
|
313
|
+
// scope the missing file must not discard it (#1618). When it does not, fall
|
|
314
|
+
// through to the original ENOENT handling with #rootConfig untouched, so a
|
|
315
|
+
// first boot still emits `ready` (not `remove`, which nothing consumes at
|
|
316
|
+
// boot → `ready` would hang forever).
|
|
317
|
+
// A ladder armed by an earlier empty read is spent here, and every other terminal path
|
|
318
|
+
// clears its deadline; leaving it armed costs the next mid-write read its whole budget.
|
|
319
|
+
this.#readRetry.reset();
|
|
320
|
+
if (this.#applyEnvOnlyConfig()) return;
|
|
321
|
+
// And a config already exists, reset it to the default
|
|
322
|
+
if (this.#rootConfig) {
|
|
323
|
+
// The arm gate's job is the *write* that landed while the watch was unarmed, and
|
|
324
|
+
// answering its ENOENT with a removal reports the deletion ahead of the `unlink`
|
|
325
|
+
// that would confirm it — on a platform with an arming grace, ahead of chokidar
|
|
326
|
+
// having finished its own teardown, so a config recreated on the strength of that
|
|
327
|
+
// early `remove` lands where its `add` is not observed at all. It cannot simply be
|
|
328
|
+
// dropped either: the unarmed window is exactly where an `unlink` can go missing.
|
|
329
|
+
// So it goes back to the loop once, and chokidar's own `unlink` cancels it.
|
|
330
|
+
if (arming) {
|
|
331
|
+
// Every resolution of the deferral recomposes and reports the env state it finds,
|
|
332
|
+
// so a failure held across it is one that may no longer be true — and the next
|
|
333
|
+
// path to report would attach it to an unrelated event.
|
|
334
|
+
this.#envComposeError = undefined;
|
|
335
|
+
this.#deferAbsenceCheck();
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
this.#resetConfig();
|
|
339
|
+
this.#emitRemove();
|
|
340
|
+
} else {
|
|
341
|
+
// Otherwise, if no config exists, then just set to default and emit ready
|
|
342
|
+
this.#resetConfig();
|
|
343
|
+
this.#emitReady();
|
|
344
|
+
}
|
|
345
|
+
this.#reportEnvComposeFailure();
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
// A failure that outlives the read emits no new watcher event when it clears, so without
|
|
349
|
+
// this the scope would hold a stale config until the next write. Both read paths: an
|
|
350
|
+
// application config on SMB or under an editor's replacement returns a transient
|
|
351
|
+
// EBUSY/EIO just the same, and only the read for it blocks — the ladder never does.
|
|
352
|
+
if (!this.#closed && this.#schedule()) return;
|
|
353
|
+
this.#surfaceFailure(error);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// A failure before anything has been applied is the boot window, where `error` alone strands the
|
|
357
|
+
// component: `Scope` logs it and componentLoader waits on `Scope.ready` with no timeout. Fall
|
|
358
|
+
// back to the defaults exactly as the ENOENT branch above does — and emit `ready` first, since
|
|
359
|
+
// `error` settles the `once(..., 'ready')` promises both of them await.
|
|
360
|
+
#surfaceFailure(error: unknown) {
|
|
361
|
+
this.#settleUnconfigured();
|
|
362
|
+
this.#emitError(error);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Settling the barrier removes the `error` listener `once(this, 'ready')` attached, and an emit
|
|
366
|
+
// with none left throws the error back at the caller — here, chokidar's dispatch or a retry
|
|
367
|
+
// timer, where nothing can absorb it. A consumer that throws is no different.
|
|
368
|
+
#emitError(error: unknown) {
|
|
369
|
+
if (this.listenerCount('error') === 0) {
|
|
370
|
+
this.#logger.error?.(`The configuration for '${this.#name}' at ${this.#filePath} could not be read`, error);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
try {
|
|
374
|
+
this.emit('error', error);
|
|
375
|
+
} catch (listenerError) {
|
|
376
|
+
this.#logger.error?.('A configuration error listener failed', listenerError);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
#emitRemove() {
|
|
381
|
+
try {
|
|
382
|
+
this.emit('remove');
|
|
383
|
+
} catch (error) {
|
|
384
|
+
this.#logger.error?.('A configuration removal listener failed', error);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// `#readyEmitted`, not the config's truthiness: a scope absent from a config that read fine
|
|
389
|
+
// leaves `#rootConfig` set with no `ready` behind it, and a later failure would return here
|
|
390
|
+
// with `Scope.ready` still pending.
|
|
391
|
+
#settleUnconfigured() {
|
|
392
|
+
if (this.#readyEmitted) return;
|
|
393
|
+
// A file the watcher cannot use does not unset env config, exactly as on the ENOENT path
|
|
394
|
+
// (#1618) — but the barrier still has to settle, including when composing that env config is
|
|
395
|
+
// itself what failed.
|
|
396
|
+
if (this.#applyEnvOnlyConfig()) {
|
|
397
|
+
if (!this.#readyEmitted) this.#emitReady(this.#scopedConfig);
|
|
398
|
+
} else {
|
|
399
|
+
this.#resetConfig();
|
|
400
|
+
this.#emitReady(this.#scopedConfig);
|
|
401
|
+
}
|
|
402
|
+
// Also on the env-only branch: the caller may be `#handleChange`'s merge-shape failure,
|
|
403
|
+
// whose error composing `undefined` never reproduces.
|
|
404
|
+
this.#reportEnvComposeFailure();
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
#emitReady(...args: [ConfigValue?]) {
|
|
408
|
+
this.#readyEmitted = true;
|
|
409
|
+
try {
|
|
410
|
+
this.emit('ready', ...args);
|
|
411
|
+
} catch (error) {
|
|
412
|
+
// The failure paths emit `ready` from a ladder timer and from chokidar's error dispatch,
|
|
413
|
+
// where a throwing listener is an uncaught exception that takes the worker down.
|
|
414
|
+
this.#logger.error?.('A configuration listener failed', error);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
194
418
|
#handleError(error: unknown) {
|
|
419
|
+
// A queued chokidar error can land after close(), which has dropped every listener.
|
|
420
|
+
if (this.#closed) return;
|
|
421
|
+
// See EntryHandler.#handleWatcherError: a lost native watch handle is benign
|
|
422
|
+
// and must not be surfaced to consumers as a config-watch failure.
|
|
423
|
+
if (claimLostNativeWatchError(error)) return;
|
|
195
424
|
if (isWatcherExhaustionError(error)) {
|
|
196
425
|
// Swallow every exhaustion error — chokidar can emit several before the
|
|
197
426
|
// failed native watcher closes, and we don't want a flurry of ENOSPC to
|
|
@@ -199,25 +428,49 @@ export class OptionsWatcher extends EventEmitter<OptionsWatcherEventMap> {
|
|
|
199
428
|
if (!this.#usingPolling) {
|
|
200
429
|
warnWatcherFallback(this.#filePath);
|
|
201
430
|
this.#usingPolling = true;
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
|
|
205
|
-
//
|
|
206
|
-
this
|
|
207
|
-
|
|
431
|
+
// The generation that just failed no longer speaks for the watch; the replacement arms
|
|
432
|
+
// on its own scan, and re-reads then as the first one did.
|
|
433
|
+
this.#armGate.reset();
|
|
434
|
+
// Start close() from a microtask, not directly here, so a synchronous throw
|
|
435
|
+
// can't escape this 'error' listener as an uncaught exception.
|
|
436
|
+
Promise.resolve()
|
|
437
|
+
.then(() => this.#watcher.close())
|
|
208
438
|
.catch(() => {
|
|
209
439
|
// Teardown errors on an already-failed watcher are not actionable.
|
|
210
440
|
})
|
|
211
|
-
.
|
|
441
|
+
.then(() => {
|
|
212
442
|
if (!this.#closed) this.#openWatcher();
|
|
213
|
-
})
|
|
443
|
+
})
|
|
444
|
+
.catch((error) => this.#logger.error?.(`Could not reopen the ${this.#filePath} watch on polling:`, error));
|
|
445
|
+
} else {
|
|
446
|
+
// Already polling — the replacement failed too, or the watch was polling from
|
|
447
|
+
// construction (`mustPoll`) and never had a fallback to take. Either way the branch
|
|
448
|
+
// above reopens only once, so no read will ever run: settle the barrier rather than
|
|
449
|
+
// leave `Scope.ready` pending forever.
|
|
450
|
+
this.#settleUnconfigured();
|
|
214
451
|
}
|
|
215
452
|
return;
|
|
216
453
|
}
|
|
217
|
-
this.
|
|
454
|
+
// Terminal for this scope: `componentLoader` awaits `Scope.ready` with no timeout, and an
|
|
455
|
+
// `error` emitted while it is pending rejects that barrier instead of settling it — the
|
|
456
|
+
// asymmetry with `RootConfigWatcher.handleError` the boot-barrier contract cannot afford.
|
|
457
|
+
const watcherError = new OptionsWatcherConfigFileError(this.#filePath, error);
|
|
458
|
+
this.#settleUnconfigured();
|
|
459
|
+
this.#emitError(watcherError);
|
|
218
460
|
}
|
|
219
461
|
|
|
220
462
|
#handleUnlink(path: string) {
|
|
463
|
+
// The deletion settles what a pending read was retrying, and a rung landing after this
|
|
464
|
+
// would find ENOENT and emit a second `remove` at consumers that treat it as teardown.
|
|
465
|
+
// Same for the arming re-read's deferred absence check: this is the event it was waiting on.
|
|
466
|
+
// Cancelling the ladder covers the rung not yet armed; a rung already in flight on the
|
|
467
|
+
// asynchronous path is outranked instead, or its ENOENT would report the same deletion again
|
|
468
|
+
// — after the settle below, as a `remove` that asks `Scope` to restart a scope that just
|
|
469
|
+
// booted on the defaults.
|
|
470
|
+
this.#readRetry.reset();
|
|
471
|
+
this.#appliedSequence = this.#readSequence;
|
|
472
|
+
if (this.#armAbsence) clearImmediate(this.#armAbsence);
|
|
473
|
+
this.#armAbsence = undefined;
|
|
221
474
|
// A real deletion still leaves env-var config in force: an env-defined scope must
|
|
222
475
|
// survive it exactly as on the ENOENT read path — same fallback, same error routing
|
|
223
476
|
// (#1618, #1726 review).
|
|
@@ -231,17 +484,23 @@ export class OptionsWatcher extends EventEmitter<OptionsWatcherEventMap> {
|
|
|
231
484
|
`Configuration file ${path} was deleted. Reverting to default configuration. Recreate it to restore the options watcher.`
|
|
232
485
|
);
|
|
233
486
|
this.#resetConfig();
|
|
234
|
-
|
|
487
|
+
// Before the first `ready` there is nothing to remove and nothing to hear it: `Scope.ready`
|
|
488
|
+
// is still pending, so a `remove` here asks for a restart of a scope that never booted while
|
|
489
|
+
// the `#readRetry.reset()` above cancelled the only thing left to settle the barrier. Same
|
|
490
|
+
// ruling as the ENOENT read path, which already boots on the defaults rather than reporting
|
|
491
|
+
// a removal `ready` would then wait behind forever.
|
|
492
|
+
if (this.#readyEmitted) this.#emitRemove();
|
|
493
|
+
else this.#emitReady(this.#scopedConfig);
|
|
494
|
+
this.#reportEnvComposeFailure();
|
|
235
495
|
}
|
|
236
496
|
|
|
237
497
|
/**
|
|
238
498
|
* Shared fallback for the ENOENT read path and `#handleUnlink`: when config env vars
|
|
239
|
-
* define this scope, apply the env-only overlay (first application → `ready`;
|
|
240
|
-
* configured → `merge`, never reset). Returns
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
* their own reset semantics.
|
|
499
|
+
* define this scope, apply the env-only overlay (first source application → `ready`;
|
|
500
|
+
* already configured → `merge`, never reset). Returns false — root config untouched, so
|
|
501
|
+
* callers keep their own reset semantics — when this is not a root config, when the env
|
|
502
|
+
* config does not provide the scope, and when composing it threw, which is held for
|
|
503
|
+
* `#reportEnvComposeFailure` rather than emitted here.
|
|
245
504
|
*/
|
|
246
505
|
#applyEnvOnlyConfig(): boolean {
|
|
247
506
|
if (!this.#isRootConfig) return false;
|
|
@@ -249,25 +508,71 @@ export class OptionsWatcher extends EventEmitter<OptionsWatcherEventMap> {
|
|
|
249
508
|
try {
|
|
250
509
|
composed = overlayRootEnvConfig(undefined) as Config | undefined;
|
|
251
510
|
} catch (composeError) {
|
|
252
|
-
|
|
253
|
-
|
|
511
|
+
// Env config that cannot be composed is not env config: `false` puts every caller on the
|
|
512
|
+
// path it already takes when there is none, so the barrier still settles on the defaults
|
|
513
|
+
// and a deletion still emits `remove`. The failure is held for `#reportEnvComposeFailure`,
|
|
514
|
+
// which callers run *after* settling — an `error` emitted first settles
|
|
515
|
+
// `once(this, 'ready')` by rejection instead.
|
|
516
|
+
this.#envComposeError = composeError;
|
|
517
|
+
return false;
|
|
254
518
|
}
|
|
255
519
|
if (!composed || !(this.#name in composed)) return false;
|
|
256
520
|
this.#rootConfig = composed;
|
|
257
|
-
|
|
258
|
-
this.#scopedConfig = composed[this.#name];
|
|
259
|
-
this.emit('ready', this.#scopedConfig);
|
|
260
|
-
} else {
|
|
261
|
-
this.#merge(composed[this.#name], this.#scopedConfig);
|
|
262
|
-
}
|
|
521
|
+
this.#applyScopedConfig(composed[this.#name]);
|
|
263
522
|
return true;
|
|
264
523
|
}
|
|
265
524
|
|
|
525
|
+
#reportEnvComposeFailure() {
|
|
526
|
+
if (this.#envComposeError === undefined) return;
|
|
527
|
+
const error = this.#envComposeError;
|
|
528
|
+
this.#envComposeError = undefined;
|
|
529
|
+
this.#emitError(error);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// A scope with no source config takes `ready`, one with a prior source value takes the granular `change`
|
|
533
|
+
// events `#merge` derives. `ready` is emitted more than once: a scope can go back to having no
|
|
534
|
+
// config of its own — see `Scope.#handleOptionsWatcherReady` for what a repeat means there.
|
|
535
|
+
#applyScopedConfig(next: ConfigValue) {
|
|
536
|
+
if (!this.#scopeConfigured) {
|
|
537
|
+
this.#scopeConfigured = true;
|
|
538
|
+
this.#scopedConfig = next;
|
|
539
|
+
this.#emitReady(this.#scopedConfig);
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
if (this.#scopedConfig) {
|
|
543
|
+
return this.#merge(next, this.#scopedConfig);
|
|
544
|
+
}
|
|
545
|
+
// A falsy scope value is still a configured scope — `myPlugin:` with nothing under it is
|
|
546
|
+
// the idiomatic "enable with defaults" — and `#merge` cannot diff from one, because
|
|
547
|
+
// `#setValue` needs a value to walk. Re-reading it is not a transition (every ladder rung
|
|
548
|
+
// and rename-burst re-read would otherwise look like one), but *filling it in* is a change
|
|
549
|
+
// like any other, not the unconfigured → configured `ready` that `Scope` answers with a
|
|
550
|
+
// restart.
|
|
551
|
+
if (isDeepStrictEqual(next, this.#scopedConfig)) return;
|
|
552
|
+
this.#scopedConfig = next;
|
|
553
|
+
this.#emitChange([], next);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Cloned, never aliased: `#merge` writes into `#scopedConfig` in place, so a scope that starts
|
|
557
|
+
// on the defaults and is then configured would otherwise write the app's values into the
|
|
558
|
+
// module-level `DEFAULT_CONFIG` every later reset hands out. And not configured: the six scopes
|
|
559
|
+
// `DEFAULT_CONFIG` names would otherwise have the next read of an unchanged file look like the
|
|
560
|
+
// block being deleted.
|
|
266
561
|
#resetConfig() {
|
|
267
|
-
this.#
|
|
562
|
+
this.#scopeConfigured = false;
|
|
563
|
+
this.#rootConfig = cloneDeep(DEFAULT_CONFIG);
|
|
268
564
|
this.#scopedConfig = this.#rootConfig[this.#name];
|
|
269
565
|
}
|
|
270
566
|
|
|
567
|
+
#deferAbsenceCheck() {
|
|
568
|
+
if (this.#armAbsence) return;
|
|
569
|
+
this.#armAbsence = setImmediate(() => {
|
|
570
|
+
this.#armAbsence = undefined;
|
|
571
|
+
if (!this.#closed) this.#read(true);
|
|
572
|
+
});
|
|
573
|
+
this.#armAbsence.unref?.();
|
|
574
|
+
}
|
|
575
|
+
|
|
271
576
|
/**
|
|
272
577
|
* This merge algorithm is best thought off as a diff and overwrite.
|
|
273
578
|
* The new config object will completely overwrite the old config object,
|
|
@@ -362,7 +667,7 @@ export class OptionsWatcher extends EventEmitter<OptionsWatcherEventMap> {
|
|
|
362
667
|
|
|
363
668
|
if (keys.length === 0) {
|
|
364
669
|
this.#scopedConfig = value;
|
|
365
|
-
this
|
|
670
|
+
this.#emitChange(keys, value);
|
|
366
671
|
return;
|
|
367
672
|
}
|
|
368
673
|
|
|
@@ -382,7 +687,19 @@ export class OptionsWatcher extends EventEmitter<OptionsWatcherEventMap> {
|
|
|
382
687
|
|
|
383
688
|
obj[keys[keys.length - 1]] = value;
|
|
384
689
|
|
|
385
|
-
this
|
|
690
|
+
this.#emitChange(keys, value);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// `#merge` runs inside chokidar's own dispatch on the unlink and env-fallback paths, where a
|
|
694
|
+
// plugin's `change` handler throwing would leave the worker with an uncaught exception on a
|
|
695
|
+
// config event the watcher had just decided to survive. It still reaches consumers as `error`
|
|
696
|
+
// — a listener fault is not a read fault, which is why it must not reach `#handleReadError`.
|
|
697
|
+
#emitChange(keys: string[], value: ConfigValue) {
|
|
698
|
+
try {
|
|
699
|
+
this.emit('change', keys, value, this.#scopedConfig);
|
|
700
|
+
} catch (error) {
|
|
701
|
+
this.#emitError(error);
|
|
702
|
+
}
|
|
386
703
|
}
|
|
387
704
|
|
|
388
705
|
// Test-only: simulate the underlying chokidar watcher emitting an error.
|
|
@@ -392,25 +709,50 @@ export class OptionsWatcher extends EventEmitter<OptionsWatcherEventMap> {
|
|
|
392
709
|
this.#handleError(error);
|
|
393
710
|
}
|
|
394
711
|
|
|
395
|
-
// Test-only:
|
|
712
|
+
// Test-only: a real unlink cannot be ordered against a read already in flight — see DESIGN.md,
|
|
713
|
+
// "Root config watchers must read synchronously".
|
|
714
|
+
_simulateUnlinkForTests(path: string): void {
|
|
715
|
+
this.#handleUnlink(path);
|
|
716
|
+
}
|
|
717
|
+
|
|
396
718
|
get _usingPollingForTests(): boolean {
|
|
397
719
|
return this.#usingPolling;
|
|
398
720
|
}
|
|
399
721
|
|
|
400
|
-
//
|
|
401
|
-
// Used to assert that a close()-during-fallback race didn't install a
|
|
402
|
-
// replacement watcher.
|
|
722
|
+
// Used to assert that a close()-during-fallback race didn't install a replacement watcher.
|
|
403
723
|
get _openCountForTests(): number {
|
|
404
724
|
return this.#openCount;
|
|
405
725
|
}
|
|
406
726
|
|
|
727
|
+
// Distinguishes a ladder rung from a watcher event.
|
|
728
|
+
get _readCountForTests(): number {
|
|
729
|
+
return this.#readCount;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
get _armedForTests(): boolean {
|
|
733
|
+
return this.#armGate.armed;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// Test-only: read now, rather than at whatever granularity a chokidar event would arrive.
|
|
737
|
+
_refreshForTests(arming: boolean = false): Promise<unknown> {
|
|
738
|
+
this.#read(true, arming);
|
|
739
|
+
return Promise.allSettled([...this.#pendingReads]);
|
|
740
|
+
}
|
|
741
|
+
|
|
407
742
|
/**
|
|
408
743
|
* Closes the underlying file watcher and drains any pending config-file reads.
|
|
409
744
|
* Emits `close` synchronously, removes all listeners, then returns a Promise that
|
|
410
745
|
* resolves once the chokidar watcher has fully stopped and all in-flight reads settle.
|
|
411
746
|
*/
|
|
412
747
|
close(): Promise<this> {
|
|
748
|
+
// Terminal like every other outcome, and `Scope.ready` has no timeout. Before `#closed` and
|
|
749
|
+
// through `#emitReady`, so a listener that throws cannot skip the teardown below it.
|
|
750
|
+
this.#settleUnconfigured();
|
|
413
751
|
this.#closed = true;
|
|
752
|
+
this.#readRetry.cancel();
|
|
753
|
+
this.#armGate.cancel();
|
|
754
|
+
if (this.#armAbsence) clearImmediate(this.#armAbsence);
|
|
755
|
+
this.#armAbsence = undefined;
|
|
414
756
|
const pendingReads = [...this.#pendingReads];
|
|
415
757
|
const watcherClose = Promise.resolve(this.#watcher.close()).catch(() => {});
|
|
416
758
|
|