@procedures/client 0.52.3 → 0.54.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.
Files changed (66) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/README.md +11 -0
  3. package/dist/adapters/http/server/cors.d.ts.map +1 -1
  4. package/dist/adapters/http/server/cors.js +2 -0
  5. package/dist/adapters/http/server/cors.js.map +1 -1
  6. package/dist/adapters/port/transport.d.ts +27 -0
  7. package/dist/adapters/port/transport.d.ts.map +1 -1
  8. package/dist/adapters/port/transport.js +30 -0
  9. package/dist/adapters/port/transport.js.map +1 -1
  10. package/dist/browser.d.ts +2 -2
  11. package/dist/browser.d.ts.map +1 -1
  12. package/dist/browser.js +1 -1
  13. package/dist/browser.js.map +1 -1
  14. package/dist/client/batch-executor.d.ts +3 -0
  15. package/dist/client/batch-executor.d.ts.map +1 -1
  16. package/dist/client/batch-executor.js +3 -0
  17. package/dist/client/batch-executor.js.map +1 -1
  18. package/dist/client/client.d.ts +2 -0
  19. package/dist/client/client.d.ts.map +1 -1
  20. package/dist/client/client.js +33 -5
  21. package/dist/client/client.js.map +1 -1
  22. package/dist/client/middleware/cache.d.ts.map +1 -1
  23. package/dist/client/middleware/cache.js +2 -0
  24. package/dist/client/middleware/cache.js.map +1 -1
  25. package/dist/client/middleware/rate-limit.d.ts.map +1 -1
  26. package/dist/client/middleware/rate-limit.js +3 -2
  27. package/dist/client/middleware/rate-limit.js.map +1 -1
  28. package/dist/procedures/interceptors.d.ts +10 -1
  29. package/dist/procedures/interceptors.d.ts.map +1 -1
  30. package/dist/procedures/interceptors.js +31 -8
  31. package/dist/procedures/interceptors.js.map +1 -1
  32. package/dist/server/host-policy.d.ts +8 -0
  33. package/dist/server/host-policy.d.ts.map +1 -1
  34. package/dist/server/host-policy.js +3 -0
  35. package/dist/server/host-policy.js.map +1 -1
  36. package/dist/server/limits.d.ts +1 -0
  37. package/dist/server/limits.d.ts.map +1 -1
  38. package/dist/server/limits.js +13 -0
  39. package/dist/server/limits.js.map +1 -1
  40. package/dist/server/procedure-server.d.ts +21 -0
  41. package/dist/server/procedure-server.d.ts.map +1 -1
  42. package/dist/server/procedure-server.js +35 -3
  43. package/dist/server/procedure-server.js.map +1 -1
  44. package/dist/tracing/otel.d.ts +55 -0
  45. package/dist/tracing/otel.d.ts.map +1 -0
  46. package/dist/tracing/otel.js +114 -0
  47. package/dist/tracing/otel.js.map +1 -0
  48. package/dist/tracing/traceparent.d.ts +40 -12
  49. package/dist/tracing/traceparent.d.ts.map +1 -1
  50. package/dist/tracing/traceparent.js +49 -14
  51. package/dist/tracing/traceparent.js.map +1 -1
  52. package/package.json +17 -6
  53. package/src/adapters/http/server/cors.ts +2 -0
  54. package/src/adapters/port/transport.ts +44 -0
  55. package/src/browser.ts +2 -2
  56. package/src/client/batch-executor.ts +3 -0
  57. package/src/client/client.ts +31 -5
  58. package/src/client/middleware/cache.ts +2 -0
  59. package/src/client/middleware/rate-limit.ts +4 -2
  60. package/src/procedures/interceptors.ts +40 -9
  61. package/src/server/host-policy.ts +11 -0
  62. package/src/server/limits.ts +13 -0
  63. package/src/server/procedure-server.ts +46 -4
  64. package/src/tracing/otel.ts +122 -0
  65. package/src/tracing/traceparent.ts +56 -15
  66. package/dist/.tsbuildinfo +0 -1
@@ -4,12 +4,22 @@
4
4
  * the metadata of a call (and HTTP as the `traceparent` header). The tracing interceptor of a
5
5
  * server continues that trace, and gives it to the nested calls, so one trace goes from the
6
6
  * client to the last handler. Before, the client and the server made two traces.
