harper 5.1.20 → 5.2.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent/operations.ts +13 -16
- package/components/Application.ts +188 -7
- package/components/ApplicationScope.ts +3 -0
- package/components/Scope.ts +12 -0
- package/components/anthropic/index.ts +9 -2
- package/components/bedrock/index.ts +25 -2
- package/components/componentLoader.ts +56 -7
- package/components/componentSecrets.ts +451 -0
- package/components/deploymentRecorder.ts +7 -1
- package/components/mcp/adapters/fastify.ts +3 -0
- package/components/mcp/adapters/harperHttp.ts +3 -0
- package/components/mcp/audit.ts +21 -6
- package/components/mcp/customResourceRegistry.ts +17 -38
- package/components/mcp/listChanged.ts +37 -20
- package/components/mcp/quota.ts +145 -0
- package/components/mcp/rateLimit.ts +152 -14
- package/components/mcp/resources.ts +2 -4
- package/components/mcp/toolRegistry.ts +73 -4
- package/components/mcp/tools/application.ts +77 -8
- package/components/mcp/tools/operations.ts +106 -36
- package/components/mcp/tools/schemas/operationDescriptions.ts +4 -4
- package/components/mcp/tools/schemas/operations.ts +5 -0
- package/components/mcp/transport.ts +55 -4
- package/components/ollama/index.ts +6 -2
- package/components/openai/index.ts +9 -2
- package/components/operations.js +204 -10
- package/components/operationsValidation.js +173 -0
- package/components/secretOperations.ts +498 -0
- package/config/configUtils.ts +25 -1
- package/config-app.schema.json +81 -0
- package/config-root.schema.json +8 -1
- package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +4 -3
- package/dataLayer/readAuditLog.ts +14 -0
- package/dataLayer/schemaDescribe.ts +1 -0
- package/dist/agent/operations.d.ts +7 -4
- package/dist/agent/operations.js +13 -15
- package/dist/agent/operations.js.map +1 -1
- package/dist/components/Application.d.ts +25 -2
- package/dist/components/Application.js +166 -7
- package/dist/components/Application.js.map +1 -1
- package/dist/components/ApplicationScope.d.ts +2 -0
- package/dist/components/ApplicationScope.js +3 -0
- package/dist/components/ApplicationScope.js.map +1 -1
- package/dist/components/Scope.d.ts +8 -0
- package/dist/components/Scope.js +11 -0
- package/dist/components/Scope.js.map +1 -1
- package/dist/components/anthropic/index.d.ts +4 -1
- package/dist/components/anthropic/index.js +4 -2
- package/dist/components/anthropic/index.js.map +1 -1
- package/dist/components/bedrock/index.d.ts +4 -1
- package/dist/components/bedrock/index.js +22 -2
- package/dist/components/bedrock/index.js.map +1 -1
- package/dist/components/componentLoader.d.ts +2 -0
- package/dist/components/componentLoader.js +52 -10
- package/dist/components/componentLoader.js.map +1 -1
- package/dist/components/componentSecrets.d.ts +63 -0
- package/dist/components/componentSecrets.js +405 -0
- package/dist/components/componentSecrets.js.map +1 -0
- package/dist/components/deploymentRecorder.d.ts +3 -0
- package/dist/components/deploymentRecorder.js +4 -3
- package/dist/components/deploymentRecorder.js.map +1 -1
- package/dist/components/mcp/adapters/fastify.d.ts +2 -0
- package/dist/components/mcp/adapters/fastify.js +1 -0
- package/dist/components/mcp/adapters/fastify.js.map +1 -1
- package/dist/components/mcp/adapters/harperHttp.d.ts +2 -0
- package/dist/components/mcp/adapters/harperHttp.js +1 -0
- package/dist/components/mcp/adapters/harperHttp.js.map +1 -1
- package/dist/components/mcp/audit.d.ts +2 -2
- package/dist/components/mcp/audit.js +20 -5
- package/dist/components/mcp/audit.js.map +1 -1
- package/dist/components/mcp/customResourceRegistry.d.ts +5 -12
- package/dist/components/mcp/customResourceRegistry.js +15 -30
- package/dist/components/mcp/customResourceRegistry.js.map +1 -1
- package/dist/components/mcp/listChanged.d.ts +7 -2
- package/dist/components/mcp/listChanged.js +28 -0
- package/dist/components/mcp/listChanged.js.map +1 -1
- package/dist/components/mcp/quota.d.ts +31 -0
- package/dist/components/mcp/quota.js +155 -0
- package/dist/components/mcp/quota.js.map +1 -0
- package/dist/components/mcp/rateLimit.d.ts +19 -3
- package/dist/components/mcp/rateLimit.js +129 -13
- package/dist/components/mcp/rateLimit.js.map +1 -1
- package/dist/components/mcp/resources.js.map +1 -1
- package/dist/components/mcp/toolRegistry.d.ts +39 -1
- package/dist/components/mcp/toolRegistry.js +60 -5
- package/dist/components/mcp/toolRegistry.js.map +1 -1
- package/dist/components/mcp/tools/application.d.ts +8 -1
- package/dist/components/mcp/tools/application.js +65 -5
- package/dist/components/mcp/tools/application.js.map +1 -1
- package/dist/components/mcp/tools/operations.d.ts +15 -3
- package/dist/components/mcp/tools/operations.js +99 -38
- package/dist/components/mcp/tools/operations.js.map +1 -1
- package/dist/components/mcp/tools/schemas/operationDescriptions.js +4 -4
- package/dist/components/mcp/tools/schemas/operationDescriptions.js.map +1 -1
- package/dist/components/mcp/tools/schemas/operations.js +4 -0
- package/dist/components/mcp/tools/schemas/operations.js.map +1 -1
- package/dist/components/mcp/transport.d.ts +6 -0
- package/dist/components/mcp/transport.js +47 -3
- package/dist/components/mcp/transport.js.map +1 -1
- package/dist/components/ollama/index.d.ts +4 -1
- package/dist/components/ollama/index.js +4 -2
- package/dist/components/ollama/index.js.map +1 -1
- package/dist/components/openai/index.d.ts +4 -1
- package/dist/components/openai/index.js +4 -2
- package/dist/components/openai/index.js.map +1 -1
- package/dist/components/operations.d.ts +39 -0
- package/dist/components/operations.js +194 -2
- package/dist/components/operations.js.map +1 -1
- package/dist/components/operationsValidation.d.ts +38 -0
- package/dist/components/operationsValidation.js +155 -0
- package/dist/components/operationsValidation.js.map +1 -1
- package/dist/components/secretOperations.d.ts +113 -0
- package/dist/components/secretOperations.js +475 -0
- package/dist/components/secretOperations.js.map +1 -0
- package/dist/config/configUtils.d.ts +4 -1
- package/dist/config/configUtils.js +18 -1
- package/dist/config/configUtils.js.map +1 -1
- package/dist/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +4 -3
- package/dist/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js.map +1 -1
- package/dist/dataLayer/readAuditLog.js +11 -0
- package/dist/dataLayer/readAuditLog.js.map +1 -1
- package/dist/dataLayer/schemaDescribe.js +2 -0
- package/dist/dataLayer/schemaDescribe.js.map +1 -1
- package/dist/globals.d.ts +1 -1
- package/dist/globals.js +1 -0
- package/dist/globals.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/json/systemSchema.json +38 -0
- package/dist/resources/DatabaseTransaction.d.ts +31 -3
- package/dist/resources/DatabaseTransaction.js +190 -29
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/LMDBTransaction.d.ts +1 -1
- package/dist/resources/LMDBTransaction.js +43 -5
- package/dist/resources/LMDBTransaction.js.map +1 -1
- package/dist/resources/PrimaryRocksDatabase.d.ts +51 -0
- package/dist/resources/PrimaryRocksDatabase.js +194 -0
- package/dist/resources/PrimaryRocksDatabase.js.map +1 -0
- package/dist/resources/RecordEncoder.d.ts +1 -0
- package/dist/resources/RecordEncoder.js +81 -75
- package/dist/resources/RecordEncoder.js.map +1 -1
- package/dist/resources/RequestTarget.d.ts +19 -0
- package/dist/resources/RequestTarget.js.map +1 -1
- package/dist/resources/Resource.d.ts +0 -1
- package/dist/resources/Resource.js +103 -16
- package/dist/resources/Resource.js.map +1 -1
- package/dist/resources/ResourceInterface.d.ts +6 -2
- package/dist/resources/ResourceInterface.js.map +1 -1
- package/dist/resources/Resources.js.map +1 -1
- package/dist/resources/Table.d.ts +6 -4
- package/dist/resources/Table.js +247 -24
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/analytics/read.js +25 -28
- package/dist/resources/analytics/read.js.map +1 -1
- package/dist/resources/blob.js +165 -22
- package/dist/resources/blob.js.map +1 -1
- package/dist/resources/crdt.d.ts +2 -0
- package/dist/resources/crdt.js +45 -36
- package/dist/resources/crdt.js.map +1 -1
- package/dist/resources/databases.d.ts +1 -0
- package/dist/resources/databases.js +134 -7
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/graphql.js +8 -0
- package/dist/resources/graphql.js.map +1 -1
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +35 -5
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +239 -14
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
- package/dist/resources/jsResource.js +6 -0
- package/dist/resources/jsResource.js.map +1 -1
- package/dist/resources/loadEnv.js +30 -3
- package/dist/resources/loadEnv.js.map +1 -1
- package/dist/resources/login.js +2 -2
- package/dist/resources/login.js.map +1 -1
- package/dist/resources/models/Models.js +14 -3
- package/dist/resources/models/Models.js.map +1 -1
- package/dist/resources/models/embedHook.js +10 -2
- package/dist/resources/models/embedHook.js.map +1 -1
- package/dist/resources/models/openaiStream.d.ts +56 -0
- package/dist/resources/models/openaiStream.js +94 -0
- package/dist/resources/models/openaiStream.js.map +1 -0
- package/dist/resources/replayLogs.js +4 -3
- package/dist/resources/replayLogs.js.map +1 -1
- package/dist/resources/search.d.ts +1 -1
- package/dist/resources/search.js +104 -11
- package/dist/resources/search.js.map +1 -1
- package/dist/resources/secretDecryptor.d.ts +54 -0
- package/dist/resources/secretDecryptor.js +131 -0
- package/dist/resources/secretDecryptor.js.map +1 -0
- package/dist/resources/tracked.js +15 -5
- package/dist/resources/tracked.js.map +1 -1
- package/dist/security/auth.js +71 -8
- package/dist/security/auth.js.map +1 -1
- package/dist/security/jsLoader.js +11 -0
- package/dist/security/jsLoader.js.map +1 -1
- package/dist/security/tokenAuthentication.d.ts +9 -1
- package/dist/security/tokenAuthentication.js +31 -1
- package/dist/security/tokenAuthentication.js.map +1 -1
- package/dist/server/DurableSubscriptionsSession.d.ts +1 -1
- package/dist/server/DurableSubscriptionsSession.js +8 -4
- package/dist/server/DurableSubscriptionsSession.js.map +1 -1
- package/dist/server/REST.js +28 -9
- package/dist/server/REST.js.map +1 -1
- package/dist/server/fastifyRoutes.js +11 -1
- package/dist/server/fastifyRoutes.js.map +1 -1
- package/dist/server/graphqlQuerying.js +4 -3
- package/dist/server/graphqlQuerying.js.map +1 -1
- package/dist/server/http.d.ts +40 -2
- package/dist/server/http.js +560 -77
- package/dist/server/http.js.map +1 -1
- package/dist/server/itc/serverHandlers.js +64 -0
- package/dist/server/itc/serverHandlers.js.map +1 -1
- package/dist/server/liveSubscriptionAuth.d.ts +12 -0
- package/dist/server/liveSubscriptionAuth.js +140 -0
- package/dist/server/liveSubscriptionAuth.js.map +1 -0
- package/dist/server/loadRootComponents.js +2 -1
- package/dist/server/loadRootComponents.js.map +1 -1
- package/dist/server/middlewareChain.d.ts +44 -1
- package/dist/server/middlewareChain.js +86 -10
- package/dist/server/middlewareChain.js.map +1 -1
- package/dist/server/mqtt.js +2 -1
- package/dist/server/mqtt.js.map +1 -1
- package/dist/server/operationsServer.js +1 -1
- package/dist/server/operationsServer.js.map +1 -1
- package/dist/server/serverHelpers/Headers.d.ts +9 -0
- package/dist/server/serverHelpers/Headers.js +22 -0
- package/dist/server/serverHelpers/Headers.js.map +1 -1
- package/dist/server/serverHelpers/JSONStream.js +3 -3
- package/dist/server/serverHelpers/JSONStream.js.map +1 -1
- package/dist/server/serverHelpers/Request.d.ts +52 -1
- package/dist/server/serverHelpers/Request.js +122 -1
- package/dist/server/serverHelpers/Request.js.map +1 -1
- package/dist/server/serverHelpers/contentTypes.js +3 -8
- package/dist/server/serverHelpers/contentTypes.js.map +1 -1
- package/dist/server/serverHelpers/progressEmitter.d.ts +19 -0
- package/dist/server/serverHelpers/progressEmitter.js +52 -4
- package/dist/server/serverHelpers/progressEmitter.js.map +1 -1
- package/dist/server/serverHelpers/registeredOperations.d.ts +40 -0
- package/dist/server/serverHelpers/registeredOperations.js +286 -0
- package/dist/server/serverHelpers/registeredOperations.js.map +1 -0
- package/dist/server/serverHelpers/serverHandlers.js +11 -5
- package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
- package/dist/server/serverHelpers/serverUtilities.d.ts +1 -0
- package/dist/server/serverHelpers/serverUtilities.js +67 -5
- package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
- package/dist/server/serverHelpers/uwsServer.d.ts +80 -0
- package/dist/server/serverHelpers/uwsServer.js +433 -0
- package/dist/server/serverHelpers/uwsServer.js.map +1 -0
- package/dist/server/static.d.ts +0 -15
- package/dist/server/static.js +161 -2
- package/dist/server/static.js.map +1 -1
- package/dist/server/status/index.d.ts +4 -1
- package/dist/server/status/index.js +64 -4
- package/dist/server/status/index.js.map +1 -1
- package/dist/server/threads/manageThreads.d.ts +10 -0
- package/dist/server/threads/manageThreads.js +107 -4
- package/dist/server/threads/manageThreads.js.map +1 -1
- package/dist/server/threads/socketRouter.js +5 -1
- package/dist/server/threads/socketRouter.js.map +1 -1
- package/dist/server/threads/threadServer.js +92 -14
- package/dist/server/threads/threadServer.js.map +1 -1
- package/dist/upgrade/directives/5-2-0.d.ts +7 -0
- package/dist/upgrade/directives/5-2-0.js +108 -0
- package/dist/upgrade/directives/5-2-0.js.map +1 -0
- package/dist/upgrade/directives/directivesController.js +2 -1
- package/dist/upgrade/directives/directivesController.js.map +1 -1
- package/dist/utility/common_utils.js +5 -0
- package/dist/utility/common_utils.js.map +1 -1
- package/dist/utility/envFile.d.ts +41 -0
- package/dist/utility/envFile.js +221 -0
- package/dist/utility/envFile.js.map +1 -0
- package/dist/utility/globalSchema.d.ts +9 -0
- package/dist/utility/hdbTerms.d.ts +25 -0
- package/dist/utility/hdbTerms.js +31 -0
- package/dist/utility/hdbTerms.js.map +1 -1
- package/dist/utility/logging/harper_logger.d.ts +21 -0
- package/dist/utility/logging/harper_logger.js +160 -8
- package/dist/utility/logging/harper_logger.js.map +1 -1
- package/dist/utility/logging/logRotator.js +29 -17
- package/dist/utility/logging/logRotator.js.map +1 -1
- package/dist/utility/logging/readLog.d.ts +1 -1
- package/dist/utility/logging/readLog.js +305 -2
- package/dist/utility/logging/readLog.js.map +1 -1
- package/dist/utility/operationPermissions.d.ts +18 -0
- package/dist/utility/operationPermissions.js +35 -1
- package/dist/utility/operationPermissions.js.map +1 -1
- package/dist/utility/operation_authorization.d.ts +17 -0
- package/dist/utility/operation_authorization.js +38 -0
- package/dist/utility/operation_authorization.js.map +1 -1
- package/dist/utility/secretEnvelope.d.ts +30 -0
- package/dist/utility/secretEnvelope.js +94 -0
- package/dist/utility/secretEnvelope.js.map +1 -0
- package/dist/utility/signalling.d.ts +7 -0
- package/dist/utility/signalling.js +16 -0
- package/dist/utility/signalling.js.map +1 -1
- package/dist/validation/configValidator.js +70 -24
- package/dist/validation/configValidator.js.map +1 -1
- package/index.ts +4 -0
- package/json/systemSchema.json +38 -0
- package/npm-shrinkwrap.json +15066 -9347
- package/package.json +13 -8
- package/resources/DESIGN.md +15 -12
- package/resources/DatabaseTransaction.ts +206 -37
- package/resources/LMDBTransaction.ts +43 -9
- package/resources/PrimaryRocksDatabase.ts +201 -0
- package/resources/RecordEncoder.ts +81 -69
- package/resources/RequestTarget.ts +21 -0
- package/resources/Resource.ts +109 -25
- package/resources/ResourceInterface.ts +6 -2
- package/resources/Resources.ts +1 -3
- package/resources/Table.ts +238 -29
- package/resources/analytics/read.ts +25 -30
- package/resources/blob.ts +159 -23
- package/resources/crdt.ts +37 -31
- package/resources/databases.ts +143 -6
- package/resources/graphql.ts +8 -0
- package/resources/indexes/HierarchicalNavigableSmallWorld.ts +251 -17
- package/resources/jsResource.ts +6 -0
- package/resources/loadEnv.ts +34 -3
- package/resources/login.ts +2 -2
- package/resources/models/Models.ts +19 -3
- package/resources/models/embedHook.ts +13 -2
- package/resources/models/openaiStream.ts +133 -0
- package/resources/replayLogs.ts +4 -3
- package/resources/search.ts +113 -11
- package/resources/secretDecryptor.ts +159 -0
- package/resources/tracked.ts +24 -17
- package/security/auth.ts +70 -10
- package/security/jsLoader.ts +12 -0
- package/security/tokenAuthentication.ts +42 -1
- package/server/DESIGN.md +10 -0
- package/server/DurableSubscriptionsSession.ts +8 -4
- package/server/REST.ts +26 -9
- package/server/fastifyRoutes.ts +10 -1
- package/server/graphqlQuerying.ts +4 -3
- package/server/http.ts +563 -77
- package/server/itc/serverHandlers.js +70 -0
- package/server/liveSubscriptionAuth.ts +152 -0
- package/server/loadRootComponents.js +2 -1
- package/server/middlewareChain.ts +107 -17
- package/server/mqtt.ts +2 -1
- package/server/operationsServer.ts +2 -2
- package/server/serverHelpers/Headers.ts +24 -0
- package/server/serverHelpers/JSONStream.ts +3 -3
- package/server/serverHelpers/Request.ts +127 -0
- package/server/serverHelpers/contentTypes.ts +3 -8
- package/server/serverHelpers/progressEmitter.ts +55 -5
- package/server/serverHelpers/registeredOperations.ts +259 -0
- package/server/serverHelpers/serverHandlers.js +11 -5
- package/server/serverHelpers/serverUtilities.ts +88 -5
- package/server/serverHelpers/uwsServer.ts +488 -0
- package/server/static.ts +180 -2
- package/server/status/index.ts +72 -4
- package/server/threads/manageThreads.js +102 -4
- package/server/threads/socketRouter.ts +5 -1
- package/server/threads/threadServer.js +93 -10
- package/studio/web/assets/{Chat-SdD1EZca.js → Chat-Cv_2paZE.js} +2 -2
- package/studio/web/assets/{Chat-SdD1EZca.js.map → Chat-Cv_2paZE.js.map} +1 -1
- package/studio/web/assets/{FloatingChat-GMq6tHK9.js → FloatingChat-CPyPIcVR.js} +4 -4
- package/studio/web/assets/{FloatingChat-GMq6tHK9.js.map → FloatingChat-CPyPIcVR.js.map} +1 -1
- package/studio/web/assets/{applications-B5hhv7uA.js → applications-C0jT2vdZ.js} +2 -2
- package/studio/web/assets/{applications-B5hhv7uA.js.map → applications-C0jT2vdZ.js.map} +1 -1
- package/studio/web/assets/{index-VFcpNWgq.js → index-D_GKOkhn.js} +6 -6
- package/studio/web/assets/index-D_GKOkhn.js.map +1 -0
- package/studio/web/assets/{index.lazy-DgcJojIA.js → index.lazy-CAmCIA65.js} +4 -4
- package/studio/web/assets/{index.lazy-DgcJojIA.js.map → index.lazy-CAmCIA65.js.map} +1 -1
- package/studio/web/assets/{profile-BqN-8vzm.js → profile-CaF-4aZe.js} +2 -2
- package/studio/web/assets/{profile-BqN-8vzm.js.map → profile-CaF-4aZe.js.map} +1 -1
- package/studio/web/assets/{setComponentFile-CHFvFq9u.js → setComponentFile-DLW1DHCt.js} +2 -2
- package/studio/web/assets/{setComponentFile-CHFvFq9u.js.map → setComponentFile-DLW1DHCt.js.map} +1 -1
- package/studio/web/assets/{setup-CQ3vWJSr.js → setup-TGCErIhq.js} +2 -2
- package/studio/web/assets/{setup-CQ3vWJSr.js.map → setup-TGCErIhq.js.map} +1 -1
- package/studio/web/assets/{status-BsW5fjuh.js → status-DG0Maoao.js} +2 -2
- package/studio/web/assets/{status-BsW5fjuh.js.map → status-DG0Maoao.js.map} +1 -1
- package/studio/web/assets/{swagger-ui-react-DITdeB9-.js → swagger-ui-react-8T4SgQ1m.js} +2 -2
- package/studio/web/assets/{swagger-ui-react-DITdeB9-.js.map → swagger-ui-react-8T4SgQ1m.js.map} +1 -1
- package/studio/web/assets/{tsMode-Dyph-OUs.js → tsMode-DJ6Sl24Q.js} +2 -2
- package/studio/web/assets/{tsMode-Dyph-OUs.js.map → tsMode-DJ6Sl24Q.js.map} +1 -1
- package/studio/web/assets/{useEntityRestURL-D0QTUqex.js → useEntityRestURL-JO2mfWTQ.js} +2 -2
- package/studio/web/assets/{useEntityRestURL-D0QTUqex.js.map → useEntityRestURL-JO2mfWTQ.js.map} +1 -1
- package/studio/web/index.html +1 -1
- package/upgrade/directives/5-2-0.ts +82 -0
- package/upgrade/directives/directivesController.ts +2 -1
- package/utility/common_utils.ts +5 -0
- package/utility/envFile.ts +218 -0
- package/utility/hdbTerms.ts +31 -0
- package/utility/logging/harper_logger.ts +156 -8
- package/utility/logging/logRotator.ts +24 -14
- package/utility/logging/readLog.ts +323 -2
- package/utility/operationPermissions.ts +35 -1
- package/utility/operation_authorization.ts +74 -1
- package/utility/secretEnvelope.ts +113 -0
- package/utility/signalling.ts +15 -0
- package/validation/configValidator.ts +78 -25
- package/studio/web/assets/index-VFcpNWgq.js.map +0 -1
package/resources/blob.ts
CHANGED
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
type FSWatcher,
|
|
35
35
|
} from 'node:fs';
|
|
36
36
|
import type { StatsFs } from 'node:fs';
|
|
37
|
-
import { createDeflate, createInflate,
|
|
37
|
+
import { createDeflate, createInflate, inflate } from 'node:zlib';
|
|
38
38
|
import { Readable, pipeline } from 'node:stream';
|
|
39
39
|
import { ensureDirSync } from 'fs-extra';
|
|
40
40
|
import { get as envGet, getHdbBasePath } from '../utility/environment/environmentManager.ts';
|
|
@@ -294,11 +294,17 @@ class FileBackedBlob extends (Blob as unknown as { new (): Blob }) implements Bl
|
|
|
294
294
|
rawBytes = Buffer.alloc(0);
|
|
295
295
|
fileMissing = true;
|
|
296
296
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
297
|
+
// The file may still be mid-write. Wait for the writer's `:blob` lock to release, then re-read.
|
|
298
|
+
// `incomplete` is the caller's "definitely not done yet" signal: for an uncompressed blob the
|
|
299
|
+
// on-disk body length must reach the (uncompressed) header size. For a compressed blob the body
|
|
300
|
+
// is shorter than the uncompressed size AND the header size is finalized up front when the size
|
|
301
|
+
// is known, so neither is a reliable completeness signal — the writer's lock is. The caller
|
|
302
|
+
// passes `mustVerifyViaLock` for compressed blobs so we probe the lock even when the bytes look
|
|
303
|
+
// plausible, and only treat the file as complete once the writer is confirmed done.
|
|
304
|
+
function waitForCompletion(incomplete: boolean, mustVerifyViaLock = false): Promise<Buffer> | undefined {
|
|
305
|
+
const store = storageInfo.store;
|
|
306
|
+
const lockKey = storageInfo.fileId + ':blob';
|
|
307
|
+
if (incomplete) {
|
|
302
308
|
if (writeFinished) {
|
|
303
309
|
// the writer released its lock but the content is still short: the file is cleanly gone
|
|
304
310
|
// (404) or confidently incomplete/corrupt (500) — not merely mid-write (#1423/#1424).
|
|
@@ -337,24 +343,77 @@ class FileBackedBlob extends (Blob as unknown as { new (): Blob }) implements Bl
|
|
|
337
343
|
resolve(readContents());
|
|
338
344
|
}
|
|
339
345
|
});
|
|
346
|
+
} else if (mustVerifyViaLock && !writeFinished) {
|
|
347
|
+
// Bytes look complete but we can't prove it for a compressed body; probe whether the writer
|
|
348
|
+
// still holds the lock. If we acquire it, the write has finished, but the bytes we read may
|
|
349
|
+
// predate the final flush, so re-read once to get the complete body. Otherwise fall through
|
|
350
|
+
// to wait for the lock to release.
|
|
351
|
+
const acquiredImmediately = store.tryLock(lockKey);
|
|
352
|
+
if (acquiredImmediately) {
|
|
353
|
+
writeFinished = true;
|
|
354
|
+
store.unlock(lockKey);
|
|
355
|
+
return readContents(); // writer done — re-read to ensure we have the final bytes
|
|
356
|
+
}
|
|
357
|
+
} else {
|
|
358
|
+
return undefined; // confirmed complete (uncompressed exact-size match)
|
|
340
359
|
}
|
|
360
|
+
return new Promise((resolve, reject) => {
|
|
361
|
+
let settled = false;
|
|
362
|
+
const timer = setTimeout(() => {
|
|
363
|
+
if (settled) return;
|
|
364
|
+
settled = true;
|
|
365
|
+
reject(
|
|
366
|
+
new BlobReadError(
|
|
367
|
+
`Blob ${storageInfo.fileId} is unavailable; the in-progress write did not complete in time`,
|
|
368
|
+
BLOB_UNAVAILABLE_STATUS
|
|
369
|
+
)
|
|
370
|
+
);
|
|
371
|
+
}, getBlobReadTimeout());
|
|
372
|
+
timer.unref();
|
|
373
|
+
const callback = () => {
|
|
374
|
+
if (settled) return;
|
|
375
|
+
settled = true;
|
|
376
|
+
clearTimeout(timer);
|
|
377
|
+
writeFinished = true;
|
|
378
|
+
resolve(readContents());
|
|
379
|
+
};
|
|
380
|
+
const lockAcquired = store.tryLock(lockKey, callback);
|
|
381
|
+
if (lockAcquired) {
|
|
382
|
+
settled = true;
|
|
383
|
+
clearTimeout(timer);
|
|
384
|
+
writeFinished = true;
|
|
385
|
+
store.unlock(lockKey);
|
|
386
|
+
resolve(readContents());
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
function sliceContent(bytes: Buffer): Buffer {
|
|
341
391
|
if (end != undefined || start != undefined) {
|
|
342
|
-
|
|
392
|
+
bytes = bytes.subarray(start ?? 0, end ?? bytes.length);
|
|
343
393
|
}
|
|
344
|
-
return
|
|
394
|
+
return bytes;
|
|
345
395
|
}
|
|
346
396
|
// Only sniff the storage-type byte and take the decompress branch when a full header is present.
|
|
347
397
|
// A file shorter than the header (truncated/corrupted, #1424) would otherwise be mis-read as a
|
|
348
398
|
// deflate body and yield garbage; instead it falls through to checkCompletion as incomplete.
|
|
349
399
|
if (rawBytes.length >= HEADER_SIZE && rawBytes[1] === DEFLATE_TYPE) {
|
|
400
|
+
// An in-flight compressed file still carrying the UNKNOWN_SIZE placeholder is definitely
|
|
401
|
+
// incomplete; otherwise the size is finalized but the compressed body may still be streaming,
|
|
402
|
+
// so verify via the write lock before inflating (inflating a partial deflate stream errors).
|
|
403
|
+
const pending = waitForCompletion(size === UNKNOWN_SIZE, true);
|
|
404
|
+
if (pending) return pending;
|
|
350
405
|
return new Promise<Buffer>((resolve, reject) => {
|
|
351
|
-
|
|
406
|
+
// start/end index into the UNCOMPRESSED content, so inflate first, then slice
|
|
407
|
+
inflate(rawBytes.subarray(HEADER_SIZE), (error, result) => {
|
|
352
408
|
if (error) reject(error);
|
|
353
|
-
else resolve(
|
|
409
|
+
else resolve(sliceContent(result));
|
|
354
410
|
});
|
|
355
411
|
});
|
|
356
412
|
}
|
|
357
|
-
|
|
413
|
+
const body = rawBytes.subarray(HEADER_SIZE);
|
|
414
|
+
const pending = waitForCompletion(size > body.length);
|
|
415
|
+
if (pending) return pending;
|
|
416
|
+
return sliceContent(body);
|
|
358
417
|
};
|
|
359
418
|
return readContents();
|
|
360
419
|
}
|
|
@@ -388,7 +447,24 @@ class FileBackedBlob extends (Blob as unknown as { new (): Blob }) implements Bl
|
|
|
388
447
|
let totalContentRead = 0;
|
|
389
448
|
let watcher: FSWatcher;
|
|
390
449
|
let timer: NodeJS.Timeout;
|
|
450
|
+
// The start() open-retry timer lives in a different scope/phase than pull()'s `timer`; track it
|
|
451
|
+
// separately so a cancel() during the file-creation wait clears it instead of leaking an fd (#1457).
|
|
452
|
+
let openTimer: NodeJS.Timeout;
|
|
453
|
+
// Stream-wide cancellation gate. cancel() can null `fd` but cannot reach pull()'s per-pull
|
|
454
|
+
// `settled` flag, so this is the cross-phase signal that stops start()/pull() from opening or
|
|
455
|
+
// reading once the consumer has cancelled (e.g. an aborted ranged response) (#1457).
|
|
456
|
+
let cancelled = false;
|
|
391
457
|
let isBeingWritten: boolean;
|
|
458
|
+
// Close the descriptor at most once. Without nulling `fd`, a later close()/cancel() (e.g. a
|
|
459
|
+
// GC-driven cancel after a successful read) would close a descriptor the OS may have reassigned to
|
|
460
|
+
// an unrelated file/socket; and a cancel() before start()'s open() resolves would call
|
|
461
|
+
// close(undefined) and throw. Guarding on `fd != null` fixes both (#1457).
|
|
462
|
+
const closeFd = () => {
|
|
463
|
+
if (fd != null) {
|
|
464
|
+
close(fd);
|
|
465
|
+
fd = null;
|
|
466
|
+
}
|
|
467
|
+
};
|
|
392
468
|
let previouslyFinishedWriting = false;
|
|
393
469
|
const blob = this;
|
|
394
470
|
// Authoritative (uncompressed) content length from the record descriptor, captured before the
|
|
@@ -406,6 +482,7 @@ class FileBackedBlob extends (Blob as unknown as { new (): Blob }) implements Bl
|
|
|
406
482
|
// lookup) is computed lazily on the first miss, so a healthy read never pays for it.
|
|
407
483
|
let deadline = 0;
|
|
408
484
|
const openFile = (resolve: (value: any) => void, reject: (error: Error) => void) => {
|
|
485
|
+
if (cancelled) return resolve(undefined); // consumer cancelled before/while opening; stop retrying
|
|
409
486
|
open(filePath, 'r', (error, openedFd) => {
|
|
410
487
|
if (error) {
|
|
411
488
|
if (error.code === 'ENOENT' && isBeingWritten !== false) {
|
|
@@ -413,10 +490,11 @@ class FileBackedBlob extends (Blob as unknown as { new (): Blob }) implements Bl
|
|
|
413
490
|
if (Date.now() < deadline) {
|
|
414
491
|
logger.debug?.('File does not exist yet, waiting for it to be created', filePath);
|
|
415
492
|
// the file doesn't exist, so we need to wait for it to be created
|
|
416
|
-
|
|
493
|
+
openTimer = setTimeout(() => {
|
|
417
494
|
checkIfIsBeingWritten();
|
|
418
495
|
openFile(resolve, reject);
|
|
419
496
|
}, 20).unref();
|
|
497
|
+
return openTimer;
|
|
420
498
|
}
|
|
421
499
|
}
|
|
422
500
|
// ENOENT: 503 if a local writer holds the lock or a replication receive is in
|
|
@@ -436,6 +514,9 @@ class FileBackedBlob extends (Blob as unknown as { new (): Blob }) implements Bl
|
|
|
436
514
|
blob.#onError?.forEach((callback) => callback(readError));
|
|
437
515
|
} else {
|
|
438
516
|
fd = openedFd;
|
|
517
|
+
// the consumer cancelled while open() was in flight: pull()/cancel() will not run
|
|
518
|
+
// again, so close the descriptor we just acquired rather than leaking it (#1457)
|
|
519
|
+
if (cancelled) closeFd();
|
|
439
520
|
resolve(openedFd);
|
|
440
521
|
}
|
|
441
522
|
});
|
|
@@ -450,9 +531,18 @@ class FileBackedBlob extends (Blob as unknown as { new (): Blob }) implements Bl
|
|
|
450
531
|
// it; bounds the case where the header reports a known size but the bytes never arrive (a
|
|
451
532
|
// present-but-truncated blob whose writer lock stays held) (#1454).
|
|
452
533
|
let incompleteDeadline = 0;
|
|
534
|
+
// Settle this pull exactly once. A slow async read that completes with an error after the
|
|
535
|
+
// watcher-timeout path already called onError() (and closed the fd) would otherwise run
|
|
536
|
+
// onError twice — double close + duplicated #onError callbacks (#1457).
|
|
537
|
+
let settled = false;
|
|
453
538
|
return new Promise(function readMore(resolve: () => void, reject: (error: Error) => void) {
|
|
539
|
+
// A queued timer/watcher callback can re-enter readMore after the stream was cancelled (fd
|
|
540
|
+
// is gone); stop here so we never issue read() on a closed/nulled descriptor (#1457).
|
|
541
|
+
if (cancelled || fd == null) return resolve();
|
|
454
542
|
function onError(error) {
|
|
455
|
-
|
|
543
|
+
if (settled) return;
|
|
544
|
+
settled = true;
|
|
545
|
+
closeFd();
|
|
456
546
|
clearTimeout(timer);
|
|
457
547
|
if (watcher) watcher.close();
|
|
458
548
|
reject(error);
|
|
@@ -461,7 +551,11 @@ class FileBackedBlob extends (Blob as unknown as { new (): Blob }) implements Bl
|
|
|
461
551
|
// allocate a buffer for reading. Note that we could do a stat to get the size, but that is a little more complicated, and might be a little extra overhead
|
|
462
552
|
const buffer = Buffer.allocUnsafe(0x40000);
|
|
463
553
|
read(fd, buffer, 0, buffer.length, position, (error, bytesRead, buffer) => {
|
|
464
|
-
//
|
|
554
|
+
// A late read completion after the pull already settled (via onError or terminal
|
|
555
|
+
// close) or after the stream was cancelled (fd nulled, EBADF/partial read) must not
|
|
556
|
+
// re-enter the state machine — otherwise a recursive read() would hit a null fd and
|
|
557
|
+
// throw synchronously, or onError() would reject an already-cancelled stream (#1457).
|
|
558
|
+
if (settled || cancelled) return resolve();
|
|
465
559
|
totalContentRead += bytesRead;
|
|
466
560
|
if (error) return onError(error);
|
|
467
561
|
if (position === 0) {
|
|
@@ -497,6 +591,36 @@ class FileBackedBlob extends (Blob as unknown as { new (): Blob }) implements Bl
|
|
|
497
591
|
// half-replicated blob whose source stream aborted; the bytes are still expected — retry (harper-pro#481)
|
|
498
592
|
return onError(new BlobReadError('Blob pending replication for ' + filePath, BLOB_UNAVAILABLE_STATUS));
|
|
499
593
|
}
|
|
594
|
+
if (buffer[1] === DEFLATE_TYPE) {
|
|
595
|
+
// We can't seek/slice a deflate stream by uncompressed offset, so hand off to the
|
|
596
|
+
// buffered inflate path (bytes() inflates then slices) and emit it as one chunk.
|
|
597
|
+
// Safe by construction: the read loop never streams the raw compressed body.
|
|
598
|
+
return blob.bytes().then(
|
|
599
|
+
(bytes: Buffer) => {
|
|
600
|
+
// bytes() resolves asynchronously; the consumer may have cancelled meanwhile.
|
|
601
|
+
// Settle exactly once and route the close through closeFd() so we never touch a
|
|
602
|
+
// reassigned/nulled descriptor (#1457).
|
|
603
|
+
if (settled || cancelled) return resolve();
|
|
604
|
+
settled = true;
|
|
605
|
+
closeFd();
|
|
606
|
+
if (bytes.length > 0) {
|
|
607
|
+
try {
|
|
608
|
+
controller.enqueue(bytes);
|
|
609
|
+
} catch (error) {
|
|
610
|
+
logger.debug?.('Error enqueuing chunk', error);
|
|
611
|
+
return resolve();
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
try {
|
|
615
|
+
controller.close();
|
|
616
|
+
} catch {
|
|
617
|
+
// controller may already be closed
|
|
618
|
+
}
|
|
619
|
+
resolve();
|
|
620
|
+
},
|
|
621
|
+
(error: Error) => onError(error)
|
|
622
|
+
);
|
|
623
|
+
}
|
|
500
624
|
size = Number(headerValue & 0xffffffffffffn);
|
|
501
625
|
if (size < UNKNOWN_SIZE) {
|
|
502
626
|
if (isFullRead && descriptorSize != null && descriptorSize < UNKNOWN_SIZE && size !== descriptorSize) {
|
|
@@ -522,6 +646,7 @@ class FileBackedBlob extends (Blob as unknown as { new (): Blob }) implements Bl
|
|
|
522
646
|
} else if (bytesRead === 0) {
|
|
523
647
|
const buffer = Buffer.allocUnsafe(8);
|
|
524
648
|
return read(fd, buffer, 0, HEADER_SIZE, 0, (error) => {
|
|
649
|
+
if (settled || cancelled) return resolve();
|
|
525
650
|
if (error) return onError(error);
|
|
526
651
|
size = Number(new DataView(buffer.buffer, buffer.byteOffset, 8).getBigUint64(0) & 0xffffffffffffn);
|
|
527
652
|
if (size > totalContentRead) {
|
|
@@ -621,7 +746,8 @@ class FileBackedBlob extends (Blob as unknown as { new (): Blob }) implements Bl
|
|
|
621
746
|
}
|
|
622
747
|
return;
|
|
623
748
|
}
|
|
624
|
-
|
|
749
|
+
settled = true;
|
|
750
|
+
closeFd();
|
|
625
751
|
controller.close();
|
|
626
752
|
resolve();
|
|
627
753
|
});
|
|
@@ -629,19 +755,26 @@ class FileBackedBlob extends (Blob as unknown as { new (): Blob }) implements Bl
|
|
|
629
755
|
buffer = buffer.subarray(0, bytesRead);
|
|
630
756
|
}
|
|
631
757
|
if (start !== undefined || end !== undefined) {
|
|
758
|
+
// content offset of the first byte currently in `buffer`. The 8-byte header is only
|
|
759
|
+
// present in — and stripped from — the first read at file position 0; every later read
|
|
760
|
+
// starts HEADER_SIZE into the file relative to its content offset.
|
|
761
|
+
const contentStart = position === 0 ? 0 : position - HEADER_SIZE;
|
|
632
762
|
if (start && totalContentRead < start) {
|
|
633
|
-
// we are before the start of the slice
|
|
634
|
-
|
|
763
|
+
// we are before the start of the slice. Seek straight to it (HEADER_SIZE + start)
|
|
764
|
+
// instead of reading and discarding a 256KB buffer per chunk from byte 0. The header
|
|
765
|
+
// has already been validated and the size detected on the first read (#1457).
|
|
766
|
+
position = HEADER_SIZE + start;
|
|
767
|
+
totalContentRead = start;
|
|
635
768
|
return readMore(resolve, reject);
|
|
636
769
|
}
|
|
637
770
|
if (end && totalContentRead >= end) {
|
|
638
771
|
// we are past or reached the end of the slice, so we have reached the end, indicate
|
|
639
|
-
if (totalContentRead > end) buffer = buffer.subarray(0, end -
|
|
772
|
+
if (totalContentRead > end) buffer = buffer.subarray(0, end - contentStart);
|
|
640
773
|
totalContentRead = size = end;
|
|
641
774
|
}
|
|
642
|
-
if (start && start >
|
|
643
|
-
// we need to skip ahead to the start of the slice
|
|
644
|
-
buffer = buffer.subarray(start -
|
|
775
|
+
if (start && start > contentStart) {
|
|
776
|
+
// we need to skip ahead to the start of the slice within this chunk
|
|
777
|
+
buffer = buffer.subarray(start - contentStart);
|
|
645
778
|
}
|
|
646
779
|
}
|
|
647
780
|
position += bytesRead;
|
|
@@ -654,7 +787,8 @@ class FileBackedBlob extends (Blob as unknown as { new (): Blob }) implements Bl
|
|
|
654
787
|
return resolve();
|
|
655
788
|
}
|
|
656
789
|
if (totalContentRead === size) {
|
|
657
|
-
|
|
790
|
+
settled = true;
|
|
791
|
+
closeFd();
|
|
658
792
|
controller.close();
|
|
659
793
|
}
|
|
660
794
|
resolve();
|
|
@@ -662,8 +796,10 @@ class FileBackedBlob extends (Blob as unknown as { new (): Blob }) implements Bl
|
|
|
662
796
|
});
|
|
663
797
|
},
|
|
664
798
|
cancel() {
|
|
665
|
-
|
|
799
|
+
cancelled = true;
|
|
800
|
+
closeFd();
|
|
666
801
|
clearTimeout(timer);
|
|
802
|
+
clearTimeout(openTimer);
|
|
667
803
|
if (watcher) watcher.close();
|
|
668
804
|
},
|
|
669
805
|
});
|
package/resources/crdt.ts
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
|
+
// Single definition of the numeric `add` fold, shared by the storage-time merge (crdt.add, via
|
|
2
|
+
// updateAndFreeze) and the read/serialize path (tracked.ts Addition.update). One implementation
|
|
3
|
+
// keeps the two from diverging: a numeric string was concatenated on the storage path but coerced
|
|
4
|
+
// on the read path, and a bigint folded on the storage path but threw (`+bigint`) on the read path.
|
|
5
|
+
export function addValues(previousValue: any, value: any) {
|
|
6
|
+
if (typeof previousValue === 'bigint') return previousValue + BigInt(value);
|
|
7
|
+
if (previousValue == null) return value; // no prior value: the add establishes it
|
|
8
|
+
const previous = +previousValue; // coerce so a numeric string adds instead of concatenating
|
|
9
|
+
if (isNaN(previous)) return value; // non-numeric prior: the add establishes it
|
|
10
|
+
// A number-typed field stays a number even if a stray bigint delta arrives, rather than throwing
|
|
11
|
+
// a number+bigint TypeError on the apply path.
|
|
12
|
+
return previous + (typeof value === 'bigint' ? Number(value) : value);
|
|
13
|
+
}
|
|
1
14
|
export function add(record, property, action) {
|
|
2
|
-
|
|
3
|
-
if (typeof previousValue === 'bigint') {
|
|
4
|
-
record[property] = previousValue + BigInt(action.value);
|
|
5
|
-
} else if (isNaN(record[property])) record[property] = action.value;
|
|
6
|
-
else {
|
|
7
|
-
record[property] = previousValue + action.value;
|
|
8
|
-
}
|
|
15
|
+
record[property] = addValues(record[property], action.value);
|
|
9
16
|
}
|
|
10
17
|
add.reverse = function (record, property, action) {
|
|
11
18
|
const previousValue = record[property];
|
|
@@ -15,9 +22,16 @@ add.reverse = function (record, property, action) {
|
|
|
15
22
|
record[property] = previousValue - action.value;
|
|
16
23
|
}
|
|
17
24
|
};
|
|
18
|
-
|
|
25
|
+
// The CRDT operation registry. Exported so the storage/apply path (tracked.ts updateAndFreeze)
|
|
26
|
+
// resolves ops against this explicit set rather than the module's export namespace — otherwise a
|
|
27
|
+
// crafted/corrupt `__op__` naming any exported function (addValues, getRecordAtTime, …) would
|
|
28
|
+
// resolve truthy and be invoked with the wrong arguments (throwing and wedging the apply path, or
|
|
29
|
+
// silently corrupting the field). applyReverse/applyForward below already resolve through this.
|
|
30
|
+
// Null-prototype so a crafted `__op__` naming an Object.prototype member (toString, valueOf,
|
|
31
|
+
// constructor, …) resolves to undefined rather than an inherited function.
|
|
32
|
+
export const operations = Object.assign(Object.create(null), {
|
|
19
33
|
add,
|
|
20
|
-
};
|
|
34
|
+
});
|
|
21
35
|
|
|
22
36
|
/**
|
|
23
37
|
* Rebuild a record update that has a timestamp before the provided newer update
|
|
@@ -179,30 +193,22 @@ export function getRecordAtTime(currentEntry, timestamp, store, tableId: number,
|
|
|
179
193
|
const boundaryEntry = auditStore.get(auditTime, tableId, recordId);
|
|
180
194
|
if (boundaryEntry?.type === 'delete') return null;
|
|
181
195
|
}
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
priorRecord = auditEntry.getValue(store);
|
|
197
|
-
break;
|
|
198
|
-
}
|
|
199
|
-
for (const key in priorRecord) {
|
|
200
|
-
if (unknowns.has(key)) {
|
|
201
|
-
record[key] = priorRecord[key];
|
|
202
|
-
unknowns.delete(key);
|
|
196
|
+
// A reversed patch that set a field to a plain value can't be undone (a plain set has no inverse),
|
|
197
|
+
// so those fields are left "unknown": their value at `timestamp` must come from older history.
|
|
198
|
+
// Reconstruct that state forward from the nearest base so CRDT ops accumulate correctly — e.g. a
|
|
199
|
+
// counter built up by `add` patches and later overwritten by a plain set. The prior code copied
|
|
200
|
+
// the nearest audit entry's raw field, which for a patch is an unresolved `{ __op__ }` object or a
|
|
201
|
+
// single delta rather than the folded value at `timestamp`. If the history needed to reconstruct a
|
|
202
|
+
// key is unavailable (pruned), the key keeps its live value (best effort, as before).
|
|
203
|
+
if (unknowns.size > 0 && auditTime > 0) {
|
|
204
|
+
const priorRecord = reconstructForward(auditStore, store, tableId, recordId, auditTime, timestamp);
|
|
205
|
+
if (priorRecord) {
|
|
206
|
+
for (const key of unknowns) {
|
|
207
|
+
// Object.hasOwn, not `in`: an unknown field named like a prototype member (toString,
|
|
208
|
+
// constructor, …) must not be filled from priorRecord's prototype chain.
|
|
209
|
+
if (Object.hasOwn(priorRecord, key)) record[key] = priorRecord[key];
|
|
203
210
|
}
|
|
204
211
|
}
|
|
205
|
-
auditTime = auditEntry.previousVersion;
|
|
206
212
|
}
|
|
207
213
|
// finally return the record in the state it was at the requested timestamp
|
|
208
214
|
return record;
|