@zlink-systems/framework 0.22.0 → 0.23.0
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/dist/.tsbuildinfo +1 -1
- package/dist/contracts/Dispatch/ZLinkDispatchOptions.d.ts +1 -0
- package/dist/contracts/Streams/IZLinkSession.d.ts +2 -1
- package/dist/runtime/actors/actor-context.js +4 -4
- package/dist/runtime/actors/actor-join-deferred-scope.d.ts +0 -1
- package/dist/runtime/actors/actor-join-deferred-scope.js +0 -18
- package/dist/runtime/actors/deferred-join-accepted-journal.js +2 -6
- package/dist/runtime/backend/contracts/index.d.ts +1 -0
- package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +3 -0
- package/dist/runtime/backend/node/node-raw-mesh-backend.js +21 -8
- package/dist/runtime/channels/channel-dispatch-pipeline.js +2 -2
- package/dist/runtime/channels/channel-transports.js +3 -9
- package/dist/runtime/channels/dispatch-error-reporter.d.ts +1 -0
- package/dist/runtime/channels/dispatch-error-reporter.js +6 -38
- package/dist/runtime/diagnostics/dispatch-error-details.d.ts +6 -0
- package/dist/runtime/diagnostics/dispatch-error-details.js +55 -0
- package/dist/runtime/diagnostics/flow-context.d.ts +2 -1
- package/dist/runtime/diagnostics/flow-context.js +2 -2
- package/dist/runtime/diagnostics/message-flow.js +11 -3
- package/dist/runtime/foundation/actor-join-recovery-codec.js +0 -4
- package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +4 -2
- package/dist/runtime/foundation/raw-service-mesh-runtime.js +34 -9
- package/dist/runtime/foundation/service-session-binding-ingress-port.d.ts +1 -0
- package/dist/runtime/foundation/service-stateful-runtime.d.ts +4 -0
- package/dist/runtime/foundation/service-stateful-runtime.js +46 -2
- package/dist/runtime/foundation/service-topology-registry.d.ts +1 -0
- package/dist/runtime/foundation/service-topology-registry.js +12 -4
- package/dist/runtime/host/index.js +1 -1
- package/dist/runtime/protocol/service_wire_pilot_codec.generated.js +20 -18
- package/dist/runtime/spots/index.js +1 -1
- package/dist/runtime/spots/spot-actor-packet-dispatch.d.ts +2 -1
- package/dist/runtime/spots/spot-actor-packet-dispatch.js +6 -1
- package/dist/runtime/spots/spot-actor-packet-drain.js +2 -0
- package/dist/runtime/spots/spot-node-runtime-manager.js +5 -1
- package/dist/runtime/spots/spot-route-packet-dispatch.js +1 -1
- package/dist/runtime/spots/spot-routed-bound-session-dispatch.js +1 -1
- package/dist/runtime/spots/spot-subscription-dispatch.js +1 -1
- package/dist/runtime/spots/spot-timer.js +1 -1
- package/dist/runtime/streams/actor-session-binding-registry.d.ts +21 -8
- package/dist/runtime/streams/actor-session-binding-registry.js +71 -42
- package/dist/runtime/streams/actor-session-binding-runtime-owner.d.ts +1 -0
- package/dist/runtime/streams/bound-actor-relay-sender.js +2 -1
- package/dist/runtime/streams/bound-session-response-target.js +8 -1
- package/dist/runtime/streams/bound-session-service.d.ts +1 -0
- package/dist/runtime/streams/bound-session-service.js +6 -3
- package/dist/runtime/streams/index.js +8 -1
- package/dist/runtime/streams/managed-stream.d.ts +3 -0
- package/dist/runtime/streams/managed-stream.js +20 -1
- package/dist/runtime/streams/protocol.d.ts +5 -1
- package/dist/runtime/streams/protocol.js +41 -4
- package/dist/runtime/streams/session-actor-coordinator.js +3 -4
- package/dist/runtime/streams/session-context.d.ts +9 -2
- package/dist/runtime/streams/session-context.js +20 -4
- package/dist/runtime/streams/session-local-actors.d.ts +3 -1
- package/dist/runtime/streams/session-local-actors.js +17 -2
- package/dist/runtime/streams/stream-frame-factory.d.ts +1 -1
- package/dist/runtime/streams/stream-frame-factory.js +3 -2
- package/dist/runtime/streams/stream-session-runtime.js +42 -12
- package/package.json +3 -3
- package/src/contracts/Dispatch/ZLinkDispatchOptions.ts +1 -0
- package/src/contracts/Streams/IZLinkSession.ts +2 -1
- package/src/runtime/actors/actor-context.ts +4 -4
- package/src/runtime/actors/actor-join-deferred-scope.ts +0 -32
- package/src/runtime/actors/deferred-join-accepted-journal.ts +2 -6
- package/src/runtime/backend/contracts/index.ts +4 -0
- package/src/runtime/backend/node/node-raw-mesh-backend.ts +39 -11
- package/src/runtime/channels/channel-dispatch-pipeline.ts +4 -2
- package/src/runtime/channels/channel-transports.ts +3 -9
- package/src/runtime/channels/dispatch-error-reporter.ts +7 -45
- package/src/runtime/diagnostics/dispatch-error-details.ts +62 -0
- package/src/runtime/diagnostics/flow-context.ts +6 -4
- package/src/runtime/diagnostics/message-flow.ts +12 -3
- package/src/runtime/foundation/actor-join-recovery-codec.ts +0 -4
- package/src/runtime/foundation/raw-service-mesh-runtime.ts +54 -13
- package/src/runtime/foundation/service-session-binding-ingress-port.ts +1 -0
- package/src/runtime/foundation/service-stateful-runtime.ts +64 -5
- package/src/runtime/foundation/service-topology-registry.ts +18 -5
- package/src/runtime/host/index.ts +1 -1
- package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +18 -17
- package/src/runtime/spots/index.ts +2 -1
- package/src/runtime/spots/spot-actor-packet-dispatch.ts +20 -3
- package/src/runtime/spots/spot-actor-packet-drain.ts +2 -0
- package/src/runtime/spots/spot-node-runtime-manager.ts +5 -1
- package/src/runtime/spots/spot-route-packet-dispatch.ts +2 -1
- package/src/runtime/spots/spot-routed-bound-session-dispatch.ts +2 -1
- package/src/runtime/spots/spot-subscription-dispatch.ts +2 -1
- package/src/runtime/spots/spot-timer.ts +6 -1
- package/src/runtime/streams/actor-session-binding-registry.ts +150 -63
- package/src/runtime/streams/actor-session-binding-runtime-owner.ts +1 -0
- package/src/runtime/streams/bound-actor-relay-sender.ts +10 -2
- package/src/runtime/streams/bound-session-response-target.ts +10 -1
- package/src/runtime/streams/bound-session-service.ts +13 -3
- package/src/runtime/streams/index.ts +8 -1
- package/src/runtime/streams/managed-stream.ts +34 -12
- package/src/runtime/streams/protocol.ts +44 -5
- package/src/runtime/streams/session-actor-coordinator.ts +12 -4
- package/src/runtime/streams/session-context.ts +31 -4
- package/src/runtime/streams/session-local-actors.ts +19 -3
- package/src/runtime/streams/stream-frame-factory.ts +4 -2
- package/src/runtime/streams/stream-session-runtime.ts +58 -13
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ZLinkSessionLocalActorBindings = void 0;
|
|
4
4
|
class ZLinkSessionLocalActorBindings {
|
|
5
5
|
actors = new Map();
|
|
6
|
+
actorIdsBySlot = new Map();
|
|
6
7
|
snapshot() {
|
|
7
8
|
const snapshot = [];
|
|
8
9
|
for (const entry of this.actors.values()) {
|
|
@@ -13,13 +14,27 @@ class ZLinkSessionLocalActorBindings {
|
|
|
13
14
|
find(actorId) {
|
|
14
15
|
return this.actors.get(actorId)?.actor;
|
|
15
16
|
}
|
|
16
|
-
|
|
17
|
-
this.
|
|
17
|
+
findBySlot(actorSlot) {
|
|
18
|
+
const actorId = this.actorIdsBySlot.get(actorSlot);
|
|
19
|
+
return actorId === undefined ? undefined : this.actors.get(actorId)?.actor;
|
|
20
|
+
}
|
|
21
|
+
bind(actor, token, actorSlot) {
|
|
22
|
+
const previous = this.actors.get(actor.actorId);
|
|
23
|
+
if (previous?.actorSlot !== undefined && previous.actorSlot !== actorSlot) {
|
|
24
|
+
this.actorIdsBySlot.delete(previous.actorSlot);
|
|
25
|
+
}
|
|
26
|
+
this.actors.set(actor.actorId, { actor, token, actorSlot });
|
|
27
|
+
if (actorSlot !== undefined) {
|
|
28
|
+
this.actorIdsBySlot.set(actorSlot, actor.actorId);
|
|
29
|
+
}
|
|
18
30
|
}
|
|
19
31
|
unbind(actorId, token) {
|
|
20
32
|
const current = this.actors.get(actorId);
|
|
21
33
|
if (current?.token === token) {
|
|
22
34
|
this.actors.delete(actorId);
|
|
35
|
+
if (current.actorSlot !== undefined) {
|
|
36
|
+
this.actorIdsBySlot.delete(current.actorSlot);
|
|
37
|
+
}
|
|
23
38
|
}
|
|
24
39
|
}
|
|
25
40
|
}
|
|
@@ -27,7 +27,7 @@ export declare class ZLinkStreamFrameMessageFactory {
|
|
|
27
27
|
constructor(options: ZLinkStreamFrameMessageFactoryOptions);
|
|
28
28
|
createTextMessage(payload: string): Message;
|
|
29
29
|
createBinaryMessage(payload: Uint8Array): Message;
|
|
30
|
-
createJsonFrameMessage(kind: ZLinkStreamMessageKind, packetName: string, metadata: ReadonlyMap<string, string>, compressed: boolean, requestSeq: bigint | undefined, payload: unknown, correlationId?: string): Message;
|
|
30
|
+
createJsonFrameMessage(kind: ZLinkStreamMessageKind, packetName: string, metadata: ReadonlyMap<string, string>, compressed: boolean, requestSeq: bigint | undefined, payload: unknown, correlationId?: string, actorSlot?: number): Message;
|
|
31
31
|
createJsonReplyFrameMessage(requestHeader: ZLinkStreamFrameHeader, kind: ZLinkStreamReplyMessageKind, metadata: ReadonlyMap<string, string>, compressed: boolean, payload: unknown): Message;
|
|
32
32
|
private createJsonFrameMessageWithHeader;
|
|
33
33
|
/**
|
|
@@ -26,7 +26,7 @@ class ZLinkStreamFrameMessageFactory {
|
|
|
26
26
|
}
|
|
27
27
|
return factory.createTextMessage((0, protocol_1.utf8Decode)(payload));
|
|
28
28
|
}
|
|
29
|
-
createJsonFrameMessage(kind, packetName, metadata, compressed, requestSeq, payload, correlationId) {
|
|
29
|
+
createJsonFrameMessage(kind, packetName, metadata, compressed, requestSeq, payload, correlationId, actorSlot) {
|
|
30
30
|
const flow = (this.options.flowCreationEnabled?.() ?? true)
|
|
31
31
|
? (0, flow_context_1.currentOrCreateFlow)('Application')
|
|
32
32
|
: undefined;
|
|
@@ -41,7 +41,8 @@ class ZLinkStreamFrameMessageFactory {
|
|
|
41
41
|
metadata,
|
|
42
42
|
correlationId,
|
|
43
43
|
flowId: flow?.flowId,
|
|
44
|
-
flowOrigin: flow?.flowOrigin
|
|
44
|
+
flowOrigin: flow?.flowOrigin,
|
|
45
|
+
actorSlot
|
|
45
46
|
}));
|
|
46
47
|
}
|
|
47
48
|
createJsonReplyFrameMessage(requestHeader, kind, metadata, compressed, payload) {
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ZLinkStreamSessionNodeRuntime = exports.ZLinkStreamSessionRuntime = void 0;
|
|
4
4
|
const framework_errors_internal_1 = require("../framework-errors-internal");
|
|
5
|
-
const contracts_1 = require("../../contracts");
|
|
6
5
|
const internal_event_contracts_1 = require("../diagnostics/internal-event-contracts");
|
|
7
6
|
const ZLinkDispatchOptions_1 = require("../../contracts/Dispatch/ZLinkDispatchOptions");
|
|
8
7
|
const abort_1 = require("../abort");
|
|
9
8
|
const channels_1 = require("../channels");
|
|
9
|
+
const bound_session_response_target_1 = require("./bound-session-response-target");
|
|
10
10
|
const diagnostics_1 = require("../diagnostics");
|
|
11
11
|
const payload_codec_1 = require("../messaging/payload-codec");
|
|
12
12
|
const protocol_1 = require("./protocol");
|
|
@@ -329,6 +329,40 @@ class ZLinkStreamSessionRuntime {
|
|
|
329
329
|
if (this.context.tryCompleteResponse(decodedHeader, dispatchPayload)) {
|
|
330
330
|
return;
|
|
331
331
|
}
|
|
332
|
+
const dispatchActor = this.context.actorForSlot(decodedHeader.actorSlot);
|
|
333
|
+
if (decodedHeader.actorSlot !== undefined && dispatchActor === undefined) {
|
|
334
|
+
const staleFlowEnabled = this.options.dispatchErrors?.flow.flowCreationEnabled() ?? true;
|
|
335
|
+
await (0, flow_context_1.runWithFlow)((0, flow_context_1.createInboundFlow)(decodedHeader.flowId, decodedHeader.flowOrigin, staleFlowEnabled, staleFlowEnabled ? this.stream.sessionId : undefined), async () => {
|
|
336
|
+
if (decodedHeader.kind === protocol_1.ZLinkStreamMessageKind.Request) {
|
|
337
|
+
const error = (0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.InvalidOperation, 'Actor slot is not a current binding.');
|
|
338
|
+
this.options.dispatchErrors?.report({
|
|
339
|
+
surface: ZLinkDispatchOptions_1.ZLinkDispatchErrorSurface.StreamSession,
|
|
340
|
+
messageKind: ZLinkDispatchOptions_1.ZLinkDispatchMessageKind.Request,
|
|
341
|
+
reason: ZLinkDispatchOptions_1.ZLinkRuntimeDispatchErrorReason.StaleTarget,
|
|
342
|
+
action: ZLinkDispatchOptions_1.ZLinkRuntimeDispatchErrorAction.ReplyError,
|
|
343
|
+
packetName: decodedHeader.name,
|
|
344
|
+
sourceRid: this.context.routingId === undefined ? undefined : String(this.context.routingId),
|
|
345
|
+
correlationId: decodedHeader.correlationId,
|
|
346
|
+
flowId: decodedHeader.flowId,
|
|
347
|
+
flowOrigin: decodedHeader.flowOrigin,
|
|
348
|
+
error
|
|
349
|
+
});
|
|
350
|
+
await this.replyDispatchError(decodedHeader, error);
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
(0, diagnostics_1.flowIfEnabled)(this.options.dispatchErrors?.flow, ZLinkDispatchOptions_1.ZLinkRuntimeMessageFlowOutcome.Dropped)?.trace({
|
|
354
|
+
outcome: ZLinkDispatchOptions_1.ZLinkRuntimeMessageFlowOutcome.Dropped,
|
|
355
|
+
surface: ZLinkDispatchOptions_1.ZLinkDispatchErrorSurface.StreamSession,
|
|
356
|
+
messageKind: ZLinkDispatchOptions_1.ZLinkDispatchMessageKind.Send,
|
|
357
|
+
packetName: decodedHeader.name,
|
|
358
|
+
correlationId: decodedHeader.correlationId,
|
|
359
|
+
sourceRid: this.context.routingId === undefined ? undefined : String(this.context.routingId),
|
|
360
|
+
errorReason: ZLinkDispatchOptions_1.ZLinkRuntimeDispatchErrorReason.StaleTarget
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
332
366
|
this.context.enterDispatch(decodedHeader);
|
|
333
367
|
enteredDispatch = true;
|
|
334
368
|
const session = await this.requireSession();
|
|
@@ -337,7 +371,8 @@ class ZLinkStreamSessionRuntime {
|
|
|
337
371
|
: ZLinkDispatchOptions_1.ZLinkDispatchMessageKind.Send;
|
|
338
372
|
const streamCorr = decodedHeader.correlationId;
|
|
339
373
|
const inboundHeader = decodedHeader;
|
|
340
|
-
|
|
374
|
+
const flowEnabled = this.options.dispatchErrors?.flow.flowCreationEnabled() ?? true;
|
|
375
|
+
await (0, flow_context_1.runWithFlow)((0, flow_context_1.createInboundFlow)(inboundHeader.flowId, inboundHeader.flowOrigin, flowEnabled, flowEnabled ? this.stream.sessionId : undefined), async () => {
|
|
341
376
|
(0, diagnostics_1.flowIfEnabled)(this.options.dispatchErrors?.flow, ZLinkDispatchOptions_1.ZLinkRuntimeMessageFlowOutcome.Received)?.trace({
|
|
342
377
|
outcome: ZLinkDispatchOptions_1.ZLinkRuntimeMessageFlowOutcome.Received,
|
|
343
378
|
surface: ZLinkDispatchOptions_1.ZLinkDispatchErrorSurface.StreamSession,
|
|
@@ -363,7 +398,7 @@ class ZLinkStreamSessionRuntime {
|
|
|
363
398
|
correlationId: streamCorr,
|
|
364
399
|
sourceRid: this.context.routingId === undefined ? undefined : String(this.context.routingId)
|
|
365
400
|
});
|
|
366
|
-
await session.onDispatch?.((0, session_context_1.createSessionDispatchContext)(inboundHeader), (0, payload_codec_1.wrapFrameworkPayloadMessage)(dispatchPayload, this.options.messageSerializers, (0, protocol_1.streamCodecContentType)(inboundHeader.codec), inboundHeader.name));
|
|
401
|
+
await session.onDispatch?.((0, session_context_1.createSessionDispatchContext)(inboundHeader, dispatchActor), (0, payload_codec_1.wrapFrameworkPayloadMessage)(dispatchPayload, this.options.messageSerializers, (0, protocol_1.streamCodecContentType)(inboundHeader.codec), inboundHeader.name));
|
|
367
402
|
if (streamKind === ZLinkDispatchOptions_1.ZLinkDispatchMessageKind.Send) {
|
|
368
403
|
(0, diagnostics_1.flowIfEnabled)(this.options.dispatchErrors?.flow, ZLinkDispatchOptions_1.ZLinkRuntimeMessageFlowOutcome.Completed)?.trace({
|
|
369
404
|
outcome: ZLinkDispatchOptions_1.ZLinkRuntimeMessageFlowOutcome.Completed,
|
|
@@ -478,14 +513,7 @@ class ZLinkStreamSessionRuntime {
|
|
|
478
513
|
// record (spec 26 §2.2) and confuse the peer's reply matching.
|
|
479
514
|
return;
|
|
480
515
|
}
|
|
481
|
-
const message = this.context.createJsonReplyFrameMessage(header, protocol_1.ZLinkStreamMessageKind.Error, new Map(), false,
|
|
482
|
-
code: error instanceof contracts_1.ZLinkFrameworkException
|
|
483
|
-
? contracts_1.ZLinkFrameworkErrorKind[error.kind]
|
|
484
|
-
: error instanceof Error
|
|
485
|
-
? error.constructor.name
|
|
486
|
-
: 'RemoteError',
|
|
487
|
-
message: error instanceof Error ? error.message : String(error)
|
|
488
|
-
});
|
|
516
|
+
const message = this.context.createJsonReplyFrameMessage(header, protocol_1.ZLinkStreamMessageKind.Error, new Map(), false, (0, bound_session_response_target_1.boundSessionErrorPayload)(error));
|
|
489
517
|
try {
|
|
490
518
|
if (!this.context.stream.writeRaw(message, ZLINK_SEND_DONT_WAIT)) {
|
|
491
519
|
throw new Error('Client stream error reply send failed.');
|
|
@@ -518,7 +546,8 @@ class ZLinkStreamSessionRuntime {
|
|
|
518
546
|
: ZLinkDispatchOptions_1.ZLinkDispatchMessageKind.Send,
|
|
519
547
|
packetName: packetName.length === 0 ? undefined : packetName,
|
|
520
548
|
correlationId,
|
|
521
|
-
sourceRid: this.context.routingId === undefined ? undefined : String(this.context.routingId)
|
|
549
|
+
sourceRid: this.context.routingId === undefined ? undefined : String(this.context.routingId),
|
|
550
|
+
streamSessionId: this.stream.sessionId
|
|
522
551
|
});
|
|
523
552
|
}
|
|
524
553
|
async complete(error, notifyDisconnected) {
|
|
@@ -627,6 +656,7 @@ class ZLinkStreamSessionRuntime {
|
|
|
627
656
|
packetName: header.name === '' ? undefined : header.name,
|
|
628
657
|
correlationId: header.correlationId,
|
|
629
658
|
sourceRid: this.context.routingId === undefined ? undefined : String(this.context.routingId),
|
|
659
|
+
streamSessionId: this.stream.sessionId,
|
|
630
660
|
flowId: flowEnabled ? header.flowId : undefined,
|
|
631
661
|
flowOrigin: flowEnabled ? header.flowOrigin : undefined,
|
|
632
662
|
errorReason: ZLinkDispatchOptions_1.ZLinkRuntimeDispatchErrorReason.Shutdown
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zlink-systems/framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@opentelemetry/api": "^1.9.1",
|
|
21
21
|
"@opentelemetry/api-logs": "^0.221.0",
|
|
22
|
-
"@zlink-systems/stream-wire": "0.
|
|
23
|
-
"@zlink-systems/zlink": "1.
|
|
22
|
+
"@zlink-systems/stream-wire": "0.23.0",
|
|
23
|
+
"@zlink-systems/zlink": "1.4.0"
|
|
24
24
|
}
|
|
25
25
|
}
|
|
@@ -80,6 +80,7 @@ export interface ZLinkRuntimeMessageFlowEvent {
|
|
|
80
80
|
readonly instanceSpotType?: string;
|
|
81
81
|
readonly activationState?: 'activating' | 'ready' | 'closing';
|
|
82
82
|
readonly actorId?: string;
|
|
83
|
+
readonly streamSessionId?: string;
|
|
83
84
|
/** Internal service-wire command carried by a RouteMesh control record. */
|
|
84
85
|
readonly commandId?: number;
|
|
85
86
|
readonly messageSize?: number;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RoutingId, ZLinkMessage } from '../Common';
|
|
2
|
-
import type { ZLinkSessionActors } from './IZLinkSessionActor';
|
|
2
|
+
import type { ZLinkSessionActor, ZLinkSessionActors } from './IZLinkSessionActor';
|
|
3
3
|
import type { ZLinkStreamError } from './ZLinkStreamError';
|
|
4
4
|
import type { ZLinkSessionHandlerRegistry } from './IZLinkSessionPacketHandler';
|
|
5
5
|
|
|
@@ -16,6 +16,7 @@ export interface ZLinkSessionDispatchContext {
|
|
|
16
16
|
readonly packetName: string;
|
|
17
17
|
readonly metadata: ReadonlyMap<string, string>;
|
|
18
18
|
readonly canReply: boolean;
|
|
19
|
+
readonly actor?: ZLinkSessionActor;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export interface ZLinkSessionFactory<TSession extends ZLinkSession = ZLinkSession> {
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
import { createRandomOperationIdentity } from '../foundation/operation-identity';
|
|
40
40
|
import { deferActorJoin } from './actor-join-deferred-scope';
|
|
41
41
|
import { captureZLinkSpotSerialTurn } from '../execution';
|
|
42
|
+
import { dispatchErrorDetails } from '../diagnostics/dispatch-error-details';
|
|
42
43
|
|
|
43
44
|
export const ZLINK_ACTOR_JOIN_ENTRY_SPOT_RUNTIME = Symbol('zlink.actor.join-entry-spot-runtime');
|
|
44
45
|
|
|
@@ -219,7 +220,6 @@ class DefaultZLinkActorJoinSpotCall implements ZLinkActorJoinSpotCall {
|
|
|
219
220
|
};
|
|
220
221
|
try {
|
|
221
222
|
deferActorJoin({
|
|
222
|
-
requestBytes: requestMessage.data().byteLength,
|
|
223
223
|
prepare: async () => {
|
|
224
224
|
if (prepared) return;
|
|
225
225
|
prepared = true;
|
|
@@ -358,7 +358,6 @@ class DefaultZLinkActorJoinEntrySpotCall implements ZLinkActorJoinEntrySpotCall
|
|
|
358
358
|
};
|
|
359
359
|
try {
|
|
360
360
|
deferActorJoin({
|
|
361
|
-
requestBytes: requestMessage.data().byteLength,
|
|
362
361
|
prepare: async () => {
|
|
363
362
|
if (prepared) return;
|
|
364
363
|
prepared = true;
|
|
@@ -588,6 +587,7 @@ async function notifyJoinFailure(
|
|
|
588
587
|
// The completion carries only an error kind, so trace the cause on the
|
|
589
588
|
// message flow under the same flow id as the Join that produced it.
|
|
590
589
|
const tracePoint = coordinator?.messageFlow?.()?.begin(ZLinkMessageFlowOutcome.Error);
|
|
590
|
+
const errorDetails = tracePoint === undefined ? undefined : dispatchErrorDetails(error);
|
|
591
591
|
tracePoint?.trace({
|
|
592
592
|
outcome: ZLinkMessageFlowOutcome.Error,
|
|
593
593
|
surface: ZLinkDispatchErrorSurface.SpotActor,
|
|
@@ -596,8 +596,8 @@ async function notifyJoinFailure(
|
|
|
596
596
|
actorId: actor.context.actorId,
|
|
597
597
|
errorReason: ZLinkDispatchErrorReason.HandlerException,
|
|
598
598
|
errorAction: ZLinkDispatchErrorAction.ReplyError,
|
|
599
|
-
errorType:
|
|
600
|
-
errorMessage:
|
|
599
|
+
errorType: errorDetails?.errorType,
|
|
600
|
+
errorMessage: errorDetails?.errorMessage
|
|
601
601
|
});
|
|
602
602
|
await actor.onJoinCompleted?.({
|
|
603
603
|
status: 'failed',
|
|
@@ -1,16 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ZLinkFrameworkInternalErrorKind,
|
|
3
|
-
createInternalFrameworkException
|
|
4
|
-
} from '../framework-errors-internal';
|
|
5
1
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
6
2
|
import { ZLinkConfigurationException } from '../configuration';
|
|
7
3
|
|
|
8
|
-
const MAX_DEFERRED_JOINS = 64;
|
|
9
|
-
const MAX_DEFERRED_JOIN_REQUEST_BYTES = 1024 * 1024;
|
|
10
|
-
const MAX_DEFERRED_JOIN_BYTES = 8 * 1024 * 1024;
|
|
11
|
-
|
|
12
4
|
interface DeferredActorJoin {
|
|
13
|
-
readonly requestBytes: number;
|
|
14
5
|
/** Starts target admission after the handler terminal, before reply admission. */
|
|
15
6
|
readonly prepare?: () => Promise<void> | void;
|
|
16
7
|
readonly execute: () => Promise<void>;
|
|
@@ -19,7 +10,6 @@ interface DeferredActorJoin {
|
|
|
19
10
|
|
|
20
11
|
class ActorJoinRegistrationScope {
|
|
21
12
|
private readonly intents: DeferredActorJoin[] = [];
|
|
22
|
-
private totalBytes = 0;
|
|
23
13
|
private open = true;
|
|
24
14
|
private prepared = false;
|
|
25
15
|
|
|
@@ -34,29 +24,7 @@ class ActorJoinRegistrationScope {
|
|
|
34
24
|
'Actor join defer requires an open Framework actor handler scope.'
|
|
35
25
|
);
|
|
36
26
|
}
|
|
37
|
-
if (this.intents.length >= MAX_DEFERRED_JOINS) {
|
|
38
|
-
discardWithoutWaiting(intent);
|
|
39
|
-
throw createInternalFrameworkException(
|
|
40
|
-
ZLinkFrameworkInternalErrorKind.InvalidConfiguration,
|
|
41
|
-
`An actor handler may defer at most ${MAX_DEFERRED_JOINS} joins.`
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
if (intent.requestBytes > MAX_DEFERRED_JOIN_REQUEST_BYTES) {
|
|
45
|
-
discardWithoutWaiting(intent);
|
|
46
|
-
throw createInternalFrameworkException(
|
|
47
|
-
ZLinkFrameworkInternalErrorKind.InvalidConfiguration,
|
|
48
|
-
`A deferred actor join request may use at most ${MAX_DEFERRED_JOIN_REQUEST_BYTES} bytes.`
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
if (this.totalBytes + intent.requestBytes > MAX_DEFERRED_JOIN_BYTES) {
|
|
52
|
-
discardWithoutWaiting(intent);
|
|
53
|
-
throw createInternalFrameworkException(
|
|
54
|
-
ZLinkFrameworkInternalErrorKind.InvalidConfiguration,
|
|
55
|
-
`Deferred actor join requests may use at most ${MAX_DEFERRED_JOIN_BYTES} bytes per handler.`
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
27
|
this.intents.push(intent);
|
|
59
|
-
this.totalBytes += intent.requestBytes;
|
|
60
28
|
}
|
|
61
29
|
|
|
62
30
|
sealAfterHandlerTerminal(): void {
|
|
@@ -44,7 +44,6 @@ import {
|
|
|
44
44
|
const RETENTION_MS = 24 * 60 * 60 * 1_000;
|
|
45
45
|
const ROOT_VERSION = 2;
|
|
46
46
|
const ROOT_MAGIC = Buffer.from('ZLJR');
|
|
47
|
-
const MAX_ROOT_BYTES = 1024 * 1024;
|
|
48
47
|
const JOURNAL_INVENTORY_DOMAIN = Buffer.from('zlink-node-deferred-join-authority-v1\0', 'utf8');
|
|
49
48
|
|
|
50
49
|
export type ZLinkDeferredJoinDeliveryCursor = 'prepared' | 'committed' | 'delivered';
|
|
@@ -695,17 +694,14 @@ function encodeRoot(
|
|
|
695
694
|
'utf8'
|
|
696
695
|
)
|
|
697
696
|
]);
|
|
698
|
-
if (encoded.byteLength > MAX_ROOT_BYTES) {
|
|
699
|
-
throw new Error('Deferred Join completion root exceeds 1 MiB.');
|
|
700
|
-
}
|
|
701
697
|
return encoded;
|
|
702
698
|
}
|
|
703
699
|
|
|
704
700
|
function decodeRoot(
|
|
705
701
|
payload: Uint8Array
|
|
706
702
|
): Omit<ZLinkDeferredJoinAcceptedRoot, 'authority' | 'reference' | 'checksumCrc32c'> {
|
|
707
|
-
if (payload.byteLength === 0
|
|
708
|
-
throw new Error('Deferred Join completion root
|
|
703
|
+
if (payload.byteLength === 0) {
|
|
704
|
+
throw new Error('Deferred Join completion root is empty.');
|
|
709
705
|
}
|
|
710
706
|
const bytes = Buffer.from(payload);
|
|
711
707
|
const encoded = hasJournalRootMagic(bytes) ? bytes.subarray(ROOT_MAGIC.byteLength) : bytes;
|
|
@@ -60,6 +60,10 @@ export type ZLinkBackendObjectPlacement =
|
|
|
60
60
|
| { readonly kind: 'unavailable' };
|
|
61
61
|
|
|
62
62
|
export interface ZLinkBackendMeshNode {
|
|
63
|
+
setDispatchErrorReporter?(
|
|
64
|
+
reporter: import('../../channels/dispatch-error-reporter').ZLinkDispatchErrorReporter,
|
|
65
|
+
meshName: string
|
|
66
|
+
): void;
|
|
63
67
|
setRoutingId(routingId: unknown): void;
|
|
64
68
|
setBind(endpoint: string): void;
|
|
65
69
|
setAdvertiseHost(host: string): void;
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
2
|
import { setImmediate as yieldToIO } from 'node:timers/promises';
|
|
3
|
+
import {
|
|
4
|
+
decodeStreamWireFrame,
|
|
5
|
+
decodeStreamWireHeader,
|
|
6
|
+
encodeStreamWireFrame,
|
|
7
|
+
encodeStreamWireHeader
|
|
8
|
+
} from '@zlink-systems/stream-wire';
|
|
3
9
|
import { disconnectStreamPeer } from './node-socket-backend-adapter';
|
|
4
10
|
import { ZLinkFrameworkException } from '../../../contracts';
|
|
5
11
|
import { internalFrameworkWireReply } from '../../framework-errors-internal';
|
|
@@ -20,6 +26,7 @@ import {
|
|
|
20
26
|
} from '../runtime-values';
|
|
21
27
|
import { ZLinkBufferMessage } from '../runtime-message';
|
|
22
28
|
import type { Message as FrameworkMessage } from '../../../contracts/Common/Message';
|
|
29
|
+
import type { ZLinkDispatchErrorReporter } from '../../channels/dispatch-error-reporter';
|
|
23
30
|
import type {
|
|
24
31
|
MeshOperationId,
|
|
25
32
|
MeshPeerEntry,
|
|
@@ -178,6 +185,7 @@ export class ZLinkNodeRawMeshBackend implements ZLinkBackendMeshNode {
|
|
|
178
185
|
private messageFollowHandler?: (
|
|
179
186
|
record: import('../../foundation/service-stateful-wire-codec').ServiceMessageFollowRecord
|
|
180
187
|
) => void;
|
|
188
|
+
private dispatchErrors?: ZLinkDispatchErrorReporter;
|
|
181
189
|
private readonly peerDisconnectedHandlers = new Set<(endpoint: string) => void>();
|
|
182
190
|
constructor(
|
|
183
191
|
private readonly meshName: string,
|
|
@@ -228,6 +236,11 @@ export class ZLinkNodeRawMeshBackend implements ZLinkBackendMeshNode {
|
|
|
228
236
|
this.stateful?.setMailboxDropHandler(handler);
|
|
229
237
|
}
|
|
230
238
|
|
|
239
|
+
setDispatchErrorReporter(reporter: ZLinkDispatchErrorReporter): void {
|
|
240
|
+
this.dispatchErrors = reporter;
|
|
241
|
+
this.stateful?.setDispatchErrorReporter(reporter, this.meshName);
|
|
242
|
+
}
|
|
243
|
+
|
|
231
244
|
setProtocolErrorHandler(
|
|
232
245
|
handler: (record: {
|
|
233
246
|
readonly sourceNodeRid: string;
|
|
@@ -381,6 +394,9 @@ export class ZLinkNodeRawMeshBackend implements ZLinkBackendMeshNode {
|
|
|
381
394
|
descriptor.nodeRoutingId,
|
|
382
395
|
descriptor.lifecycleGeneration
|
|
383
396
|
);
|
|
397
|
+
if (this.dispatchErrors !== undefined) {
|
|
398
|
+
this.stateful.setDispatchErrorReporter(this.dispatchErrors, this.meshName);
|
|
399
|
+
}
|
|
384
400
|
if (this.mailboxRecordDropped !== undefined) {
|
|
385
401
|
this.stateful.setMailboxDropHandler(this.mailboxRecordDropped);
|
|
386
402
|
}
|
|
@@ -649,12 +665,7 @@ export class ZLinkNodeRawMeshBackend implements ZLinkBackendMeshNode {
|
|
|
649
665
|
channelName: string,
|
|
650
666
|
parts: MessageLike | readonly MessageLike[]
|
|
651
667
|
): Promise<SubmitResultValue> {
|
|
652
|
-
return
|
|
653
|
-
channelName,
|
|
654
|
-
encodeMultipartApplicationFrame(parts)
|
|
655
|
-
))
|
|
656
|
-
? SubmitResult.Ok
|
|
657
|
-
: SubmitResult.NotConnected;
|
|
668
|
+
return this.requireRuntime().sendToChannel(channelName, encodeMultipartApplicationFrame(parts));
|
|
658
669
|
}
|
|
659
670
|
|
|
660
671
|
requestToChannel(
|
|
@@ -667,9 +678,6 @@ export class ZLinkNodeRawMeshBackend implements ZLinkBackendMeshNode {
|
|
|
667
678
|
encodeMultipartApplicationFrame(parts),
|
|
668
679
|
options?.timeoutMs ?? 30_000
|
|
669
680
|
);
|
|
670
|
-
if (pending === undefined) {
|
|
671
|
-
return this.enqueueImmediateFailure(OperationKind.ChannelRequest, RequestResult.NotFound);
|
|
672
|
-
}
|
|
673
681
|
return this.observeCompletion(pending.id, OperationKind.ChannelRequest, pending.promise);
|
|
674
682
|
}
|
|
675
683
|
|
|
@@ -1875,7 +1883,8 @@ class RawStreamSessionService implements StreamSessionService {
|
|
|
1875
1883
|
sessionKey,
|
|
1876
1884
|
actor,
|
|
1877
1885
|
timeoutMs,
|
|
1878
|
-
(targetSessionRid, payloadFrame) =>
|
|
1886
|
+
(targetSessionRid, payloadFrame) =>
|
|
1887
|
+
this.deliver(targetSessionRid, actor.actorId, payloadFrame),
|
|
1879
1888
|
onBindingReplaced,
|
|
1880
1889
|
serviceSessionBindingIngressPortIfRegistered(this),
|
|
1881
1890
|
actorAuthority
|
|
@@ -1913,12 +1922,22 @@ class RawStreamSessionService implements StreamSessionService {
|
|
|
1913
1922
|
)) as SubmitResultValue;
|
|
1914
1923
|
}
|
|
1915
1924
|
|
|
1916
|
-
private async deliver(
|
|
1925
|
+
private async deliver(
|
|
1926
|
+
sessionRid: string,
|
|
1927
|
+
actorId: string,
|
|
1928
|
+
payload: Uint8Array
|
|
1929
|
+
): Promise<boolean> {
|
|
1917
1930
|
const target = this.sessionTargets.get(sessionRid);
|
|
1918
1931
|
if (target === undefined) return false;
|
|
1919
1932
|
const operation = this.stream.send(target as unknown as BindingRoutingId);
|
|
1920
1933
|
const parts = decodeMultipartBuffers(decodeApplicationPayloadView(payload).payload);
|
|
1921
1934
|
if (parts.length === 0) return false;
|
|
1935
|
+
const actorSlot = await serviceSessionBindingIngressPortIfRegistered(this)?.actorSlot(
|
|
1936
|
+
actorId,
|
|
1937
|
+
sessionRid
|
|
1938
|
+
);
|
|
1939
|
+
if (actorSlot === undefined) return false;
|
|
1940
|
+
parts[0] = Buffer.from(streamFrameWithActorSlot(parts[0]!, actorSlot));
|
|
1922
1941
|
let submit = operation.message(parts[0]!);
|
|
1923
1942
|
for (let index = 1; index < parts.length; index++) {
|
|
1924
1943
|
submit = submit.message(parts[index]!);
|
|
@@ -1956,6 +1975,15 @@ class RawStreamSessionService implements StreamSessionService {
|
|
|
1956
1975
|
}
|
|
1957
1976
|
}
|
|
1958
1977
|
|
|
1978
|
+
function streamFrameWithActorSlot(frame: Uint8Array, actorSlot: number): Uint8Array {
|
|
1979
|
+
const decodedFrame = decodeStreamWireFrame(frame);
|
|
1980
|
+
const decodedHeader = decodeStreamWireHeader(decodedFrame.header);
|
|
1981
|
+
return encodeStreamWireFrame(
|
|
1982
|
+
encodeStreamWireHeader({ ...decodedHeader, actorSlot }),
|
|
1983
|
+
decodedFrame.payload
|
|
1984
|
+
);
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1959
1987
|
class RawReadyBatch implements ReadyBatch {
|
|
1960
1988
|
readonly records: ReadyRecord[] = [];
|
|
1961
1989
|
private readonly claims: RawClaim[] = [];
|
|
@@ -110,7 +110,8 @@ export class ZLinkChannelDispatchPipeline {
|
|
|
110
110
|
const flow = createInboundFlow(
|
|
111
111
|
dispatch.fields.flowId,
|
|
112
112
|
dispatch.fields.flowOrigin,
|
|
113
|
-
this.options.dispatchErrors.flow.flowCreationEnabled()
|
|
113
|
+
this.options.dispatchErrors.flow.flowCreationEnabled(),
|
|
114
|
+
undefined
|
|
114
115
|
);
|
|
115
116
|
const invocation = await runWithFlow(flow, () =>
|
|
116
117
|
this.invoke(
|
|
@@ -161,7 +162,8 @@ export class ZLinkChannelDispatchPipeline {
|
|
|
161
162
|
const flow = createInboundFlow(
|
|
162
163
|
dispatch.fields.flowId,
|
|
163
164
|
dispatch.fields.flowOrigin,
|
|
164
|
-
this.options.dispatchErrors.flow.flowCreationEnabled()
|
|
165
|
+
this.options.dispatchErrors.flow.flowCreationEnabled(),
|
|
166
|
+
undefined
|
|
165
167
|
);
|
|
166
168
|
const invocation = await runWithFlow(flow, () =>
|
|
167
169
|
this.invoke(
|
|
@@ -1140,15 +1140,9 @@ export function meshRequestFailure(
|
|
|
1140
1140
|
const wireKind = canonical
|
|
1141
1141
|
? internalFrameworkErrorKindFromWireReply(result, nativeErrno)
|
|
1142
1142
|
: undefined;
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
// not the protocol error a non-canonical pair would otherwise produce
|
|
1147
|
-
// (06-framework-api "no eligible select-one member"). A named target that
|
|
1148
|
-
// does not exist arrives with an errno and still ends as NotFound.
|
|
1149
|
-
const noEligibleMember = result === RequestResult.NotFound && nativeErrno === 0;
|
|
1150
|
-
const kind: ZLinkFrameworkInternalErrorKindType = noEligibleMember
|
|
1151
|
-
? ZLinkFrameworkInternalErrorKind.RouteNotConnected
|
|
1143
|
+
const localNoMember = result === RequestResult.NotFound && nativeErrno === 0;
|
|
1144
|
+
const kind: ZLinkFrameworkInternalErrorKindType = localNoMember
|
|
1145
|
+
? ZLinkFrameworkInternalErrorKind.RequestTargetNotFound
|
|
1152
1146
|
: !canonical
|
|
1153
1147
|
? ZLinkFrameworkInternalErrorKind.RequestProtocolError
|
|
1154
1148
|
: result === RequestResult.NotFound
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
type ZLinkDiagnosticsContext
|
|
14
14
|
} from '../diagnostics';
|
|
15
15
|
import type { ZLinkDispatchErrorSink } from '../diagnostics/dispatch-error-port';
|
|
16
|
+
import { dispatchErrorDetails } from '../diagnostics/dispatch-error-details';
|
|
16
17
|
|
|
17
18
|
export type { ZLinkDispatchErrorSink } from '../diagnostics/dispatch-error-port';
|
|
18
19
|
|
|
@@ -46,7 +47,6 @@ export class ZLinkDispatchErrorReporter {
|
|
|
46
47
|
|
|
47
48
|
report(event: ZLinkRuntimeDispatchFailure): void {
|
|
48
49
|
const normalized = normalizeDispatchFailure(event);
|
|
49
|
-
const tracePoint = this.flow.begin(ZLinkMessageFlowOutcome.Error);
|
|
50
50
|
const dropReason = channelDropReason(normalized);
|
|
51
51
|
if (dropReason !== undefined) {
|
|
52
52
|
this.metrics?.count('zlink.mesh_node.messages.dropped', 1, {
|
|
@@ -55,8 +55,9 @@ export class ZLinkDispatchErrorReporter {
|
|
|
55
55
|
reason: dropReason
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
|
+
const tracePoint = this.flow.begin(ZLinkMessageFlowOutcome.Error);
|
|
58
59
|
if (tracePoint === undefined) return;
|
|
59
|
-
const errorInfo =
|
|
60
|
+
const errorInfo = dispatchErrorDetails(normalized.error);
|
|
60
61
|
this.reportedEvents += 1;
|
|
61
62
|
tracePoint.trace({
|
|
62
63
|
outcome: ZLinkMessageFlowOutcome.Error,
|
|
@@ -87,6 +88,10 @@ export class ZLinkDispatchErrorReporter {
|
|
|
87
88
|
});
|
|
88
89
|
}
|
|
89
90
|
|
|
91
|
+
captureEnabled(): boolean {
|
|
92
|
+
return this.flow.enabled(ZLinkMessageFlowOutcome.Error);
|
|
93
|
+
}
|
|
94
|
+
|
|
90
95
|
get reportedCount(): number {
|
|
91
96
|
return this.reportedEvents;
|
|
92
97
|
}
|
|
@@ -123,46 +128,3 @@ function channelDropReason(event: ZLinkRuntimeDispatchFailure): string | undefin
|
|
|
123
128
|
return undefined;
|
|
124
129
|
}
|
|
125
130
|
}
|
|
126
|
-
|
|
127
|
-
function dispatchErrorInfo(event: ZLinkRuntimeDispatchFailure): {
|
|
128
|
-
readonly errorType?: string;
|
|
129
|
-
readonly errorMessage?: string;
|
|
130
|
-
readonly errorCauseType?: string;
|
|
131
|
-
readonly errorCauseMessage?: string;
|
|
132
|
-
} {
|
|
133
|
-
if (event.errorType !== undefined || event.errorMessage !== undefined) {
|
|
134
|
-
return {
|
|
135
|
-
errorType: event.errorType,
|
|
136
|
-
errorMessage: event.errorMessage,
|
|
137
|
-
errorCauseType: event.errorCauseType,
|
|
138
|
-
errorCauseMessage: event.errorCauseMessage
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
if (event.error === undefined) {
|
|
142
|
-
return {};
|
|
143
|
-
}
|
|
144
|
-
if (event.error instanceof Error) {
|
|
145
|
-
const cause = deepestErrorCause(event.error);
|
|
146
|
-
return {
|
|
147
|
-
errorType: event.error.name,
|
|
148
|
-
errorMessage: event.error.message,
|
|
149
|
-
...(cause === event.error
|
|
150
|
-
? {}
|
|
151
|
-
: {
|
|
152
|
-
errorCauseType: cause instanceof Error ? cause.name : typeof cause,
|
|
153
|
-
errorCauseMessage: cause instanceof Error ? cause.message : String(cause)
|
|
154
|
-
})
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
return { errorType: typeof event.error, errorMessage: String(event.error) };
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function deepestErrorCause(error: Error): unknown {
|
|
161
|
-
let current: unknown = error;
|
|
162
|
-
const seen = new Set<unknown>();
|
|
163
|
-
while (current instanceof Error && current.cause !== undefined && !seen.has(current)) {
|
|
164
|
-
seen.add(current);
|
|
165
|
-
current = current.cause;
|
|
166
|
-
}
|
|
167
|
-
return current;
|
|
168
|
-
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const ERROR_MESSAGE_MAX_LENGTH = 512;
|
|
2
|
+
|
|
3
|
+
const CREDENTIAL_PATTERNS: ReadonlyArray<readonly [RegExp, string]> = [
|
|
4
|
+
[/Authorization\s*:\s*(?:(?:Bearer|Basic)\s+)?[^\s,;]+/gi, 'Authorization: <redacted>'],
|
|
5
|
+
[/Bearer\s+[^\s,;]+/gi, 'Bearer <redacted>'],
|
|
6
|
+
[/password\s*=\s*[^\s,;]+/gi, 'password=<redacted>'],
|
|
7
|
+
[/token\s*=\s*[^\s,;]+/gi, 'token=<redacted>']
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
export function dispatchErrorDetails(error: unknown): {
|
|
11
|
+
readonly errorType?: string;
|
|
12
|
+
readonly errorMessage?: string;
|
|
13
|
+
readonly errorCauseType?: string;
|
|
14
|
+
readonly errorCauseMessage?: string;
|
|
15
|
+
} {
|
|
16
|
+
if (error === undefined || error === null) return {};
|
|
17
|
+
if (error instanceof Error) {
|
|
18
|
+
const cause = deepestErrorCause(error);
|
|
19
|
+
return {
|
|
20
|
+
errorType: error.name,
|
|
21
|
+
errorMessage: sanitizeErrorMessage(error.message),
|
|
22
|
+
...(cause === error
|
|
23
|
+
? {}
|
|
24
|
+
: {
|
|
25
|
+
errorCauseType: cause instanceof Error ? cause.name : typeof cause,
|
|
26
|
+
errorCauseMessage: sanitizeErrorMessage(
|
|
27
|
+
cause instanceof Error ? cause.message : String(cause)
|
|
28
|
+
)
|
|
29
|
+
})
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const errorType = typeof error === 'object' ? error.constructor.name : typeof error;
|
|
33
|
+
const message =
|
|
34
|
+
typeof error === 'string' ||
|
|
35
|
+
typeof error === 'number' ||
|
|
36
|
+
typeof error === 'boolean' ||
|
|
37
|
+
typeof error === 'bigint'
|
|
38
|
+
? `${error}`
|
|
39
|
+
: Object.prototype.toString.call(error);
|
|
40
|
+
return { errorType, errorMessage: sanitizeErrorMessage(message) };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function deepestErrorCause(error: Error): unknown {
|
|
44
|
+
let current: unknown = error;
|
|
45
|
+
const seen = new Set<unknown>();
|
|
46
|
+
while (current instanceof Error && current.cause !== undefined && !seen.has(current)) {
|
|
47
|
+
seen.add(current);
|
|
48
|
+
current = current.cause;
|
|
49
|
+
}
|
|
50
|
+
return current;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function sanitizeErrorMessage(message: string): string {
|
|
54
|
+
if (message.length === 0) return '';
|
|
55
|
+
const lineEnd = message.search(/[\r\n]/u);
|
|
56
|
+
let sanitized = lineEnd < 0 ? message : message.slice(0, lineEnd);
|
|
57
|
+
for (const [pattern, replacement] of CREDENTIAL_PATTERNS) {
|
|
58
|
+
pattern.lastIndex = 0;
|
|
59
|
+
sanitized = sanitized.replace(pattern, replacement);
|
|
60
|
+
}
|
|
61
|
+
return sanitized.slice(0, ERROR_MESSAGE_MAX_LENGTH);
|
|
62
|
+
}
|