7
+ *
8
+ * The checks of the ids are `isValidTraceId` and `isValidSpanId` of `@opentelemetry/api`
9
+ * (decision D7 of otel-ts). They refuse an id of only zeros.
10
+ */
11
+ import { isValidSpanId, isValidTraceId } from "@opentelemetry/api";
12
+ /**
13
+ * The metadata key of the W3C trace context of a call.
14
+ *
15
+ * @deprecated With an SDK of OpenTelemetry, the registered propagator writes and reads the trace
16
+ * context (roadmap C8 of otel-ts). A later MINOR removes this export, when each app registers an SDK (D6).
7
17
  */
8
- /** The metadata key of the W3C trace context of a call. */
9
18
  export const TRACEPARENT_METADATA_KEY = "traceparent";
19
+ /** The form of version 00. The ids in it have only lowercase digits, as W3C Trace Context requires. */
10
20
  const TRACEPARENT = /^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;
11
- const ZERO_TRACE = "0".repeat(32);
12
- const ZERO_SPAN = "0".repeat(16);
21
+ /** An uppercase hex digit. The checks of the API accept it, but W3C Trace Context does not. */
22
+ const UPPERCASE_HEX = /[A-F]/;
13
23
  /**
14
24
  * The size of the pool of random bytes. One fill gives 256 trace ids or 512 span ids. Before
15
25
  * (roadmap O94), each id called `getRandomValues` for 8 or 16 bytes: in a chain of 200 steps, the
@@ -41,34 +51,59 @@ function randomHex(bytes) {
41
51
  next += bytes;
42
52
  return hex;
43
53
  }
44
- /** This function makes a new trace id: 32 random hex digits. */
54
+ /**
55
+ * This function makes a new trace id: 32 random hex digits.
56
+ *
57
+ * @deprecated With an SDK of OpenTelemetry, the registered propagator writes and reads the trace
58
+ * context (roadmap C8 of otel-ts). A later MINOR removes this export, when each app registers an SDK (D6).
59
+ */
45
60
  export function newTraceId() {
46
61
  const id = randomHex(16);
47
- return id === ZERO_TRACE ? newTraceId() : id;
62
+ return isValidTraceId(id) ? id : newTraceId();
48
63
  }
49
- /** This function makes a new span id: 16 random hex digits. */
64
+ /**
65
+ * This function makes a new span id: 16 random hex digits.
66
+ *
67
+ * @deprecated With an SDK of OpenTelemetry, the registered propagator writes and reads the trace
68
+ * context (roadmap C8 of otel-ts). A later MINOR removes this export, when each app registers an SDK (D6).
69
+ */
50
70
  export function newSpanId() {
51
71
  const id = randomHex(8);
52
- return id === ZERO_SPAN ? newSpanId() : id;
72
+ return isValidSpanId(id) ? id : newSpanId();
53
73
  }
54
- /** True when the ids have the W3C form. */
74
+ /**
75
+ * This function gives true when the ids have the W3C form.
76
+ *
77
+ * @deprecated With an SDK of OpenTelemetry, the registered propagator writes and reads the trace
78
+ * context (roadmap C8 of otel-ts). A later MINOR removes this export, when each app registers an SDK (D6).
79
+ */
55
80
  export function isW3cIds(traceId, spanId) {
56
- return /^[0-9a-f]{32}$/.test(traceId) && /^[0-9a-f]{16}$/.test(spanId) && traceId !== ZERO_TRACE && spanId !== ZERO_SPAN;
81
+ return isValidTraceId(traceId) && isValidSpanId(spanId) && !UPPERCASE_HEX.test(traceId) && !UPPERCASE_HEX.test(spanId);
57
82
  }
58
- /** The `traceparent` of a trace and a span. */
83
+ /**
84
+ * This function gives the `traceparent` of a trace and a span.
85
+ *
86
+ * @deprecated With an SDK of OpenTelemetry, the registered propagator writes and reads the trace
87
+ * context (roadmap C8 of otel-ts). A later MINOR removes this export, when each app registers an SDK (D6).
88
+ */
59
89
  export function formatTraceparent(traceId, spanId, sampled = true) {
60
90
  return `00-${traceId}-${spanId}-${sampled ? "01" : "00"}`;
61
91
  }
