@qlever-llc/trellis 0.10.11 → 0.10.12
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/esm/errors/TrellisError.d.ts +3 -3
- package/esm/errors/TrellisError.js +3 -3
- package/esm/server/internal_jobs/job-manager.d.ts.map +1 -1
- package/esm/server/internal_jobs/job-manager.js +32 -1
- package/esm/server/runtime.d.ts +3 -0
- package/esm/server/runtime.d.ts.map +1 -1
- package/esm/server/service.d.ts +15 -0
- package/esm/server/service.d.ts.map +1 -1
- package/esm/server/service.js +8 -0
- package/esm/server.d.ts.map +1 -1
- package/esm/server.js +54 -6
- package/esm/service/deno.d.ts +1 -1
- package/esm/service/deno.d.ts.map +1 -1
- package/esm/service/mod.d.ts +1 -1
- package/esm/service/mod.d.ts.map +1 -1
- package/esm/service/node.d.ts +1 -1
- package/esm/service/node.d.ts.map +1 -1
- package/esm/service/outbox_inbox.d.ts.map +1 -1
- package/esm/service/outbox_inbox.js +14 -0
- package/esm/telemetry/core.d.ts.map +1 -1
- package/esm/telemetry/core.js +1 -1
- package/esm/telemetry/env.d.ts.map +1 -1
- package/esm/telemetry/env.js +6 -1
- package/esm/telemetry/init.d.ts +3 -0
- package/esm/telemetry/init.d.ts.map +1 -0
- package/esm/telemetry/init.js +7 -0
- package/esm/telemetry/metrics.d.ts +34 -0
- package/esm/telemetry/metrics.d.ts.map +1 -0
- package/esm/telemetry/metrics.js +181 -0
- package/esm/telemetry/mod.d.ts +3 -0
- package/esm/telemetry/mod.d.ts.map +1 -1
- package/esm/telemetry/mod.js +2 -0
- package/esm/telemetry/runtime.d.ts +2 -0
- package/esm/telemetry/runtime.d.ts.map +1 -0
- package/esm/telemetry/runtime.js +134 -0
- package/esm/telemetry.d.ts +3 -0
- package/esm/telemetry.d.ts.map +1 -0
- package/esm/telemetry.js +2 -0
- package/esm/transfer.d.ts.map +1 -1
- package/esm/transfer.js +27 -16
- package/esm/trellis.d.ts.map +1 -1
- package/esm/trellis.js +460 -56
- package/package.json +7 -5
- package/script/errors/TrellisError.d.ts +3 -3
- package/script/errors/TrellisError.js +3 -3
- package/script/server/internal_jobs/job-manager.d.ts.map +1 -1
- package/script/server/internal_jobs/job-manager.js +32 -1
- package/script/server/runtime.d.ts +3 -0
- package/script/server/runtime.d.ts.map +1 -1
- package/script/server/service.d.ts +15 -0
- package/script/server/service.d.ts.map +1 -1
- package/script/server/service.js +8 -0
- package/script/server.d.ts.map +1 -1
- package/script/server.js +54 -6
- package/script/service/deno.d.ts +1 -1
- package/script/service/deno.d.ts.map +1 -1
- package/script/service/mod.d.ts +1 -1
- package/script/service/mod.d.ts.map +1 -1
- package/script/service/node.d.ts +1 -1
- package/script/service/node.d.ts.map +1 -1
- package/script/service/outbox_inbox.d.ts.map +1 -1
- package/script/service/outbox_inbox.js +14 -0
- package/script/telemetry/core.d.ts.map +1 -1
- package/script/telemetry/core.js +1 -1
- package/script/telemetry/env.d.ts.map +1 -1
- package/script/telemetry/env.js +6 -1
- package/script/telemetry/init.d.ts +3 -0
- package/script/telemetry/init.d.ts.map +1 -0
- package/script/telemetry/init.js +10 -0
- package/script/telemetry/metrics.d.ts +34 -0
- package/script/telemetry/metrics.d.ts.map +1 -0
- package/script/telemetry/metrics.js +186 -0
- package/script/telemetry/mod.d.ts +3 -0
- package/script/telemetry/mod.d.ts.map +1 -1
- package/script/telemetry/mod.js +7 -1
- package/script/telemetry/runtime.d.ts +2 -0
- package/script/telemetry/runtime.d.ts.map +1 -0
- package/script/telemetry/runtime.js +137 -0
- package/script/telemetry.d.ts +3 -0
- package/script/telemetry.d.ts.map +1 -0
- package/script/telemetry.js +18 -0
- package/script/transfer.d.ts.map +1 -1
- package/script/transfer.js +28 -17
- package/script/trellis.d.ts.map +1 -1
- package/script/trellis.js +490 -86
- package/src/errors/TrellisError.ts +4 -4
- package/src/server/internal_jobs/job-manager.ts +35 -5
- package/src/server/runtime.ts +4 -0
- package/src/server/service.ts +27 -0
- package/src/server.ts +66 -11
- package/src/service/deno.ts +1 -0
- package/src/service/mod.ts +1 -0
- package/src/service/node.ts +1 -0
- package/src/service/outbox_inbox.ts +14 -0
- package/src/telemetry/core.ts +1 -1
- package/src/telemetry/env.ts +5 -1
- package/src/telemetry/init.ts +8 -0
- package/src/telemetry/metrics.ts +294 -0
- package/src/telemetry/mod.ts +7 -0
- package/src/telemetry/runtime.ts +218 -0
- package/src/telemetry.ts +2 -0
- package/src/transfer.ts +69 -30
- package/src/trellis.ts +487 -88
- package/esm/tracing.d.ts +0 -5
- package/esm/tracing.d.ts.map +0 -1
- package/esm/tracing.js +0 -8
- package/script/tracing.d.ts +0 -5
- package/script/tracing.d.ts.map +0 -1
- package/script/tracing.js +0 -27
- package/src/tracing.ts +0 -28
package/src/trellis.ts
CHANGED
|
@@ -47,12 +47,14 @@ import {
|
|
|
47
47
|
createNatsHeaderCarrier,
|
|
48
48
|
extractTraceContext,
|
|
49
49
|
injectTraceContext,
|
|
50
|
+
recordTrellisError,
|
|
50
51
|
SpanStatusCode,
|
|
51
52
|
startClientSpan,
|
|
52
53
|
startServerSpan,
|
|
53
54
|
trace,
|
|
55
|
+
type TrellisErrorMetricAttributes,
|
|
54
56
|
withSpanAsync,
|
|
55
|
-
} from "./
|
|
57
|
+
} from "./telemetry/mod.js";
|
|
56
58
|
import { Type } from "typebox";
|
|
57
59
|
import { AssertError, Pointer } from "typebox/value";
|
|
58
60
|
import { ulid } from "ulid";
|
|
@@ -781,6 +783,16 @@ export function annotateHandlerBoundaryError(
|
|
|
781
783
|
return error;
|
|
782
784
|
}
|
|
783
785
|
|
|
786
|
+
function recordRuntimeError(
|
|
787
|
+
error: unknown,
|
|
788
|
+
attributes: TrellisErrorMetricAttributes,
|
|
789
|
+
): void {
|
|
790
|
+
recordTrellisError(error, {
|
|
791
|
+
messagingSystem: "nats",
|
|
792
|
+
...attributes,
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
|
|
784
796
|
export type RuntimeOperationDesc = {
|
|
785
797
|
subject: string;
|
|
786
798
|
input: unknown;
|
|
@@ -2464,11 +2476,23 @@ export class Trellis<
|
|
|
2464
2476
|
|
|
2465
2477
|
const msg = encodeRuntimeSchema(ctx.input, input).take();
|
|
2466
2478
|
if (isErr(msg)) {
|
|
2479
|
+
recordRuntimeError(msg.error, {
|
|
2480
|
+
surface: "rpc",
|
|
2481
|
+
direction: "client",
|
|
2482
|
+
operation: method,
|
|
2483
|
+
phase: "request_encoding",
|
|
2484
|
+
});
|
|
2467
2485
|
return msg;
|
|
2468
2486
|
}
|
|
2469
2487
|
|
|
2470
2488
|
const subject = this.template(ctx.subject, input).take();
|
|
2471
2489
|
if (isErr(subject)) {
|
|
2490
|
+
recordRuntimeError(subject.error, {
|
|
2491
|
+
surface: "rpc",
|
|
2492
|
+
direction: "client",
|
|
2493
|
+
operation: method,
|
|
2494
|
+
phase: "request_encoding",
|
|
2495
|
+
});
|
|
2472
2496
|
return subject;
|
|
2473
2497
|
}
|
|
2474
2498
|
|
|
@@ -2493,13 +2517,19 @@ export class Trellis<
|
|
|
2493
2517
|
});
|
|
2494
2518
|
const response = msgResult.take();
|
|
2495
2519
|
if (isErr(response)) {
|
|
2520
|
+
recordRuntimeError(response.error, {
|
|
2521
|
+
surface: "rpc",
|
|
2522
|
+
direction: "client",
|
|
2523
|
+
operation: method,
|
|
2524
|
+
phase: "request_send",
|
|
2525
|
+
});
|
|
2496
2526
|
return response;
|
|
2497
2527
|
}
|
|
2498
2528
|
|
|
2499
2529
|
if (response.headers?.get("status") === "error") {
|
|
2500
2530
|
const json = safeJson(response).take();
|
|
2501
2531
|
if (isErr(json)) {
|
|
2502
|
-
|
|
2532
|
+
const error = requestFailedTransportError({
|
|
2503
2533
|
code: "trellis.request.invalid_response",
|
|
2504
2534
|
message: "Trellis returned an invalid response.",
|
|
2505
2535
|
hint:
|
|
@@ -2507,12 +2537,19 @@ export class Trellis<
|
|
|
2507
2537
|
method,
|
|
2508
2538
|
subject,
|
|
2509
2539
|
cause: json.error.cause,
|
|
2510
|
-
})
|
|
2540
|
+
});
|
|
2541
|
+
recordRuntimeError(error, {
|
|
2542
|
+
surface: "rpc",
|
|
2543
|
+
direction: "client",
|
|
2544
|
+
operation: method,
|
|
2545
|
+
phase: "response_decoding",
|
|
2546
|
+
});
|
|
2547
|
+
return err(error);
|
|
2511
2548
|
}
|
|
2512
2549
|
|
|
2513
2550
|
const errorData = parse(TrellisErrorDataSchema, json).take();
|
|
2514
2551
|
if (isErr(errorData)) {
|
|
2515
|
-
|
|
2552
|
+
const error = requestFailedTransportError({
|
|
2516
2553
|
code: "trellis.request.invalid_response",
|
|
2517
2554
|
message: "Trellis returned an invalid response.",
|
|
2518
2555
|
hint:
|
|
@@ -2520,7 +2557,14 @@ export class Trellis<
|
|
|
2520
2557
|
method,
|
|
2521
2558
|
subject,
|
|
2522
2559
|
cause: errorData.error,
|
|
2523
|
-
})
|
|
2560
|
+
});
|
|
2561
|
+
recordRuntimeError(error, {
|
|
2562
|
+
surface: "rpc",
|
|
2563
|
+
direction: "client",
|
|
2564
|
+
operation: method,
|
|
2565
|
+
phase: "response_decoding",
|
|
2566
|
+
});
|
|
2567
|
+
return err(error);
|
|
2524
2568
|
}
|
|
2525
2569
|
|
|
2526
2570
|
const declaredErrorTypes = Array.isArray(ctx.declaredErrorTypes)
|
|
@@ -2539,17 +2583,29 @@ export class Trellis<
|
|
|
2539
2583
|
);
|
|
2540
2584
|
if (reconstructed) {
|
|
2541
2585
|
await this.#handleBrowserAuthRequired(reconstructed);
|
|
2586
|
+
recordRuntimeError(new RemoteError({ error: errorData }), {
|
|
2587
|
+
surface: "rpc",
|
|
2588
|
+
direction: "client",
|
|
2589
|
+
operation: method,
|
|
2590
|
+
phase: "remote_error",
|
|
2591
|
+
});
|
|
2542
2592
|
return err(reconstructed);
|
|
2543
2593
|
}
|
|
2544
2594
|
|
|
2545
2595
|
const remoteError = new RemoteError({ error: errorData });
|
|
2546
2596
|
await this.#handleBrowserAuthRequired(remoteError);
|
|
2597
|
+
recordRuntimeError(remoteError, {
|
|
2598
|
+
surface: "rpc",
|
|
2599
|
+
direction: "client",
|
|
2600
|
+
operation: method,
|
|
2601
|
+
phase: "remote_error",
|
|
2602
|
+
});
|
|
2547
2603
|
return err(remoteError);
|
|
2548
2604
|
}
|
|
2549
2605
|
|
|
2550
2606
|
const json = safeJson(response).take();
|
|
2551
2607
|
if (isErr(json)) {
|
|
2552
|
-
|
|
2608
|
+
const error = requestFailedTransportError({
|
|
2553
2609
|
code: "trellis.request.invalid_response",
|
|
2554
2610
|
message: "Trellis returned an invalid response.",
|
|
2555
2611
|
hint:
|
|
@@ -2557,11 +2613,24 @@ export class Trellis<
|
|
|
2557
2613
|
method,
|
|
2558
2614
|
subject,
|
|
2559
2615
|
cause: json.error.cause,
|
|
2560
|
-
})
|
|
2616
|
+
});
|
|
2617
|
+
recordRuntimeError(error, {
|
|
2618
|
+
surface: "rpc",
|
|
2619
|
+
direction: "client",
|
|
2620
|
+
operation: method,
|
|
2621
|
+
phase: "response_decoding",
|
|
2622
|
+
});
|
|
2623
|
+
return err(error);
|
|
2561
2624
|
}
|
|
2562
2625
|
|
|
2563
2626
|
const outputResult = parseRuntimeSchema(ctx.output, json).take();
|
|
2564
2627
|
if (isErr(outputResult)) {
|
|
2628
|
+
recordRuntimeError(outputResult.error, {
|
|
2629
|
+
surface: "rpc",
|
|
2630
|
+
direction: "client",
|
|
2631
|
+
operation: method,
|
|
2632
|
+
phase: "response_decoding",
|
|
2633
|
+
});
|
|
2565
2634
|
return err(outputResult.error);
|
|
2566
2635
|
}
|
|
2567
2636
|
|
|
@@ -2590,6 +2659,12 @@ export class Trellis<
|
|
|
2590
2659
|
message: unexpected.message,
|
|
2591
2660
|
});
|
|
2592
2661
|
span.recordException(unexpected);
|
|
2662
|
+
recordRuntimeError(unexpected, {
|
|
2663
|
+
surface: "rpc",
|
|
2664
|
+
direction: "client",
|
|
2665
|
+
operation: method,
|
|
2666
|
+
phase: "unexpected",
|
|
2667
|
+
});
|
|
2593
2668
|
return err(unexpected);
|
|
2594
2669
|
} finally {
|
|
2595
2670
|
span.end();
|
|
@@ -2738,13 +2813,29 @@ export class Trellis<
|
|
|
2738
2813
|
): AsyncResult<FeedSubscription<TEvent>, BaseError> {
|
|
2739
2814
|
return AsyncResult.from((async () => {
|
|
2740
2815
|
const payload = encodeRuntimeSchema(descriptor.input, input).take();
|
|
2741
|
-
if (isErr(payload))
|
|
2816
|
+
if (isErr(payload)) {
|
|
2817
|
+
recordRuntimeError(payload.error, {
|
|
2818
|
+
surface: "feed",
|
|
2819
|
+
direction: "client",
|
|
2820
|
+
operation: feed,
|
|
2821
|
+
phase: "request_encoding",
|
|
2822
|
+
});
|
|
2823
|
+
return payload;
|
|
2824
|
+
}
|
|
2742
2825
|
|
|
2743
2826
|
const subject = this.template(
|
|
2744
2827
|
descriptor.subject,
|
|
2745
2828
|
input as Record<string, unknown>,
|
|
2746
2829
|
).take();
|
|
2747
|
-
if (isErr(subject))
|
|
2830
|
+
if (isErr(subject)) {
|
|
2831
|
+
recordRuntimeError(subject.error, {
|
|
2832
|
+
surface: "feed",
|
|
2833
|
+
direction: "client",
|
|
2834
|
+
operation: feed,
|
|
2835
|
+
phase: "request_template",
|
|
2836
|
+
});
|
|
2837
|
+
return subject;
|
|
2838
|
+
}
|
|
2748
2839
|
|
|
2749
2840
|
const authHeaders = await this.#createProof(subject, payload);
|
|
2750
2841
|
const headers = natsHeaders();
|
|
@@ -2766,14 +2857,21 @@ export class Trellis<
|
|
|
2766
2857
|
} catch (cause) {
|
|
2767
2858
|
opts?.signal?.removeEventListener("abort", abort);
|
|
2768
2859
|
sub.unsubscribe();
|
|
2769
|
-
|
|
2860
|
+
const error = createTransportError({
|
|
2770
2861
|
code: "trellis.feed.subscribe_failed",
|
|
2771
2862
|
message: "Trellis could not subscribe to the feed.",
|
|
2772
2863
|
hint:
|
|
2773
2864
|
"Retry the subscription. If it keeps failing, check Trellis runtime health.",
|
|
2774
2865
|
cause,
|
|
2775
2866
|
context: { feed, subject },
|
|
2776
|
-
})
|
|
2867
|
+
});
|
|
2868
|
+
recordRuntimeError(error, {
|
|
2869
|
+
surface: "feed",
|
|
2870
|
+
direction: "client",
|
|
2871
|
+
operation: feed,
|
|
2872
|
+
phase: "request_send",
|
|
2873
|
+
});
|
|
2874
|
+
return err(error);
|
|
2777
2875
|
}
|
|
2778
2876
|
|
|
2779
2877
|
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
@@ -2804,7 +2902,7 @@ export class Trellis<
|
|
|
2804
2902
|
if (firstFrame === "timeout" || firstFrame === "aborted") {
|
|
2805
2903
|
opts?.signal?.removeEventListener("abort", abort);
|
|
2806
2904
|
sub.unsubscribe();
|
|
2807
|
-
|
|
2905
|
+
const error = createTransportError({
|
|
2808
2906
|
code: firstFrame === "timeout"
|
|
2809
2907
|
? "trellis.feed.subscribe_timeout"
|
|
2810
2908
|
: "trellis.feed.subscribe_aborted",
|
|
@@ -2815,30 +2913,51 @@ export class Trellis<
|
|
|
2815
2913
|
? "Check that the target service is running and has the current deployment digest, then retry."
|
|
2816
2914
|
: "Retry the subscription if the feed is still needed.",
|
|
2817
2915
|
context: { feed, subject },
|
|
2818
|
-
})
|
|
2916
|
+
});
|
|
2917
|
+
recordRuntimeError(error, {
|
|
2918
|
+
surface: "feed",
|
|
2919
|
+
direction: "client",
|
|
2920
|
+
operation: feed,
|
|
2921
|
+
phase: "handshake",
|
|
2922
|
+
});
|
|
2923
|
+
return err(error);
|
|
2819
2924
|
}
|
|
2820
2925
|
if (firstFrame.done) {
|
|
2821
2926
|
opts?.signal?.removeEventListener("abort", abort);
|
|
2822
2927
|
sub.unsubscribe();
|
|
2823
|
-
|
|
2928
|
+
const error = createTransportError({
|
|
2824
2929
|
code: "trellis.feed.subscribe_closed",
|
|
2825
2930
|
message: "Trellis closed the feed before acknowledging it.",
|
|
2826
2931
|
hint:
|
|
2827
2932
|
"Retry the subscription. If it keeps failing, check Trellis runtime health.",
|
|
2828
2933
|
context: { feed, subject },
|
|
2829
|
-
})
|
|
2934
|
+
});
|
|
2935
|
+
recordRuntimeError(error, {
|
|
2936
|
+
surface: "feed",
|
|
2937
|
+
direction: "client",
|
|
2938
|
+
operation: feed,
|
|
2939
|
+
phase: "handshake",
|
|
2940
|
+
});
|
|
2941
|
+
return err(error);
|
|
2830
2942
|
}
|
|
2831
2943
|
const firstMessage = firstFrame.value;
|
|
2832
2944
|
if (firstMessage.headers?.get("status") === "error") {
|
|
2833
2945
|
opts?.signal?.removeEventListener("abort", abort);
|
|
2834
2946
|
sub.unsubscribe();
|
|
2835
|
-
|
|
2947
|
+
const error = createTransportError({
|
|
2836
2948
|
code: "trellis.feed.failed",
|
|
2837
2949
|
message: "Trellis rejected the feed subscription.",
|
|
2838
2950
|
hint:
|
|
2839
2951
|
"Retry the subscription. If it keeps failing, check Trellis runtime health and permissions.",
|
|
2840
2952
|
context: { feed, subject, frame: firstMessage.string() },
|
|
2841
|
-
})
|
|
2953
|
+
});
|
|
2954
|
+
recordRuntimeError(error, {
|
|
2955
|
+
surface: "feed",
|
|
2956
|
+
direction: "client",
|
|
2957
|
+
operation: feed,
|
|
2958
|
+
phase: "remote_error",
|
|
2959
|
+
});
|
|
2960
|
+
return err(error);
|
|
2842
2961
|
}
|
|
2843
2962
|
const firstEvent = firstMessage.headers?.get("feed-status") === "ready"
|
|
2844
2963
|
? undefined
|
|
@@ -2849,18 +2968,41 @@ export class Trellis<
|
|
|
2849
2968
|
try {
|
|
2850
2969
|
const parseFeedFrame = (msg: Msg): TEvent => {
|
|
2851
2970
|
if (msg.headers?.get("status") === "error") {
|
|
2852
|
-
|
|
2971
|
+
const error = createTransportError({
|
|
2853
2972
|
code: "trellis.feed.failed",
|
|
2854
2973
|
message: "Trellis stopped the feed.",
|
|
2855
2974
|
hint:
|
|
2856
2975
|
"Retry the subscription. If it keeps failing, check Trellis runtime health.",
|
|
2857
2976
|
context: { feed, subject, frame: msg.string() },
|
|
2858
2977
|
});
|
|
2978
|
+
recordRuntimeError(error, {
|
|
2979
|
+
surface: "feed",
|
|
2980
|
+
direction: "client",
|
|
2981
|
+
operation: feed,
|
|
2982
|
+
phase: "remote_error",
|
|
2983
|
+
});
|
|
2984
|
+
throw error;
|
|
2859
2985
|
}
|
|
2860
2986
|
const json = safeJson(msg).take();
|
|
2861
|
-
if (isErr(json))
|
|
2987
|
+
if (isErr(json)) {
|
|
2988
|
+
recordRuntimeError(json.error, {
|
|
2989
|
+
surface: "feed",
|
|
2990
|
+
direction: "client",
|
|
2991
|
+
operation: feed,
|
|
2992
|
+
phase: "event_decoding",
|
|
2993
|
+
});
|
|
2994
|
+
throw json.error;
|
|
2995
|
+
}
|
|
2862
2996
|
const parsed = parseRuntimeSchema(eventSchema, json).take();
|
|
2863
|
-
if (isErr(parsed))
|
|
2997
|
+
if (isErr(parsed)) {
|
|
2998
|
+
recordRuntimeError(parsed.error, {
|
|
2999
|
+
surface: "feed",
|
|
3000
|
+
direction: "client",
|
|
3001
|
+
operation: feed,
|
|
3002
|
+
phase: "event_validation",
|
|
3003
|
+
});
|
|
3004
|
+
throw parsed.error;
|
|
3005
|
+
}
|
|
2864
3006
|
return parsed as TEvent;
|
|
2865
3007
|
};
|
|
2866
3008
|
if (firstEvent) yield parseFeedFrame(firstEvent);
|
|
@@ -2891,7 +3033,7 @@ export class Trellis<
|
|
|
2891
3033
|
sub = this.nats.subscribe(subject);
|
|
2892
3034
|
await this.nats.flush();
|
|
2893
3035
|
} catch (cause) {
|
|
2894
|
-
|
|
3036
|
+
const error = createTransportError({
|
|
2895
3037
|
code: "trellis.feed.listen_failed",
|
|
2896
3038
|
message: "Trellis could not listen for feed requests.",
|
|
2897
3039
|
hint:
|
|
@@ -2899,6 +3041,13 @@ export class Trellis<
|
|
|
2899
3041
|
cause,
|
|
2900
3042
|
context: { feed, subject },
|
|
2901
3043
|
});
|
|
3044
|
+
recordRuntimeError(error, {
|
|
3045
|
+
surface: "feed",
|
|
3046
|
+
direction: "server",
|
|
3047
|
+
operation: feed,
|
|
3048
|
+
phase: "listen",
|
|
3049
|
+
});
|
|
3050
|
+
throw error;
|
|
2902
3051
|
}
|
|
2903
3052
|
const task = AsyncResult.try(async () => {
|
|
2904
3053
|
for await (const msg of sub) {
|
|
@@ -2923,6 +3072,12 @@ export class Trellis<
|
|
|
2923
3072
|
contractDigest: this.contractDigest,
|
|
2924
3073
|
traceId: traceIdFromTraceparent(msg.headers?.get("traceparent")),
|
|
2925
3074
|
});
|
|
3075
|
+
recordRuntimeError(error, {
|
|
3076
|
+
surface: "feed",
|
|
3077
|
+
direction: "server",
|
|
3078
|
+
operation: feed,
|
|
3079
|
+
phase: "handler_throw",
|
|
3080
|
+
});
|
|
2926
3081
|
this.#respondWithError(msg, error);
|
|
2927
3082
|
}
|
|
2928
3083
|
})();
|
|
@@ -2940,9 +3095,25 @@ export class Trellis<
|
|
|
2940
3095
|
) => unknown | Promise<unknown>,
|
|
2941
3096
|
): Promise<Result<void, BaseError>> {
|
|
2942
3097
|
const json = safeJson(msg).take();
|
|
2943
|
-
if (isErr(json))
|
|
3098
|
+
if (isErr(json)) {
|
|
3099
|
+
recordRuntimeError(json.error, {
|
|
3100
|
+
surface: "feed",
|
|
3101
|
+
direction: "server",
|
|
3102
|
+
operation: feed,
|
|
3103
|
+
phase: "request_decoding",
|
|
3104
|
+
});
|
|
3105
|
+
return json;
|
|
3106
|
+
}
|
|
2944
3107
|
const parsed = parseRuntimeSchema(descriptor.input, json).take();
|
|
2945
|
-
if (isErr(parsed))
|
|
3108
|
+
if (isErr(parsed)) {
|
|
3109
|
+
recordRuntimeError(parsed.error, {
|
|
3110
|
+
surface: "feed",
|
|
3111
|
+
direction: "server",
|
|
3112
|
+
operation: feed,
|
|
3113
|
+
phase: "input_validation",
|
|
3114
|
+
});
|
|
3115
|
+
return parsed;
|
|
3116
|
+
}
|
|
2946
3117
|
|
|
2947
3118
|
const caller = await this.#authenticateFeedRequest({
|
|
2948
3119
|
feed,
|
|
@@ -2952,13 +3123,26 @@ export class Trellis<
|
|
|
2952
3123
|
requiredCapabilities: descriptor.subscribeCapabilities,
|
|
2953
3124
|
});
|
|
2954
3125
|
const callerValue = caller.take();
|
|
2955
|
-
if (isErr(callerValue))
|
|
3126
|
+
if (isErr(callerValue)) {
|
|
3127
|
+
recordRuntimeError(callerValue.error, {
|
|
3128
|
+
surface: "feed",
|
|
3129
|
+
direction: "server",
|
|
3130
|
+
operation: feed,
|
|
3131
|
+
phase: "auth",
|
|
3132
|
+
});
|
|
3133
|
+
return callerValue;
|
|
3134
|
+
}
|
|
2956
3135
|
if (!msg.reply) {
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
3136
|
+
const error = new UnexpectedError({
|
|
3137
|
+
context: { feed, reason: "missing_reply" },
|
|
3138
|
+
});
|
|
3139
|
+
recordRuntimeError(error, {
|
|
3140
|
+
surface: "feed",
|
|
3141
|
+
direction: "server",
|
|
3142
|
+
operation: feed,
|
|
3143
|
+
phase: "handshake",
|
|
3144
|
+
});
|
|
3145
|
+
return err(error);
|
|
2962
3146
|
}
|
|
2963
3147
|
const readyHeaders = natsHeaders();
|
|
2964
3148
|
readyHeaders.set("feed-status", "ready");
|
|
@@ -2974,16 +3158,43 @@ export class Trellis<
|
|
|
2974
3158
|
emit: (event: TEvent) =>
|
|
2975
3159
|
AsyncResult.from((async () => {
|
|
2976
3160
|
const payload = encodeRuntimeSchema(descriptor.event, event).take();
|
|
2977
|
-
if (isErr(payload))
|
|
3161
|
+
if (isErr(payload)) {
|
|
3162
|
+
recordRuntimeError(payload.error, {
|
|
3163
|
+
surface: "feed",
|
|
3164
|
+
direction: "server",
|
|
3165
|
+
operation: feed,
|
|
3166
|
+
phase: "event_encoding",
|
|
3167
|
+
});
|
|
3168
|
+
return payload;
|
|
3169
|
+
}
|
|
2978
3170
|
if (!msg.reply) {
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
3171
|
+
const error = new UnexpectedError({
|
|
3172
|
+
context: { feed, reason: "missing_reply" },
|
|
3173
|
+
});
|
|
3174
|
+
recordRuntimeError(error, {
|
|
3175
|
+
surface: "feed",
|
|
3176
|
+
direction: "server",
|
|
3177
|
+
operation: feed,
|
|
3178
|
+
phase: "event_publish",
|
|
3179
|
+
});
|
|
3180
|
+
return err(error);
|
|
3181
|
+
}
|
|
3182
|
+
try {
|
|
3183
|
+
this.nats.publish(msg.reply, payload);
|
|
3184
|
+
await this.nats.flush();
|
|
3185
|
+
} catch (cause) {
|
|
3186
|
+
const error = new UnexpectedError({
|
|
3187
|
+
cause,
|
|
3188
|
+
context: { feed },
|
|
3189
|
+
});
|
|
3190
|
+
recordRuntimeError(error, {
|
|
3191
|
+
surface: "feed",
|
|
3192
|
+
direction: "server",
|
|
3193
|
+
operation: feed,
|
|
3194
|
+
phase: "event_publish",
|
|
3195
|
+
});
|
|
3196
|
+
return err(error);
|
|
2984
3197
|
}
|
|
2985
|
-
this.nats.publish(msg.reply, payload);
|
|
2986
|
-
await this.nats.flush();
|
|
2987
3198
|
return ok(undefined);
|
|
2988
3199
|
})()),
|
|
2989
3200
|
});
|
|
@@ -2991,14 +3202,21 @@ export class Trellis<
|
|
|
2991
3202
|
? handlerResult.take()
|
|
2992
3203
|
: handlerResult;
|
|
2993
3204
|
if (isErr(handlerOutcome)) {
|
|
2994
|
-
|
|
3205
|
+
const error = annotateHandlerBoundaryError(handlerOutcome.error, {
|
|
2995
3206
|
feed,
|
|
2996
3207
|
requestId: msg.headers?.get("request-id"),
|
|
2997
3208
|
service: this.name,
|
|
2998
3209
|
contractId: this.contractId,
|
|
2999
3210
|
contractDigest: this.contractDigest,
|
|
3000
3211
|
traceId: traceIdFromTraceparent(msg.headers?.get("traceparent")),
|
|
3001
|
-
})
|
|
3212
|
+
});
|
|
3213
|
+
recordRuntimeError(error, {
|
|
3214
|
+
surface: "feed",
|
|
3215
|
+
direction: "server",
|
|
3216
|
+
operation: feed,
|
|
3217
|
+
phase: "handler_result",
|
|
3218
|
+
});
|
|
3219
|
+
return err(error);
|
|
3002
3220
|
}
|
|
3003
3221
|
return ok(undefined);
|
|
3004
3222
|
} finally {
|
|
@@ -3186,6 +3404,12 @@ export class Trellis<
|
|
|
3186
3404
|
code: SpanStatusCode.ERROR,
|
|
3187
3405
|
message: "Failed to parse JSON",
|
|
3188
3406
|
});
|
|
3407
|
+
recordRuntimeError(jsonData.error, {
|
|
3408
|
+
surface: "rpc",
|
|
3409
|
+
direction: "server",
|
|
3410
|
+
operation: String(method),
|
|
3411
|
+
phase: "parse",
|
|
3412
|
+
});
|
|
3189
3413
|
return jsonData;
|
|
3190
3414
|
}
|
|
3191
3415
|
|
|
@@ -3195,6 +3419,12 @@ export class Trellis<
|
|
|
3195
3419
|
code: SpanStatusCode.ERROR,
|
|
3196
3420
|
message: "Input validation failed",
|
|
3197
3421
|
});
|
|
3422
|
+
recordRuntimeError(parsedInput.error, {
|
|
3423
|
+
surface: "rpc",
|
|
3424
|
+
direction: "server",
|
|
3425
|
+
operation: String(method),
|
|
3426
|
+
phase: "input_validation",
|
|
3427
|
+
});
|
|
3198
3428
|
return parsedInput;
|
|
3199
3429
|
}
|
|
3200
3430
|
|
|
@@ -3221,7 +3451,14 @@ export class Trellis<
|
|
|
3221
3451
|
code: SpanStatusCode.ERROR,
|
|
3222
3452
|
message: "Missing session-key",
|
|
3223
3453
|
});
|
|
3224
|
-
|
|
3454
|
+
const error = new AuthError({ reason: "missing_session_key" });
|
|
3455
|
+
recordRuntimeError(error, {
|
|
3456
|
+
surface: "rpc",
|
|
3457
|
+
direction: "server",
|
|
3458
|
+
operation: String(method),
|
|
3459
|
+
phase: "auth",
|
|
3460
|
+
});
|
|
3461
|
+
return err(error);
|
|
3225
3462
|
}
|
|
3226
3463
|
if (!proof) {
|
|
3227
3464
|
this.#log.warn({ method }, "Missing proof in request");
|
|
@@ -3229,11 +3466,25 @@ export class Trellis<
|
|
|
3229
3466
|
code: SpanStatusCode.ERROR,
|
|
3230
3467
|
message: "Missing proof",
|
|
3231
3468
|
});
|
|
3232
|
-
|
|
3469
|
+
const error = new AuthError({ reason: "missing_proof" });
|
|
3470
|
+
recordRuntimeError(error, {
|
|
3471
|
+
surface: "rpc",
|
|
3472
|
+
direction: "server",
|
|
3473
|
+
operation: String(method),
|
|
3474
|
+
phase: "auth",
|
|
3475
|
+
});
|
|
3476
|
+
return err(error);
|
|
3233
3477
|
}
|
|
3234
3478
|
const iat = Number(iatHeader);
|
|
3235
3479
|
if (!Number.isSafeInteger(iat) || !requestId) {
|
|
3236
|
-
|
|
3480
|
+
const error = new AuthError({ reason: "invalid_signature" });
|
|
3481
|
+
recordRuntimeError(error, {
|
|
3482
|
+
surface: "rpc",
|
|
3483
|
+
direction: "server",
|
|
3484
|
+
operation: String(method),
|
|
3485
|
+
phase: "auth",
|
|
3486
|
+
});
|
|
3487
|
+
return err(error);
|
|
3237
3488
|
}
|
|
3238
3489
|
|
|
3239
3490
|
// Verify proof signature locally using the raw request bytes we received.
|
|
@@ -3272,12 +3523,17 @@ export class Trellis<
|
|
|
3272
3523
|
code: SpanStatusCode.ERROR,
|
|
3273
3524
|
message: "Invalid signature",
|
|
3274
3525
|
});
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3526
|
+
const error = new AuthError({
|
|
3527
|
+
reason: "invalid_signature",
|
|
3528
|
+
context: { sessionKey },
|
|
3529
|
+
});
|
|
3530
|
+
recordRuntimeError(error, {
|
|
3531
|
+
surface: "rpc",
|
|
3532
|
+
direction: "server",
|
|
3533
|
+
operation: String(method),
|
|
3534
|
+
phase: "auth",
|
|
3535
|
+
});
|
|
3536
|
+
return err(error);
|
|
3281
3537
|
}
|
|
3282
3538
|
|
|
3283
3539
|
let auth:
|
|
@@ -3323,11 +3579,16 @@ export class Trellis<
|
|
|
3323
3579
|
}
|
|
3324
3580
|
|
|
3325
3581
|
if (!auth) {
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3582
|
+
const error = new UnexpectedError({
|
|
3583
|
+
context: { reason: "missing_auth_validate_result" },
|
|
3584
|
+
});
|
|
3585
|
+
recordRuntimeError(error, {
|
|
3586
|
+
surface: "rpc",
|
|
3587
|
+
direction: "server",
|
|
3588
|
+
operation: String(method),
|
|
3589
|
+
phase: "auth",
|
|
3590
|
+
});
|
|
3591
|
+
return err(error);
|
|
3331
3592
|
}
|
|
3332
3593
|
|
|
3333
3594
|
if (auth instanceof Error) {
|
|
@@ -3347,9 +3608,22 @@ export class Trellis<
|
|
|
3347
3608
|
message: "Auth.Requests.Validate failed",
|
|
3348
3609
|
});
|
|
3349
3610
|
if (auth instanceof BaseError) {
|
|
3611
|
+
recordRuntimeError(auth, {
|
|
3612
|
+
surface: "rpc",
|
|
3613
|
+
direction: "server",
|
|
3614
|
+
operation: String(method),
|
|
3615
|
+
phase: "auth",
|
|
3616
|
+
});
|
|
3350
3617
|
return err(auth);
|
|
3351
3618
|
}
|
|
3352
|
-
|
|
3619
|
+
const error = new UnexpectedError({ cause: auth });
|
|
3620
|
+
recordRuntimeError(error, {
|
|
3621
|
+
surface: "rpc",
|
|
3622
|
+
direction: "server",
|
|
3623
|
+
operation: String(method),
|
|
3624
|
+
phase: "auth",
|
|
3625
|
+
});
|
|
3626
|
+
return err(error);
|
|
3353
3627
|
}
|
|
3354
3628
|
|
|
3355
3629
|
if (!auth.allowed) {
|
|
@@ -3357,15 +3631,20 @@ export class Trellis<
|
|
|
3357
3631
|
code: SpanStatusCode.ERROR,
|
|
3358
3632
|
message: "Insufficient permissions",
|
|
3359
3633
|
});
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3634
|
+
const error = new AuthError({
|
|
3635
|
+
reason: "insufficient_permissions",
|
|
3636
|
+
context: {
|
|
3637
|
+
requiredCapabilities: ctx.callerCapabilities,
|
|
3638
|
+
userCapabilities: auth.caller.capabilities,
|
|
3639
|
+
},
|
|
3640
|
+
});
|
|
3641
|
+
recordRuntimeError(error, {
|
|
3642
|
+
surface: "rpc",
|
|
3643
|
+
direction: "server",
|
|
3644
|
+
operation: String(method),
|
|
3645
|
+
phase: "auth",
|
|
3646
|
+
});
|
|
3647
|
+
return err(error);
|
|
3369
3648
|
}
|
|
3370
3649
|
|
|
3371
3650
|
if (
|
|
@@ -3376,12 +3655,17 @@ export class Trellis<
|
|
|
3376
3655
|
code: SpanStatusCode.ERROR,
|
|
3377
3656
|
message: "Reply subject mismatch",
|
|
3378
3657
|
});
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3658
|
+
const error = new AuthError({
|
|
3659
|
+
reason: "reply_subject_mismatch",
|
|
3660
|
+
context: { expected: auth.inboxPrefix, actual: msg.reply },
|
|
3661
|
+
});
|
|
3662
|
+
recordRuntimeError(error, {
|
|
3663
|
+
surface: "rpc",
|
|
3664
|
+
direction: "server",
|
|
3665
|
+
operation: String(method),
|
|
3666
|
+
phase: "auth",
|
|
3667
|
+
});
|
|
3668
|
+
return err(error);
|
|
3385
3669
|
}
|
|
3386
3670
|
|
|
3387
3671
|
caller = auth.caller;
|
|
@@ -3449,6 +3733,12 @@ export class Trellis<
|
|
|
3449
3733
|
message: error.message,
|
|
3450
3734
|
});
|
|
3451
3735
|
span.recordException(error);
|
|
3736
|
+
recordRuntimeError(error, {
|
|
3737
|
+
surface: "rpc",
|
|
3738
|
+
direction: "server",
|
|
3739
|
+
operation: String(method),
|
|
3740
|
+
phase: "handler_throw",
|
|
3741
|
+
});
|
|
3452
3742
|
return err(error);
|
|
3453
3743
|
}
|
|
3454
3744
|
|
|
@@ -3481,6 +3771,12 @@ export class Trellis<
|
|
|
3481
3771
|
code: SpanStatusCode.ERROR,
|
|
3482
3772
|
message: error.message,
|
|
3483
3773
|
});
|
|
3774
|
+
recordRuntimeError(error, {
|
|
3775
|
+
surface: "rpc",
|
|
3776
|
+
direction: "server",
|
|
3777
|
+
operation: String(method),
|
|
3778
|
+
phase: "handler_result",
|
|
3779
|
+
});
|
|
3484
3780
|
return err(error);
|
|
3485
3781
|
}
|
|
3486
3782
|
|
|
@@ -3490,6 +3786,12 @@ export class Trellis<
|
|
|
3490
3786
|
code: SpanStatusCode.ERROR,
|
|
3491
3787
|
message: "Output encoding failed",
|
|
3492
3788
|
});
|
|
3789
|
+
recordRuntimeError(encoded.error, {
|
|
3790
|
+
surface: "rpc",
|
|
3791
|
+
direction: "server",
|
|
3792
|
+
operation: String(method),
|
|
3793
|
+
phase: "output_encoding",
|
|
3794
|
+
});
|
|
3493
3795
|
return encoded;
|
|
3494
3796
|
}
|
|
3495
3797
|
|
|
@@ -3614,17 +3916,28 @@ export class Trellis<
|
|
|
3614
3916
|
typeof eventName
|
|
3615
3917
|
>;
|
|
3616
3918
|
if (!ctx) {
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3919
|
+
const error = new UnexpectedError({
|
|
3920
|
+
cause: this.#unknownApiError("event", event.toString()),
|
|
3921
|
+
context: { event: event.toString() },
|
|
3922
|
+
});
|
|
3923
|
+
recordRuntimeError(error, {
|
|
3924
|
+
surface: "event",
|
|
3925
|
+
direction: "publisher",
|
|
3926
|
+
operation: event,
|
|
3927
|
+
phase: "prepare",
|
|
3928
|
+
});
|
|
3929
|
+
return err(error);
|
|
3623
3930
|
}
|
|
3624
3931
|
|
|
3625
3932
|
const subject = this.template(ctx.subject, data).take();
|
|
3626
3933
|
if (isErr(subject)) {
|
|
3627
3934
|
logger.error({ err: subject.error }, "Failed to template event.");
|
|
3935
|
+
recordRuntimeError(subject.error, {
|
|
3936
|
+
surface: "event",
|
|
3937
|
+
direction: "publisher",
|
|
3938
|
+
operation: event,
|
|
3939
|
+
phase: "request_encoding",
|
|
3940
|
+
});
|
|
3628
3941
|
return subject;
|
|
3629
3942
|
}
|
|
3630
3943
|
|
|
@@ -3639,7 +3952,14 @@ export class Trellis<
|
|
|
3639
3952
|
const msg = encodeSchema(ctx.event, payload).take();
|
|
3640
3953
|
if (isErr(msg)) {
|
|
3641
3954
|
logger.error({ err: msg.error }, "Failed to encode event.");
|
|
3642
|
-
|
|
3955
|
+
const error = new UnexpectedError({ cause: msg.error });
|
|
3956
|
+
recordRuntimeError(error, {
|
|
3957
|
+
surface: "event",
|
|
3958
|
+
direction: "publisher",
|
|
3959
|
+
operation: event,
|
|
3960
|
+
phase: "request_encoding",
|
|
3961
|
+
});
|
|
3962
|
+
return err(error);
|
|
3643
3963
|
}
|
|
3644
3964
|
|
|
3645
3965
|
const headers = natsHeaders();
|
|
@@ -3659,9 +3979,17 @@ export class Trellis<
|
|
|
3659
3979
|
headers: Object.freeze(headerRecord),
|
|
3660
3980
|
}));
|
|
3661
3981
|
} catch (cause) {
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3982
|
+
const error = new UnexpectedError({
|
|
3983
|
+
cause,
|
|
3984
|
+
context: { event: event.toString() },
|
|
3985
|
+
});
|
|
3986
|
+
recordRuntimeError(error, {
|
|
3987
|
+
surface: "event",
|
|
3988
|
+
direction: "publisher",
|
|
3989
|
+
operation: event,
|
|
3990
|
+
phase: "prepare",
|
|
3991
|
+
});
|
|
3992
|
+
return err(error);
|
|
3665
3993
|
}
|
|
3666
3994
|
}
|
|
3667
3995
|
|
|
@@ -3686,9 +4014,17 @@ export class Trellis<
|
|
|
3686
4014
|
await this.js.publish(event.subject, event.encodedPayload, { headers });
|
|
3687
4015
|
return ok(undefined);
|
|
3688
4016
|
} catch (cause) {
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
4017
|
+
const error = new UnexpectedError({
|
|
4018
|
+
cause,
|
|
4019
|
+
context: { event: event.event },
|
|
4020
|
+
});
|
|
4021
|
+
recordRuntimeError(error, {
|
|
4022
|
+
surface: "event",
|
|
4023
|
+
direction: "publisher",
|
|
4024
|
+
operation: event.event,
|
|
4025
|
+
phase: "publish",
|
|
4026
|
+
});
|
|
4027
|
+
return err(error);
|
|
3692
4028
|
}
|
|
3693
4029
|
})());
|
|
3694
4030
|
}
|
|
@@ -3795,6 +4131,12 @@ export class Trellis<
|
|
|
3795
4131
|
const m = parsedEvent.take();
|
|
3796
4132
|
if (isErr(m)) {
|
|
3797
4133
|
this.#log.error({ error: m.error }, "Event validation failed");
|
|
4134
|
+
recordRuntimeError(m.error, {
|
|
4135
|
+
surface: "event",
|
|
4136
|
+
direction: "consumer",
|
|
4137
|
+
operation: String(event),
|
|
4138
|
+
phase: "input_validation",
|
|
4139
|
+
});
|
|
3798
4140
|
continue;
|
|
3799
4141
|
}
|
|
3800
4142
|
|
|
@@ -3807,6 +4149,12 @@ export class Trellis<
|
|
|
3807
4149
|
});
|
|
3808
4150
|
const handlerValue = handlerResult.take();
|
|
3809
4151
|
if (isErr(handlerValue)) {
|
|
4152
|
+
recordRuntimeError(handlerValue.error, {
|
|
4153
|
+
surface: "event",
|
|
4154
|
+
direction: "consumer",
|
|
4155
|
+
operation: String(event),
|
|
4156
|
+
phase: "handler_result",
|
|
4157
|
+
});
|
|
3810
4158
|
this.#log.error(
|
|
3811
4159
|
{
|
|
3812
4160
|
error: handlerValue.error.toSerializable(),
|
|
@@ -4124,6 +4472,12 @@ export class Trellis<
|
|
|
4124
4472
|
{ error: eventPayload.error },
|
|
4125
4473
|
"Event validation failed",
|
|
4126
4474
|
);
|
|
4475
|
+
recordRuntimeError(eventPayload.error, {
|
|
4476
|
+
surface: "event",
|
|
4477
|
+
direction: "consumer",
|
|
4478
|
+
operation: String(registration.event),
|
|
4479
|
+
phase: "input_validation",
|
|
4480
|
+
});
|
|
4127
4481
|
msg.term();
|
|
4128
4482
|
failed = true;
|
|
4129
4483
|
break;
|
|
@@ -4139,6 +4493,12 @@ export class Trellis<
|
|
|
4139
4493
|
});
|
|
4140
4494
|
const handlerValue = handlerResult.take();
|
|
4141
4495
|
if (isErr(handlerValue)) {
|
|
4496
|
+
recordRuntimeError(handlerValue.error, {
|
|
4497
|
+
surface: "event",
|
|
4498
|
+
direction: "consumer",
|
|
4499
|
+
operation: String(registration.event),
|
|
4500
|
+
phase: "handler_result",
|
|
4501
|
+
});
|
|
4142
4502
|
this.#log.error(
|
|
4143
4503
|
{
|
|
4144
4504
|
error: handlerValue.error.toSerializable(),
|
|
@@ -4348,6 +4708,12 @@ export class Trellis<
|
|
|
4348
4708
|
code: SpanStatusCode.ERROR,
|
|
4349
4709
|
message: response.error.message,
|
|
4350
4710
|
});
|
|
4711
|
+
recordRuntimeError(response.error, {
|
|
4712
|
+
surface: "operation",
|
|
4713
|
+
direction: "client",
|
|
4714
|
+
operation: "requestJson",
|
|
4715
|
+
phase: "request_send",
|
|
4716
|
+
});
|
|
4351
4717
|
return response;
|
|
4352
4718
|
}
|
|
4353
4719
|
|
|
@@ -4365,6 +4731,12 @@ export class Trellis<
|
|
|
4365
4731
|
code: SpanStatusCode.ERROR,
|
|
4366
4732
|
message: error.message,
|
|
4367
4733
|
});
|
|
4734
|
+
recordRuntimeError(error, {
|
|
4735
|
+
surface: "operation",
|
|
4736
|
+
direction: "client",
|
|
4737
|
+
operation: "requestJson",
|
|
4738
|
+
phase: "response_decoding",
|
|
4739
|
+
});
|
|
4368
4740
|
return err(error);
|
|
4369
4741
|
}
|
|
4370
4742
|
|
|
@@ -4377,6 +4749,12 @@ export class Trellis<
|
|
|
4377
4749
|
message: error.message,
|
|
4378
4750
|
});
|
|
4379
4751
|
span.recordException(error);
|
|
4752
|
+
recordRuntimeError(error, {
|
|
4753
|
+
surface: "operation",
|
|
4754
|
+
direction: "client",
|
|
4755
|
+
operation: "requestJson",
|
|
4756
|
+
phase: "unexpected",
|
|
4757
|
+
});
|
|
4380
4758
|
return err(error);
|
|
4381
4759
|
} finally {
|
|
4382
4760
|
span.end();
|
|
@@ -4413,40 +4791,61 @@ export class Trellis<
|
|
|
4413
4791
|
await this.nats.flush();
|
|
4414
4792
|
} catch (cause) {
|
|
4415
4793
|
sub.unsubscribe();
|
|
4416
|
-
|
|
4794
|
+
const error = createTransportError({
|
|
4417
4795
|
code: "trellis.watch.failed",
|
|
4418
4796
|
message: "Trellis could not start the operation watch.",
|
|
4419
4797
|
hint:
|
|
4420
4798
|
"Retry watching the operation. If it keeps failing, reconnect to Trellis and try again.",
|
|
4421
4799
|
cause,
|
|
4422
4800
|
context: { subject },
|
|
4423
|
-
})
|
|
4801
|
+
});
|
|
4802
|
+
recordRuntimeError(error, {
|
|
4803
|
+
surface: "operation",
|
|
4804
|
+
direction: "client",
|
|
4805
|
+
operation: "watchJson",
|
|
4806
|
+
phase: "request_send",
|
|
4807
|
+
});
|
|
4808
|
+
return err(error);
|
|
4424
4809
|
}
|
|
4425
4810
|
|
|
4426
4811
|
return ok((async function* () {
|
|
4427
4812
|
try {
|
|
4428
4813
|
for await (const msg of sub) {
|
|
4429
4814
|
if (msg.headers?.get("status") === "error") {
|
|
4430
|
-
|
|
4815
|
+
const error = createTransportError({
|
|
4431
4816
|
code: "trellis.watch.failed",
|
|
4432
4817
|
message: "Trellis stopped the operation watch.",
|
|
4433
4818
|
hint:
|
|
4434
4819
|
"Retry watching the operation. If it keeps happening, reconnect to Trellis and try again.",
|
|
4435
4820
|
context: { subject, frame: msg.string() },
|
|
4436
|
-
})
|
|
4821
|
+
});
|
|
4822
|
+
recordRuntimeError(error, {
|
|
4823
|
+
surface: "operation",
|
|
4824
|
+
direction: "client",
|
|
4825
|
+
operation: "watchJson",
|
|
4826
|
+
phase: "remote_error",
|
|
4827
|
+
});
|
|
4828
|
+
yield err(error);
|
|
4437
4829
|
continue;
|
|
4438
4830
|
}
|
|
4439
4831
|
|
|
4440
4832
|
const json = safeJson(msg).take();
|
|
4441
4833
|
if (isErr(json)) {
|
|
4442
|
-
|
|
4834
|
+
const error = createTransportError({
|
|
4443
4835
|
code: "trellis.watch.invalid_response",
|
|
4444
4836
|
message: "Trellis returned an invalid watch update.",
|
|
4445
4837
|
hint:
|
|
4446
4838
|
"Retry watching the operation. If it keeps happening, reconnect to Trellis and try again.",
|
|
4447
4839
|
cause: json.error.cause,
|
|
4448
4840
|
context: { subject },
|
|
4449
|
-
})
|
|
4841
|
+
});
|
|
4842
|
+
recordRuntimeError(error, {
|
|
4843
|
+
surface: "operation",
|
|
4844
|
+
direction: "client",
|
|
4845
|
+
operation: "watchJson",
|
|
4846
|
+
phase: "response_decoding",
|
|
4847
|
+
});
|
|
4848
|
+
yield err(error);
|
|
4450
4849
|
continue;
|
|
4451
4850
|
}
|
|
4452
4851
|
|