@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/esm/client.d.ts +2 -0
- package/esm/client.d.ts.map +1 -1
- package/esm/client.js +2 -0
- package/esm/client_connect.d.ts +3 -2
- package/esm/client_connect.d.ts.map +1 -1
- package/esm/client_connect.js +4 -1
- package/esm/server/health.d.ts.map +1 -1
- package/esm/server/health.js +34 -3
- package/esm/server/service.d.ts.map +1 -1
- package/esm/server/service.js +33 -3
- package/esm/server.d.ts.map +1 -1
- package/esm/server.js +49 -8
- package/esm/trellis.d.ts +28 -4
- package/esm/trellis.d.ts.map +1 -1
- package/esm/trellis.js +117 -26
- package/package.json +2 -2
- package/script/client.d.ts +2 -0
- package/script/client.d.ts.map +1 -1
- package/script/client.js +2 -0
- package/script/client_connect.d.ts +3 -2
- package/script/client_connect.d.ts.map +1 -1
- package/script/client_connect.js +4 -1
- package/script/server/health.d.ts.map +1 -1
- package/script/server/health.js +34 -3
- package/script/server/service.d.ts.map +1 -1
- package/script/server/service.js +32 -2
- package/script/server.d.ts.map +1 -1
- package/script/server.js +48 -7
- package/script/trellis.d.ts +28 -4
- package/script/trellis.d.ts.map +1 -1
- package/script/trellis.js +118 -26
- package/src/client.ts +4 -0
- package/src/client_connect.ts +11 -9
- package/src/server/health.ts +41 -3
- package/src/server/service.ts +48 -3
- package/src/server.ts +62 -7
- package/src/trellis.ts +167 -55
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
|
|
1547
|
-
|
|
1548
|
-
:
|
|
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
|
|
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
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
:
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
}
|
package/src/client_connect.ts
CHANGED
|
@@ -224,13 +224,12 @@ const ClientTransportsSchema = Type.Object({
|
|
|
224
224
|
type ClientConnectDeps = {
|
|
225
225
|
loadTransport(): Promise<RuntimeTransport>;
|
|
226
226
|
now(): number;
|
|
227
|
-
setInterval?: (
|
|
228
|
-
|
|
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
|
-
):
|
|
804
|
-
globalThis.setInterval(handler, ms));
|
|
802
|
+
): IntervalHandle => globalThis.setInterval(handler, ms));
|
|
805
803
|
const clearRefreshInterval = args.deps.clearInterval ??
|
|
806
|
-
((id:
|
|
807
|
-
globalThis.clearInterval(
|
|
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);
|
package/src/server/health.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
362
|
-
return
|
|
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">> {
|
package/src/server/service.ts
CHANGED
|
@@ -77,7 +77,10 @@ import type {
|
|
|
77
77
|
RpcHandlerContext,
|
|
78
78
|
RpcHandlerErrorOf,
|
|
79
79
|
} from "../trellis.js";
|
|
80
|
-
import {
|
|
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
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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 = (
|
|
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:
|
|
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
|
|
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(
|
|
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(
|
|
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
|
}
|