@qlever-llc/trellis 0.10.10 → 0.10.11

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/script/trellis.js CHANGED
@@ -10,7 +10,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
10
10
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11
11
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
12
  };
13
- var _Trellis_instances, _Trellis_log, _Trellis_tasks, _Trellis_hasExplicitApi, _Trellis_noResponderMaxRetries, _Trellis_noResponderRetryMs, _Trellis_onSessionNotFound, _Trellis_operationStore, _Trellis_eventConsumers, _Trellis_durableEventLoops, _Trellis_createStateFacade, _Trellis_createRpcFacade, _Trellis_createRpcHandleFacade, _Trellis_createEventFacade, _Trellis_createEventPublishFacade, _Trellis_createFeedFacade, _Trellis_createHandlerTrellis, _Trellis_createOperationFacade, _Trellis_unknownApiError, _Trellis_requestBuiltRpcUnknown, _Trellis_requestBuiltRpc, _Trellis_handleBrowserAuthRequired, _Trellis_authenticateFeedRequest, _Trellis_subscribeFeed, _Trellis_handleFeed, _Trellis_processFeedMessage, _Trellis_handleRPC, _Trellis_processRPCMessage, _Trellis_respondWithPayload, _Trellis_respondWithError, _Trellis_startEphemeralEvent, _Trellis_resolveEventConsumerGroup, _Trellis_registerDurableEventHandler, _Trellis_startDurableEventConsumer, _Trellis_durableEventConsumerGroupReady, _Trellis_runDurableEventConsumer, _Trellis_handleDurableEvent, _Trellis_handleDurableEventConsumer, _Trellis_parseEventMessage, _Trellis_escapeSubjectToken, _Trellis_currentIat, _Trellis_createProof, _Trellis_requestMessageWithRetry, _Trellis_requestJson, _Trellis_watchJson;
13
+ var _Trellis_instances, _Trellis_log, _Trellis_tasks, _Trellis_hasExplicitApi, _Trellis_noResponderMaxRetries, _Trellis_noResponderRetryMs, _Trellis_onSessionNotFound, _Trellis_operationStore, _Trellis_eventConsumers, _Trellis_durableEventLoops, _Trellis_createStateFacade, _Trellis_createRpcFacade, _Trellis_createRpcHandleFacade, _Trellis_createEventFacade, _Trellis_createEventPublishFacade, _Trellis_createFeedFacade, _Trellis_createHandlerTrellis, _Trellis_createOperationFacade, _Trellis_unknownApiError, _Trellis_requestBuiltRpcUnknown, _Trellis_requestBuiltRpc, _Trellis_handleBrowserAuthRequired, _Trellis_authenticateFeedRequest, _Trellis_subscribeFeed, _Trellis_handleFeed, _Trellis_processFeedMessage, _Trellis_handleRPC, _Trellis_processRPCMessage, _Trellis_respondWithPayload, _Trellis_respondWithError, _Trellis_startEphemeralEvent, _Trellis_invokeEventHandler, _Trellis_resolveEventConsumerGroup, _Trellis_registerDurableEventHandler, _Trellis_startDurableEventConsumer, _Trellis_durableEventConsumerGroupReady, _Trellis_runDurableEventConsumer, _Trellis_handleDurableEvent, _Trellis_handleDurableEventConsumer, _Trellis_parseEventMessage, _Trellis_escapeSubjectToken, _Trellis_currentIat, _Trellis_createProof, _Trellis_requestMessageWithRetry, _Trellis_requestJson, _Trellis_watchJson;
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.Trellis = exports.DurableOperationRecordSchema = void 0;
16
16
  exports.safeJson = safeJson;
@@ -21,6 +21,7 @@ exports.sha256 = sha256;
21
21
  exports.buildProofInput = buildProofInput;
22
22
  exports.isOperationDeferred = isOperationDeferred;
23
23
  exports.isResultLike = isResultLike;
24
+ exports.annotateHandlerBoundaryError = annotateHandlerBoundaryError;
24
25
  exports.buildRuntimeOperationSnapshot = buildRuntimeOperationSnapshot;
25
26
  exports.isTerminalRuntimeOperationSnapshot = isTerminalRuntimeOperationSnapshot;
26
27
  exports.createTrellisInternal = createTrellisInternal;
