@harperfast/harper 5.3.0-alpha.1 → 5.3.0-beta.2
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/bin/cliOperations.ts +12 -0
- package/bin/restart.ts +66 -6
- package/components/Application.ts +1134 -112
- package/components/DESIGN.md +421 -0
- package/components/OptionsWatcher.ts +368 -102
- package/components/Scope.ts +20 -1
- package/components/componentLoader.ts +43 -4
- package/components/deploymentOperations.ts +4 -1
- package/components/deploymentRecorder.ts +9 -2
- package/components/mcp/DESIGN.md +109 -0
- package/components/mcp/audit.ts +21 -17
- package/components/operations.js +284 -52
- package/components/operationsValidation.js +49 -2
- package/components/packageComponent.ts +25 -1
- package/components/requestRestart.ts +11 -0
- package/config/DESIGN.md +306 -0
- package/config/RootConfigWatcher.ts +191 -37
- package/config/configReadRetry.ts +62 -0
- package/config/configUtils.ts +78 -26
- package/config/parseConfigFile.ts +34 -0
- package/config/readConfigFileSync.ts +44 -0
- package/config/watcherArming.ts +59 -0
- package/config-root.schema.json +4 -0
- package/dataLayer/DESIGN.md +179 -0
- package/dataLayer/harperBridge/ResourceBridge.ts +28 -2
- package/dataLayer/restoreMarker.ts +92 -25
- package/dist/bin/cliOperations.js +13 -0
- package/dist/bin/cliOperations.js.map +1 -1
- package/dist/bin/restart.js +42 -6
- package/dist/bin/restart.js.map +1 -1
- package/dist/components/Application.d.ts +104 -9
- package/dist/components/Application.js +954 -102
- package/dist/components/Application.js.map +1 -1
- package/dist/components/OptionsWatcher.d.ts +4 -1
- package/dist/components/OptionsWatcher.js +378 -104
- package/dist/components/OptionsWatcher.js.map +1 -1
- package/dist/components/Scope.js +15 -1
- package/dist/components/Scope.js.map +1 -1
- package/dist/components/componentLoader.js +35 -3
- package/dist/components/componentLoader.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/audit.d.ts +2 -1
- package/dist/components/mcp/audit.js +21 -17
- package/dist/components/mcp/audit.js.map +1 -1
- package/dist/components/operations.d.ts +28 -0
- package/dist/components/operations.js +263 -54
- package/dist/components/operations.js.map +1 -1
- package/dist/components/operationsValidation.js +48 -2
- 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/config/RootConfigWatcher.d.ts +2 -0
- package/dist/config/RootConfigWatcher.js +189 -35
- 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 +10 -9
- package/dist/config/configUtils.js +63 -27
- package/dist/config/configUtils.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/harperBridge/ResourceBridge.js +21 -2
- package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
- package/dist/dataLayer/restoreMarker.d.ts +21 -8
- package/dist/dataLayer/restoreMarker.js +94 -27
- package/dist/dataLayer/restoreMarker.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/json/systemSchema.json +3 -0
- package/dist/resources/DatabaseTransaction.d.ts +25 -0
- package/dist/resources/DatabaseTransaction.js +224 -18
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/LMDBTransaction.d.ts +2 -1
- package/dist/resources/LMDBTransaction.js +22 -3
- package/dist/resources/LMDBTransaction.js.map +1 -1
- package/dist/resources/PrimaryRocksDatabase.js +22 -6
- package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
- package/dist/resources/RecordEncoder.d.ts +1 -1
- package/dist/resources/RecordEncoder.js +46 -10
- package/dist/resources/RecordEncoder.js.map +1 -1
- package/dist/resources/Resource.js +97 -13
- package/dist/resources/Resource.js.map +1 -1
- package/dist/resources/ResourceInterface.d.ts +8 -0
- package/dist/resources/RocksIndexStore.js +2 -1
- package/dist/resources/RocksIndexStore.js.map +1 -1
- package/dist/resources/RocksTransactionLogStore.d.ts +10 -0
- package/dist/resources/RocksTransactionLogStore.js +104 -33
- package/dist/resources/RocksTransactionLogStore.js.map +1 -1
- package/dist/resources/Table.d.ts +58 -7
- package/dist/resources/Table.js +1143 -348
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/analytics/write.d.ts +3 -0
- package/dist/resources/analytics/write.js +49 -16
- package/dist/resources/analytics/write.js.map +1 -1
- package/dist/resources/auditStore.d.ts +170 -0
- package/dist/resources/auditStore.js +457 -11
- package/dist/resources/auditStore.js.map +1 -1
- package/dist/resources/crdt.d.ts +10 -0
- package/dist/resources/crdt.js +22 -0
- 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 +16 -13
- package/dist/resources/databases.js +625 -177
- package/dist/resources/databases.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.js +3 -2
- package/dist/resources/graphql.js.map +1 -1
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +102 -9
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +867 -39
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
- package/dist/resources/indexes/fullTextDerivedIndex.d.ts +81 -0
- package/dist/resources/indexes/fullTextDerivedIndex.js +1004 -0
- package/dist/resources/indexes/fullTextDerivedIndex.js.map +1 -0
- package/dist/resources/indexes/fullTextNativeBinding.d.ts +78 -0
- package/dist/resources/indexes/fullTextNativeBinding.js +85 -0
- package/dist/resources/indexes/fullTextNativeBinding.js.map +1 -0
- 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/indexes/nativeFullTextDerivedIndexLifecycle.d.ts +24 -0
- package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js +149 -0
- package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js.map +1 -0
- 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 +47 -4
- package/dist/resources/recordLock.js +138 -7
- package/dist/resources/recordLock.js.map +1 -1
- package/dist/resources/recordLockCoordinator.d.ts +557 -0
- package/dist/resources/recordLockCoordinator.js +2597 -0
- package/dist/resources/recordLockCoordinator.js.map +1 -0
- package/dist/resources/replayLogs.js +5 -0
- package/dist/resources/replayLogs.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 +160 -40
- 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.js +4 -6
- package/dist/resources/transactionBroadcast.js.map +1 -1
- package/dist/security/auth.js +59 -23
- package/dist/security/auth.js.map +1 -1
- package/dist/security/deferredAuthentication.d.ts +11 -0
- package/dist/security/deferredAuthentication.js +25 -3
- package/dist/security/deferredAuthentication.js.map +1 -1
- package/dist/security/jsLoader.js +9 -4
- package/dist/security/jsLoader.js.map +1 -1
- package/dist/server/REST.js +39 -5
- package/dist/server/REST.js.map +1 -1
- package/dist/server/http.d.ts +5 -1
- package/dist/server/http.js +34 -2
- package/dist/server/http.js.map +1 -1
- package/dist/server/mqtt.js +5 -1
- package/dist/server/mqtt.js.map +1 -1
- package/dist/server/serverHelpers/Headers.d.ts +2 -0
- package/dist/server/serverHelpers/Headers.js +6 -0
- 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 +2 -0
- package/dist/server/serverHelpers/contentTypes.js +189 -15
- package/dist/server/serverHelpers/contentTypes.js.map +1 -1
- package/dist/server/serverHelpers/serverUtilities.d.ts +3 -3
- package/dist/server/serverHelpers/serverUtilities.js +110 -20
- package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
- package/dist/server/serverHelpers/uwsServer.js +4 -1
- package/dist/server/serverHelpers/uwsServer.js.map +1 -1
- package/dist/server/serverHelpers/webSocketCloseReason.d.ts +2 -0
- package/dist/server/serverHelpers/webSocketCloseReason.js +29 -0
- package/dist/server/serverHelpers/webSocketCloseReason.js.map +1 -0
- package/dist/server/storageReclamation.js +1 -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/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 +64 -6
- package/dist/server/threads/manageThreads.js +261 -12
- package/dist/server/threads/manageThreads.js.map +1 -1
- package/dist/server/threads/socketRouter.d.ts +1 -0
- package/dist/server/threads/socketRouter.js +196 -13
- package/dist/server/threads/socketRouter.js.map +1 -1
- package/dist/server/threads/threadServer.js +30 -7
- package/dist/server/threads/threadServer.js.map +1 -1
- package/dist/utility/errors/hdbError.d.ts +24 -0
- package/dist/utility/errors/hdbError.js +58 -1
- package/dist/utility/errors/hdbError.js.map +1 -1
- package/dist/utility/hdbTerms.d.ts +2 -0
- package/dist/utility/hdbTerms.js +2 -0
- package/dist/utility/hdbTerms.js.map +1 -1
- package/dist/utility/logging/harper_logger.js +218 -38
- 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 +172 -92
- package/dist/utility/logging/logRotator.js.map +1 -1
- package/dist/utility/npmUtilities.js +6 -4
- package/dist/utility/npmUtilities.js.map +1 -1
- package/dist/utility/watcherFallback.d.ts +0 -45
- package/dist/utility/watcherFallback.js +1 -125
- package/dist/utility/watcherFallback.js.map +1 -1
- package/dist/validation/configValidator.js +6 -3
- package/dist/validation/configValidator.js.map +1 -1
- package/index.ts +9 -0
- package/json/systemSchema.json +3 -0
- package/npm-shrinkwrap.json +177 -87
- package/package.json +13 -5
- package/resources/DESIGN.md +689 -19
- package/resources/DatabaseTransaction.ts +230 -17
- package/resources/LMDBTransaction.ts +21 -3
- package/resources/PrimaryRocksDatabase.ts +20 -7
- package/resources/RecordEncoder.ts +52 -10
- package/resources/Resource.ts +97 -13
- package/resources/ResourceInterface.ts +8 -0
- package/resources/RocksIndexStore.ts +2 -1
- package/resources/RocksTransactionLogStore.ts +111 -31
- package/resources/Table.ts +1273 -396
- package/resources/analytics/DESIGN.md +38 -0
- package/resources/analytics/write.ts +50 -17
- package/resources/auditStore.ts +460 -11
- package/resources/crdt.ts +22 -0
- package/resources/dataLoader.ts +3 -4
- package/resources/databases.ts +611 -147
- package/resources/derivedIndexRegistry.ts +56 -0
- package/resources/derivedIndexRuntime.ts +2292 -0
- package/resources/graphql.ts +3 -2
- package/resources/indexes/DESIGN.md +833 -0
- package/resources/indexes/HierarchicalNavigableSmallWorld.ts +924 -46
- package/resources/indexes/fullTextDerivedIndex.ts +1165 -0
- package/resources/indexes/fullTextNativeBinding.ts +146 -0
- package/resources/indexes/hnswDerivedIndex.ts +531 -0
- package/resources/indexes/hnswPlaneBinding.ts +174 -0
- package/resources/indexes/nativeFullTextDerivedIndexLifecycle.ts +181 -0
- package/resources/nodeIdMapping.ts +50 -0
- package/resources/record-locks.md +1407 -0
- package/resources/recordLock.ts +173 -7
- package/resources/recordLockCoordinator.ts +3082 -0
- package/resources/replayLogs.ts +5 -0
- package/resources/replicatedApplyFailure.ts +77 -0
- package/resources/scheduler/DESIGN.md +40 -0
- package/resources/scheduler/scheduler.ts +4 -4
- package/resources/search.ts +169 -49
- package/resources/tracked.ts +73 -22
- package/resources/transactionBroadcast.ts +4 -4
- package/security/DESIGN.md +175 -0
- package/security/auth.ts +53 -24
- package/security/deferredAuthentication.ts +24 -2
- package/security/jsLoader.ts +9 -4
- package/server/DESIGN.md +275 -0
- package/server/REST.ts +42 -6
- package/server/http.ts +34 -2
- package/server/mqtt.ts +6 -4
- package/server/serverHelpers/Headers.ts +5 -1
- package/server/serverHelpers/NodeAdapterResponse.ts +221 -0
- package/server/serverHelpers/Request.ts +33 -131
- package/server/serverHelpers/contentTypes.ts +188 -15
- package/server/serverHelpers/serverUtilities.ts +157 -27
- package/server/serverHelpers/uwsServer.ts +4 -1
- package/server/serverHelpers/webSocketCloseReason.ts +25 -0
- package/server/storageReclamation.ts +2 -2
- package/server/threads/isolatedApplications.ts +157 -0
- package/server/threads/logRotationTransport.ts +40 -0
- package/server/threads/manageThreads.js +254 -12
- package/server/threads/socketRouter.ts +217 -11
- package/server/threads/threadServer.js +30 -7
- package/studio/web/assets/{Chat-BnCBegQz.js → Chat-DADFFGe_.js} +1 -1
- package/studio/web/assets/{FloatingChat-CoDW1ySS.js → FloatingChat-D_mI-rZ7.js} +3 -3
- package/studio/web/assets/{apiToken-Bwk5BLXW.js → apiToken-c2NiSDHa.js} +1 -1
- package/studio/web/assets/{applications-DHxGi7JH.js → applications-DktUqh7G.js} +1 -1
- package/studio/web/assets/{cssMode-s0cWI-_M.js → cssMode-Cs_75Xhw.js} +1 -1
- package/studio/web/assets/{editor-DNcRHK54.js → editor-19b-Y1IN.js} +1 -1
- package/studio/web/assets/{html-Bdssedlg.js → html-DiYEQMpB.js} +1 -1
- package/studio/web/assets/{htmlMode-CoDlJ3fw.js → htmlMode-CmR0y7P_.js} +1 -1
- package/studio/web/assets/{index-D6sxmFLR.js → index-Dm0rfkJ7.js} +5 -5
- package/studio/web/assets/{index.lazy-tmU5BS8s.js → index.lazy-7vqt2CC3.js} +1 -1
- package/studio/web/assets/{javascript-B8meVSTH.js → javascript-BWtCFuOt.js} +1 -1
- package/studio/web/assets/{jsonMode-DpIPd35T.js → jsonMode-Buzzbv9y.js} +1 -1
- package/studio/web/assets/{languageServices-C_5FMJzQ.js → languageServices-SqsFWfTM.js} +1 -1
- package/studio/web/assets/{lspLanguageFeatures-BIzNBzPK.js → lspLanguageFeatures-EMV5cmjo.js} +1 -1
- package/studio/web/assets/{notifications-CvZivSbh.js → notifications-CAB-LZWT.js} +1 -1
- package/studio/web/assets/{notifications-CQf18QKb.js → notifications-DRzmSRxM.js} +1 -1
- package/studio/web/assets/{profile-DdOwtntb.js → profile-BNKAl79n.js} +1 -1
- package/studio/web/assets/{regions-n69fwagr.js → regions-CUow_Zw2.js} +1 -1
- package/studio/web/assets/{register-PfWTCXWB.js → register-Dkt3WUMp.js} +2 -2
- package/studio/web/assets/{setComponentFile-Bg6O7X0S.js → setComponentFile-BZRfMD0N.js} +1 -1
- package/studio/web/assets/{setup-CUx_aUDl.js → setup-D_yiEPO2.js} +2 -2
- package/studio/web/assets/{status-D7BVKqX9.js → status-DhHh1Ge-.js} +1 -1
- package/studio/web/assets/{toggleHighContrast-DBSyXzMr.js → toggleHighContrast-D7L1PDtV.js} +1 -1
- package/studio/web/assets/{tsMode-BByKCjBS.js → tsMode-CCwLk1YS.js} +1 -1
- package/studio/web/assets/{typescript-DDLnLpw9.js → typescript-BP1j1mjn.js} +1 -1
- package/studio/web/assets/{useEntityRestURL-31CHGaHk.js → useEntityRestURL-D7bnYxLw.js} +1 -1
- package/studio/web/assets/{workers-pR3jRY9D.js → workers-tOuCNT17.js} +1 -1
- package/studio/web/assets/{xml-2iRnMhQO.js → xml-BSG_3mQT.js} +1 -1
- package/studio/web/assets/{yaml-Bf92gJpd.js → yaml-DxiLprBB.js} +1 -1
- package/studio/web/index.html +1 -1
- package/utility/DESIGN.md +55 -0
- package/utility/errors/hdbError.ts +54 -0
- package/utility/hdbTerms.ts +2 -0
- package/utility/logging/harper_logger.ts +210 -30
- package/utility/logging/logGenerationCoordinator.ts +196 -0
- package/utility/logging/logRotation.ts +367 -0
- package/utility/logging/logRotator.ts +196 -91
- package/utility/npmUtilities.ts +6 -4
- package/utility/watcherFallback.ts +0 -122
- package/validation/configValidator.ts +6 -3
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { STATUS_CODES } from 'node:http';
|
|
2
|
+
import type {
|
|
3
|
+
IncomingMessage as NodeIncomingMessage,
|
|
4
|
+
OutgoingHttpHeader,
|
|
5
|
+
OutgoingHttpHeaders,
|
|
6
|
+
ServerResponse as NodeServerResponse,
|
|
7
|
+
} from 'node:http';
|
|
8
|
+
import type { Socket } from 'node:net';
|
|
9
|
+
import { PassThrough } from 'node:stream';
|
|
10
|
+
import { Headers as ResponseHeaders, applyWriteHeadHeaders } from './Headers.ts';
|
|
11
|
+
|
|
12
|
+
export interface AdaptedResponse {
|
|
13
|
+
status: number;
|
|
14
|
+
headers: ResponseHeaders;
|
|
15
|
+
body: PassThrough;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
class HeadersSentError extends Error {
|
|
19
|
+
code = 'ERR_HTTP_HEADERS_SENT';
|
|
20
|
+
constructor(action: string) {
|
|
21
|
+
super(`Cannot ${action} headers after they are sent to the client`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
class UnsupportedResponseMethodError extends Error {
|
|
26
|
+
constructor(method: string) {
|
|
27
|
+
super(`${method}() is not supported on a withNodeAdapter() response`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const ignoreError = () => {};
|
|
32
|
+
|
|
33
|
+
export class NodeAdapterResponse extends PassThrough implements NodeServerResponse {
|
|
34
|
+
statusCode = 200;
|
|
35
|
+
statusMessage = '';
|
|
36
|
+
strictContentLength = false;
|
|
37
|
+
chunkedEncoding = false;
|
|
38
|
+
shouldKeepAlive = true;
|
|
39
|
+
useChunkedEncodingByDefault = true;
|
|
40
|
+
sendDate = true;
|
|
41
|
+
readonly req: NodeIncomingMessage;
|
|
42
|
+
readonly socket: Socket | null;
|
|
43
|
+
#headers = new ResponseHeaders();
|
|
44
|
+
#committedStatus: number | undefined;
|
|
45
|
+
#headerText: string | undefined;
|
|
46
|
+
#nodeResponse: NodeServerResponse | undefined;
|
|
47
|
+
#resolve: (response: AdaptedResponse) => void;
|
|
48
|
+
#reject: (reason: unknown) => void;
|
|
49
|
+
|
|
50
|
+
constructor(
|
|
51
|
+
req: NodeIncomingMessage,
|
|
52
|
+
nodeResponse: NodeServerResponse | undefined,
|
|
53
|
+
resolve: (response: AdaptedResponse) => void,
|
|
54
|
+
reject: (reason: unknown) => void
|
|
55
|
+
) {
|
|
56
|
+
super();
|
|
57
|
+
this.req = req;
|
|
58
|
+
this.socket = req.socket ?? null;
|
|
59
|
+
this.#nodeResponse = nodeResponse;
|
|
60
|
+
this.#resolve = resolve;
|
|
61
|
+
this.#reject = reject;
|
|
62
|
+
this.on('error', ignoreError);
|
|
63
|
+
if (typeof nodeResponse?.on === 'function') {
|
|
64
|
+
const forward = (...args: any[]) => this.emit('timeout', ...args);
|
|
65
|
+
let forwarding = false;
|
|
66
|
+
const syncForwarding = (wanted: boolean) => {
|
|
67
|
+
if (wanted === forwarding) return;
|
|
68
|
+
forwarding = wanted;
|
|
69
|
+
if (wanted) nodeResponse.on('timeout', forward);
|
|
70
|
+
else nodeResponse.removeListener('timeout', forward);
|
|
71
|
+
};
|
|
72
|
+
const onNewListener = (event: string | symbol) => event === 'timeout' && syncForwarding(true);
|
|
73
|
+
const onRemoveListener = (event: string | symbol) =>
|
|
74
|
+
event === 'timeout' && syncForwarding(this.listenerCount('timeout') > 0);
|
|
75
|
+
this.on('newListener', onNewListener);
|
|
76
|
+
this.on('removeListener', onRemoveListener);
|
|
77
|
+
this.once('close', () => {
|
|
78
|
+
syncForwarding(false);
|
|
79
|
+
this.removeListener('newListener', onNewListener);
|
|
80
|
+
this.removeListener('removeListener', onRemoveListener);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
get headersSent() {
|
|
86
|
+
return this.#committedStatus !== undefined;
|
|
87
|
+
}
|
|
88
|
+
get finished() {
|
|
89
|
+
return this.writableEnded;
|
|
90
|
+
}
|
|
91
|
+
get connection() {
|
|
92
|
+
return this.socket;
|
|
93
|
+
}
|
|
94
|
+
get _header(): string | null {
|
|
95
|
+
if (this.#committedStatus === undefined) return null;
|
|
96
|
+
if (this.#headerText === undefined) {
|
|
97
|
+
let text = `HTTP/1.1 ${this.#committedStatus} ${this.statusMessage}\r\n`;
|
|
98
|
+
for (const [name, value] of this.#headers) {
|
|
99
|
+
if (Array.isArray(value)) for (const entry of value) text += `${name}: ${entry}\r\n`;
|
|
100
|
+
else text += `${name}: ${value}\r\n`;
|
|
101
|
+
}
|
|
102
|
+
this.#headerText = text + '\r\n';
|
|
103
|
+
}
|
|
104
|
+
return this.#headerText;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
setHeader(name: string, value: number | string | readonly string[]) {
|
|
108
|
+
if (this.headersSent) throw new HeadersSentError('set');
|
|
109
|
+
this.#headers.set(name, value);
|
|
110
|
+
return this;
|
|
111
|
+
}
|
|
112
|
+
setHeaders(headers: Headers | Map<string, number | string | readonly string[]> | ResponseHeaders) {
|
|
113
|
+
let cookies: string[] | undefined;
|
|
114
|
+
for (const [name, value] of headers) {
|
|
115
|
+
if (name.toLowerCase() !== 'set-cookie') this.setHeader(name, value);
|
|
116
|
+
else if (Array.isArray(value)) (cookies ??= []).push(...value);
|
|
117
|
+
else (cookies ??= []).push(String(value));
|
|
118
|
+
}
|
|
119
|
+
if (cookies) this.setHeader('set-cookie', cookies);
|
|
120
|
+
return this;
|
|
121
|
+
}
|
|
122
|
+
appendHeader(name: string, value: string | readonly string[]) {
|
|
123
|
+
if (this.headersSent) throw new HeadersSentError('append');
|
|
124
|
+
if (Array.isArray(value)) for (const entry of value) this.#headers.append(name, entry);
|
|
125
|
+
else this.#headers.append(name, value);
|
|
126
|
+
return this;
|
|
127
|
+
}
|
|
128
|
+
getHeader(name: string) {
|
|
129
|
+
return this.#headers.get(name);
|
|
130
|
+
}
|
|
131
|
+
getHeaders() {
|
|
132
|
+
const headers: OutgoingHttpHeaders = Object.create(null);
|
|
133
|
+
for (const [name, value] of this.#headers) headers[name.toLowerCase()] = value;
|
|
134
|
+
return headers;
|
|
135
|
+
}
|
|
136
|
+
getHeaderNames() {
|
|
137
|
+
return [...this.#headers.keys()];
|
|
138
|
+
}
|
|
139
|
+
hasHeader(name: string) {
|
|
140
|
+
return this.#headers.has(name);
|
|
141
|
+
}
|
|
142
|
+
removeHeader(name: string) {
|
|
143
|
+
if (this.headersSent) throw new HeadersSentError('remove');
|
|
144
|
+
this.#headers.delete(name);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
writeHead(statusCode: number, statusMessage?: string, headers?: OutgoingHttpHeaders | OutgoingHttpHeader[]): this;
|
|
148
|
+
writeHead(statusCode: number, headers?: OutgoingHttpHeaders | OutgoingHttpHeader[]): this;
|
|
149
|
+
writeHead(
|
|
150
|
+
statusCode: number,
|
|
151
|
+
statusMessageOrHeaders?: string | OutgoingHttpHeaders | OutgoingHttpHeader[],
|
|
152
|
+
headers?: OutgoingHttpHeaders | OutgoingHttpHeader[]
|
|
153
|
+
) {
|
|
154
|
+
if (this.headersSent) throw new HeadersSentError('write');
|
|
155
|
+
this.statusCode = statusCode;
|
|
156
|
+
if (typeof statusMessageOrHeaders === 'string') this.statusMessage = statusMessageOrHeaders;
|
|
157
|
+
else {
|
|
158
|
+
this.statusMessage ||= STATUS_CODES[statusCode] || 'unknown';
|
|
159
|
+
// writeHead(302, undefined, headers) is the three-argument form with the status message
|
|
160
|
+
// omitted; the hole must not erase the headers the third argument carries.
|
|
161
|
+
headers ??= statusMessageOrHeaders;
|
|
162
|
+
}
|
|
163
|
+
if (headers) applyWriteHeadHeaders(this, headers);
|
|
164
|
+
this.#committedStatus = statusCode;
|
|
165
|
+
this.#resolve({ status: statusCode, headers: this.#headers, body: this });
|
|
166
|
+
return this;
|
|
167
|
+
}
|
|
168
|
+
_implicitHeader() {
|
|
169
|
+
this.writeHead(this.statusCode);
|
|
170
|
+
}
|
|
171
|
+
flushHeaders() {
|
|
172
|
+
if (!this.headersSent) this._implicitHeader();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
write(
|
|
176
|
+
chunk: unknown,
|
|
177
|
+
encoding?: BufferEncoding | ((error?: Error | null) => void),
|
|
178
|
+
callback?: (error?: Error | null) => void
|
|
179
|
+
) {
|
|
180
|
+
if (!this.headersSent) this._implicitHeader();
|
|
181
|
+
return super.write(chunk, encoding as BufferEncoding, callback);
|
|
182
|
+
}
|
|
183
|
+
end(chunk?: unknown, encoding?: BufferEncoding | (() => void), callback?: () => void) {
|
|
184
|
+
if (!this.headersSent) this._implicitHeader();
|
|
185
|
+
return super.end(chunk, encoding as BufferEncoding, callback);
|
|
186
|
+
}
|
|
187
|
+
_destroy(error: Error | null, callback: (error?: Error | null) => void) {
|
|
188
|
+
if (!this.headersSent) this.#reject(error ?? new Error('Response destroyed before headers were sent'));
|
|
189
|
+
callback(error);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
setTimeout(msecs: number, callback?: () => void) {
|
|
193
|
+
if (callback) this.on('timeout', callback);
|
|
194
|
+
const nodeResponse = this.#nodeResponse;
|
|
195
|
+
if (typeof nodeResponse?.setTimeout !== 'function') return this;
|
|
196
|
+
nodeResponse.setTimeout(msecs);
|
|
197
|
+
return this;
|
|
198
|
+
}
|
|
199
|
+
writeContinue(callback?: () => void) {
|
|
200
|
+
if (typeof this.#nodeResponse?.writeContinue === 'function') this.#nodeResponse.writeContinue(callback);
|
|
201
|
+
else callback?.();
|
|
202
|
+
}
|
|
203
|
+
writeProcessing(callback?: () => void) {
|
|
204
|
+
if (typeof this.#nodeResponse?.writeProcessing === 'function') this.#nodeResponse.writeProcessing(callback);
|
|
205
|
+
else callback?.();
|
|
206
|
+
}
|
|
207
|
+
writeEarlyHints(hints: Record<string, string | string[]>, callback?: () => void) {
|
|
208
|
+
if (typeof this.#nodeResponse?.writeEarlyHints === 'function') this.#nodeResponse.writeEarlyHints(hints, callback);
|
|
209
|
+
else callback?.();
|
|
210
|
+
}
|
|
211
|
+
// Trailers need chunked encoding on the wire, which Harper's response layer owns; dropping one silently (a Digest, say) is worse than failing.
|
|
212
|
+
addTrailers(): never {
|
|
213
|
+
throw new UnsupportedResponseMethodError('addTrailers');
|
|
214
|
+
}
|
|
215
|
+
assignSocket(): never {
|
|
216
|
+
throw new UnsupportedResponseMethodError('assignSocket');
|
|
217
|
+
}
|
|
218
|
+
detachSocket(): never {
|
|
219
|
+
throw new UnsupportedResponseMethodError('detachSocket');
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { IncomingMessage as NodeIncomingMessage, ServerResponse as NodeServerResponse } from 'node:http';
|
|
2
2
|
import type { Socket } from 'node:net';
|
|
3
3
|
import { TLSSocket } from 'node:tls';
|
|
4
|
-
import {
|
|
5
|
-
import { Readable, PassThrough } from 'node:stream';
|
|
4
|
+
import { Readable } from 'node:stream';
|
|
6
5
|
import { Headers as ResponseHeaders } from './Headers.ts';
|
|
6
|
+
import { NodeAdapterResponse, type AdaptedResponse } from './NodeAdapterResponse.ts';
|
|
7
7
|
import type { ConnectionInfo } from './proxyProtocol.ts';
|
|
8
|
+
import harperLogger from '../../utility/logging/harper_logger.ts';
|
|
8
9
|
|
|
9
10
|
export const isBun = typeof globalThis.Bun !== 'undefined';
|
|
10
11
|
|
|
@@ -178,10 +179,8 @@ export class Request {
|
|
|
178
179
|
* status, headers, and body, resolving the returned promise as soon as headers are available with a
|
|
179
180
|
* streaming body that can be piped back through the Harper middleware chain.
|
|
180
181
|
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
* after headers are sent, the body stream is destroyed with an error — without a listener, Node.js
|
|
184
|
-
* will throw an uncaught exception.
|
|
182
|
+
* Consume the returned body with `pipeline()`, `finished()` or async iteration so stored stream errors
|
|
183
|
+
* and premature closes are reported.
|
|
185
184
|
*
|
|
186
185
|
* Example:
|
|
187
186
|
* server.http((request, next) =>
|
|
@@ -190,11 +189,13 @@ export class Request {
|
|
|
190
189
|
*/
|
|
191
190
|
withNodeAdapter(
|
|
192
191
|
handler: (request: NodeIncomingMessage, response: NodeServerResponse) => void | Promise<void>
|
|
193
|
-
): Promise<
|
|
194
|
-
|
|
195
|
-
const reqHeaders: Record<string, string | string[]> = Object.create(null);
|
|
192
|
+
): Promise<AdaptedResponse> {
|
|
193
|
+
const reqHeaders: Record<string, string | string[]> = {};
|
|
196
194
|
for (const [key, value] of this.headers) {
|
|
197
|
-
|
|
195
|
+
const lowerKey = key.toLowerCase();
|
|
196
|
+
if (lowerKey === '__proto__')
|
|
197
|
+
Object.defineProperty(reqHeaders, lowerKey, { value, configurable: true, enumerable: true, writable: true });
|
|
198
|
+
else reqHeaders[lowerKey] = value;
|
|
198
199
|
}
|
|
199
200
|
|
|
200
201
|
// Proxy the underlying IncomingMessage so body streaming works, but expose
|
|
@@ -209,131 +210,32 @@ export class Request {
|
|
|
209
210
|
},
|
|
210
211
|
}) as NodeIncomingMessage;
|
|
211
212
|
|
|
212
|
-
let
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
resolveResponse = resolve;
|
|
216
|
-
rejectResponse = reject;
|
|
213
|
+
let nodeRes!: NodeAdapterResponse;
|
|
214
|
+
const response = new Promise<AdaptedResponse>((resolve, reject) => {
|
|
215
|
+
nodeRes = new NodeAdapterResponse(nodeReq, this._nodeResponse, resolve, reject);
|
|
217
216
|
});
|
|
218
217
|
|
|
219
|
-
const
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
218
|
+
const signal = this.signal;
|
|
219
|
+
const onAbort = () => nodeRes.destroy(nodeRes.headersSent ? undefined : signal.reason);
|
|
220
|
+
if (signal.aborted) onAbort();
|
|
221
|
+
else {
|
|
222
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
223
|
+
nodeRes.once('close', () => signal.removeEventListener('abort', onAbort));
|
|
224
|
+
}
|
|
223
225
|
|
|
224
|
-
const
|
|
225
|
-
if (!
|
|
226
|
-
|
|
227
|
-
nodeRes.headersSent = true;
|
|
228
|
-
resolveResponse({ status: nodeRes.statusCode as number, headers: capturedHeaders, body: responseBody });
|
|
229
|
-
}
|
|
226
|
+
const onHandlerFailure = (error: Error) => {
|
|
227
|
+
if (!nodeRes.writableEnded) nodeRes.destroy(error);
|
|
228
|
+
else harperLogger.warn('withNodeAdapter handler failed after ending its response', error);
|
|
230
229
|
};
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
for (const [k, v] of Object.entries(hdrs)) capturedHeaders.set(k, v as string);
|
|
241
|
-
}
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
nodeRes = Object.assign(new EventEmitter(), {
|
|
245
|
-
statusCode: 200 as number,
|
|
246
|
-
statusMessage: '',
|
|
247
|
-
headersSent: false,
|
|
248
|
-
writable: true,
|
|
249
|
-
writableEnded: false,
|
|
250
|
-
writableFinished: false,
|
|
251
|
-
socket: this._nodeRequest.socket,
|
|
252
|
-
|
|
253
|
-
setHeader(name: string, value: string | number | string[]) {
|
|
254
|
-
if (Array.isArray(value)) {
|
|
255
|
-
// Use set() for first value (overwrites any existing entry) then append() for
|
|
256
|
-
// the rest so multiple values — critical for Set-Cookie — are preserved as an
|
|
257
|
-
// array rather than collapsed into a single comma-joined string.
|
|
258
|
-
if (value.length > 0) {
|
|
259
|
-
capturedHeaders.set(name, String(value[0]));
|
|
260
|
-
for (let i = 1; i < value.length; i++) capturedHeaders.append(name, String(value[i]));
|
|
261
|
-
}
|
|
262
|
-
} else {
|
|
263
|
-
capturedHeaders.set(name, String(value));
|
|
264
|
-
}
|
|
265
|
-
return nodeRes;
|
|
266
|
-
},
|
|
267
|
-
getHeader(name: string) {
|
|
268
|
-
return capturedHeaders.get(name);
|
|
269
|
-
},
|
|
270
|
-
getHeaders() {
|
|
271
|
-
return Object.fromEntries(capturedHeaders);
|
|
272
|
-
},
|
|
273
|
-
hasHeader(name: string) {
|
|
274
|
-
return capturedHeaders.has(name);
|
|
275
|
-
},
|
|
276
|
-
removeHeader(name: string) {
|
|
277
|
-
capturedHeaders.delete(name);
|
|
278
|
-
},
|
|
279
|
-
flushHeaders() {
|
|
280
|
-
flushHeaders();
|
|
281
|
-
},
|
|
282
|
-
writeHead(statusCode: number, statusMessageOrHeaders?: string | object, maybeHeaders?: object) {
|
|
283
|
-
if (headersFlushed) return nodeRes;
|
|
284
|
-
nodeRes.statusCode = statusCode;
|
|
285
|
-
const hdrs = typeof statusMessageOrHeaders === 'string' ? maybeHeaders : statusMessageOrHeaders;
|
|
286
|
-
if (hdrs) applyHeaders(hdrs as object | unknown[]);
|
|
287
|
-
flushHeaders();
|
|
288
|
-
return nodeRes;
|
|
289
|
-
},
|
|
290
|
-
write(
|
|
291
|
-
chunk: unknown,
|
|
292
|
-
encoding?: BufferEncoding | ((error?: Error | null) => void),
|
|
293
|
-
callback?: (error?: Error | null) => void
|
|
294
|
-
) {
|
|
295
|
-
flushHeaders();
|
|
296
|
-
if (typeof encoding === 'function') return responseBody.write(chunk as any, encoding);
|
|
297
|
-
return responseBody.write(chunk as any, encoding, callback);
|
|
298
|
-
},
|
|
299
|
-
end(chunk?: unknown, encoding?: BufferEncoding | (() => void), callback?: () => void) {
|
|
300
|
-
flushHeaders();
|
|
301
|
-
nodeRes.writableEnded = true;
|
|
302
|
-
if (typeof chunk === 'function') responseBody.end(chunk as () => void);
|
|
303
|
-
else if (typeof encoding === 'function') responseBody.end(chunk as any, encoding);
|
|
304
|
-
else responseBody.end(chunk as any, encoding, callback);
|
|
305
|
-
return nodeRes;
|
|
306
|
-
},
|
|
307
|
-
destroy(error?: Error) {
|
|
308
|
-
if (!headersFlushed) {
|
|
309
|
-
if (error) rejectResponse(error);
|
|
310
|
-
else rejectResponse(new Error('Response destroyed before headers were sent'));
|
|
311
|
-
// The error has been forwarded to the response promise; suppress the
|
|
312
|
-
// PassThrough 'error' event for this one destroy call so Node doesn't
|
|
313
|
-
// throw due to having no other listeners.
|
|
314
|
-
responseBody.once('error', () => {});
|
|
315
|
-
}
|
|
316
|
-
responseBody.destroy(error);
|
|
317
|
-
return nodeRes;
|
|
318
|
-
},
|
|
319
|
-
}) as unknown as NodeServerResponse;
|
|
320
|
-
|
|
321
|
-
responseBody.on('finish', () => {
|
|
322
|
-
nodeRes.writableFinished = true;
|
|
323
|
-
(nodeRes as unknown as EventEmitter).emit('finish');
|
|
324
|
-
});
|
|
325
|
-
responseBody.on('drain', () => {
|
|
326
|
-
(nodeRes as unknown as EventEmitter).emit('drain');
|
|
327
|
-
});
|
|
328
|
-
responseBody.on('close', () => {
|
|
329
|
-
(nodeRes as unknown as EventEmitter).emit('close');
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
const handlerResult = handler(nodeReq, nodeRes);
|
|
333
|
-
if (handlerResult != null && typeof (handlerResult as unknown as Promise<void>).then === 'function') {
|
|
334
|
-
(handlerResult as unknown as Promise<void>).catch((err: unknown) => {
|
|
335
|
-
if (!headersFlushed) rejectResponse(err);
|
|
336
|
-
});
|
|
230
|
+
let handlerResult: void | Promise<void>;
|
|
231
|
+
try {
|
|
232
|
+
handlerResult = handler(nodeReq, nodeRes);
|
|
233
|
+
} catch (error) {
|
|
234
|
+
onHandlerFailure(error as Error);
|
|
235
|
+
return response;
|
|
236
|
+
}
|
|
237
|
+
if (typeof (handlerResult as Promise<void>)?.then === 'function') {
|
|
238
|
+
Promise.resolve(handlerResult).catch(onHandlerFailure);
|
|
337
239
|
}
|
|
338
240
|
return response;
|
|
339
241
|
}
|
|
@@ -10,6 +10,10 @@ import * as envMgr from '../../utility/environment/environmentManager.ts';
|
|
|
10
10
|
import { CONFIG_PARAMS } from '../../utility/hdbTerms.ts';
|
|
11
11
|
import * as YAML from 'yaml';
|
|
12
12
|
import { logger } from '../../utility/logging/logger.ts';
|
|
13
|
+
import {
|
|
14
|
+
errorToString as harperErrorToString,
|
|
15
|
+
errorForLog as harperErrorForLog,
|
|
16
|
+
} from '../../utility/logging/harper_logger.ts';
|
|
13
17
|
import { Blob } from '../../resources/blob.ts';
|
|
14
18
|
// TODO: Only load this if fastify is loaded
|
|
15
19
|
import fp from 'fastify-plugin';
|
|
@@ -17,6 +21,36 @@ import { parseMultipartRequest } from './multipartParser.ts';
|
|
|
17
21
|
const SERIALIZATION_BIGINT = envMgr.get(CONFIG_PARAMS.SERIALIZATION_BIGINT) !== false;
|
|
18
22
|
const JSONStringify = SERIALIZATION_BIGINT ? stringify : JSON.stringify;
|
|
19
23
|
const JSONParse = SERIALIZATION_BIGINT ? parse : JSON.parse;
|
|
24
|
+
const streamStartup = Symbol('streamStartup');
|
|
25
|
+
const serializedStreamError = Symbol('serializedStreamError');
|
|
26
|
+
|
|
27
|
+
type StreamStartup = {
|
|
28
|
+
result: Promise<{ failed?: true; error?: unknown }>;
|
|
29
|
+
abort: () => void;
|
|
30
|
+
start: () => void;
|
|
31
|
+
cancel?: () => void;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function streamErrorRecord(error: any) {
|
|
35
|
+
let name = 'Error';
|
|
36
|
+
let message;
|
|
37
|
+
try {
|
|
38
|
+
const code = error?.code;
|
|
39
|
+
if (typeof code === 'string' || typeof code === 'number') name = String(code);
|
|
40
|
+
else if (typeof error?.constructor?.name === 'string') name = error.constructor.name;
|
|
41
|
+
else if (typeof error?.name === 'string') name = error.name;
|
|
42
|
+
} catch {}
|
|
43
|
+
try {
|
|
44
|
+
if (typeof error?.message === 'string') message = error.message;
|
|
45
|
+
} catch {}
|
|
46
|
+
message ??= harperErrorToString(error);
|
|
47
|
+
const record: { error: string; message: string; status?: number } = { error: name, message };
|
|
48
|
+
try {
|
|
49
|
+
const status = error?.statusCode ?? error?.status;
|
|
50
|
+
if (typeof status === 'number') record.status = status;
|
|
51
|
+
} catch {}
|
|
52
|
+
return record;
|
|
53
|
+
}
|
|
20
54
|
|
|
21
55
|
const PUBLIC_ENCODE_OPTIONS = {
|
|
22
56
|
useRecords: false,
|
|
@@ -103,9 +137,14 @@ mediaTypes.set('text/yaml', {
|
|
|
103
137
|
});
|
|
104
138
|
|
|
105
139
|
const ndjsonHandler = {
|
|
106
|
-
serializeStream(data: any) {
|
|
140
|
+
serializeStream(data: any, _response?: Response, request?: any) {
|
|
107
141
|
if (data?.[Symbol.iterator] || data?.[Symbol.asyncIterator]) {
|
|
108
|
-
return
|
|
142
|
+
return errorFrameStream(
|
|
143
|
+
data,
|
|
144
|
+
(msg: any) => JSONStringify(msg) + '\n',
|
|
145
|
+
(error) => JSONStringify({ $harperStreamError: streamErrorRecord(error) }) + '\n',
|
|
146
|
+
request?.method === 'GET'
|
|
147
|
+
);
|
|
109
148
|
}
|
|
110
149
|
return JSONStringify(data) + '\n';
|
|
111
150
|
},
|
|
@@ -132,9 +171,14 @@ function serializeSSEData(data: any) {
|
|
|
132
171
|
|
|
133
172
|
mediaTypes.set('text/event-stream', {
|
|
134
173
|
// Server-Sent Events (SSE)
|
|
135
|
-
serializeStream: function (iterable) {
|
|
174
|
+
serializeStream: function (iterable, _response?: Response, request?: any) {
|
|
136
175
|
// create a readable stream that we use to stream out events from our subscription
|
|
137
|
-
return
|
|
176
|
+
return errorFrameStream(
|
|
177
|
+
iterable,
|
|
178
|
+
this.serialize,
|
|
179
|
+
(error) => this.serialize({ event: 'harper-error', data: streamErrorRecord(error) }),
|
|
180
|
+
request?.method === 'GET'
|
|
181
|
+
);
|
|
138
182
|
},
|
|
139
183
|
serialize: function (message) {
|
|
140
184
|
if (message.acknowledge) message.acknowledge();
|
|
@@ -396,7 +440,12 @@ export function serialize(responseData, request, responseObject) {
|
|
|
396
440
|
(responseData[Symbol.iterator] || responseData[Symbol.asyncIterator]) &&
|
|
397
441
|
serializer.serializer.serializeStream
|
|
398
442
|
) {
|
|
399
|
-
if (
|
|
443
|
+
if (
|
|
444
|
+
responseData.mapError &&
|
|
445
|
+
serializer.type !== 'application/x-ndjson' &&
|
|
446
|
+
serializer.type !== 'application/ndjson' &&
|
|
447
|
+
serializer.type !== 'text/event-stream'
|
|
448
|
+
) {
|
|
400
449
|
// indicate that we want iterator errors to be returned so we can serialize them in a meaningful way, if possible
|
|
401
450
|
const getColumns = responseData.getColumns;
|
|
402
451
|
responseData = responseData.mapError((error) => {
|
|
@@ -407,9 +456,10 @@ export function serialize(responseData, request, responseObject) {
|
|
|
407
456
|
});
|
|
408
457
|
responseData.getColumns = getColumns;
|
|
409
458
|
}
|
|
410
|
-
let stream = serializer.serializer.serializeStream(responseData, responseObject);
|
|
459
|
+
let stream = serializer.serializer.serializeStream(responseData, responseObject, request);
|
|
411
460
|
if (canCompress) {
|
|
412
461
|
responseObject.headers.set('Content-Encoding', 'br');
|
|
462
|
+
const uncompressedStream = stream;
|
|
413
463
|
stream = stream.pipe(
|
|
414
464
|
createBrotliCompress({
|
|
415
465
|
params: {
|
|
@@ -421,6 +471,7 @@ export function serialize(responseData, request, responseObject) {
|
|
|
421
471
|
},
|
|
422
472
|
})
|
|
423
473
|
);
|
|
474
|
+
if (uncompressedStream[streamStartup]) stream[streamStartup] = uncompressedStream[streamStartup];
|
|
424
475
|
}
|
|
425
476
|
return stream;
|
|
426
477
|
}
|
|
@@ -623,24 +674,146 @@ function deserializerUnknownType(contentType: ContentType): Deserialize {
|
|
|
623
674
|
}
|
|
624
675
|
}
|
|
625
676
|
|
|
626
|
-
function
|
|
677
|
+
function errorFrameStream(iterable, transform, serializeError, eager: boolean) {
|
|
678
|
+
const transformed = transformIterable(iterable, transform, serializeError, eager);
|
|
679
|
+
const readable = Readable.from(transformed);
|
|
680
|
+
const startup: StreamStartup = transformed[streamStartup];
|
|
681
|
+
startup.cancel = () => readable.destroy();
|
|
682
|
+
readable[streamStartup] = startup;
|
|
683
|
+
return readable;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
export function discardSerializedStream(stream) {
|
|
687
|
+
stream?.[streamStartup]?.cancel?.();
|
|
688
|
+
stream?.destroy?.();
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
export function waitForStreamStartup(stream) {
|
|
692
|
+
const startup: StreamStartup | undefined = stream?.[streamStartup];
|
|
693
|
+
if (!startup) return;
|
|
694
|
+
startup.start();
|
|
695
|
+
return startup.result.then((result) => {
|
|
696
|
+
if (result.failed) {
|
|
697
|
+
startup.abort();
|
|
698
|
+
discardSerializedStream(stream);
|
|
699
|
+
throw result.error;
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function transformIterable(iterable, transform, serializeError, eager) {
|
|
705
|
+
let iterator;
|
|
706
|
+
let terminal = false;
|
|
707
|
+
let first = true;
|
|
708
|
+
let started = false;
|
|
709
|
+
let firstStep;
|
|
710
|
+
let startupSettled = false;
|
|
711
|
+
const startupResult = Promise.withResolvers<{ failed?: true; error?: unknown }>();
|
|
712
|
+
const errorDecision = Promise.withResolvers<boolean>();
|
|
713
|
+
let startupTimer;
|
|
714
|
+
|
|
715
|
+
const commit = () => {
|
|
716
|
+
if (startupTimer) clearImmediate(startupTimer);
|
|
717
|
+
if (!startupSettled) {
|
|
718
|
+
startupSettled = true;
|
|
719
|
+
startupResult.resolve({});
|
|
720
|
+
}
|
|
721
|
+
errorDecision.resolve(true);
|
|
722
|
+
};
|
|
723
|
+
const abort = () => {
|
|
724
|
+
if (startupTimer) clearImmediate(startupTimer);
|
|
725
|
+
errorDecision.resolve(false);
|
|
726
|
+
};
|
|
727
|
+
const handleError = async (error) => {
|
|
728
|
+
terminal = true;
|
|
729
|
+
try {
|
|
730
|
+
const returned = iterator.return?.();
|
|
731
|
+
returned?.catch?.(() => {});
|
|
732
|
+
} catch {}
|
|
733
|
+
if (!startupSettled) startupResult.resolve({ failed: true, error });
|
|
734
|
+
if (!startupSettled && !(await errorDecision.promise)) return { done: true };
|
|
735
|
+
logger.warn?.('Error serializing in stream', harperErrorForLog(error));
|
|
736
|
+
return { value: serializeError(error), done: false, [serializedStreamError]: true };
|
|
737
|
+
};
|
|
738
|
+
const transformStep = (step) => {
|
|
739
|
+
if (step?.[serializedStreamError]) return step;
|
|
740
|
+
if (terminal) return { done: true };
|
|
741
|
+
if (step.done) return step;
|
|
742
|
+
try {
|
|
743
|
+
return { value: transform(step.value), done: false };
|
|
744
|
+
} catch (error) {
|
|
745
|
+
return handleError(error);
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
const getRawNext = () => {
|
|
749
|
+
if (terminal) return { done: true };
|
|
750
|
+
let step;
|
|
751
|
+
try {
|
|
752
|
+
step = iterator.next();
|
|
753
|
+
} catch (error) {
|
|
754
|
+
return handleError(error);
|
|
755
|
+
}
|
|
756
|
+
if (step.then) return step.then(undefined, handleError);
|
|
757
|
+
return step;
|
|
758
|
+
};
|
|
759
|
+
const getNext = () => {
|
|
760
|
+
if (terminal) return { done: true };
|
|
761
|
+
let step;
|
|
762
|
+
try {
|
|
763
|
+
step = iterator.next();
|
|
764
|
+
} catch (error) {
|
|
765
|
+
return handleError(error);
|
|
766
|
+
}
|
|
767
|
+
if (step.then) return step.then(transformStep, handleError);
|
|
768
|
+
return transformStep(step);
|
|
769
|
+
};
|
|
770
|
+
const start = () => {
|
|
771
|
+
if (started) return;
|
|
772
|
+
started = true;
|
|
773
|
+
iterator = iterable[Symbol.asyncIterator] ? iterable[Symbol.asyncIterator]() : iterable[Symbol.iterator]();
|
|
774
|
+
firstStep = getRawNext();
|
|
775
|
+
Promise.resolve(firstStep).then(
|
|
776
|
+
() => {
|
|
777
|
+
if (!terminal) commit();
|
|
778
|
+
},
|
|
779
|
+
() => {}
|
|
780
|
+
);
|
|
781
|
+
// After this turn, the HTTP status can no longer wait for a first item without delaying long-lived streams.
|
|
782
|
+
startupTimer = setImmediate(commit);
|
|
783
|
+
};
|
|
784
|
+
if (eager) start();
|
|
785
|
+
|
|
627
786
|
return {
|
|
787
|
+
[streamStartup]: {
|
|
788
|
+
result: startupResult.promise,
|
|
789
|
+
abort,
|
|
790
|
+
start,
|
|
791
|
+
},
|
|
628
792
|
[Symbol.asyncIterator]() {
|
|
629
|
-
const iterator = iterable[Symbol.asyncIterator] ? iterable[Symbol.asyncIterator]() : iterable[Symbol.iterator]();
|
|
630
793
|
return {
|
|
631
794
|
next() {
|
|
632
|
-
|
|
633
|
-
if (
|
|
634
|
-
|
|
635
|
-
|
|
795
|
+
if (terminal) return { done: true };
|
|
796
|
+
if (first) {
|
|
797
|
+
first = false;
|
|
798
|
+
start();
|
|
799
|
+
const step = firstStep;
|
|
800
|
+
firstStep = undefined;
|
|
801
|
+
if (step.then) return step.then(transformStep);
|
|
802
|
+
return transformStep(step);
|
|
636
803
|
}
|
|
637
|
-
return
|
|
804
|
+
return getNext();
|
|
638
805
|
},
|
|
639
806
|
return(value) {
|
|
640
|
-
|
|
807
|
+
terminal = true;
|
|
808
|
+
started = true;
|
|
809
|
+
if (!iterator)
|
|
810
|
+
iterator = iterable[Symbol.asyncIterator]
|
|
811
|
+
? iterable[Symbol.asyncIterator]()
|
|
812
|
+
: iterable[Symbol.iterator]?.();
|
|
813
|
+
return iterator?.return?.(value) ?? { value, done: true };
|
|
641
814
|
},
|
|
642
815
|
throw(error) {
|
|
643
|
-
return iterator
|
|
816
|
+
return iterator?.throw?.(error) ?? Promise.reject(error);
|
|
644
817
|
},
|
|
645
818
|
};
|
|
646
819
|
},
|