62
- /** The trace context of a `traceparent` value, or undefined when it is not valid. */
92
+ /**
93
+ * This function gives the trace context of a `traceparent` value, or undefined when it is not valid.
94
+ *
95
+ * @deprecated With an SDK of OpenTelemetry, the registered propagator writes and reads the trace
96
+ * context (roadmap C8 of otel-ts). A later MINOR removes this export, when each app registers an SDK (D6).
97
+ */
63
98
  export function parseTraceparent(value) {
64
99
  if (typeof value !== "string")
65
100
  return undefined;
66
101
  const match = TRACEPARENT.exec(value.trim().toLowerCase());
67
102
  if (!match)
68
103
  return undefined;
69
- const [, traceId, spanId, flags] = match;
70
- if (traceId === ZERO_TRACE || spanId === ZERO_SPAN)
104
+ const [, traceId = "", spanId = "", flags = ""] = match;
105
+ if (!isValidTraceId(traceId) || !isValidSpanId(spanId))
71
106
  return undefined;
72
- return { traceId: traceId, spanId: spanId, sampled: (parseInt(flags, 16) & 1) === 1 };
107
+ return { traceId, spanId, sampled: (parseInt(flags, 16) & 1) === 1 };
73
108
  }
74
109
  //# sourceMappingURL=traceparent.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"traceparent.js","sourceRoot":"","sources":["../../src/tracing/traceparent.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"traceparent.js","sourceRoot":"","sources":["../../src/tracing/traceparent.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package",
3
3
  "name": "@procedures/client",
4
- "version": "0.52.3",
4
+ "version": "0.54.0",
5
5
  "description": "Procedures as data for TypeScript: define typed procedures once and call them in process, over HTTP, WebSocket, the CLI or MCP. Streaming, middleware, retries, caching and a JSON program format.",
6
6
  "keywords": [
7
7
  "rpc",
@@ -82,6 +82,7 @@
82
82
  },
83
83
  "files": [
84
84
  "dist",
85
+ "!dist/.tsbuildinfo",
85
86
  "src",
86
87
  "!src/**/*.test.ts",
87
88
  "!src/**/*.test.tsx",
@@ -112,19 +113,29 @@
112
113
  },
113
114
  "dependencies": {
114
115
  "@mark1russell7/algebra": "^0.8.0",
115
- "@mark1russell7/coeval": "^0.22.0",
116
- "@mark1russell7/collections": "^0.5.0",
116
+ "@mark1russell7/coeval": "^0.26.1",
117
+ "@mark1russell7/collections": "^0.10.0",
117
118
  "@mark1russell7/quantity": "^0.3.0",
118
- "@mark1russell7/service": "^0.6.0",
119
+ "@mark1russell7/service": "^0.8.3",
120
+ "@opentelemetry/api": "^1.9.0",
119
121
  "@types/ws": "^8.18.1",
120
122
  "ws": "^8.18.3",
121
123
  "zod": "^4.6.5"
122
124
  },