@@ -204,6 +205,21 @@ function isOperationDeferred(value) {
204
205
  function isResultLike(value) {
205
206
  return value instanceof result_1.Result;
206
207
  }
208
+ function compactHandlerErrorContext(context) {
209
+ return Object.fromEntries(Object.entries(context).filter(([key, value]) => key !== "traceId" && value !== undefined));
210
+ }
211
+ function sanitizeHandlerErrorContext(error) {
212
+ delete error.getContext().subject;
213
+ }
214
+ function annotateHandlerBoundaryError(cause, context) {
215
+ const error = cause instanceof result_1.BaseError && !(cause instanceof RemoteError_js_1.RemoteError)
216
+ ? cause
217
+ : new index_js_1.UnexpectedError({ cause });
218
+ sanitizeHandlerErrorContext(error);
219
+ error.withContext(compactHandlerErrorContext(context));
220
+ error.withTraceId(context.traceId);
221
+ return error;
222
+ }
207
223
  const DurableOperationSignalSchema = typebox_1.Type.Object({
208
224
  operationId: typebox_1.Type.String(),
209
225
  sequence: typebox_1.Type.Number(),
@@ -234,8 +250,11 @@ const DurableOperationSnapshotSchema = typebox_1.Type.Object({
234
250
  })),
235
251
  output: typebox_1.Type.Optional(typebox_1.Type.Any()),
236
252
  error: typebox_1.Type.Optional(typebox_1.Type.Object({
253
+ id: typebox_1.Type.Optional(typebox_1.Type.String()),
237
254
  type: typebox_1.Type.String(),
238
255
  message: typebox_1.Type.String(),
256
+ context: typebox_1.Type.Optional(typebox_1.Type.Record(typebox_1.Type.String(), typebox_1.Type.Unknown())),
257
+ traceId: typebox_1.Type.Optional(typebox_1.Type.String()),
239
258
  })),
240
259
  });
241
260
  exports.DurableOperationRecordSchema = typebox_1.Type.Object({
@@ -741,6 +760,18 @@ class Trellis {
741
760
  writable: true,
742
761
  value: void 0
743
762
  });
763
+ Object.defineProperty(this, "contractId", {
764
+ enumerable: true,
765
+ configurable: true,
766
+ writable: true,
767
+ value: void 0
768
+ });
769
+ Object.defineProperty(this, "contractDigest", {
770
+ enumerable: true,
771
+ configurable: true,
772
+ writable: true,
773
+ value: void 0
774
+ });
744
775
  Object.defineProperty(this, "nats", {
745
776
  enumerable: true,
746
777
  configurable: true,
@@ -783,6 +814,8 @@ class Trellis {
783
814
  __classPrivateFieldSet(this, _Trellis_log, (opts?.log ?? globals_js_1.logger).child({ lib: "trellis" }), "f");
784
815
  this.timeout = opts?.timeout ?? 3000;
785
816
  this.stream = opts?.stream ?? "trellis";
817
+ this.contractId = opts?.contractId;
818
+ this.contractDigest = opts?.contractDigest;
786
819
  __classPrivateFieldSet(this, _Trellis_hasExplicitApi, api !== undefined, "f");
787
820
  __classPrivateFieldSet(this, _Trellis_noResponderMaxRetries, opts?.noResponderRetry?.maxAttempts ??
788
821
  DEFAULT_NO_RESPONDER_MAX_RETRIES, "f");
@@ -1543,9 +1576,14 @@ _Trellis_log = new WeakMap(), _Trellis_tasks = new WeakMap(), _Trellis_hasExplic
1543
1576
  }
1544
1577
  }
1545
1578
  catch (cause) {
1546
- const error = cause instanceof result_1.BaseError
1547
- ? cause
1548
- : new index_js_1.UnexpectedError({ cause });
1579
+ const error = annotateHandlerBoundaryError(cause, {
1580
+ feed,
1581
+ requestId: msg.headers?.get("request-id"),
1582
+ service: this.name,
1583
+ contractId: this.contractId,
1584
+ contractDigest: this.contractDigest,
1585
+ traceId: traceIdFromTraceparent(msg.headers?.get("traceparent")),
1586
+ });
1549
1587
  __classPrivateFieldGet(this, _Trellis_instances, "m", _Trellis_respondWithError).call(this, msg, error);
1550
1588
  }
1551
1589
  })();
