@qlever-llc/trellis 0.10.11 → 0.10.13
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/contract_support/mod.d.ts +1 -0
- package/esm/contract_support/mod.d.ts.map +1 -1
- package/esm/contract_support/mod.js +1 -0
- package/esm/contract_support/schema_pointers.d.ts +6 -0
- package/esm/contract_support/schema_pointers.d.ts.map +1 -1
- package/esm/contract_support/schema_pointers.js +59 -7
- 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/contract_support/mod.d.ts +1 -0
- package/script/contract_support/mod.d.ts.map +1 -1
- package/script/contract_support/mod.js +4 -1
- package/script/contract_support/schema_pointers.d.ts +6 -0
- package/script/contract_support/schema_pointers.d.ts.map +1 -1
- package/script/contract_support/schema_pointers.js +59 -7
- 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/contract_support/mod.ts +4 -0
- package/src/contract_support/schema_pointers.ts +80 -7
- 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
|
@@ -115,6 +115,10 @@ export {
|
|
|
115
115
|
type StoreResourceBinding,
|
|
116
116
|
StoreResourceBindingSchema,
|
|
117
117
|
} from "./protocol.js";
|
|
118
|
+
export {
|
|
119
|
+
assertDataPointersExistAndAreTokenable,
|
|
120
|
+
getSubschemaAtDataPointer,
|
|
121
|
+
} from "./schema_pointers.js";
|
|
118
122
|
|
|
119
123
|
export const CONTRACT_FORMAT_V1 = "trellis.contract.v1" as const;
|
|
120
124
|
export const CATALOG_FORMAT_V1 = "trellis.catalog.v1" as const;
|
|
@@ -62,7 +62,71 @@ function decodeJsonPointerSegment(segment: string): string {
|
|
|
62
62
|
return segment.replaceAll("~1", "/").replaceAll("~0", "~");
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
type PointerResolution = {
|
|
66
|
+
found: boolean;
|
|
67
|
+
schemas: unknown[];
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
function pointerResolution(
|
|
71
|
+
found: boolean,
|
|
72
|
+
schemas: unknown[],
|
|
73
|
+
): PointerResolution {
|
|
74
|
+
return { found, schemas };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function collectSubschemas(
|
|
78
|
+
schema: unknown,
|
|
79
|
+
segments: readonly string[],
|
|
80
|
+
): PointerResolution {
|
|
81
|
+
if (segments.length === 0) return pointerResolution(true, [schema]);
|
|
82
|
+
if (!isJsonObject(schema)) return pointerResolution(false, []);
|
|
83
|
+
|
|
84
|
+
const resolved: unknown[] = [];
|
|
85
|
+
let found = false;
|
|
86
|
+
|
|
87
|
+
const properties = schema.properties;
|
|
88
|
+
const segment = segments[0];
|
|
89
|
+
if (
|
|
90
|
+
segment !== undefined && isJsonObject(properties) &&
|
|
91
|
+
Object.hasOwn(properties, segment)
|
|
92
|
+
) {
|
|
93
|
+
const direct = collectSubschemas(properties[segment], segments.slice(1));
|
|
94
|
+
found = found || direct.found;
|
|
95
|
+
resolved.push(...direct.schemas);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const allOf = schema.allOf;
|
|
99
|
+
if (Array.isArray(allOf)) {
|
|
100
|
+
for (const branch of allOf) {
|
|
101
|
+
const branchResult = collectSubschemas(branch, segments);
|
|
102
|
+
found = found || branchResult.found;
|
|
103
|
+
resolved.push(...branchResult.schemas);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
for (const key of ["anyOf", "oneOf"] as const) {
|
|
108
|
+
const variants = schema[key];
|
|
109
|
+
if (!Array.isArray(variants) || variants.length === 0) continue;
|
|
110
|
+
|
|
111
|
+
const variantSchemas: unknown[] = [];
|
|
112
|
+
let everyVariantResolved = true;
|
|
113
|
+
for (const variant of variants) {
|
|
114
|
+
const variantResult = collectSubschemas(variant, segments);
|
|
115
|
+
if (!variantResult.found) {
|
|
116
|
+
everyVariantResolved = false;
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
variantSchemas.push(...variantResult.schemas);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (everyVariantResolved) found = true;
|
|
123
|
+
resolved.push(...variantSchemas);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return pointerResolution(found, resolved);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function resolveFirstSubschema(
|
|
66
130
|
schema: unknown,
|
|
67
131
|
segments: readonly string[],
|
|
68
132
|
): unknown | undefined {
|
|
@@ -73,7 +137,7 @@ function resolveSubschema(
|
|
|
73
137
|
const variants = schema[key];
|
|
74
138
|
if (!Array.isArray(variants)) continue;
|
|
75
139
|
for (const variant of variants) {
|
|
76
|
-
const resolved =
|
|
140
|
+
const resolved = resolveFirstSubschema(variant, segments);
|
|
77
141
|
if (resolved !== undefined) return resolved;
|
|
78
142
|
}
|
|
79
143
|
}
|
|
@@ -85,7 +149,7 @@ function resolveSubschema(
|
|
|
85
149
|
if (!isJsonObject(properties) || !Object.hasOwn(properties, segment)) {
|
|
86
150
|
return undefined;
|
|
87
151
|
}
|
|
88
|
-
return
|
|
152
|
+
return resolveFirstSubschema(properties[segment], segments.slice(1));
|
|
89
153
|
}
|
|
90
154
|
|
|
91
155
|
function isTokenableSchema(schema: unknown): boolean {
|
|
@@ -131,30 +195,39 @@ function isTokenableSchema(schema: unknown): boolean {
|
|
|
131
195
|
return false;
|
|
132
196
|
}
|
|
133
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Returns the first schema node reachable by following a payload JSON Pointer.
|
|
200
|
+
*/
|
|
134
201
|
export function getSubschemaAtDataPointer(
|
|
135
202
|
schema: unknown,
|
|
136
203
|
pointer: string,
|
|
137
204
|
): unknown | undefined {
|
|
138
|
-
return
|
|
205
|
+
return resolveFirstSubschema(
|
|
139
206
|
schema,
|
|
140
207
|
pointer.slice(1).split("/").map(decodeJsonPointerSegment),
|
|
141
208
|
);
|
|
142
209
|
}
|
|
143
210
|
|
|
211
|
+
/**
|
|
212
|
+
* Verifies that event subject parameter pointers resolve to tokenable schemas.
|
|
213
|
+
*/
|
|
144
214
|
export function assertDataPointersExistAndAreTokenable(
|
|
145
215
|
name: string,
|
|
146
216
|
schema: unknown,
|
|
147
217
|
pointers: readonly string[],
|
|
148
218
|
): void {
|
|
149
219
|
for (const pointer of pointers) {
|
|
150
|
-
const
|
|
151
|
-
|
|
220
|
+
const resolved = collectSubschemas(
|
|
221
|
+
schema,
|
|
222
|
+
pointer.slice(1).split("/").map(decodeJsonPointerSegment),
|
|
223
|
+
);
|
|
224
|
+
if (!resolved.found) {
|
|
152
225
|
throw new Error(
|
|
153
226
|
`Invalid event subject param pointer '${pointer}' for event '${name}' (path not found in schema)`,
|
|
154
227
|
);
|
|
155
228
|
}
|
|
156
229
|
|
|
157
|
-
if (!isTokenableSchema
|
|
230
|
+
if (!resolved.schemas.every(isTokenableSchema)) {
|
|
158
231
|
throw new Error(
|
|
159
232
|
`Invalid event subject param pointer '${pointer}' for event '${name}' (must resolve to string/number schema)`,
|
|
160
233
|
);
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Base class for all Trellis-specific errors.
|
|
3
|
-
* Extends BaseError and relies on the traceId getter being configured via
|
|
3
|
+
* Extends BaseError and relies on the traceId getter being configured via initTelemetry.
|
|
4
4
|
*/
|
|
5
5
|
import { BaseError, type BaseErrorSchema } from "@qlever-llc/result";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Abstract base class for Trellis errors.
|
|
9
|
-
* Trellis errors automatically include traceId when
|
|
9
|
+
* Trellis errors automatically include traceId when initTelemetry() has been called
|
|
10
10
|
* and a span is active in the current context.
|
|
11
11
|
*
|
|
12
|
-
* The traceId integration is configured by the
|
|
12
|
+
* The traceId integration is configured by the telemetry module's initTelemetry() function,
|
|
13
13
|
* which sets up BaseError.traceIdGetter to retrieve the traceId from the active span.
|
|
14
14
|
*/
|
|
15
15
|
export abstract class TrellisError<
|
|
16
16
|
TData extends BaseErrorSchema = BaseErrorSchema,
|
|
17
17
|
> extends BaseError<TData> {
|
|
18
18
|
// TrellisError inherits getTraceId() from BaseError which uses the static traceIdGetter.
|
|
19
|
-
// The traceIdGetter is configured by
|
|
19
|
+
// The traceIdGetter is configured by initTelemetry() in the telemetry module.
|
|
20
20
|
}
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
createMapCarrier,
|
|
5
5
|
injectTraceContext,
|
|
6
6
|
} from "../../telemetry/carrier.js";
|
|
7
|
+
import { recordTrellisError } from "../../telemetry/mod.js";
|
|
7
8
|
|
|
8
9
|
import {
|
|
9
10
|
ActiveJob,
|
|
@@ -103,11 +104,22 @@ export class JobManager<TPayload = unknown, TResult = unknown> {
|
|
|
103
104
|
): Promise<void> {
|
|
104
105
|
const binding = this.#getQueueBinding(type);
|
|
105
106
|
const headers = headersFromJobContext(event.context);
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
107
|
+
try {
|
|
108
|
+
await this.#context.nc.publish(
|
|
109
|
+
`${binding.publishPrefix}.${jobId}.${event.eventType}`,
|
|
110
|
+
new TextEncoder().encode(JSON.stringify(event)),
|
|
111
|
+
{ headers },
|
|
112
|
+
);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
recordTrellisError(error, {
|
|
115
|
+
surface: "job",
|
|
116
|
+
direction: "worker",
|
|
117
|
+
operation: type,
|
|
118
|
+
phase: "publish",
|
|
119
|
+
messagingSystem: "nats",
|
|
120
|
+
});
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
111
123
|
}
|
|
112
124
|
|
|
113
125
|
async create(
|
|
@@ -247,6 +259,12 @@ export class JobManager<TPayload = unknown, TResult = unknown> {
|
|
|
247
259
|
if (error instanceof JobProcessError) {
|
|
248
260
|
const detail = error.message;
|
|
249
261
|
if (error.kind === "retryable") {
|
|
262
|
+
recordTrellisError(error, {
|
|
263
|
+
surface: "job",
|
|
264
|
+
direction: "worker",
|
|
265
|
+
operation: job.type,
|
|
266
|
+
phase: "handler_result",
|
|
267
|
+
});
|
|
250
268
|
await this.#publishJobEvent(job.type, job.id, {
|
|
251
269
|
jobId: job.id,
|
|
252
270
|
service: job.service,
|
|
@@ -262,6 +280,12 @@ export class JobManager<TPayload = unknown, TResult = unknown> {
|
|
|
262
280
|
return { outcome: "retry", tries, error: detail };
|
|
263
281
|
}
|
|
264
282
|
|
|
283
|
+
recordTrellisError(error, {
|
|
284
|
+
surface: "job",
|
|
285
|
+
direction: "worker",
|
|
286
|
+
operation: job.type,
|
|
287
|
+
phase: "handler_result",
|
|
288
|
+
});
|
|
265
289
|
await this.#publishJobEvent(job.type, job.id, {
|
|
266
290
|
jobId: job.id,
|
|
267
291
|
service: job.service,
|
|
@@ -277,6 +301,12 @@ export class JobManager<TPayload = unknown, TResult = unknown> {
|
|
|
277
301
|
return { outcome: "failed", tries, error: detail };
|
|
278
302
|
}
|
|
279
303
|
|
|
304
|
+
recordTrellisError(error, {
|
|
305
|
+
surface: "job",
|
|
306
|
+
direction: "worker",
|
|
307
|
+
operation: job.type,
|
|
308
|
+
phase: "runtime",
|
|
309
|
+
});
|
|
280
310
|
throw error;
|
|
281
311
|
}
|
|
282
312
|
}
|
package/src/server/runtime.ts
CHANGED
|
@@ -20,8 +20,12 @@ export type NatsCredsAuthenticatorFn = (creds: Uint8Array) => unknown;
|
|
|
20
20
|
|
|
21
21
|
export type ReadFileSyncFn = (path: string) => Uint8Array;
|
|
22
22
|
|
|
23
|
+
/** Initializes telemetry for a service runtime. */
|
|
24
|
+
export type InitTelemetryFn = (serviceName: string) => void;
|
|
25
|
+
|
|
23
26
|
export type TrellisServiceRuntimeDeps = {
|
|
24
27
|
connect: NatsConnectFn;
|
|
25
28
|
credsAuthenticator?: NatsCredsAuthenticatorFn;
|
|
26
29
|
readFileSync?: ReadFileSyncFn;
|
|
30
|
+
initTelemetry?: InitTelemetryFn;
|
|
27
31
|
};
|
package/src/server/service.ts
CHANGED
|
@@ -128,6 +128,7 @@ import {
|
|
|
128
128
|
observeNatsTrellisConnection,
|
|
129
129
|
type TrellisConnection,
|
|
130
130
|
} from "../connection.js";
|
|
131
|
+
import { initTelemetry } from "../telemetry/init.js";
|
|
131
132
|
|
|
132
133
|
type ExtraNatsConnectOpts = Omit<
|
|
133
134
|
NatsConnectOpts,
|
|
@@ -431,6 +432,7 @@ async function loadDefaultServiceRuntimeDeps(): Promise<
|
|
|
431
432
|
> {
|
|
432
433
|
const transport = await loadDefaultRuntimeTransport();
|
|
433
434
|
return {
|
|
435
|
+
initTelemetry,
|
|
434
436
|
connect: (
|
|
435
437
|
{ servers, token, authenticator, inboxPrefix, ...extraOptions },
|
|
436
438
|
) =>
|
|
@@ -444,6 +446,12 @@ async function loadDefaultServiceRuntimeDeps(): Promise<
|
|
|
444
446
|
};
|
|
445
447
|
}
|
|
446
448
|
|
|
449
|
+
function automaticTelemetryEnabled(
|
|
450
|
+
telemetry: TrellisServiceConnectTelemetryOpts | undefined,
|
|
451
|
+
): boolean {
|
|
452
|
+
return telemetry !== false && telemetry?.enabled !== false;
|
|
453
|
+
}
|
|
454
|
+
|
|
447
455
|
const ServiceBootstrapReadySchema = Type.Object({
|
|
448
456
|
status: Type.Literal("ready"),
|
|
449
457
|
serverNow: Type.Integer(),
|
|
@@ -871,9 +879,20 @@ export type TrellisServiceConnectOpts<
|
|
|
871
879
|
contract: ServiceContract<TOwnedApi, TTrellisApi>;
|
|
872
880
|
name: string;
|
|
873
881
|
sessionKeySeed: string;
|
|
882
|
+
/**
|
|
883
|
+
* Controls automatic telemetry initialization for this service connection.
|
|
884
|
+
* Enabled by default; pass `false` or `{ enabled: false }` to disable it.
|
|
885
|
+
*/
|
|
886
|
+
telemetry?: TrellisServiceConnectTelemetryOpts;
|
|
874
887
|
server?: TrellisServiceServerOpts;
|
|
875
888
|
};
|
|
876
889
|
|
|
890
|
+
/** Controls automatic telemetry initialization for `TrellisService.connect()`. */
|
|
891
|
+
export type TrellisServiceConnectTelemetryOpts = false | {
|
|
892
|
+
/** Whether automatic telemetry initialization is enabled. Defaults to `true`. */
|
|
893
|
+
enabled?: boolean;
|
|
894
|
+
};
|
|
895
|
+
|
|
877
896
|
type ServiceKvFacade<TKv extends ContractKvMetadata> = {
|
|
878
897
|
[K in keyof TKv]: TKv[K]["required"] extends false
|
|
879
898
|
? TypedKV<TKv[K]["schema"]> | undefined
|
|
@@ -1760,6 +1779,11 @@ export type TrellisServiceConnectArgs<
|
|
|
1760
1779
|
contract: TContract;
|
|
1761
1780
|
name: string;
|
|
1762
1781
|
sessionKeySeed: string;
|
|
1782
|
+
/**
|
|
1783
|
+
* Controls automatic telemetry initialization for this service connection.
|
|
1784
|
+
* Enabled by default; pass `false` or `{ enabled: false }` to disable it.
|
|
1785
|
+
*/
|
|
1786
|
+
telemetry?: TrellisServiceConnectTelemetryOpts;
|
|
1763
1787
|
server?: TrellisServiceServerOpts;
|
|
1764
1788
|
};
|
|
1765
1789
|
|
|
@@ -3142,6 +3166,9 @@ export class TrellisService<
|
|
|
3142
3166
|
...(await loadDefaultServiceRuntimeDeps()),
|
|
3143
3167
|
...deps,
|
|
3144
3168
|
} satisfies TrellisServiceRuntimeDeps;
|
|
3169
|
+
if (automaticTelemetryEnabled(args.telemetry)) {
|
|
3170
|
+
runtimeDeps.initTelemetry?.(args.name);
|
|
3171
|
+
}
|
|
3145
3172
|
const auth = await createAuth({ sessionKeySeed: args.sessionKeySeed });
|
|
3146
3173
|
const bootstrapLog = resolveServiceLogger(args.server?.log);
|
|
3147
3174
|
const bootstrap = await fetchServiceBootstrapInfo({
|
package/src/server.ts
CHANGED
|
@@ -25,6 +25,10 @@ import {
|
|
|
25
25
|
import { RemoteError } from "./errors/RemoteError.js";
|
|
26
26
|
import type { LoggerLike } from "./globals.js";
|
|
27
27
|
import { serverLogger } from "./server_logger.js";
|
|
28
|
+
import {
|
|
29
|
+
recordTrellisError,
|
|
30
|
+
type TrellisErrorMetricAttributes,
|
|
31
|
+
} from "./telemetry/mod.js";
|
|
28
32
|
import {
|
|
29
33
|
type AcceptedOperation,
|
|
30
34
|
annotateHandlerBoundaryError,
|
|
@@ -220,6 +224,18 @@ function traceIdFromTraceparent(
|
|
|
220
224
|
return traceId;
|
|
221
225
|
}
|
|
222
226
|
|
|
227
|
+
function recordOperationServerError(
|
|
228
|
+
error: unknown,
|
|
229
|
+
attributes: TrellisErrorMetricAttributes,
|
|
230
|
+
): void {
|
|
231
|
+
recordTrellisError(error, {
|
|
232
|
+
messagingSystem: "nats",
|
|
233
|
+
surface: "operation",
|
|
234
|
+
direction: "server",
|
|
235
|
+
...attributes,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
223
239
|
export class TrellisServiceRuntime extends Trellis<TrellisAPI, TrellisMode> {
|
|
224
240
|
#version?: string;
|
|
225
241
|
#log: LoggerLike;
|
|
@@ -937,6 +953,10 @@ export class TrellisServiceRuntime extends Trellis<TrellisAPI, TrellisMode> {
|
|
|
937
953
|
const trellisError = error instanceof BaseError
|
|
938
954
|
? error
|
|
939
955
|
: new UnexpectedError({ cause: error });
|
|
956
|
+
recordOperationServerError(trellisError, {
|
|
957
|
+
operation,
|
|
958
|
+
phase: "control",
|
|
959
|
+
});
|
|
940
960
|
msg.respond(JSON.stringify({
|
|
941
961
|
kind: "error",
|
|
942
962
|
error: trellisError.toSerializable(),
|
|
@@ -1539,6 +1559,10 @@ export class TrellisServiceRuntime extends Trellis<TrellisAPI, TrellisMode> {
|
|
|
1539
1559
|
const validated = await authenticate(msg, true);
|
|
1540
1560
|
const value = validated.take();
|
|
1541
1561
|
if (isErr(value)) {
|
|
1562
|
+
recordOperationServerError(value.error, {
|
|
1563
|
+
operation: String(operation),
|
|
1564
|
+
phase: "start",
|
|
1565
|
+
});
|
|
1542
1566
|
this.respondWithError(msg, value.error);
|
|
1543
1567
|
continue;
|
|
1544
1568
|
}
|
|
@@ -1546,15 +1570,20 @@ export class TrellisServiceRuntime extends Trellis<TrellisAPI, TrellisMode> {
|
|
|
1546
1570
|
let transferSession: RuntimeOperationTransferSession | undefined;
|
|
1547
1571
|
if (ctx.transfer) {
|
|
1548
1572
|
if (!this.#transferSupport) {
|
|
1573
|
+
const error = new UnexpectedError({
|
|
1574
|
+
cause: new Error(
|
|
1575
|
+
`Operation '${
|
|
1576
|
+
String(operation)
|
|
1577
|
+
}' declared transfer support but no runtime transfer support is configured`,
|
|
1578
|
+
),
|
|
1579
|
+
});
|
|
1580
|
+
recordOperationServerError(error, {
|
|
1581
|
+
operation: String(operation),
|
|
1582
|
+
phase: "start",
|
|
1583
|
+
});
|
|
1549
1584
|
this.respondWithError(
|
|
1550
1585
|
msg,
|
|
1551
|
-
|
|
1552
|
-
cause: new Error(
|
|
1553
|
-
`Operation '${
|
|
1554
|
-
String(operation)
|
|
1555
|
-
}' declared transfer support but no runtime transfer support is configured`,
|
|
1556
|
-
),
|
|
1557
|
-
}),
|
|
1586
|
+
error,
|
|
1558
1587
|
);
|
|
1559
1588
|
continue;
|
|
1560
1589
|
}
|
|
@@ -1566,6 +1595,10 @@ export class TrellisServiceRuntime extends Trellis<TrellisAPI, TrellisMode> {
|
|
|
1566
1595
|
"key",
|
|
1567
1596
|
).take();
|
|
1568
1597
|
if (isErr(key)) {
|
|
1598
|
+
recordOperationServerError(key.error, {
|
|
1599
|
+
operation: String(operation),
|
|
1600
|
+
phase: "start",
|
|
1601
|
+
});
|
|
1569
1602
|
this.respondWithError(msg, key.error);
|
|
1570
1603
|
continue;
|
|
1571
1604
|
}
|
|
@@ -1575,6 +1608,10 @@ export class TrellisServiceRuntime extends Trellis<TrellisAPI, TrellisMode> {
|
|
|
1575
1608
|
ctx.transfer.contentType,
|
|
1576
1609
|
).take();
|
|
1577
1610
|
if (isErr(contentType)) {
|
|
1611
|
+
recordOperationServerError(contentType.error, {
|
|
1612
|
+
operation: String(operation),
|
|
1613
|
+
phase: "start",
|
|
1614
|
+
});
|
|
1578
1615
|
this.respondWithError(msg, contentType.error);
|
|
1579
1616
|
continue;
|
|
1580
1617
|
}
|
|
@@ -1584,6 +1621,10 @@ export class TrellisServiceRuntime extends Trellis<TrellisAPI, TrellisMode> {
|
|
|
1584
1621
|
ctx.transfer.metadata,
|
|
1585
1622
|
).take();
|
|
1586
1623
|
if (isErr(metadata)) {
|
|
1624
|
+
recordOperationServerError(metadata.error, {
|
|
1625
|
+
operation: String(operation),
|
|
1626
|
+
phase: "start",
|
|
1627
|
+
});
|
|
1587
1628
|
this.respondWithError(msg, metadata.error);
|
|
1588
1629
|
continue;
|
|
1589
1630
|
}
|
|
@@ -1601,6 +1642,10 @@ export class TrellisServiceRuntime extends Trellis<TrellisAPI, TrellisMode> {
|
|
|
1601
1642
|
...(metadata !== undefined ? { metadata } : {}),
|
|
1602
1643
|
}).take();
|
|
1603
1644
|
if (isErr(openedTransferValue)) {
|
|
1645
|
+
recordOperationServerError(openedTransferValue.error, {
|
|
1646
|
+
operation: String(operation),
|
|
1647
|
+
phase: "start",
|
|
1648
|
+
});
|
|
1604
1649
|
this.respondWithError(msg, openedTransferValue.error);
|
|
1605
1650
|
continue;
|
|
1606
1651
|
}
|
|
@@ -1694,7 +1739,7 @@ export class TrellisServiceRuntime extends Trellis<TrellisAPI, TrellisMode> {
|
|
|
1694
1739
|
? handlerResult.take()
|
|
1695
1740
|
: handlerResult;
|
|
1696
1741
|
if (isErr(handlerOutcome)) {
|
|
1697
|
-
|
|
1742
|
+
const error = annotateHandlerBoundaryError(
|
|
1698
1743
|
handlerOutcome.error,
|
|
1699
1744
|
{
|
|
1700
1745
|
operation: String(operation),
|
|
@@ -1704,7 +1749,12 @@ export class TrellisServiceRuntime extends Trellis<TrellisAPI, TrellisMode> {
|
|
|
1704
1749
|
contractDigest: this.contractDigest,
|
|
1705
1750
|
traceId: operationContext.traceId,
|
|
1706
1751
|
},
|
|
1707
|
-
)
|
|
1752
|
+
);
|
|
1753
|
+
recordOperationServerError(error, {
|
|
1754
|
+
operation: String(operation),
|
|
1755
|
+
phase: "handler_result",
|
|
1756
|
+
});
|
|
1757
|
+
await op.fail(error);
|
|
1708
1758
|
return;
|
|
1709
1759
|
}
|
|
1710
1760
|
|
|
@@ -1724,14 +1774,19 @@ export class TrellisServiceRuntime extends Trellis<TrellisAPI, TrellisMode> {
|
|
|
1724
1774
|
await op.complete(handlerOutcome);
|
|
1725
1775
|
}
|
|
1726
1776
|
} catch (cause) {
|
|
1727
|
-
|
|
1777
|
+
const error = annotateHandlerBoundaryError(cause, {
|
|
1728
1778
|
operation: String(operation),
|
|
1729
1779
|
requestId: operationContext.requestId,
|
|
1730
1780
|
service: this.name,
|
|
1731
1781
|
contractId: this.contractId,
|
|
1732
1782
|
contractDigest: this.contractDigest,
|
|
1733
1783
|
traceId: operationContext.traceId,
|
|
1734
|
-
})
|
|
1784
|
+
});
|
|
1785
|
+
recordOperationServerError(error, {
|
|
1786
|
+
operation: String(operation),
|
|
1787
|
+
phase: "handler_throw",
|
|
1788
|
+
});
|
|
1789
|
+
await op.fail(error);
|
|
1735
1790
|
}
|
|
1736
1791
|
})();
|
|
1737
1792
|
}
|
package/src/service/deno.ts
CHANGED
package/src/service/mod.ts
CHANGED
package/src/service/node.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { type AsyncResult, type BaseError, isErr } from "@qlever-llc/result";
|
|
|
3
3
|
import { type StaticDecode, Type } from "typebox";
|
|
4
4
|
import type { PreparedTrellisEvent, Trellis } from "../trellis.js";
|
|
5
5
|
import { TypedKV } from "../kv.js";
|
|
6
|
+
import { recordTrellisError } from "../telemetry/mod.js";
|
|
6
7
|
|
|
7
8
|
export type OutboxMessageState = "pending" | "dispatched" | "failed";
|
|
8
9
|
|
|
@@ -646,6 +647,12 @@ export class OutboxDispatcher {
|
|
|
646
647
|
retryDelayMs: this.#retryDelayMs(),
|
|
647
648
|
});
|
|
648
649
|
} catch (error) {
|
|
650
|
+
recordTrellisError(error, {
|
|
651
|
+
surface: "outbox",
|
|
652
|
+
direction: "dispatcher",
|
|
653
|
+
operation: "batch",
|
|
654
|
+
phase: "dispatch",
|
|
655
|
+
});
|
|
649
656
|
this.#scheduleRetryWakeup();
|
|
650
657
|
try {
|
|
651
658
|
this.#options.onError?.(error);
|
|
@@ -693,6 +700,13 @@ export async function dispatchOutbox(
|
|
|
693
700
|
);
|
|
694
701
|
const value = result.take();
|
|
695
702
|
if (isErr(value)) {
|
|
703
|
+
recordTrellisError(value.error, {
|
|
704
|
+
surface: "outbox",
|
|
705
|
+
direction: "dispatcher",
|
|
706
|
+
operation: message.event,
|
|
707
|
+
phase: "publish",
|
|
708
|
+
messagingSystem: "nats",
|
|
709
|
+
});
|
|
696
710
|
failed += 1;
|
|
697
711
|
await repository.markFailed(message.id, {
|
|
698
712
|
error: value.error.message,
|
package/src/telemetry/core.ts
CHANGED
package/src/telemetry/env.ts
CHANGED
|
@@ -13,7 +13,11 @@ type ProcessLike = {
|
|
|
13
13
|
export function getEnv(key: string): string | undefined {
|
|
14
14
|
const deno = dntShim.dntGlobalThis as typeof dntShim.dntGlobalThis & { Deno?: DenoLike };
|
|
15
15
|
if (deno.Deno?.env?.get) {
|
|
16
|
-
|
|
16
|
+
try {
|
|
17
|
+
return deno.Deno.env.get(key);
|
|
18
|
+
} catch {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
const processGlobal = dntShim.dntGlobalThis as typeof dntShim.dntGlobalThis & {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { configureErrorTraceId } from "./result.js";
|
|
2
|
+
import { initTelemetryRuntime } from "./runtime.js";
|
|
3
|
+
|
|
4
|
+
/** Initializes Trellis telemetry for a service runtime. */
|
|
5
|
+
export function initTelemetry(serviceName: string): void {
|
|
6
|
+
configureErrorTraceId();
|
|
7
|
+
initTelemetryRuntime(serviceName);
|
|
8
|
+
}
|