123
125
  "devDependencies": {
124
- "@mark1russell7/api-report": "^0.4.1",
126
+ "@mark1russell7/api-report": "^0.5.0",
125
127
  "@mark1russell7/cue": "^2.1.1",
126
- "@mark1russell7/rigor-bench": "^0.2.25",
128
+ "@mark1russell7/otel-ts": "^0.6.0",
129
+ "@mark1russell7/rigor-bench": "^0.2.33",
127
130
  "@mark1russell7/rigor-statistics": "^0.1.9",
131
+ "@mark1russell7/rigor-testkit": "^0.1.2",
132
+ "@opentelemetry/api-logs": "^0.223.0",
133
+ "@opentelemetry/context-async-hooks": "^2.12.0",
134
+ "@opentelemetry/core": "^2.12.0",
135
+ "@opentelemetry/sdk-logs": "^0.223.0",
136
+ "@opentelemetry/sdk-metrics": "^2.12.0",
137
+ "@opentelemetry/sdk-node": "~0.223.0",
138
+ "@opentelemetry/sdk-trace-base": "^2.12.0",
128
139
  "@stryker-mutator/core": "10.0.0",
129
140
  "@stryker-mutator/vitest-runner": "10.0.0",
130
141
  "@types/express": "^5.0.0",
@@ -50,6 +50,8 @@ export const DEFAULT_CORS_HEADERS: readonly string[] = Object.freeze([
50
50
  HTTPHeaders.SPAN_ID,
51
51
  HTTPHeaders.PARENT_SPAN_ID,
52
52
  "traceparent",
53
+ "tracestate",
54
+ "baggage",
53
55
  PROTOCOL_HEADER,
54
56
  HTTPHeaders.METADATA,
55
57
  // The narrowing key of a call (CLI-30)
@@ -45,6 +45,50 @@ export interface PortLike {
45
45
  start?(): void;
46
46
  }
47
47
 
48
+ /** The listener of a port of the main process of Electron: it gets an event, and the message is its `data`. */
49
+ type MainListener = (event: { readonly data: unknown }) => void;
50
+
51
+ /**
52
+ * A port of the main process of Electron (`MessagePortMain`). The listener of its `on` gets an event with the message
53
+ * in `data`. The Node form of `PortLike` gets the message itself, so `electronPortOf` changes the form.
54
+ */
55
+ export interface MainPortLike {
56
+ postMessage(message: unknown): void;
57
+ on(type: "message", listener: MainListener): unknown;
58
+ off?(type: "message", listener: MainListener): unknown;
59
+ start(): void;
60
+ }
61
+
62
+ /**
63
+ * This function gives the `PortLike` of a port of the main process of Electron. Each listener gets the `data` of the
64
+ * event. `off` removes the listener that `on` added, so the close of a `PortTransport` stops to listen.
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * const { port1, port2 } = new MessageChannelMain();
69
+ * new PortTransport(electronPortOf(port1), { serve: server });
70
+ * window.webContents.postMessage("ports", null, [port2]);
71
+ * ```
72
+ */
73
+ export function electronPortOf(port: MainPortLike): PortLike {
74
+ const listeners = new Map<(data: unknown) => void, MainListener>();
75
+ return {
76
+ postMessage: (message) => port.postMessage(message),
77
+ on(type, listener) {
78
+ const each: MainListener = (event) => listener(event.data);
79
+ listeners.set(listener, each);
80
+ return port.on(type, each);
81
+ },
82
+ off(type, listener) {
83
+ const each = listeners.get(listener);
84
+ if (each === undefined) return undefined;
85
+ listeners.delete(listener);
86
+ return port.off?.(type, each);
87
+ },
88
+ start: () => port.start(),
89
+ };
90
+ }
91
+
48
92
  /** The options of a `PortTransport`. */
49
93
  export interface PortTransportOptions {
50
94
  /** The handler of the calls of the other side, for example a `ProcedureServer`. */
package/src/browser.ts CHANGED
@@ -469,8 +469,8 @@ export type { MachineDocument, MachineTransition, MachineTransitionDef, MachineT
469
469
  export { TIMER_DEFAULTS } from "./timers.js";
470
470
  export type { TimerDefaults } from "./timers.js";
471
471
  // The transport over a message port: a Web Worker, a MessageChannel (roadmap R27)
472
- export { PortTransport } from "./adapters/port/transport.js";
473
- export type { PortLike, PortTransportOptions } from "./adapters/port/transport.js";
472
+ export { PortTransport, electronPortOf } from "./adapters/port/transport.js";
473
+ export type { MainPortLike, PortLike, PortTransportOptions } from "./adapters/port/transport.js";
474
474
  // The W3C trace context: one trace from the client to the handler (roadmap R16)
475
475
  export { TRACEPARENT_METADATA_KEY, formatTraceparent, parseTraceparent, newTraceId, newSpanId, isW3cIds } from "./tracing/traceparent.js";
476
476
  export type { TraceContext } from "./tracing/traceparent.js";
@@ -460,6 +460,9 @@ export function determineStrategy(
460
460
 
461
461
  /**
462
462
  * Semaphore for limiting concurrent executions.
463
+ *
464
+ * @deprecated Use `semaphore()` of `@mark1russell7/collections`, the home of the semaphore. Its `run(task)` replaces
465
+ * `withPermit(fn)`, and its `acquire()` gives the release function. The next MINOR break of `@procedures/client` removes this class.
463
466
  */
464
467
  export class Semaphore {
465
468
  private permits: number;
@@ -70,6 +70,8 @@ import {
70
70
  import { narrowCapability } from "../procedures/capability.js";
71
71
  import { DEADLINE_METADATA_KEY, TIMEOUT_METADATA_KEY, absorbTimeout, writeRemainingTime } from "./deadline.js";
72
72
  import { ms } from "../duration.js";
73
+ import { context as otelContext, SpanKind, trace } from "@opentelemetry/api";
74
+ import { asInternalCall, callParent, callerTraceOf, endApiSpan, injectContext, startApiSpan, type CallerTrace } from "../tracing/otel.js";
73
75
  import type { Capability, ClearanceOrder, NestedCallOptions } from "../procedures/types.js";
74
76
  import { PROCEDURE_REGISTRY } from "../procedures/registry.js";
75
77
  import { validateSchema } from "../procedures/schema.js";
@@ -825,8 +827,9 @@ export class Client<TContext = {}> {
825
827
  // remaining time again for each try (roadmap R10)
826
828
  absorbTimeout(message.metadata);
827
829
 
828
- // Compose middleware chain
829
- const runner = this.composeMiddleware<TReq, TRes>();
830
+ // Compose middleware chain. The trace context that the caller gave in the metadata is read
831
+ // before a middleware or a try writes its own (roadmap C19 of otel-ts).
832
+ const runner = this.composeMiddleware<TReq, TRes>(callerTraceOf(message.metadata as Record<string, unknown>));
830
833
 
831
834
  // Execute through middleware chain
832
835
  const context: ClientContext<TReq> = { message };
@@ -897,7 +900,7 @@ export class Client<TContext = {}> {
897
900
  metadata["timeout"] = NO_SUBSCRIPTION_TIMEOUT as unknown as NonNullable<Metadata["timeout"]>;
898
901
  Object.assign(metadata, message.metadata);
899
902
  absorbTimeout(metadata);
900
- return this.composeMiddleware<TReq, TRes>()({ message: { ...message, metadata } });
903
+ return this.composeMiddleware<TReq, TRes>(callerTraceOf(metadata as Record<string, unknown>))({ message: { ...message, metadata } });
901
904
  };
902
905
  this.subscriptionTransport = { send };
903
906
  }
@@ -907,17 +910,40 @@ export class Client<TContext = {}> {
907
910
  /**
908
911
  * Compose middleware chain using universal middleware composition.
909
912
  *
913
+ * @param caller - The trace context that the caller gave in the metadata of the call. It is the
914
+ * parent of the span of each try when no span is active (roadmap C19 of otel-ts).
910
915
  * @returns Composed runner function
911
916
  * @private
912
917
  */
913
- private composeMiddleware<TReq, TRes>(): ClientRunner<TReq, TRes> {
918
+ private composeMiddleware<TReq, TRes>(caller: CallerTrace): ClientRunner<TReq, TRes> {
914
919
  // Innermost function: call transport
915
920
  const self = this;
916
921
  const transportRunner: ClientRunner<TReq, TRes> = async function* (context: ClientContext<TReq>) {
917
922
  // The remaining time of the deadline goes with each try, so the server stops the handler
918
923
  // when this client stops waiting (roadmap R10)
919
924
  writeRemainingTime(context.message.metadata);
920
- yield* self.transport.send<TReq, TRes>(context.message);
925
+ // With an SDK of OpenTelemetry, each try is a span of the API, and the propagator writes its
926
+ // context into the metadata, with the mark of an internal call (roadmap C8 of otel-ts)
927
+ const { metadata, method } = context.message;
928
+ const active = callParent(caller, otelContext.active());
929
+ const span = startApiSpan(`${method.service}.${method.operation}`, SpanKind.CLIENT, active);
930
+ if (span === undefined) {
931
+ yield* self.transport.send<TReq, TRes>(context.message);
932
+ return;
933
+ }
934
+ const own = span.spanContext();
935
+ const parent = trace.getSpanContext(active);
936
+ metadata.tracing = { traceId: own.traceId, spanId: own.spanId, ...(parent ? { parentSpanId: parent.spanId } : {}) };
937
+ injectContext(asInternalCall(trace.setSpan(active, span)), metadata as Record<string, unknown>);
938
+ let failure: unknown;
939
+ try {
940
+ yield* self.transport.send<TReq, TRes>(context.message);
941
+ } catch (error) {
942
+ failure = error;
943
+ throw error;
944
+ } finally {
945
+ endApiSpan(span, failure);
946
+ }
921
947
  };
922
948
 
923
949
  // The first middleware is the outermost one (onion model)
@@ -137,6 +137,8 @@ const VOLATILE_METADATA = new Set([
137
137
  "idempotency-key",
138
138
  "procedure-timeout",
139
139
  "traceparent",
140
+ "tracestate",
141
+ "baggage",
140
142
  ]);
141
143
 
142
144
  /** The part of the metadata that can change a response. */
@@ -308,8 +308,10 @@ export function createRateLimitMiddleware(
308
308
  while (queue.length > 0) {
309
309
  const decision = take();
310
310
  if (!decision.allowed) {
311
- // The time until the next whole token
312
- const wait = Math.max(1, Math.ceil(valueIn(decision.retryAfter, "ms")));
311
+ // The time until the next whole token. A take of one unit is never over a burst of 1 or more,
312
+ // so a capacity refusal does not occur here. If it occurs, the queue waits one window.
313
+ const wait =
314
+ decision.cause === "capacity" ? Math.max(1, window) : Math.max(1, Math.ceil(valueIn(decision.retryAfter, "ms")));
313
315
  drainTimer = clock.after(wait, () => {
314
316
  drainTimer = undefined;
315
317
  scheduleDrain();
@@ -10,6 +10,8 @@ import { InvocationError, closableUnread, type Interceptor, type Invocation, typ
10
10
  import { pathToKey, type ProcedurePath } from "./types.js";
11
11
  import { toMs, type Duration } from "../duration.js";
12
12
  import { TRACEPARENT_METADATA_KEY, formatTraceparent, newSpanId, newTraceId, parseTraceparent } from "../tracing/traceparent.js";
13
+ import { context as otelContext, isSpanContextValid, SpanKind, trace, type Span } from "@opentelemetry/api";
14
+ import { endApiSpan, remoteParent, startApiSpan } from "../tracing/otel.js";
13
15
 
14
16
  // =============================================================================
15
17
  // Timeout
@@ -134,7 +136,10 @@ export interface InvocationSpan {
134
136
  * remote caller, when the caller sent a `traceparent` (roadmap R16), or none.
135
137
  */
136
138
  readonly parentId: string | undefined;
137
- /** True when the parent span is in another process: the caller of a host. */
139
+ /**
140
+ * True when the parent span came from the `traceparent` of the call: the caller of a host. With an
141
+ * SDK of OpenTelemetry, a local call of a client also has a remote parent: the span of its client.
142
+ */
138
143
  readonly remoteParent?: boolean | undefined;
139
144
  /** The same for every span of one call of a host, and of its caller when it sent a trace. */
140
145
  readonly traceId: string;
@@ -162,6 +167,12 @@ export interface TracingInterceptorOptions {
162
167
  onStart?: ((span: InvocationSpan) => void) | undefined;
163
168
  /** The clock. The default is `performance.now()`. */
164
169
  now?: (() => number) | undefined;
170
+ /**
171
+ * With an SDK of OpenTelemetry, each invocation is also a span of the API, and the span ids come
172
+ * from it: a host span (`SERVER`) for a call of a host, an `INTERNAL` span for a nested call.
173
+ * The handler runs in the context of its span. Default: true. With no SDK, this option does nothing.
174
+ */
175
+ apiSpans?: boolean | undefined;
165
176
  }
166
177
 
167
178
 
@@ -173,6 +184,8 @@ export function tracingInterceptor(options: TracingInterceptorOptions): Intercep
173
184
  const now = options.now ?? (() => performance.now());
174
185
  /** The open spans, by the id of their invocation */
175
186
  const open = new Map<number, InvocationSpan>();
187
+ /** The open spans of the API, by the id of their invocation */
188
+ const apiOpen = new Map<number, Span>();
176
189
 
177
190
  const finish = (span: InvocationSpan, invocationId: number, error?: unknown): void => {
178
191
  span.end = now();
@@ -186,22 +199,35 @@ export function tracingInterceptor(options: TracingInterceptorOptions): Intercep
186
199
  span.status = "ok";
187
200
  }
188
201
  open.delete(invocationId);
202
+ const apiSpan = apiOpen.get(invocationId);
203
+ if (apiSpan !== undefined) {
204
+ apiOpen.delete(invocationId);
205
+ endApiSpan(apiSpan, error);
206
+ }
189
207
  options.onSpan(span);
190
208
  };
191
209
 
192
210
  return async (invocation, next) => {
193
211
  const parent = invocation.parent ? open.get(invocation.parent.id) : undefined;
194
- // A call of a host continues the trace of its caller (W3C traceparent, roadmap R16)
195
- const remote = parent ? undefined : parseTraceparent(invocation.metadata[TRACEPARENT_METADATA_KEY]);
212
+ const key = pathToKey(invocation.path);
213
+ // With an SDK, the span of the API gives the ids (roadmap C8 of otel-ts, D5). The parent is the
214
+ // span of the parent invocation, else the span of the traceparent of the call.
215
+ const parentApi = invocation.parent ? apiOpen.get(invocation.parent.id) : undefined;
216
+ const apiParent = parentApi !== undefined ? trace.setSpan(otelContext.active(), parentApi) : remoteParent(invocation.metadata);
217
+ const apiSpan = options.apiSpans === false ? undefined : startApiSpan(key, invocation.parent ? SpanKind.INTERNAL : SpanKind.SERVER, apiParent);
218
+ const apiParentSpan = apiSpan === undefined ? undefined : trace.getSpanContext(apiParent);
219
+ // With no SDK: a call of a host continues the trace of its caller (W3C traceparent, roadmap R16)
220
+ const remote = parent || apiSpan ? undefined : parseTraceparent(invocation.metadata[TRACEPARENT_METADATA_KEY]);
196
221
  // W3C span ids, so a trace backend can join the spans of the client and of the server
197
- const id = newSpanId();
222
+ const id = apiSpan?.spanContext().spanId ?? newSpanId();
223
+ const apiParentId = apiParentSpan !== undefined && isSpanContextValid(apiParentSpan) ? apiParentSpan.spanId : undefined;
198
224
  const span: InvocationSpan = {
199
225
  id,
200
- parentId: parent?.id ?? remote?.spanId,
201
- ...(remote ? { remoteParent: true } : {}),
202
- traceId: parent?.traceId ?? remote?.traceId ?? newTraceId(),
226
+ parentId: apiSpan ? apiParentId : (parent?.id ?? remote?.spanId),
227
+ ...(remote || (apiParentId !== undefined && apiParentSpan?.isRemote === true) ? { remoteParent: true } : {}),
228
+ traceId: apiSpan?.spanContext().traceId ?? parent?.traceId ?? remote?.traceId ?? newTraceId(),
203
229
  path: invocation.path,
204
- key: pathToKey(invocation.path),
230
+ key,
205
231
  depth: invocation.depth,
206
232
  start: now(),
207
233
  end: undefined,
@@ -209,12 +235,16 @@ export function tracingInterceptor(options: TracingInterceptorOptions): Intercep
209
235
  status: "running",
210
236
  };
211
237
  open.set(invocation.id, span);
238
+ if (apiSpan !== undefined) apiOpen.set(invocation.id, apiSpan);
212
239
  options.onStart?.(span);
213
240
 
214
241
  let output: ProcedureOutput<unknown>;
215
242
  try {
216
243
  // The nested calls (and their remote calls) carry the span of this invocation (R16)
217
- output = await next({ metadata: { ...invocation.metadata, [TRACEPARENT_METADATA_KEY]: formatTraceparent(span.traceId, id) } });
244
+ const sampled = apiSpan === undefined || (apiSpan.spanContext().traceFlags & 1) === 1;
245
+ const run = () => next({ metadata: { ...invocation.metadata, [TRACEPARENT_METADATA_KEY]: formatTraceparent(span.traceId, id, sampled) } });
246
+ // The handler runs in the context of its span, so its logs, metrics and calls get the ids
247
+ output = await (apiSpan === undefined ? run() : otelContext.with(trace.setSpan(apiParent, apiSpan), run));
218
248
  } catch (error) {
219
249
  finish(span, invocation.id, error);
220
250
  throw error;
@@ -286,6 +316,7 @@ export function metricsInterceptor(options: { now?: () => number } = {}): Metric
286
316
  };
287
317
  return {
288
318
  interceptor: tracingInterceptor({
319
+ apiSpans: false,
289
320
  onSpan: (span) => record(span.key, span.durationMs ?? 0, span.status === "error"),
290
321
  ...(options.now ? { now: options.now } : {}),
291
322
  }),
@@ -96,6 +96,12 @@ export interface HostPolicyOptions {
96
96
  * query) and gives a bearer token, a principal and a narrowing (`ProcedureServerOptions`).
97
97
  */
98
98
  principalOf?: PrincipalOf | undefined;
99
+ /**
100
+ * The callers whose `baggage` the handler gets (C10), more than a local request and a request
101
+ * with the token. The function runs after the checks of the request, so it can read
102
+ * `request.principal`. Without it, no other caller sets the baggage of the services.
103
+ */
104
+ trustBaggage?: ((request: ServerRequest<unknown>) => boolean) | undefined;
99
105
  /** The browser checks of the network transports (`allowedOrigins`, `allowedHosts`, `allowGet`). */
100
106
  browser?: BrowserGuardOptions | undefined;
101
107
  /**
@@ -137,6 +143,8 @@ export interface HostPolicy {
137
143
  readonly token: string | undefined;
138
144
  /** The request hook of the network requests, or undefined for none. */
139
145
  readonly principalOf?: PrincipalOf | undefined;
146
+ /** The callers whose baggage the handler gets (C10), or undefined for none more. */
147
+ readonly trustBaggage?: ((request: ServerRequest<unknown>) => boolean) | undefined;
140
148
  readonly browser: BrowserGuardOptions;
141
149
  /** The capability of a call from a caller, or undefined for no limits. */
142
150
  readonly capabilityOf: ((request?: ServerRequest<unknown>) => Capability | undefined) | undefined;
@@ -190,6 +198,7 @@ export function hostPolicy(options: HostPolicyOptions = {}): HostPolicy {
190
198
  interceptors,
191
199
  token: options.token,
192
200
  ...(options.principalOf ? { principalOf: options.principalOf } : {}),
201
+ ...(options.trustBaggage ? { trustBaggage: options.trustBaggage } : {}),
193
202
  browser: options.browser ?? {},
194
203
  capabilityOf: capabilityOfOptions(options),
195
204
  clearanceOrder: options.clearanceOrder,
@@ -212,6 +221,8 @@ export function procedureServerOptions(policy: HostPolicy, registry?: ProcedureR
212
221
  ...(policy.token !== undefined ? { token: policy.token } : {}),
213
222
  // The request hook of the policy: the credentials of a cookie, a header or the query (CLI-2a)
214
223
  ...(policy.principalOf ? { principalOf: policy.principalOf } : {}),
224
+ // The callers whose baggage the handler gets (C10)
225
+ ...(policy.trustBaggage ? { trustBaggage: policy.trustBaggage } : {}),
215
226
  ...(policy.interceptors.length > 0 ? { interceptors: policy.interceptors } : {}),
216
227
  ...(policy.capabilityOf ? { capability: policy.capabilityOf } : {}),
217
228
  ...(policy.clearanceOrder ? { clearanceOrder: policy.clearanceOrder } : {}),
@@ -236,9 +236,22 @@ export function limitedCall(procedure: { readonly path: ProcedurePath; readonly
236
236
  /**
237
237
  * This function gives the response of a refused call: `RATE_LIMITED`, retryable, with the wait in
238
238
  * `retry-after` (milliseconds) and the cause in `reason`. The failure of a store stays on the server.
239
+ * A refusal with the cause `capacity` is not retryable and has no wait: no wait lets its units in.
239
240
  */
240
241
  export function rateLimitedResponse(id: string, refusal: CallLimitRefusal): ServerResponse<unknown> {
241
242
  const { limit, refused } = refusal;
243
+ if (refused.cause === "capacity") {
244
+ return {
245
+ id,
246
+ status: {
247
+ type: "error",
248
+ code: "RATE_LIMITED",
249
+ message: `The limit ${limit} of the host refuses the call at each time: ${refused.units} units are over its capacity of ${refused.capacity}`,
250
+ retryable: false,
251
+ },
252
+ metadata: { reason: refused.cause },
253
+ };
254
+ }
242
255
  const waitMs = valueIn(refused.retryAfter, "ms");
243
256
  const seconds = retryAfterSeconds(refused.retryAfter);
244
257
  return {