@@ -1580,7 +1618,7 @@ _Trellis_log = new WeakMap(), _Trellis_tasks = new WeakMap(), _Trellis_hasExplic
1580
1618
  await this.nats.flush();
1581
1619
  const controller = new AbortController();
1582
1620
  try {
1583
- await handler({
1621
+ const handlerResult = await handler({
1584
1622
  input: parsed,
1585
1623
  caller: callerValue,
1586
1624
  signal: controller.signal,
@@ -1598,6 +1636,19 @@ _Trellis_log = new WeakMap(), _Trellis_tasks = new WeakMap(), _Trellis_hasExplic
1598
1636
  return (0, result_1.ok)(undefined);
1599
1637
  })()),
1600
1638
  });
1639
+ const handlerOutcome = isResultLike(handlerResult)
1640
+ ? handlerResult.take()
1641
+ : handlerResult;
1642
+ if ((0, result_1.isErr)(handlerOutcome)) {
1643
+ return (0, result_1.err)(annotateHandlerBoundaryError(handlerOutcome.error, {
1644
+ feed,
1645
+ requestId: msg.headers?.get("request-id"),
1646
+ service: this.name,
1647
+ contractId: this.contractId,
1648
+ contractDigest: this.contractDigest,
1649
+ traceId: traceIdFromTraceparent(msg.headers?.get("traceparent")),
1650
+ }));
1651
+ }
1601
1652
  return (0, result_1.ok)(undefined);
1602
1653
  }
1603
1654
  finally {
@@ -1830,7 +1881,14 @@ _Trellis_log = new WeakMap(), _Trellis_tasks = new WeakMap(), _Trellis_hasExplic
1830
1881
  client: handlerTrellis,
1831
1882
  })));
