@nifrajs/otel 1.11.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -0
- package/dist/causality.d.ts +9 -0
- package/dist/causality.d.ts.map +1 -0
- package/dist/causality.js +21 -0
- package/dist/causality.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lifecycle.d.ts +3 -1
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/lifecycle.js +11 -0
- package/dist/lifecycle.js.map +1 -1
- package/dist/span.d.ts +8 -0
- package/dist/span.d.ts.map +1 -1
- package/dist/span.js.map +1 -1
- package/dist/tracing.d.ts +18 -3
- package/dist/tracing.d.ts.map +1 -1
- package/dist/tracing.js +50 -6
- package/dist/tracing.js.map +1 -1
- package/package.json +2 -2
- package/src/causality.ts +22 -0
- package/src/index.ts +2 -0
- package/src/lifecycle.ts +24 -1
- package/src/span.ts +9 -0
- package/src/tracing.ts +80 -5
package/README.md
CHANGED
|
@@ -27,10 +27,38 @@ const app = server()
|
|
|
27
27
|
(`error` for 5xx, `ok` otherwise).
|
|
28
28
|
- **Exposes `c.trace`** (`{ traceId, spanId, parentSpanId?, sampled, traceparent }`) — spread
|
|
29
29
|
`traceHeaders(c.trace)` into any downstream `fetch`/`ctx.api` call to continue the trace.
|
|
30
|
+
- **Exposes `c.causality`** - a bounded, payload-free request node that survives durable command,
|
|
31
|
+
event, workflow, projection, and repair seams. `traceHeaders(c.trace, c.causality)` forwards both
|
|
32
|
+
conventions to a trusted downstream service.
|
|
30
33
|
- **Exposes `c.observation`** — integrations can start correctly-parented child observations or
|
|
31
34
|
attach an adapter without rebuilding request lifecycle state.
|
|
32
35
|
- `responseHeader: true` also sets `traceparent` on the response (browser/client correlation).
|
|
33
36
|
|
|
37
|
+
## Durable causality and trust
|
|
38
|
+
|
|
39
|
+
Pass a durable recorder to append the request root before the handler runs. An explicitly configured
|
|
40
|
+
recorder is correctness evidence, so its failure fails the request closed:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
app.use(tracing({
|
|
44
|
+
exporter,
|
|
45
|
+
causality: {
|
|
46
|
+
recorder: durableGraphStore,
|
|
47
|
+
acceptInbound: (request) => verifyInternalServiceCredential(request),
|
|
48
|
+
},
|
|
49
|
+
}))
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Inbound causality headers are **not trusted by default**. This prevents an internet client from
|
|
53
|
+
injecting fake parents into another execution timeline. Supply `acceptInbound` only at an authenticated
|
|
54
|
+
service-to-service boundary; a false, thrown, or rejected decision starts a fresh graph. W3C
|
|
55
|
+
`traceparent` remains normal observability context, but a fresh durable execution id includes the
|
|
56
|
+
server-generated span id and is not copied from the untrusted header.
|
|
57
|
+
|
|
58
|
+
Use `causalitySpanLink(context)` when durable work opens a later observation. It creates a real OTel
|
|
59
|
+
link to the nearest observed causal ancestor and drops invalid/unanchored contexts rather than
|
|
60
|
+
inventing trace identity.
|
|
61
|
+
|
|
34
62
|
## Adapters
|
|
35
63
|
|
|
36
64
|
Implement `ObservationAdapter` to send spans wherever you collect them. `SpanExporter` remains as a
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** OpenTelemetry adapter for the framework-neutral durable causality context. */
|
|
2
|
+
import { type CausalityContext } from "@nifrajs/core/causality";
|
|
3
|
+
import type { ObservationLink } from "./span.js";
|
|
4
|
+
/**
|
|
5
|
+
* Convert the nearest observed causal ancestor into an OTel span link. Returns `undefined` instead
|
|
6
|
+
* of inventing a trace identity when the durable context has no observation anchor.
|
|
7
|
+
*/
|
|
8
|
+
export declare function causalitySpanLink(context: CausalityContext): ObservationLink | undefined;
|
|
9
|
+
//# sourceMappingURL=causality.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"causality.d.ts","sourceRoot":"","sources":["../src/causality.ts"],"names":[],"mappings":"AAAA,iFAAiF;AAEjF,OAAO,EAAE,KAAK,gBAAgB,EAAyB,MAAM,yBAAyB,CAAA;AACtF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAEhD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,gBAAgB,GAAG,eAAe,GAAG,SAAS,CAYxF"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** OpenTelemetry adapter for the framework-neutral durable causality context. */
|
|
2
|
+
import { parseCausalityContext } from "@nifrajs/core/causality";
|
|
3
|
+
/**
|
|
4
|
+
* Convert the nearest observed causal ancestor into an OTel span link. Returns `undefined` instead
|
|
5
|
+
* of inventing a trace identity when the durable context has no observation anchor.
|
|
6
|
+
*/
|
|
7
|
+
export function causalitySpanLink(context) {
|
|
8
|
+
const parsed = parseCausalityContext(context);
|
|
9
|
+
if (!parsed.success || parsed.context.trace === undefined)
|
|
10
|
+
return undefined;
|
|
11
|
+
return Object.freeze({
|
|
12
|
+
traceId: parsed.context.trace.traceId,
|
|
13
|
+
spanId: parsed.context.trace.spanId,
|
|
14
|
+
attributes: Object.freeze({
|
|
15
|
+
"nifra.execution.id": parsed.context.executionId,
|
|
16
|
+
"nifra.causality.kind": parsed.context.current.kind,
|
|
17
|
+
"nifra.causality.id": parsed.context.current.id,
|
|
18
|
+
}),
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=causality.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"causality.js","sourceRoot":"","sources":["../src/causality.ts"],"names":[],"mappings":"AAAA,iFAAiF;AAEjF,OAAO,EAAyB,qBAAqB,EAAE,MAAM,yBAAyB,CAAA;AAGtF;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAyB;IACzD,MAAM,MAAM,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAA;IAC7C,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IAC3E,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO;QACrC,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM;QACnC,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC;YACxB,oBAAoB,EAAE,MAAM,CAAC,OAAO,CAAC,WAAW;YAChD,sBAAsB,EAAE,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI;YACnD,oBAAoB,EAAE,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE;SAChD,CAAC;KACH,CAAC,CAAA;AACJ,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
* import { tracing, traceHeaders, consoleSpanExporter } from "@nifrajs/otel"
|
|
9
9
|
* app.use(tracing({ exporter: consoleSpanExporter(), serviceName: "orders-api" }))
|
|
10
10
|
*/
|
|
11
|
+
export { causalitySpanLink } from "./causality.js";
|
|
11
12
|
export { type ActiveObservation, createObservationLifecycle, type EndObservation, type ObservationClock, type ObservationContext, type ObservationLifecycle, type ObservationLifecycleOptions, type ObservationParent, type StartObservation, } from "./lifecycle.js";
|
|
12
|
-
export { type AttributeValue, combineObservationAdapters, consoleSpanExporter, type NifraSpan, type ObservationAdapter, type SpanExporter, type SpanStatus, } from "./span.js";
|
|
13
|
+
export { type AttributeValue, combineObservationAdapters, consoleSpanExporter, type NifraSpan, type ObservationAdapter, type ObservationLink, type SpanExporter, type SpanStatus, } from "./span.js";
|
|
13
14
|
export { formatTraceparent, generateSpanId, generateTraceId, type ParsedTraceparent, parseTraceparent, } from "./traceparent.js";
|
|
14
15
|
export { type TraceContext, type TracingOptions, traceHeaders, tracing } from "./tracing.js";
|
|
15
16
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,KAAK,iBAAiB,EACtB,0BAA0B,EAC1B,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,2BAA2B,EAChC,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EACL,KAAK,cAAc,EACnB,0BAA0B,EAC1B,mBAAmB,EACnB,KAAK,SAAS,EACd,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,KAAK,UAAU,GAChB,MAAM,WAAW,CAAA;AAClB,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,eAAe,EACf,KAAK,iBAAiB,EACtB,gBAAgB,GACjB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,cAAc,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAClD,OAAO,EACL,KAAK,iBAAiB,EACtB,0BAA0B,EAC1B,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,2BAA2B,EAChC,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EACL,KAAK,cAAc,EACnB,0BAA0B,EAC1B,mBAAmB,EACnB,KAAK,SAAS,EACd,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,UAAU,GAChB,MAAM,WAAW,CAAA;AAClB,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,eAAe,EACf,KAAK,iBAAiB,EACtB,gBAAgB,GACjB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,cAAc,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* import { tracing, traceHeaders, consoleSpanExporter } from "@nifrajs/otel"
|
|
9
9
|
* app.use(tracing({ exporter: consoleSpanExporter(), serviceName: "orders-api" }))
|
|
10
10
|
*/
|
|
11
|
+
export { causalitySpanLink } from "./causality.js";
|
|
11
12
|
export { createObservationLifecycle, } from "./lifecycle.js";
|
|
12
13
|
export { combineObservationAdapters, consoleSpanExporter, } from "./span.js";
|
|
13
14
|
export { formatTraceparent, generateSpanId, generateTraceId, parseTraceparent, } from "./traceparent.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAEL,0BAA0B,GAQ3B,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAEL,0BAA0B,EAC1B,mBAAmB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAClD,OAAO,EAEL,0BAA0B,GAQ3B,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAEL,0BAA0B,EAC1B,mBAAmB,GAMpB,MAAM,WAAW,CAAA;AAClB,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,eAAe,EAEf,gBAAgB,GACjB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAA0C,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA"}
|
package/dist/lifecycle.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* timing, error recording, final status classification, and exactly-once completion. Integrations
|
|
4
4
|
* should adapt completed spans; they should not reimplement this state machine.
|
|
5
5
|
*/
|
|
6
|
-
import type { AttributeValue, NifraSpan, ObservationAdapter, SpanStatus } from "./span.js";
|
|
6
|
+
import type { AttributeValue, NifraSpan, ObservationAdapter, ObservationLink, SpanStatus } from "./span.js";
|
|
7
7
|
export interface ObservationContext {
|
|
8
8
|
readonly traceId: string;
|
|
9
9
|
readonly spanId: string;
|
|
@@ -24,6 +24,8 @@ export interface StartObservation {
|
|
|
24
24
|
/** Inbound W3C header used when `parent` is undefined. */
|
|
25
25
|
readonly traceparent?: string | null;
|
|
26
26
|
readonly attributes?: Readonly<Record<string, AttributeValue>>;
|
|
27
|
+
/** Non-parent causal relationships, for example an outbox event that resumed this workflow. */
|
|
28
|
+
readonly links?: readonly ObservationLink[];
|
|
27
29
|
}
|
|
28
30
|
export interface EndObservation {
|
|
29
31
|
readonly statusCode?: number;
|
package/dist/lifecycle.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"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"}
|
package/dist/lifecycle.js
CHANGED
|
@@ -51,6 +51,17 @@ export function createObservationLifecycle(options = {}) {
|
|
|
51
51
|
startTime,
|
|
52
52
|
status: "unset",
|
|
53
53
|
attributes: { ...input.attributes },
|
|
54
|
+
...(input.links === undefined
|
|
55
|
+
? {}
|
|
56
|
+
: {
|
|
57
|
+
links: Object.freeze(input.links.map((link) => Object.freeze({
|
|
58
|
+
traceId: link.traceId,
|
|
59
|
+
spanId: link.spanId,
|
|
60
|
+
...(link.attributes === undefined
|
|
61
|
+
? {}
|
|
62
|
+
: { attributes: Object.freeze({ ...link.attributes }) }),
|
|
63
|
+
}))),
|
|
64
|
+
}),
|
|
54
65
|
};
|
|
55
66
|
const context = {
|
|
56
67
|
traceId,
|
package/dist/lifecycle.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"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"}
|
package/dist/span.d.ts
CHANGED
|
@@ -7,6 +7,12 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export type SpanStatus = "unset" | "ok" | "error";
|
|
9
9
|
export type AttributeValue = string | number | boolean;
|
|
10
|
+
/** A non-parent causal relationship to a span in another trace (the OTel `Link` model). */
|
|
11
|
+
export interface ObservationLink {
|
|
12
|
+
readonly traceId: string;
|
|
13
|
+
readonly spanId: string;
|
|
14
|
+
readonly attributes?: Readonly<Record<string, AttributeValue>>;
|
|
15
|
+
}
|
|
10
16
|
/** A completed (or in-flight) server span for one request. */
|
|
11
17
|
export interface NifraSpan {
|
|
12
18
|
/** 32-hex W3C trace id — shared across every span/service in the trace. */
|
|
@@ -28,6 +34,8 @@ export interface NifraSpan {
|
|
|
28
34
|
status: SpanStatus;
|
|
29
35
|
/** OTel-semantic-convention attributes. */
|
|
30
36
|
readonly attributes: Record<string, AttributeValue>;
|
|
37
|
+
/** Durable/asynchronous parents that are related but are not this span's single trace parent. */
|
|
38
|
+
readonly links?: readonly ObservationLink[];
|
|
31
39
|
}
|
|
32
40
|
/**
|
|
33
41
|
* Where ended spans go. Implement this to bridge to the OpenTelemetry SDK (map each field onto a
|
package/dist/span.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"span.d.ts","sourceRoot":"","sources":["../src/span.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,IAAI,GAAG,OAAO,CAAA;AACjD,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;AAEtD,8DAA8D;AAC9D,MAAM,WAAW,SAAS;IACxB,2EAA2E;IAC3E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,8BAA8B;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,0EAA0E;IAC1E,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,mDAAmD;IACnD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,uCAAuC;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,mCAAmC;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,qDAAqD;IACrD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,kCAAkC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,UAAU,CAAA;IAClB,2CAA2C;IAC3C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"span.d.ts","sourceRoot":"","sources":["../src/span.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,IAAI,GAAG,OAAO,CAAA;AACjD,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;AAEtD,2FAA2F;AAC3F,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAA;CAC/D;AAED,8DAA8D;AAC9D,MAAM,WAAW,SAAS;IACxB,2EAA2E;IAC3E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,8BAA8B;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,0EAA0E;IAC1E,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,mDAAmD;IACnD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,uCAAuC;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,mCAAmC;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,qDAAqD;IACrD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,kCAAkC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,UAAU,CAAA;IAClB,2CAA2C;IAC3C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAA;IACnD,iGAAiG;IACjG,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,eAAe,EAAE,CAAA;CAC5C;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,CAAA;IAC/B,KAAK,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,CAAA;CAC7B;AAED,yDAAyD;AACzD,MAAM,MAAM,YAAY,GAAG,kBAAkB,CAAA;AAE7C;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,SAAS,kBAAkB,EAAE,GACtC,kBAAkB,CAqBpB;AAED;kDACkD;AAClD,wBAAgB,mBAAmB,CACjC,GAAG,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAEtB,GACA,YAAY,CAgBd"}
|
package/dist/span.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"span.js","sourceRoot":"","sources":["../src/span.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"span.js","sourceRoot":"","sources":["../src/span.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAkDH;;;GAGG;AACH,MAAM,UAAU,0BAA0B,CACxC,QAAuC;IAEvC,OAAO;QACL,OAAO,CAAC,IAAI;YACV,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;gBAC/B,IAAI,CAAC;oBACH,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAA;gBACzB,CAAC;gBAAC,MAAM,CAAC;oBACP,wCAAwC;gBAC1C,CAAC;YACH,CAAC;QACH,CAAC;QACD,KAAK,CAAC,IAAI;YACR,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;gBAC/B,IAAI,CAAC;oBACH,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;gBACrB,CAAC;gBAAC,MAAM,CAAC;oBACP,wCAAwC;gBAC1C,CAAC;YACH,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC;AAED;kDACkD;AAClD,MAAM,UAAU,mBAAmB,CACjC,MAA8B,CAAC,CAAC,EAAE,EAAE;IAClC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;AAChB,CAAC;IAED,OAAO;QACL,KAAK,CAAC,IAAI;YACR,GAAG,CACD,IAAI,CAAC,SAAS,CAAC;gBACb,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,YAAY,EAAE,IAAI,CAAC,YAAY;gBAC/B,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,GAAG,IAAI,CAAC,UAAU;aACnB,CAAC,CACH,CAAA;QACH,CAAC;KACF,CAAA;AACH,CAAC"}
|
package/dist/tracing.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* (traceparent) and attribute names (HTTP semantic conventions); the span itself is exported through
|
|
5
5
|
* your {@link SpanExporter} (bridge to the OTel SDK, or log via `consoleSpanExporter`).
|
|
6
6
|
*/
|
|
7
|
+
import { type CausalityContext, type CausalityRecorder } from "@nifrajs/core";
|
|
7
8
|
import { type ObservationContext } from "./lifecycle.js";
|
|
8
9
|
import { type ObservationAdapter, type SpanExporter } from "./span.js";
|
|
9
10
|
/** The trace context exposed on the handler `c.trace` (typed, threaded via `derive`). */
|
|
@@ -20,12 +21,26 @@ export interface TracingOptions {
|
|
|
20
21
|
* Default false — most setups only propagate downstream, not back to the caller.
|
|
21
22
|
*/
|
|
22
23
|
readonly responseHeader?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Optional durable graph recorder. When configured, the request root is appended before the
|
|
26
|
+
* handler runs and recorder failure fails closed. Without one, `c.causality` is still propagated.
|
|
27
|
+
*/
|
|
28
|
+
readonly causality?: {
|
|
29
|
+
readonly recorder?: CausalityRecorder;
|
|
30
|
+
/** Injectable epoch clock for deterministic tests. */
|
|
31
|
+
readonly now?: () => number;
|
|
32
|
+
/**
|
|
33
|
+
* Explicit trust gate for service-to-service causality headers. Internet clients are untrusted
|
|
34
|
+
* by default; a false result or thrown/rejected check starts a fresh execution graph.
|
|
35
|
+
*/
|
|
36
|
+
readonly acceptInbound?: (request: Request, context: CausalityContext) => boolean | Promise<boolean>;
|
|
37
|
+
};
|
|
23
38
|
}
|
|
24
39
|
/** Spread into an outgoing `fetch`/`ctx.api` call's headers to continue the trace downstream:
|
|
25
40
|
* `fetch(url, { headers: traceHeaders(c.trace) })`. */
|
|
26
|
-
export declare function traceHeaders(trace: TraceContext): {
|
|
27
|
-
traceparent: string;
|
|
28
|
-
}
|
|
41
|
+
export declare function traceHeaders(trace: TraceContext, causality?: CausalityContext): {
|
|
42
|
+
readonly traceparent: string;
|
|
43
|
+
} & Readonly<Record<string, string>>;
|
|
29
44
|
/**
|
|
30
45
|
* Distributed-tracing plugin. Each request continues the inbound trace (or starts one), opens a
|
|
31
46
|
* server span, and ends it on response with the status + HTTP attributes. Idempotent.
|
package/dist/tracing.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tracing.d.ts","sourceRoot":"","sources":["../src/tracing.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"tracing.d.ts","sourceRoot":"","sources":["../src/tracing.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EAMvB,MAAM,eAAe,CAAA;AACtB,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAuB,KAAK,kBAAkB,EAAE,KAAK,YAAY,EAAE,MAAM,WAAW,CAAA;AAE3F,yFAAyF;AACzF,MAAM,MAAM,YAAY,GAAG,kBAAkB,CAAA;AAE7C,MAAM,WAAW,cAAc;IAC7B,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,CAAC,EAAE,YAAY,CAAA;IAChC,2FAA2F;IAC3F,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,kBAAkB,EAAE,CAAA;IACjD,uDAAuD;IACvD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAA;IACjC;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE;QACnB,QAAQ,CAAC,QAAQ,CAAC,EAAE,iBAAiB,CAAA;QACrC,sDAAsD;QACtD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;QAC3B;;;WAGG;QACH,QAAQ,CAAC,aAAa,CAAC,EAAE,CACvB,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,gBAAgB,KACtB,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;KAChC,CAAA;CACF;AAED;uDACuD;AACvD,wBAAgB,YAAY,CAC1B,KAAK,EAAE,YAAY,EACnB,SAAS,CAAC,EAAE,gBAAgB,GAC3B;IAAE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAKrE;AAUD;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,OAAO,GAAE,cAAmB,oHAqGnD"}
|
package/dist/tracing.js
CHANGED
|
@@ -4,13 +4,16 @@
|
|
|
4
4
|
* (traceparent) and attribute names (HTTP semantic conventions); the span itself is exported through
|
|
5
5
|
* your {@link SpanExporter} (bridge to the OTel SDK, or log via `consoleSpanExporter`).
|
|
6
6
|
*/
|
|
7
|
-
import { definePlugin } from "@nifrajs/core";
|
|
7
|
+
import { causalityHeaders, continueCausality, definePlugin, readCausalityHeaders, startCausality, } from "@nifrajs/core";
|
|
8
8
|
import { createObservationLifecycle, } from "./lifecycle.js";
|
|
9
9
|
import { consoleSpanExporter } from "./span.js";
|
|
10
10
|
/** Spread into an outgoing `fetch`/`ctx.api` call's headers to continue the trace downstream:
|
|
11
11
|
* `fetch(url, { headers: traceHeaders(c.trace) })`. */
|
|
12
|
-
export function traceHeaders(trace) {
|
|
13
|
-
return {
|
|
12
|
+
export function traceHeaders(trace, causality) {
|
|
13
|
+
return {
|
|
14
|
+
traceparent: trace.traceparent,
|
|
15
|
+
...(causality === undefined ? {} : causalityHeaders(causality)),
|
|
16
|
+
};
|
|
14
17
|
}
|
|
15
18
|
const pathOf = (url) => {
|
|
16
19
|
try {
|
|
@@ -55,9 +58,50 @@ export function tracing(options = {}) {
|
|
|
55
58
|
},
|
|
56
59
|
});
|
|
57
60
|
inFlight.set(c.req, observation);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
const causalAt = options.causality?.now?.();
|
|
62
|
+
const derive = (parent) => {
|
|
63
|
+
const causal = parent === undefined
|
|
64
|
+
? startCausality("request", `req_${observation.context.spanId}`, {
|
|
65
|
+
// `traceparent` is untrusted. The server-generated span id keeps graph identity fresh.
|
|
66
|
+
executionId: `exec_${observation.context.traceId}_${observation.context.spanId}`,
|
|
67
|
+
...(causalAt === undefined ? {} : { at: causalAt }),
|
|
68
|
+
trace: {
|
|
69
|
+
traceId: observation.context.traceId,
|
|
70
|
+
spanId: observation.context.spanId,
|
|
71
|
+
},
|
|
72
|
+
})
|
|
73
|
+
: continueCausality(parent, "request", `req_${observation.context.spanId}`, {
|
|
74
|
+
relation: "called",
|
|
75
|
+
...(causalAt === undefined ? {} : { at: causalAt }),
|
|
76
|
+
trace: {
|
|
77
|
+
traceId: observation.context.traceId,
|
|
78
|
+
spanId: observation.context.spanId,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
const derived = { trace: observation.context, observation, causality: causal.context };
|
|
82
|
+
if (options.responseHeader)
|
|
83
|
+
c.set.headers.traceparent = observation.context.traceparent;
|
|
84
|
+
const recorder = options.causality?.recorder;
|
|
85
|
+
return recorder === undefined
|
|
86
|
+
? derived
|
|
87
|
+
: recorder.record(causal.record).then(() => derived);
|
|
88
|
+
};
|
|
89
|
+
const inbound = readCausalityHeaders(c.req.headers);
|
|
90
|
+
const acceptInbound = options.causality?.acceptInbound;
|
|
91
|
+
if (!inbound.success || acceptInbound === undefined)
|
|
92
|
+
return derive();
|
|
93
|
+
let accepted;
|
|
94
|
+
try {
|
|
95
|
+
accepted = acceptInbound(c.req, inbound.context);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return derive();
|
|
99
|
+
}
|
|
100
|
+
// Keep recorder execution outside the trust-gate catch. A synchronous recorder failure is a
|
|
101
|
+
// durability failure, not a rejected inbound parent, and must fail closed without retrying.
|
|
102
|
+
return typeof accepted === "boolean"
|
|
103
|
+
? derive(accepted ? inbound.context : undefined)
|
|
104
|
+
: Promise.resolve(accepted).then((allowed) => derive(allowed === true ? inbound.context : undefined), () => derive());
|
|
61
105
|
})
|
|
62
106
|
.use({
|
|
63
107
|
name: "tracing-end",
|
package/dist/tracing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tracing.js","sourceRoot":"","sources":["../src/tracing.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"tracing.js","sourceRoot":"","sources":["../src/tracing.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nifrajs/otel",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"description": "Distributed tracing for nifra — W3C traceparent/tracestate propagation + OpenTelemetry-semantic-convention spans via a pluggable exporter. Bridge to the OpenTelemetry SDK or log spans directly; no SDK bundled. Edge-safe.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"access": "public"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
|
-
"@nifrajs/core": "^1.
|
|
42
|
+
"@nifrajs/core": "^1.12.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@nifrajs/core": "workspace:*"
|
package/src/causality.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** OpenTelemetry adapter for the framework-neutral durable causality context. */
|
|
2
|
+
|
|
3
|
+
import { type CausalityContext, parseCausalityContext } from "@nifrajs/core/causality"
|
|
4
|
+
import type { ObservationLink } from "./span.ts"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Convert the nearest observed causal ancestor into an OTel span link. Returns `undefined` instead
|
|
8
|
+
* of inventing a trace identity when the durable context has no observation anchor.
|
|
9
|
+
*/
|
|
10
|
+
export function causalitySpanLink(context: CausalityContext): ObservationLink | undefined {
|
|
11
|
+
const parsed = parseCausalityContext(context)
|
|
12
|
+
if (!parsed.success || parsed.context.trace === undefined) return undefined
|
|
13
|
+
return Object.freeze({
|
|
14
|
+
traceId: parsed.context.trace.traceId,
|
|
15
|
+
spanId: parsed.context.trace.spanId,
|
|
16
|
+
attributes: Object.freeze({
|
|
17
|
+
"nifra.execution.id": parsed.context.executionId,
|
|
18
|
+
"nifra.causality.kind": parsed.context.current.kind,
|
|
19
|
+
"nifra.causality.id": parsed.context.current.id,
|
|
20
|
+
}),
|
|
21
|
+
})
|
|
22
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* app.use(tracing({ exporter: consoleSpanExporter(), serviceName: "orders-api" }))
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
export { causalitySpanLink } from "./causality.ts"
|
|
12
13
|
export {
|
|
13
14
|
type ActiveObservation,
|
|
14
15
|
createObservationLifecycle,
|
|
@@ -26,6 +27,7 @@ export {
|
|
|
26
27
|
consoleSpanExporter,
|
|
27
28
|
type NifraSpan,
|
|
28
29
|
type ObservationAdapter,
|
|
30
|
+
type ObservationLink,
|
|
29
31
|
type SpanExporter,
|
|
30
32
|
type SpanStatus,
|
|
31
33
|
} from "./span.ts"
|
package/src/lifecycle.ts
CHANGED
|
@@ -4,7 +4,13 @@
|
|
|
4
4
|
* should adapt completed spans; they should not reimplement this state machine.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type {
|
|
7
|
+
import type {
|
|
8
|
+
AttributeValue,
|
|
9
|
+
NifraSpan,
|
|
10
|
+
ObservationAdapter,
|
|
11
|
+
ObservationLink,
|
|
12
|
+
SpanStatus,
|
|
13
|
+
} from "./span.ts"
|
|
8
14
|
import {
|
|
9
15
|
formatTraceparent,
|
|
10
16
|
generateSpanId,
|
|
@@ -34,6 +40,8 @@ export interface StartObservation {
|
|
|
34
40
|
/** Inbound W3C header used when `parent` is undefined. */
|
|
35
41
|
readonly traceparent?: string | null
|
|
36
42
|
readonly attributes?: Readonly<Record<string, AttributeValue>>
|
|
43
|
+
/** Non-parent causal relationships, for example an outbox event that resumed this workflow. */
|
|
44
|
+
readonly links?: readonly ObservationLink[]
|
|
37
45
|
}
|
|
38
46
|
|
|
39
47
|
export interface EndObservation {
|
|
@@ -142,6 +150,21 @@ export function createObservationLifecycle(
|
|
|
142
150
|
startTime,
|
|
143
151
|
status: "unset",
|
|
144
152
|
attributes: { ...input.attributes },
|
|
153
|
+
...(input.links === undefined
|
|
154
|
+
? {}
|
|
155
|
+
: {
|
|
156
|
+
links: Object.freeze(
|
|
157
|
+
input.links.map((link) =>
|
|
158
|
+
Object.freeze({
|
|
159
|
+
traceId: link.traceId,
|
|
160
|
+
spanId: link.spanId,
|
|
161
|
+
...(link.attributes === undefined
|
|
162
|
+
? {}
|
|
163
|
+
: { attributes: Object.freeze({ ...link.attributes }) }),
|
|
164
|
+
}),
|
|
165
|
+
),
|
|
166
|
+
),
|
|
167
|
+
}),
|
|
145
168
|
}
|
|
146
169
|
const context: ObservationContext = {
|
|
147
170
|
traceId,
|
package/src/span.ts
CHANGED
|
@@ -9,6 +9,13 @@
|
|
|
9
9
|
export type SpanStatus = "unset" | "ok" | "error"
|
|
10
10
|
export type AttributeValue = string | number | boolean
|
|
11
11
|
|
|
12
|
+
/** A non-parent causal relationship to a span in another trace (the OTel `Link` model). */
|
|
13
|
+
export interface ObservationLink {
|
|
14
|
+
readonly traceId: string
|
|
15
|
+
readonly spanId: string
|
|
16
|
+
readonly attributes?: Readonly<Record<string, AttributeValue>>
|
|
17
|
+
}
|
|
18
|
+
|
|
12
19
|
/** A completed (or in-flight) server span for one request. */
|
|
13
20
|
export interface NifraSpan {
|
|
14
21
|
/** 32-hex W3C trace id — shared across every span/service in the trace. */
|
|
@@ -30,6 +37,8 @@ export interface NifraSpan {
|
|
|
30
37
|
status: SpanStatus
|
|
31
38
|
/** OTel-semantic-convention attributes. */
|
|
32
39
|
readonly attributes: Record<string, AttributeValue>
|
|
40
|
+
/** Durable/asynchronous parents that are related but are not this span's single trace parent. */
|
|
41
|
+
readonly links?: readonly ObservationLink[]
|
|
33
42
|
}
|
|
34
43
|
|
|
35
44
|
/**
|
package/src/tracing.ts
CHANGED
|
@@ -5,7 +5,15 @@
|
|
|
5
5
|
* your {@link SpanExporter} (bridge to the OTel SDK, or log via `consoleSpanExporter`).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
type CausalityContext,
|
|
10
|
+
type CausalityRecorder,
|
|
11
|
+
causalityHeaders,
|
|
12
|
+
continueCausality,
|
|
13
|
+
definePlugin,
|
|
14
|
+
readCausalityHeaders,
|
|
15
|
+
startCausality,
|
|
16
|
+
} from "@nifrajs/core"
|
|
9
17
|
import {
|
|
10
18
|
type ActiveObservation,
|
|
11
19
|
createObservationLifecycle,
|
|
@@ -28,12 +36,35 @@ export interface TracingOptions {
|
|
|
28
36
|
* Default false — most setups only propagate downstream, not back to the caller.
|
|
29
37
|
*/
|
|
30
38
|
readonly responseHeader?: boolean
|
|
39
|
+
/**
|
|
40
|
+
* Optional durable graph recorder. When configured, the request root is appended before the
|
|
41
|
+
* handler runs and recorder failure fails closed. Without one, `c.causality` is still propagated.
|
|
42
|
+
*/
|
|
43
|
+
readonly causality?: {
|
|
44
|
+
readonly recorder?: CausalityRecorder
|
|
45
|
+
/** Injectable epoch clock for deterministic tests. */
|
|
46
|
+
readonly now?: () => number
|
|
47
|
+
/**
|
|
48
|
+
* Explicit trust gate for service-to-service causality headers. Internet clients are untrusted
|
|
49
|
+
* by default; a false result or thrown/rejected check starts a fresh execution graph.
|
|
50
|
+
*/
|
|
51
|
+
readonly acceptInbound?: (
|
|
52
|
+
request: Request,
|
|
53
|
+
context: CausalityContext,
|
|
54
|
+
) => boolean | Promise<boolean>
|
|
55
|
+
}
|
|
31
56
|
}
|
|
32
57
|
|
|
33
58
|
/** Spread into an outgoing `fetch`/`ctx.api` call's headers to continue the trace downstream:
|
|
34
59
|
* `fetch(url, { headers: traceHeaders(c.trace) })`. */
|
|
35
|
-
export function traceHeaders(
|
|
36
|
-
|
|
60
|
+
export function traceHeaders(
|
|
61
|
+
trace: TraceContext,
|
|
62
|
+
causality?: CausalityContext,
|
|
63
|
+
): { readonly traceparent: string } & Readonly<Record<string, string>> {
|
|
64
|
+
return {
|
|
65
|
+
traceparent: trace.traceparent,
|
|
66
|
+
...(causality === undefined ? {} : causalityHeaders(causality)),
|
|
67
|
+
}
|
|
37
68
|
}
|
|
38
69
|
|
|
39
70
|
const pathOf = (url: string): string => {
|
|
@@ -81,8 +112,52 @@ export function tracing(options: TracingOptions = {}) {
|
|
|
81
112
|
},
|
|
82
113
|
})
|
|
83
114
|
inFlight.set(c.req, observation)
|
|
84
|
-
|
|
85
|
-
|
|
115
|
+
const causalAt = options.causality?.now?.()
|
|
116
|
+
const derive = (parent?: CausalityContext) => {
|
|
117
|
+
const causal =
|
|
118
|
+
parent === undefined
|
|
119
|
+
? startCausality("request", `req_${observation.context.spanId}`, {
|
|
120
|
+
// `traceparent` is untrusted. The server-generated span id keeps graph identity fresh.
|
|
121
|
+
executionId: `exec_${observation.context.traceId}_${observation.context.spanId}`,
|
|
122
|
+
...(causalAt === undefined ? {} : { at: causalAt }),
|
|
123
|
+
trace: {
|
|
124
|
+
traceId: observation.context.traceId,
|
|
125
|
+
spanId: observation.context.spanId,
|
|
126
|
+
},
|
|
127
|
+
})
|
|
128
|
+
: continueCausality(parent, "request", `req_${observation.context.spanId}`, {
|
|
129
|
+
relation: "called",
|
|
130
|
+
...(causalAt === undefined ? {} : { at: causalAt }),
|
|
131
|
+
trace: {
|
|
132
|
+
traceId: observation.context.traceId,
|
|
133
|
+
spanId: observation.context.spanId,
|
|
134
|
+
},
|
|
135
|
+
})
|
|
136
|
+
const derived = { trace: observation.context, observation, causality: causal.context }
|
|
137
|
+
if (options.responseHeader) c.set.headers.traceparent = observation.context.traceparent
|
|
138
|
+
const recorder = options.causality?.recorder
|
|
139
|
+
return recorder === undefined
|
|
140
|
+
? derived
|
|
141
|
+
: recorder.record(causal.record).then(() => derived)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const inbound = readCausalityHeaders(c.req.headers)
|
|
145
|
+
const acceptInbound = options.causality?.acceptInbound
|
|
146
|
+
if (!inbound.success || acceptInbound === undefined) return derive()
|
|
147
|
+
let accepted: boolean | Promise<boolean>
|
|
148
|
+
try {
|
|
149
|
+
accepted = acceptInbound(c.req, inbound.context)
|
|
150
|
+
} catch {
|
|
151
|
+
return derive()
|
|
152
|
+
}
|
|
153
|
+
// Keep recorder execution outside the trust-gate catch. A synchronous recorder failure is a
|
|
154
|
+
// durability failure, not a rejected inbound parent, and must fail closed without retrying.
|
|
155
|
+
return typeof accepted === "boolean"
|
|
156
|
+
? derive(accepted ? inbound.context : undefined)
|
|
157
|
+
: Promise.resolve(accepted).then(
|
|
158
|
+
(allowed) => derive(allowed === true ? inbound.context : undefined),
|
|
159
|
+
() => derive(),
|
|
160
|
+
)
|
|
86
161
|
})
|
|
87
162
|
.use({
|
|
88
163
|
name: "tracing-end",
|