1832
1883
  if (handlerResultWrapped.isErr()) {
1833
- const error = handlerResultWrapped.error.withContext({ method });
1884
+ const error = annotateHandlerBoundaryError(handlerResultWrapped.error, {
1885
+ method: String(method),
1886
+ requestId: msg.headers?.get("request-id"),
1887
+ service: this.name,
1888
+ contractId: this.contractId,
1889
+ contractDigest: this.contractDigest,
1890
+ traceId: activeTraceId(span) ?? incomingTraceId,
1891
+ });
1834
1892
  __classPrivateFieldGet(this, _Trellis_log, "f").error({
1835
1893
  method,
1836
1894
  error: error.message,
@@ -1848,11 +1906,14 @@ _Trellis_log = new WeakMap(), _Trellis_tasks = new WeakMap(), _Trellis_hasExplic
1848
1906
  const handlerResult = handlerResultWrapped.take();
1849
1907
  const handlerOutcome = handlerResult.take();
1850
1908
  if ((0, result_1.isErr)(handlerOutcome)) {
1851
- const handlerError = handlerOutcome.error;
1852
- const error = handlerError instanceof result_1.BaseError &&
1853
- !(handlerError instanceof RemoteError_js_1.RemoteError)
1854
- ? handlerError
1855
- : new index_js_1.UnexpectedError({ cause: handlerError });
1909
+ const error = annotateHandlerBoundaryError(handlerOutcome.error, {
1910
+ method: String(method),
1911
+ requestId: msg.headers?.get("request-id"),
1912
+ service: this.name,
1913
+ contractId: this.contractId,
1914
+ contractDigest: this.contractDigest,
1915
+ traceId: activeTraceId(span) ?? incomingTraceId,
1916
+ });
1856
1917
  __classPrivateFieldGet(this, _Trellis_log, "f").error({
1857
1918
  method,
1858
1919
  error: error.message,
@@ -1951,15 +2012,17 @@ _Trellis_log = new WeakMap(), _Trellis_tasks = new WeakMap(), _Trellis_hasExplic
1951
2012
  __classPrivateFieldGet(this, _Trellis_log, "f").error({ error: m.error }, "Event validation failed");
1952
2013
  continue;
1953
2014
  }
1954
- const handlerResult = await result_1.AsyncResult.lift(fn(m, createEventListenerContext({
2015
+ const handlerResult = await __classPrivateFieldGet(this, _Trellis_instances, "m", _Trellis_invokeEventHandler).call(this, {
2016
+ event,
1955
2017
  payload: m,
1956
- subject: msg.subject,
1957
2018
  mode: "ephemeral",
1958
2019
  message: msg,
1959
- })));
1960
- if (handlerResult.isErr()) {
2020
+ fn,
2021
+ });
2022
+ const handlerValue = handlerResult.take();
2023
+ if ((0, result_1.isErr)(handlerValue)) {
1961
2024
  __classPrivateFieldGet(this, _Trellis_log, "f").error({
1962
- error: handlerResult.error.toSerializable(),
2025
+ error: handlerValue.error.toSerializable(),
1963
2026
  event,
1964
2027
  subject: msg.subject,
1965
2028
  }, "Event handler failed");
@@ -1968,6 +2031,31 @@ _Trellis_log = new WeakMap(), _Trellis_tasks = new WeakMap(), _Trellis_hasExplic
1968
2031
  });
1969
2032
  __classPrivateFieldGet(this, _Trellis_tasks, "f").add(`event:${event}:${(0, ulid_1.ulid)()}`, task);
1970
2033
  return (0, result_1.ok)(undefined);
2034
+ }, _Trellis_invokeEventHandler = async function _Trellis_invokeEventHandler(args) {
2035
+ const annotation = {
2036
+ event: String(args.event),
2037
+ service: this.name,
2038
+ contractId: this.contractId,
2039
+ contractDigest: this.contractDigest,
2040
+ traceId: traceIdFromTraceparent(args.message.headers?.get("traceparent")),
2041
+ };
2042
+ try {
2043
+ const result = await Promise.resolve(args.fn(args.payload, createEventListenerContext({
2044
+ payload: args.payload,
2045
+ subject: args.message.subject,
2046
+ mode: args.mode,
2047
+ ...(args.group ? { group: args.group } : {}),
2048
+ message: args.message,
2049
+ })));
2050
+ const outcome = isResultLike(result) ? result.take() : result;
2051
+ if ((0, result_1.isErr)(outcome)) {
2052
+ return (0, result_1.err)(annotateHandlerBoundaryError(outcome.error, annotation));
2053
+ }
2054
+ return (0, result_1.ok)(undefined);
2055
+ }
2056
+ catch (cause) {
2057
+ return (0, result_1.err)(annotateHandlerBoundaryError(cause, annotation));
2058
+ }
1971
2059
  }, _Trellis_resolveEventConsumerGroup = function _Trellis_resolveEventConsumerGroup(event, opts) {
1972
2060
  const metadata = __classPrivateFieldGet(this, _Trellis_eventConsumers, "f").metadata;
1973
2061
  const bindings = __classPrivateFieldGet(this, _Trellis_eventConsumers, "f").bindings ?? {};
@@ -2093,15 +2181,17 @@ _Trellis_log = new WeakMap(), _Trellis_tasks = new WeakMap(), _Trellis_hasExplic
2093
2181
  msg.term();
2094
2182
  continue;
2095
2183
  }
2096
- const handlerResult = await result_1.AsyncResult.lift(fn(m, createEventListenerContext({
2184
+ const handlerResult = await __classPrivateFieldGet(this, _Trellis_instances, "m", _Trellis_invokeEventHandler).call(this, {
2185
+ event,
2097
2186
  payload: m,
2098
- subject: msg.subject,
2099
2187
  mode: "durable",
2100
2188
  message: msg,
2101
- })));
2102
- if (handlerResult.isErr()) {
2189
+ fn,
2190
+ });
2191
+ const handlerValue = handlerResult.take();
2192
+ if ((0, result_1.isErr)(handlerValue)) {
2103
2193
  __classPrivateFieldGet(this, _Trellis_log, "f").error({
2104
- error: handlerResult.error.toSerializable(),
2194
+ error: handlerValue.error.toSerializable(),
2105
2195
  event,
2106
2196
  subject: msg.subject,
2107
2197
  }, "Event handler failed");
@@ -2134,16 +2224,18 @@ _Trellis_log = new WeakMap(), _Trellis_tasks = new WeakMap(), _Trellis_hasExplic
2134
2224
  failed = true;
2135
2225
  break;
2136
2226
  }
2137
- const handlerResult = await result_1.AsyncResult.lift(registration.fn(eventPayload, createEventListenerContext({
2227
+ const handlerResult = await __classPrivateFieldGet(this, _Trellis_instances, "m", _Trellis_invokeEventHandler).call(this, {
2228
+ event: registration.event,
2138
2229
  payload: eventPayload,
2139
- subject: msg.subject,
2140
2230
  mode: "durable",
2141
2231
  group,
2142
2232
  message: msg,
2143
- })));
2144
- if (handlerResult.isErr()) {
2233
+ fn: registration.fn,
2234
+ });
2235
+ const handlerValue = handlerResult.take();
2236
+ if ((0, result_1.isErr)(handlerValue)) {
2145
2237
  __classPrivateFieldGet(this, _Trellis_log, "f").error({
2146
- error: handlerResult.error.toSerializable(),
2238
+ error: handlerValue.error.toSerializable(),
2147
2239
  event: registration.event,
2148
2240
  subject: msg.subject,
2149
2241
  }, "Event handler failed");
package/src/client.ts CHANGED
@@ -50,6 +50,8 @@ type ClientContractManifest = {
50
50
  };
51
51
 
52
52
  type ClientContractShape = {
53
+ CONTRACT_ID?: string;
54
+ CONTRACT_DIGEST?: string;
53
55
  CONTRACT: ClientContractManifest;
54
56
  API: {
55
57
  owned?: TrellisAPI;
@@ -120,6 +122,8 @@ export function createClient<
120
122
  noResponderRetry: opts?.noResponderRetry,
121
123
  api,
122
124
  state: contract[CONTRACT_STATE_METADATA],
125
+ contractId: contract.CONTRACT_ID,
126
+ contractDigest: contract.CONTRACT_DIGEST,
123
127
  },
124
128
  );
125
129
  }
@@ -224,13 +224,12 @@ const ClientTransportsSchema = Type.Object({
224
224
  type ClientConnectDeps = {
225
225
  loadTransport(): Promise<RuntimeTransport>;
226
226
  now(): number;
227
- setInterval?: (
228
- handler: () => void,
229
- ms: number,
230
- ) => ReturnType<typeof globalThis.setInterval>;
231
- clearInterval?: (id: ReturnType<typeof globalThis.setInterval>) => void;
227
+ setInterval?: (handler: () => void, ms: number) => IntervalHandle;
228
+ clearInterval?: (id: IntervalHandle) => void;
232
229
  };
233
230
 
231
+ type IntervalHandle = ReturnType<typeof globalThis.setInterval> | number;
232
+
234
233
  const ClientBootstrapReadySchema = Type.Object({
235
234
  status: Type.Literal("ready"),
236
235
  serverNow: Type.Integer(),
@@ -800,11 +799,14 @@ async function createRuntimeUserAuthenticator(args: {
800
799
  ((
801
800
  handler: () => void,
802
801
  ms: number,
803
- ): ReturnType<typeof globalThis.setInterval> =>
804
- globalThis.setInterval(handler, ms));
802
+ ): IntervalHandle => globalThis.setInterval(handler, ms));
805
803
  const clearRefreshInterval = args.deps.clearInterval ??
806
- ((id: ReturnType<typeof globalThis.setInterval>) =>
807
- globalThis.clearInterval(id));
804
+ ((id: IntervalHandle) => {
805
+ const clearIntervalFn = globalThis.clearInterval as (
806
+ id: IntervalHandle,
807
+ ) => void;
808
+ clearIntervalFn(id);
809
+ });
808
810
  const refreshIntervalId = setRefreshInterval(() => {
809
811
  void refresh();
810
812
  }, 10_000);
@@ -104,6 +104,25 @@ function summarizeHealthChecks(
104
104
  return `${failedCount} check${failedCount === 1 ? "" : "s"} failing`;
105
105
  }
106
106
 
107
+ function annotateServiceHealthCheck(
108
+ result: HealthCheckResult,
109
+ metadata: { service: string; contractId: string; contractDigest: string },
110
+ ): HealthCheckResult {
111
+ if (result.status !== "failed") {
112
+ return result;
113
+ }
114
+
115
+ return {
116
+ ...result,
117
+ info: {
118
+ ...(result.info ?? {}),
119
+ service: metadata.service,
120
+ contractId: metadata.contractId,
121
+ contractDigest: metadata.contractDigest,
122
+ },
123
+ };
124
+ }
125
+
107
126
  function normalizeLegacyHealthCheck(
108
127
  check: HealthCheckFn,
109
128
  ): ServiceHealthCheckFn {
@@ -114,6 +133,10 @@ function normalizeLegacyHealthCheck(
114
133
  status: "failed",
115
134
  error: result.error.message,
116
135
  summary: result.error.message,
136
+ info: {
137
+ errorType: result.error.name,
138
+ errorId: result.error.id,
139
+ },
117
140
  };
118
141
  }
119
142
 
@@ -206,6 +229,9 @@ export async function runServiceHealthCheck(
206
229
  status: "failed",
207
230
  error: message,
208
231
  summary: message,
232
+ info: {
233
+ errorType: error instanceof Error ? error.name : typeof error,
234
+ },
209
235
  latencyMs,
210
236
  };
211
237
  }
@@ -350,16 +376,28 @@ export class ServiceHealth {
350
376
  }
351
377
 
352
378
  async checks(): Promise<HealthCheckResult[]> {
353
- return await Promise.all(
379
+ const results = await Promise.all(
354
380
  Array.from(this.#checks.entries()).map(([name, check]) =>
355
381
  runServiceHealthCheck(name, check)
356
382
  ),
357
383
  );
384
+ return results.map((result) =>
385
+ annotateServiceHealthCheck(result, {
386
+ service: this.serviceName,
387
+ contractId: this.contractId,
388
+ contractDigest: this.contractDigest,
389
+ })
390
+ );
358
391
  }
359
392
 
360
393
  async response(): Promise<HealthResponse> {
361
- const checks = Object.fromEntries(this.#checks.entries());
362
- return await runAllServiceHealthChecks(this.serviceName, checks);
394
+ const results = await this.checks();
395
+ return {
396
+ status: summarizeHealthStatus(results),
397
+ service: this.serviceName,
398
+ timestamp: new Date().toISOString(),
399
+ checks: results,
400
+ };
363
401
  }
364
402
 
365
403
  async heartbeat(): Promise<Omit<HealthHeartbeat, "header">> {
@@ -77,7 +77,10 @@ import type {
77
77
  RpcHandlerContext,
78
78
  RpcHandlerErrorOf,
79
79
  } from "../trellis.js";
80
- import { createTrellisInternal } from "../trellis.js";
80
+ import {
81
+ annotateHandlerBoundaryError,
82
+ createTrellisInternal,
83
+ } from "../trellis.js";
81
84
  import type {
82
85
  NatsConnectFn,
83
86
  NatsConnectOpts,
@@ -1818,6 +1821,8 @@ export async function createConnectedService<
1818
1821
  stream: args.server.stream,
1819
1822
  noResponderRetry: args.server.noResponderRetry,
1820
1823
  api: runtimeApi,
1824
+ contractId: args.contractId,
1825
+ contractDigest: args.contractDigest,
1821
1826
  connection,
1822
1827
  transferSupport: {
1823
1828
  openOperationTransfer: (transferArgs) =>
@@ -1837,6 +1842,8 @@ export async function createConnectedService<
1837
1842
  stream: args.server.stream,
1838
1843
  noResponderRetry: args.server.noResponderRetry,
1839
1844
  api: runtimeApi,
1845
+ contractId: args.contractId,
1846
+ contractDigest: args.contractDigest,
1840
1847
  eventConsumers: {
1841
1848
  metadata: args.contractEventConsumers,
1842
1849
  bindings: args.bindings.eventConsumers,
@@ -2009,6 +2016,14 @@ function toUnexpectedError(cause: unknown): UnexpectedError {
2009
2016
  : new UnexpectedError({ cause });
2010
2017
  }
2011
2018
 
2019
+ function serializeJobHandlerError(error: BaseError): string {
2020
+ try {
2021
+ return JSON.stringify(error.toSerializable());
2022
+ } catch {
2023
+ return error.message;
2024
+ }
2025
+ }
2026
+
2012
2027
  function okVoid(): Result<void, never> {
2013
2028
  return Result.ok(undefined);
2014
2029
  }
@@ -2403,6 +2418,8 @@ function createJobsFacade<
2403
2418
  TKv extends ContractKvMetadata = ContractKvMetadata,
2404
2419
  >(args: {
2405
2420
  serviceName: string;
2421
+ contractId?: string;
2422
+ contractDigest?: string;
2406
2423
  nc: NatsConnection;
2407
2424
  contractJobs: TJobs;
2408
2425
  client: Trellis<TTrellisApi, TKv, TJobs>;
@@ -2566,9 +2583,35 @@ function createJobsFacade<
2566
2583
  },
2567
2584
  );
2568
2585
 
2569
- const handled = (await handler(publicJob)).take();
2586
+ const jobErrorContext = {
2587
+ jobType: queueType,
2588
+ requestId: job.context().requestId,
2589
+ service: args.serviceName,
2590
+ contractId: args.contractId,
2591
+ contractDigest: args.contractDigest,
2592
+ traceId: job.context().traceId,
2593
+ };
2594
+
2595
+ let handled: unknown | Result<never, BaseError>;
2596
+ try {
2597
+ handled = (await handler(publicJob)).take();
2598
+ } catch (cause) {
2599
+ const annotatedError = annotateHandlerBoundaryError(
2600
+ cause,
2601
+ jobErrorContext,
2602
+ );
2603
+ throw InternalJobProcessError.failed(
2604
+ serializeJobHandlerError(annotatedError),
2605
+ );
2606
+ }
2570
2607
  if (isErr(handled)) {
2571
- throw InternalJobProcessError.failed(handled.error.message);
2608
+ const annotatedError = annotateHandlerBoundaryError(
2609
+ handled.error,
2610
+ jobErrorContext,
2611
+ );
2612
+ throw InternalJobProcessError.failed(
2613
+ serializeJobHandlerError(annotatedError),
2614
+ );
2572
2615
  }
2573
2616
  return handled;
2574
2617
  },
@@ -2756,6 +2799,8 @@ export class TrellisService<
2756
2799
  this.#operationTransfer = operationTransfer;
2757
2800
  const jobs = createJobsFacade<TJobs, TTrellisApi, TKv>({
2758
2801
  serviceName: name,
2802
+ contractId: health.contractId,
2803
+ contractDigest: health.contractDigest,
2759
2804
  nc,
2760
2805
  contractJobs,
2761
2806
  client: handlerTrellis,
package/src/server.ts CHANGED
@@ -27,6 +27,7 @@ import type { LoggerLike } from "./globals.js";
27
27
  import { serverLogger } from "./server_logger.js";
28
28
  import {
29
29
  type AcceptedOperation,
30
+ annotateHandlerBoundaryError,
30
31
  type AnyTrellisAPI,
31
32
  type AuthRequestsValidateResponse,
32
33
  base64urlDecode,
@@ -199,6 +200,26 @@ function asOptionalStringRecordPointerValue(
199
200
  return ok(Object.fromEntries(entries) as Record<string, string>);
200
201
  }
201
202
 
203
+ function traceIdFromTraceparent(
204
+ traceparent: string | undefined,
205
+ ): string | undefined {
206
+ const [version, traceId, parentId, flags, extra] = traceparent?.split("-") ??
207
+ [];
208
+ if (
209
+ extra !== undefined ||
210
+ !/^[0-9a-f]{2}$/u.test(version ?? "") ||
211
+ version === "ff" ||
212
+ !/^[0-9a-f]{32}$/u.test(traceId ?? "") ||
213
+ traceId === "00000000000000000000000000000000" ||
214
+ !/^[0-9a-f]{16}$/u.test(parentId ?? "") ||
215
+ parentId === "0000000000000000" ||
216
+ !/^[0-9a-f]{2}$/u.test(flags ?? "")
217
+ ) {
218
+ return undefined;
219
+ }
220
+ return traceId;
221
+ }
222
+
202
223
  export class TrellisServiceRuntime extends Trellis<TrellisAPI, TrellisMode> {
203
224
  #version?: string;
204
225
  #log: LoggerLike;
@@ -243,7 +264,7 @@ export class TrellisServiceRuntime extends Trellis<TrellisAPI, TrellisMode> {
243
264
  }),
244
265
  fail: (operationId, error) =>
245
266
  this.#applyOperationUpdate(operationId, "failed", {
246
- patch: { error: { type: error.name, message: error.message } },
267
+ patch: { error: error.toSerializable() },
247
268
  event: { type: "failed" },
248
269
  }),
249
270
  cancel: (operationId) =>
@@ -506,7 +527,7 @@ export class TrellisServiceRuntime extends Trellis<TrellisAPI, TrellisMode> {
506
527
  }),
507
528
  fail: (error: BaseError) =>
508
529
  this.#applyControlledOperationUpdate(runtime, ctx, "failed", {
509
- patch: { error: { type: error.name, message: error.message } },
530
+ patch: { error: error.toSerializable() },
510
531
  event: { type: "failed" },
511
532
  }),
512
533
  cancel: () => {
@@ -1210,7 +1231,10 @@ export class TrellisServiceRuntime extends Trellis<TrellisAPI, TrellisMode> {
1210
1231
  runtime.waiters.clear();
1211
1232
  };
1212
1233
 
1213
- const makeOperation = (runtime: RuntimeOperationRecord) => {
1234
+ const makeOperation = (
1235
+ runtime: RuntimeOperationRecord,
1236
+ context: { requestId?: string; traceId?: string },
1237
+ ) => {
1214
1238
  const ensureActive = () => {
1215
1239
  if (runtime.terminal) {
1216
1240
  return err(
@@ -1291,11 +1315,19 @@ export class TrellisServiceRuntime extends Trellis<TrellisAPI, TrellisMode> {
1291
1315
  AsyncResult.from((async () => {
1292
1316
  const active = ensureActive();
1293
1317
  if (active) return active;
1318
+ const annotatedError = annotateHandlerBoundaryError(error, {
1319
+ operation: String(operation),
1320
+ requestId: context.requestId,
1321
+ service: this.name,
1322
+ contractId: this.contractId,
1323
+ contractDigest: this.contractDigest,
1324
+ traceId: context.traceId,
1325
+ });
1294
1326
  const snapshot = buildRuntimeOperationSnapshot(
1295
1327
  runtime,
1296
1328
  "failed",
1297
1329
  {
1298
- error: { type: error.name, message: error.message },
1330
+ error: annotatedError.toSerializable(),
1299
1331
  completedAt: now(),
1300
1332
  },
1301
1333
  );
@@ -1636,7 +1668,13 @@ export class TrellisServiceRuntime extends Trellis<TrellisAPI, TrellisMode> {
1636
1668
  msg.respond(JSON.stringify(accepted));
1637
1669
 
1638
1670
  void (async () => {
1639
- const op = makeOperation(runtime);
1671
+ const operationContext = {
1672
+ requestId: msg.headers?.get("request-id"),
1673
+ traceId: traceIdFromTraceparent(
1674
+ msg.headers?.get("traceparent"),
1675
+ ),
1676
+ };
1677
+ const op = makeOperation(runtime, operationContext);
1640
1678
  try {
1641
1679
  const handlerResult: unknown = await handler(
1642
1680
  transferSession
@@ -1656,7 +1694,17 @@ export class TrellisServiceRuntime extends Trellis<TrellisAPI, TrellisMode> {
1656
1694
  ? handlerResult.take()
1657
1695
  : handlerResult;
1658
1696
  if (isErr(handlerOutcome)) {
1659
- await op.fail(handlerOutcome.error);
1697
+ await op.fail(annotateHandlerBoundaryError(
1698
+ handlerOutcome.error,
1699
+ {
1700
+ operation: String(operation),
1701
+ requestId: operationContext.requestId,
1702
+ service: this.name,
1703
+ contractId: this.contractId,
1704
+ contractDigest: this.contractDigest,
1705
+ traceId: operationContext.traceId,
1706
+ },
1707
+ ));
1660
1708
  return;
1661
1709
  }
1662
1710
 
@@ -1676,7 +1724,14 @@ export class TrellisServiceRuntime extends Trellis<TrellisAPI, TrellisMode> {
1676
1724
  await op.complete(handlerOutcome);
1677
1725
  }
1678
1726
  } catch (cause) {
1679
- await op.fail(new UnexpectedError({ cause }));
1727
+ await op.fail(annotateHandlerBoundaryError(cause, {
1728
+ operation: String(operation),
1729
+ requestId: operationContext.requestId,
1730
+ service: this.name,
1731
+ contractId: this.contractId,
1732
+ contractDigest: this.contractDigest,
1733
+ traceId: operationContext.traceId,
1734
+ }));
1680
1735
  }
1681
1736
  })();
1682
1737
  }