@procedures/client 0.2.2 → 0.3.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/CHANGELOG.md +71 -0
- package/README.md +61 -38
- package/dist/.tsbuildinfo +1 -1
- package/dist/adapters/http/client/transport.d.ts.map +1 -1
- package/dist/adapters/http/client/transport.js +35 -3
- package/dist/adapters/http/client/transport.js.map +1 -1
- package/dist/adapters/http/client/types.d.ts +3 -2
- package/dist/adapters/http/client/types.d.ts.map +1 -1
- package/dist/adapters/http/server/transport.d.ts +2 -2
- package/dist/adapters/http/server/transport.d.ts.map +1 -1
- package/dist/adapters/http/server/transport.js +53 -20
- package/dist/adapters/http/server/transport.js.map +1 -1
- package/dist/adapters/http/shared/headers.d.ts +1 -1
- package/dist/adapters/http/shared/headers.d.ts.map +1 -1
- package/dist/adapters/http/shared/headers.js +7 -6
- package/dist/adapters/http/shared/headers.js.map +1 -1
- package/dist/adapters/local/client/transport.d.ts +3 -0
- package/dist/adapters/local/client/transport.d.ts.map +1 -1
- package/dist/adapters/local/client/transport.js +63 -4
- package/dist/adapters/local/client/transport.js.map +1 -1
- package/dist/adapters/local/client/types.d.ts +15 -0
- package/dist/adapters/local/client/types.d.ts.map +1 -1
- package/dist/adapters/local/client/types.js.map +1 -1
- package/dist/adapters/mock/client/transport.d.ts +3 -2
- package/dist/adapters/mock/client/transport.d.ts.map +1 -1
- package/dist/adapters/mock/client/transport.js +8 -5
- package/dist/adapters/mock/client/transport.js.map +1 -1
- package/dist/adapters/mock/client/types.d.ts +7 -6
- package/dist/adapters/mock/client/types.d.ts.map +1 -1
- package/dist/adapters/port/transport.d.ts +62 -0
- package/dist/adapters/port/transport.d.ts.map +1 -0
- package/dist/adapters/port/transport.js +94 -0
- package/dist/adapters/port/transport.js.map +1 -0
- package/dist/adapters/websocket/client/index.d.ts +1 -1
- package/dist/adapters/websocket/client/index.d.ts.map +1 -1
- package/dist/adapters/websocket/client/index.js.map +1 -1
- package/dist/adapters/websocket/client/transport.d.ts +47 -7
- package/dist/adapters/websocket/client/transport.d.ts.map +1 -1
- package/dist/adapters/websocket/client/transport.js +213 -67
- package/dist/adapters/websocket/client/transport.js.map +1 -1
- package/dist/adapters/websocket/client/types.d.ts +70 -21
- package/dist/adapters/websocket/client/types.d.ts.map +1 -1
- package/dist/adapters/websocket/client/types.js.map +1 -1
- package/dist/adapters/websocket/server/transport.d.ts +65 -24
- package/dist/adapters/websocket/server/transport.d.ts.map +1 -1
- package/dist/adapters/websocket/server/transport.js +192 -263
- package/dist/adapters/websocket/server/transport.js.map +1 -1
- package/dist/adapters/websocket/server/types.d.ts +20 -1
- package/dist/adapters/websocket/server/types.d.ts.map +1 -1
- package/dist/adapters/websocket/shared/rpc.d.ts +129 -0
- package/dist/adapters/websocket/shared/rpc.d.ts.map +1 -0
- package/dist/adapters/websocket/shared/rpc.js +425 -0
- package/dist/adapters/websocket/shared/rpc.js.map +1 -0
- package/dist/browser.d.ts +29 -7
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +26 -3
- package/dist/browser.js.map +1 -1
- package/dist/client/call-types.d.ts +10 -10
- package/dist/client/call-types.d.ts.map +1 -1
- package/dist/client/call-types.js +1 -6
- package/dist/client/call-types.js.map +1 -1
- package/dist/client/client.d.ts +6 -1
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +23 -9
- package/dist/client/client.js.map +1 -1
- package/dist/client/deadline.d.ts +38 -0
- package/dist/client/deadline.d.ts.map +1 -0
- package/dist/client/deadline.js +60 -0
- package/dist/client/deadline.js.map +1 -0
- package/dist/client/errors/factory.d.ts.map +1 -1
- package/dist/client/errors/factory.js +11 -7
- package/dist/client/errors/factory.js.map +1 -1
- package/dist/client/index.d.ts +9 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +8 -2
- package/dist/client/index.js.map +1 -1
- package/dist/client/middleware/cache.d.ts +19 -5
- package/dist/client/middleware/cache.d.ts.map +1 -1
- package/dist/client/middleware/cache.js +106 -18
- package/dist/client/middleware/cache.js.map +1 -1
- package/dist/client/middleware/circuit-breaker.d.ts +28 -10
- package/dist/client/middleware/circuit-breaker.d.ts.map +1 -1
- package/dist/client/middleware/circuit-breaker.js +35 -24
- package/dist/client/middleware/circuit-breaker.js.map +1 -1
- package/dist/client/middleware/contexts.d.ts +16 -10
- package/dist/client/middleware/contexts.d.ts.map +1 -1
- package/dist/client/middleware/contexts.js.map +1 -1
- package/dist/client/middleware/items.d.ts +31 -2
- package/dist/client/middleware/items.d.ts.map +1 -1
- package/dist/client/middleware/items.js +86 -6
- package/dist/client/middleware/items.js.map +1 -1
- package/dist/client/middleware/lru.d.ts +2 -0
- package/dist/client/middleware/lru.d.ts.map +1 -1
- package/dist/client/middleware/lru.js +11 -0
- package/dist/client/middleware/lru.js.map +1 -1
- package/dist/client/middleware/rate-limit.d.ts +7 -6
- package/dist/client/middleware/rate-limit.d.ts.map +1 -1
- package/dist/client/middleware/rate-limit.js +9 -6
- package/dist/client/middleware/rate-limit.js.map +1 -1
- package/dist/client/middleware/resilience.d.ts +61 -0
- package/dist/client/middleware/resilience.d.ts.map +1 -0
- package/dist/client/middleware/resilience.js +59 -0
- package/dist/client/middleware/resilience.js.map +1 -0
- package/dist/client/middleware/retry.d.ts +45 -11
- package/dist/client/middleware/retry.d.ts.map +1 -1
- package/dist/client/middleware/retry.js +102 -22
- package/dist/client/middleware/retry.js.map +1 -1
- package/dist/client/middleware/timeout.d.ts +15 -12
- package/dist/client/middleware/timeout.d.ts.map +1 -1
- package/dist/client/middleware/timeout.js +46 -37
- package/dist/client/middleware/timeout.js.map +1 -1
- package/dist/client/middleware/tracing.d.ts.map +1 -1
- package/dist/client/middleware/tracing.js +11 -3
- package/dist/client/middleware/tracing.js.map +1 -1
- package/dist/client/types.d.ts +9 -1
- package/dist/client/types.d.ts.map +1 -1
- package/dist/client/types.js.map +1 -1
- package/dist/clock.d.ts +19 -0
- package/dist/clock.d.ts.map +1 -0
- package/dist/clock.js +18 -0
- package/dist/clock.js.map +1 -0
- package/dist/duration.d.ts +37 -0
- package/dist/duration.d.ts.map +1 -0
- package/dist/duration.js +44 -0
- package/dist/duration.js.map +1 -0
- package/dist/machines.d.ts +90 -0
- package/dist/machines.d.ts.map +1 -0
- package/dist/machines.js +127 -0
- package/dist/machines.js.map +1 -0
- package/dist/procedures/capability.d.ts +3 -6
- package/dist/procedures/capability.d.ts.map +1 -1
- package/dist/procedures/capability.js +11 -9
- package/dist/procedures/capability.js.map +1 -1
- package/dist/procedures/collection/index.d.ts +3 -3
- package/dist/procedures/collection/index.d.ts.map +1 -1
- package/dist/procedures/collection/index.js +1 -1
- package/dist/procedures/collection/index.js.map +1 -1
- package/dist/procedures/collection/procedures.d.ts.map +1 -1
- package/dist/procedures/collection/procedures.js +49 -1
- package/dist/procedures/collection/procedures.js.map +1 -1
- package/dist/procedures/collection/schemas.d.ts +33 -0
- package/dist/procedures/collection/schemas.d.ts.map +1 -1
- package/dist/procedures/collection/schemas.js +68 -0
- package/dist/procedures/collection/schemas.js.map +1 -1
- package/dist/procedures/collection/storage.d.ts +37 -0
- package/dist/procedures/collection/storage.d.ts.map +1 -1
- package/dist/procedures/core/array.d.ts.map +1 -1
- package/dist/procedures/core/array.js +26 -15
- package/dist/procedures/core/array.js.map +1 -1
- package/dist/procedures/core/index.d.ts.map +1 -1
- package/dist/procedures/core/index.js +8 -0
- package/dist/procedures/core/index.js.map +1 -1
- package/dist/procedures/core/meta.d.ts.map +1 -1
- package/dist/procedures/core/meta.js +2 -1
- package/dist/procedures/core/meta.js.map +1 -1
- package/dist/procedures/core/object.d.ts.map +1 -1
- package/dist/procedures/core/object.js +42 -13
- package/dist/procedures/core/object.js.map +1 -1
- package/dist/procedures/core/operands.d.ts.map +1 -1
- package/dist/procedures/core/operands.js +6 -1
- package/dist/procedures/core/operands.js.map +1 -1
- package/dist/procedures/core/string.d.ts.map +1 -1
- package/dist/procedures/core/string.js +4 -2
- package/dist/procedures/core/string.js.map +1 -1
- package/dist/procedures/define-procedure.d.ts +19 -7
- package/dist/procedures/define-procedure.d.ts.map +1 -1
- package/dist/procedures/define-procedure.js +66 -21
- package/dist/procedures/define-procedure.js.map +1 -1
- package/dist/procedures/effects.d.ts +16 -0
- package/dist/procedures/effects.d.ts.map +1 -1
- package/dist/procedures/effects.js +33 -0
- package/dist/procedures/effects.js.map +1 -1
- package/dist/procedures/idempotency.d.ts +73 -0
- package/dist/procedures/idempotency.d.ts.map +1 -0
- package/dist/procedures/idempotency.js +131 -0
- package/dist/procedures/idempotency.js.map +1 -0
- package/dist/procedures/index.d.ts +7 -3
- package/dist/procedures/index.d.ts.map +1 -1
- package/dist/procedures/index.js +6 -2
- package/dist/procedures/index.js.map +1 -1
- package/dist/procedures/interceptors.d.ts +17 -4
- package/dist/procedures/interceptors.d.ts.map +1 -1
- package/dist/procedures/interceptors.js +18 -7
- package/dist/procedures/interceptors.js.map +1 -1
- package/dist/procedures/invoke.d.ts +15 -3
- package/dist/procedures/invoke.d.ts.map +1 -1
- package/dist/procedures/invoke.js +8 -3
- package/dist/procedures/invoke.js.map +1 -1
- package/dist/procedures/manifest.d.ts +83 -0
- package/dist/procedures/manifest.d.ts.map +1 -0
- package/dist/procedures/manifest.js +90 -0
- package/dist/procedures/manifest.js.map +1 -0
- package/dist/procedures/runtime-programs.d.ts +19 -0
- package/dist/procedures/runtime-programs.d.ts.map +1 -0
- package/dist/procedures/runtime-programs.js +24 -0
- package/dist/procedures/runtime-programs.js.map +1 -0
- package/dist/procedures/types.d.ts +20 -1
- package/dist/procedures/types.d.ts.map +1 -1
- package/dist/procedures/types.js.map +1 -1
- package/dist/protocol.d.ts +40 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +58 -0
- package/dist/protocol.js.map +1 -0
- package/dist/server/host-policy.d.ts +4 -3
- package/dist/server/host-policy.d.ts.map +1 -1
- package/dist/server/host-policy.js +6 -3
- package/dist/server/host-policy.js.map +1 -1
- package/dist/server/host-procedures.d.ts +31 -0
- package/dist/server/host-procedures.d.ts.map +1 -0
- package/dist/server/host-procedures.js +75 -0
- package/dist/server/host-procedures.js.map +1 -0
- package/dist/server/index.d.ts +3 -3
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/procedure-server.d.ts +110 -4
- package/dist/server/procedure-server.d.ts.map +1 -1
- package/dist/server/procedure-server.js +269 -17
- package/dist/server/procedure-server.js.map +1 -1
- package/dist/server/request-auth.d.ts +19 -0
- package/dist/server/request-auth.d.ts.map +1 -0
- package/dist/server/request-auth.js +32 -0
- package/dist/server/request-auth.js.map +1 -0
- package/dist/server/types.d.ts +32 -0
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/types.js.map +1 -1
- package/dist/timers.d.ts +60 -0
- package/dist/timers.d.ts.map +1 -0
- package/dist/timers.js +33 -0
- package/dist/timers.js.map +1 -0
- package/dist/tracing/traceparent.d.ts +26 -0
- package/dist/tracing/traceparent.d.ts.map +1 -0
- package/dist/tracing/traceparent.js +53 -0
- package/dist/tracing/traceparent.js.map +1 -0
- package/package.json +7 -1
- package/src/adapters/http/client/transport.ts +31 -3
- package/src/adapters/http/client/types.ts +3 -2
- package/src/adapters/http/server/transport.ts +60 -24
- package/src/adapters/http/shared/headers.ts +7 -6
- package/src/adapters/local/client/transport.ts +70 -5
- package/src/adapters/local/client/types.ts +18 -0
- package/src/adapters/mock/client/transport.ts +9 -6
- package/src/adapters/mock/client/types.ts +7 -6
- package/src/adapters/port/transport.ts +124 -0
- package/src/adapters/websocket/client/index.ts +1 -0
- package/src/adapters/websocket/client/transport.ts +232 -73
- package/src/adapters/websocket/client/types.ts +80 -21
- package/src/adapters/websocket/server/transport.ts +199 -301
- package/src/adapters/websocket/server/types.ts +20 -1
- package/src/adapters/websocket/shared/rpc.ts +453 -0
- package/src/browser.ts +51 -3
- package/src/client/call-types.ts +10 -14
- package/src/client/client.ts +28 -9
- package/src/client/deadline.ts +64 -0
- package/src/client/errors/factory.ts +12 -8
- package/src/client/index.ts +21 -3
- package/src/client/middleware/cache.ts +120 -23
- package/src/client/middleware/circuit-breaker.ts +65 -35
- package/src/client/middleware/contexts.ts +17 -10
- package/src/client/middleware/items.ts +101 -6
- package/src/client/middleware/lru.ts +12 -0
- package/src/client/middleware/rate-limit.ts +9 -7
- package/src/client/middleware/resilience.ts +81 -0
- package/src/client/middleware/retry.ts +146 -34
- package/src/client/middleware/timeout.ts +55 -46
- package/src/client/middleware/tracing.ts +13 -4
- package/src/client/types.ts +9 -1
- package/src/clock.ts +21 -0
- package/src/duration.ts +56 -0
- package/src/machines.ts +186 -0
- package/src/procedures/capability.ts +11 -9
- package/src/procedures/collection/index.ts +9 -1
- package/src/procedures/collection/procedures.ts +57 -0
- package/src/procedures/collection/schemas.ts +110 -0
- package/src/procedures/collection/storage.ts +38 -0
- package/src/procedures/core/array.ts +27 -16
- package/src/procedures/core/index.ts +8 -0
- package/src/procedures/core/meta.ts +2 -1
- package/src/procedures/core/object.ts +48 -14
- package/src/procedures/core/operands.ts +7 -0
- package/src/procedures/core/string.ts +4 -2
- package/src/procedures/define-procedure.ts +82 -24
- package/src/procedures/effects.ts +35 -0
- package/src/procedures/idempotency.ts +152 -0
- package/src/procedures/index.ts +18 -2
- package/src/procedures/interceptors.ts +28 -11
- package/src/procedures/invoke.ts +19 -5
- package/src/procedures/manifest.ts +158 -0
- package/src/procedures/runtime-programs.ts +27 -0
- package/src/procedures/types.ts +20 -1
- package/src/protocol.ts +97 -0
- package/src/server/host-policy.ts +8 -5
- package/src/server/host-procedures.ts +102 -0
- package/src/server/index.ts +5 -2
- package/src/server/procedure-server.ts +324 -10
- package/src/server/request-auth.ts +35 -0
- package/src/server/types.ts +37 -0
- package/src/timers.ts +82 -0
- package/src/tracing/traceparent.ts +61 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The W3C trace context (roadmap R16): `traceparent` is `00-<trace id>-<span id>-<flags>`, with a
|
|
3
|
+
* trace id of 32 hex digits and a span id of 16. The tracing middleware of a client sends it in
|
|
4
|
+
* the metadata of a call (and HTTP as the `traceparent` header). The tracing interceptor of a
|
|
5
|
+
* server continues that trace, and gives it to the nested calls, so one trace goes from the
|
|
6
|
+
* client to the last handler. Before, the client and the server made two traces.
|
|
7
|
+
*/
|
|
8
|
+
/** The metadata key of the W3C trace context of a call. */
|
|
9
|
+
export declare const TRACEPARENT_METADATA_KEY = "traceparent";
|
|
10
|
+
/** A trace context: the trace, the span of the caller, and the sampled flag. */
|
|
11
|
+
export interface TraceContext {
|
|
12
|
+
readonly traceId: string;
|
|
13
|
+
readonly spanId: string;
|
|
14
|
+
readonly sampled: boolean;
|
|
15
|
+
}
|
|
16
|
+
/** This function makes a new trace id: 32 random hex digits. */
|
|
17
|
+
export declare function newTraceId(): string;
|
|
18
|
+
/** This function makes a new span id: 16 random hex digits. */
|
|
19
|
+
export declare function newSpanId(): string;
|
|
20
|
+
/** True when the ids have the W3C form. */
|
|
21
|
+
export declare function isW3cIds(traceId: string, spanId: string): boolean;
|
|
22
|
+
/** The `traceparent` of a trace and a span. */
|
|
23
|
+
export declare function formatTraceparent(traceId: string, spanId: string, sampled?: boolean): string;
|
|
24
|
+
/** The trace context of a `traceparent` value, or undefined when it is not valid. */
|
|
25
|
+
export declare function parseTraceparent(value: unknown): TraceContext | undefined;
|
|
26
|
+
//# sourceMappingURL=traceparent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"traceparent.d.ts","sourceRoot":"","sources":["../../src/tracing/traceparent.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,2DAA2D;AAC3D,eAAO,MAAM,wBAAwB,gBAAgB,CAAC;AAMtD,gFAAgF;AAChF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAUD,gEAAgE;AAChE,wBAAgB,UAAU,IAAI,MAAM,CAGnC;AAED,+DAA+D;AAC/D,wBAAgB,SAAS,IAAI,MAAM,CAGlC;AAED,2CAA2C;AAC3C,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAEjE;AAED,+CAA+C;AAC/C,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,UAAO,GAAG,MAAM,CAEzF;AAED,qFAAqF;AACrF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,GAAG,SAAS,CAOzE"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The W3C trace context (roadmap R16): `traceparent` is `00-<trace id>-<span id>-<flags>`, with a
|
|
3
|
+
* trace id of 32 hex digits and a span id of 16. The tracing middleware of a client sends it in
|
|
4
|
+
* the metadata of a call (and HTTP as the `traceparent` header). The tracing interceptor of a
|
|
5
|
+
* server continues that trace, and gives it to the nested calls, so one trace goes from the
|
|
6
|
+
* client to the last handler. Before, the client and the server made two traces.
|
|
7
|
+
*/
|
|
8
|
+
/** The metadata key of the W3C trace context of a call. */
|
|
9
|
+
export const TRACEPARENT_METADATA_KEY = "traceparent";
|
|
10
|
+
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);
|
|
13
|
+
function randomHex(bytes) {
|
|
14
|
+
const values = new Uint8Array(bytes);
|
|
15
|
+
const crypto = globalThis.crypto;
|
|
16
|
+
if (crypto?.getRandomValues)
|
|
17
|
+
crypto.getRandomValues(values);
|
|
18
|
+
else
|
|
19
|
+
for (let index = 0; index < bytes; index++)
|
|
20
|
+
values[index] = Math.floor(Math.random() * 256);
|
|
21
|
+
return Array.from(values, (value) => value.toString(16).padStart(2, "0")).join("");
|
|
22
|
+
}
|
|
23
|
+
/** This function makes a new trace id: 32 random hex digits. */
|
|
24
|
+
export function newTraceId() {
|
|
25
|
+
const id = randomHex(16);
|
|
26
|
+
return id === ZERO_TRACE ? newTraceId() : id;
|
|
27
|
+
}
|
|
28
|
+
/** This function makes a new span id: 16 random hex digits. */
|
|
29
|
+
export function newSpanId() {
|
|
30
|
+
const id = randomHex(8);
|
|
31
|
+
return id === ZERO_SPAN ? newSpanId() : id;
|
|
32
|
+
}
|
|
33
|
+
/** True when the ids have the W3C form. */
|
|
34
|
+
export function isW3cIds(traceId, spanId) {
|
|
35
|
+
return /^[0-9a-f]{32}$/.test(traceId) && /^[0-9a-f]{16}$/.test(spanId) && traceId !== ZERO_TRACE && spanId !== ZERO_SPAN;
|
|
36
|
+
}
|
|
37
|
+
/** The `traceparent` of a trace and a span. */
|
|
38
|
+
export function formatTraceparent(traceId, spanId, sampled = true) {
|
|
39
|
+
return `00-${traceId}-${spanId}-${sampled ? "01" : "00"}`;
|
|
40
|
+
}
|
|
41
|
+
/** The trace context of a `traceparent` value, or undefined when it is not valid. */
|
|
42
|
+
export function parseTraceparent(value) {
|
|
43
|
+
if (typeof value !== "string")
|
|
44
|
+
return undefined;
|
|
45
|
+
const match = TRACEPARENT.exec(value.trim().toLowerCase());
|
|
46
|
+
if (!match)
|
|
47
|
+
return undefined;
|
|
48
|
+
const [, traceId, spanId, flags] = match;
|
|
49
|
+
if (traceId === ZERO_TRACE || spanId === ZERO_SPAN)
|
|
50
|
+
return undefined;
|
|
51
|
+
return { traceId: traceId, spanId: spanId, sampled: (parseInt(flags, 16) & 1) === 1 };
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=traceparent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"traceparent.js","sourceRoot":"","sources":["../../src/tracing/traceparent.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,2DAA2D;AAC3D,MAAM,CAAC,MAAM,wBAAwB,GAAG,aAAa,CAAC;AAEtD,MAAM,WAAW,GAAG,kDAAkD,CAAC;AACvE,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AAClC,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AASjC,SAAS,SAAS,CAAC,KAAa;IAC9B,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC;IACrC,MAAM,MAAM,GAAI,UAAmF,CAAC,MAAM,CAAC;IAC3G,IAAI,MAAM,EAAE,eAAe;QAAE,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;;QACvD,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,EAAE,KAAK,EAAE;YAAE,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC;IACjG,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACrF,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,UAAU;IACxB,MAAM,EAAE,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC;IACzB,OAAO,EAAE,KAAK,UAAU,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AAC/C,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,SAAS;IACvB,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;IACxB,OAAO,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AAC7C,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,QAAQ,CAAC,OAAe,EAAE,MAAc;IACtD,OAAO,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,OAAO,KAAK,UAAU,IAAI,MAAM,KAAK,SAAS,CAAC;AAC3H,CAAC;AAED,+CAA+C;AAC/C,MAAM,UAAU,iBAAiB,CAAC,OAAe,EAAE,MAAc,EAAE,OAAO,GAAG,IAAI;IAC/E,OAAO,MAAM,OAAO,IAAI,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAC5D,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAChD,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;IAC3D,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG,KAAK,CAAC;IACzC,IAAI,OAAO,KAAK,UAAU,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACrE,OAAO,EAAE,OAAO,EAAE,OAAQ,EAAE,MAAM,EAAE,MAAO,EAAE,OAAO,EAAE,CAAC,QAAQ,CAAC,KAAM,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;AAC3F,CAAC"}
|
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.
|
|
4
|
+
"version": "0.3.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",
|
|
@@ -90,14 +90,20 @@
|
|
|
90
90
|
"provenance": true
|
|
91
91
|
},
|
|
92
92
|
"dependencies": {
|
|
93
|
+
"@mark1russell7/algebra": "^0.5.0",
|
|
94
|
+
"@mark1russell7/quantity": "^0.2.0",
|
|
95
|
+
"@mark1russell7/service": "^0.4.0",
|
|
93
96
|
"ws": "^8.18.3",
|
|
94
97
|
"zod": "^4.6.5"
|
|
95
98
|
},
|
|
96
99
|
"devDependencies": {
|
|
97
100
|
"@mark1russell7/cue": "^1.0.0",
|
|
101
|
+
"@stryker-mutator/core": "10.0.0",
|
|
102
|
+
"@stryker-mutator/vitest-runner": "10.0.0",
|
|
98
103
|
"@types/express": "^5.0.0",
|
|
99
104
|
"@types/node": "^22.0.0",
|
|
100
105
|
"@types/ws": "^8.18.1",
|
|
106
|
+
"fast-check": "^4.10.2",
|
|
101
107
|
"typescript": "^7.0.2",
|
|
102
108
|
"vitest": "^5.0.3"
|
|
103
109
|
},
|
|
@@ -12,6 +12,21 @@ import type {
|
|
|
12
12
|
} from "../../../client/types.js";
|
|
13
13
|
|
|
14
14
|
import type { HttpTransportOptions } from "./types.js";
|
|
15
|
+
import { DELIVERY_METADATA_KEY, RETRY_AFTER_METADATA_KEY, isNotSentError } from "../../../client/middleware/items.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The wait that an error response asks for, in milliseconds: `retryAfterMs` of the body of the
|
|
19
|
+
* server transport, or else the Retry-After header (seconds, or an HTTP date).
|
|
20
|
+
*/
|
|
21
|
+
function retryAfterMsOf(header: string | null, body: unknown): number | undefined {
|
|
22
|
+
const fromBody = (body as { retryAfterMs?: unknown } | null | undefined)?.retryAfterMs;
|
|
23
|
+
if (typeof fromBody === "number" && Number.isFinite(fromBody) && fromBody >= 0) return fromBody;
|
|
24
|
+
if (header === null || header.trim() === "") return undefined;
|
|
25
|
+
const seconds = Number(header);
|
|
26
|
+
if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1000;
|
|
27
|
+
const date = Date.parse(header);
|
|
28
|
+
return Number.isNaN(date) ? undefined : Math.max(0, date - Date.now());
|
|
29
|
+
}
|
|
15
30
|
|
|
16
31
|
/** The media type of a stream response: one JSON value on each line. */
|
|
17
32
|
const NDJSON = "application/x-ndjson";
|
|
@@ -32,6 +47,8 @@ import {
|
|
|
32
47
|
createAbortError,
|
|
33
48
|
createErrorFromException,
|
|
34
49
|
} from "../shared/index.js";
|
|
50
|
+
import { optionalMs } from "../../../duration.js";
|
|
51
|
+
import { PROTOCOL_HEADER, PROTOCOL_VERSION } from "../../../protocol.js";
|
|
35
52
|
|
|
36
53
|
/**
|
|
37
54
|
* HTTP Client Transport
|
|
@@ -74,7 +91,7 @@ export class HttpTransport implements Transport {
|
|
|
74
91
|
this.headerConverter =
|
|
75
92
|
options.headerConverter || createDefaultHeaderConverter();
|
|
76
93
|
this.defaultHeaders = options.defaultHeaders || {};
|
|
77
|
-
this.timeout = options.timeout;
|
|
94
|
+
this.timeout = optionalMs(options.timeout, "timeout");
|
|
78
95
|
}
|
|
79
96
|
|
|
80
97
|
/**
|
|
@@ -123,6 +140,8 @@ export class HttpTransport implements Transport {
|
|
|
123
140
|
[HTTPHeaders.CONTENT_TYPE]: "application/json",
|
|
124
141
|
// A streaming procedure answers with NDJSON; other procedures with JSON
|
|
125
142
|
Accept: `${NDJSON}, application/json`,
|
|
143
|
+
// The protocol version of this client (roadmap R25)
|
|
144
|
+
[PROTOCOL_HEADER]: String(PROTOCOL_VERSION),
|
|
126
145
|
...this.defaultHeaders,
|
|
127
146
|
...metadataHeaders,
|
|
128
147
|
},
|
|
@@ -153,6 +172,13 @@ export class HttpTransport implements Transport {
|
|
|
153
172
|
// the client kept only the HTTP status ("HTTP 500").
|
|
154
173
|
const status = this.httpStatusToUniversal(response.status as HTTPStatus, responseBody);
|
|
155
174
|
finished = true;
|
|
175
|
+
if (status.type === "error") {
|
|
176
|
+
const retryAfter = retryAfterMsOf(response.headers.get("Retry-After"), responseBody);
|
|
177
|
+
if (retryAfter !== undefined) metadata[RETRY_AFTER_METADATA_KEY] = retryAfter;
|
|
178
|
+
// The reason of a FORBIDDEN error (roadmap R25)
|
|
179
|
+
const reason = (responseBody as { reason?: unknown } | null | undefined)?.reason;
|
|
180
|
+
if (typeof reason === "string") metadata["reason"] = reason;
|
|
181
|
+
}
|
|
156
182
|
|
|
157
183
|
// Yield single response item
|
|
158
184
|
yield {
|
|
@@ -183,7 +209,9 @@ export class HttpTransport implements Transport {
|
|
|
183
209
|
retryable: errorDetails.retryable,
|
|
184
210
|
},
|
|
185
211
|
payload: null as TRes,
|
|
186
|
-
|
|
212
|
+
// A refused connection sent nothing. After a reset or a timeout, the server can have
|
|
213
|
+
// the request, so only an idempotent call can go again (R1).
|
|
214
|
+
metadata: { [DELIVERY_METADATA_KEY]: isNotSentError(error) ? "not-sent" : "indeterminate" },
|
|
187
215
|
};
|
|
188
216
|
} finally {
|
|
189
217
|
if (timeoutId) clearTimeout(timeoutId);
|
|
@@ -251,7 +279,7 @@ export class HttpTransport implements Transport {
|
|
|
251
279
|
id,
|
|
252
280
|
status: { type: "error", code: "STREAM_ENDED", message: "The stream ended without its end line", retryable: true },
|
|
253
281
|
payload: null as TRes,
|
|
254
|
-
metadata,
|
|
282
|
+
metadata: { ...metadata, [DELIVERY_METADATA_KEY]: "indeterminate" },
|
|
255
283
|
};
|
|
256
284
|
} finally {
|
|
257
285
|
reader.releaseLock();
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { UrlStrategy, HttpMethodStrategy, HeaderConverter } from "../shared/index.js";
|
|
9
|
+
import type { Duration } from "../../../duration.js";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* HTTP Client Transport Configuration
|
|
@@ -28,6 +29,6 @@ export interface HttpTransportOptions {
|
|
|
28
29
|
/** Default headers applied to all requests */
|
|
29
30
|
defaultHeaders?: Record<string, string>;
|
|
30
31
|
|
|
31
|
-
/**
|
|
32
|
-
timeout?:
|
|
32
|
+
/** The time to the first item of a response, for example `seconds(30)` */
|
|
33
|
+
timeout?: Duration;
|
|
33
34
|
}
|
|
@@ -10,16 +10,23 @@ import { createServer } from "http";
|
|
|
10
10
|
import type { Server as HttpServer } from "http";
|
|
11
11
|
import type { ServerTransport, ServerRequest, ServerResponse } from "../../../server/types.js";
|
|
12
12
|
import type { Metadata } from "../../../client/types.js";
|
|
13
|
-
import type {
|
|
13
|
+
import type { RequestHandler } from "../../../server/types.js";
|
|
14
14
|
import { HTTPMethod, HTTPHeaders, decodeMetadataHeader, METADATA_HEADER_LIMIT } from "../shared/index.js";
|
|
15
15
|
import { ERROR_REGISTRY } from "../../../client/errors/index.js";
|
|
16
16
|
import type { HttpServerTransportOptions } from "./types.js";
|
|
17
17
|
import { createPatternServerUrlStrategy } from "./strategies.js";
|
|
18
18
|
import { checkBrowserRequest, type BrowserGuardOptions } from "../../../server/browser-guard.js";
|
|
19
|
+
import { bearerOf, withoutSecretHeaders } from "../../../server/request-auth.js";
|
|
20
|
+
import { PROTOCOL_HEADER, PROTOCOL_VERSION, httpStatusOf, protocolVersionOf } from "../../../protocol.js";
|
|
19
21
|
|
|
20
22
|
/** The media type of a stream response: one JSON value on each line. */
|
|
21
23
|
const NDJSON = "application/x-ndjson";
|
|
22
24
|
|
|
25
|
+
/** True when a stream response can run without an end (`STREAM_METADATA_KEY` of procedure-server.ts). */
|
|
26
|
+
function isUnboundedStream(response: { metadata?: Record<string, unknown> | undefined }): boolean {
|
|
27
|
+
return response.metadata?.["stream"] === "unbounded";
|
|
28
|
+
}
|
|
29
|
+
|
|
23
30
|
/** The metadata keys that the transport sets: a query parameter or a header cannot replace them. */
|
|
24
31
|
const RESERVED_METADATA = new Set(["headers", "query", "params"]);
|
|
25
32
|
|
|
@@ -64,24 +71,6 @@ function write(res: Response, chunk: string, signal: AbortSignal): Promise<void>
|
|
|
64
71
|
});
|
|
65
72
|
}
|
|
66
73
|
|
|
67
|
-
/**
|
|
68
|
-
* The HTTP status of the error codes of a procedure invocation (they are not in the error
|
|
69
|
-
* registry, so before, a validation error was a 500).
|
|
70
|
-
*/
|
|
71
|
-
const INVOCATION_HTTP_STATUS: Record<string, number> = {
|
|
72
|
-
VALIDATION_ERROR: 400,
|
|
73
|
-
NOT_EXPOSED: 403,
|
|
74
|
-
NOT_DECLARED: 403,
|
|
75
|
-
FORBIDDEN: 403,
|
|
76
|
-
NOT_FOUND: 404,
|
|
77
|
-
NO_HANDLER: 404,
|
|
78
|
-
ABORTED: 499,
|
|
79
|
-
TIMEOUT: 504,
|
|
80
|
-
OUTPUT_VALIDATION_ERROR: 500,
|
|
81
|
-
NO_OUTPUT: 500,
|
|
82
|
-
HANDLER_ERROR: 500,
|
|
83
|
-
};
|
|
84
|
-
|
|
85
74
|
/**
|
|
86
75
|
* HTTP server transport adapter for Express.
|
|
87
76
|
*
|
|
@@ -126,7 +115,7 @@ export class HttpServerTransport implements ServerTransport {
|
|
|
126
115
|
corsOptions: HttpServerTransportOptions["corsOptions"] | undefined;
|
|
127
116
|
httpServer: HttpServer | undefined;
|
|
128
117
|
};
|
|
129
|
-
private server:
|
|
118
|
+
private server: RequestHandler;
|
|
130
119
|
/** The controllers of the requests in progress: stop() aborts them (deep dive TRN-15) */
|
|
131
120
|
private openRequests = new Set<AbortController>();
|
|
132
121
|
private stopped = false;
|
|
@@ -134,7 +123,7 @@ export class HttpServerTransport implements ServerTransport {
|
|
|
134
123
|
/** The browser checks of each procedure call (see `server/browser-guard.ts`) */
|
|
135
124
|
private readonly guard: BrowserGuardOptions;
|
|
136
125
|
|
|
137
|
-
constructor(server:
|
|
126
|
+
constructor(server: RequestHandler, options: HttpServerTransportOptions = {}) {
|
|
138
127
|
this.server = server;
|
|
139
128
|
const basePath = options.basePath ?? "/api";
|
|
140
129
|
this.options = {
|
|
@@ -198,7 +187,8 @@ export class HttpServerTransport implements ServerTransport {
|
|
|
198
187
|
HTTPHeaders.TRACE_ID,
|
|
199
188
|
HTTPHeaders.SPAN_ID,
|
|
200
189
|
HTTPHeaders.PARENT_SPAN_ID,
|
|
201
|
-
|
|
190
|
+
"traceparent",
|
|
191
|
+
PROTOCOL_HEADER,
|
|
202
192
|
HTTPHeaders.METADATA,
|
|
203
193
|
"collection",
|
|
204
194
|
"collectionName",
|
|
@@ -256,6 +246,18 @@ export class HttpServerTransport implements ServerTransport {
|
|
|
256
246
|
* Converts to unified RPC format, processes, and sends response.
|
|
257
247
|
*/
|
|
258
248
|
private async handleHttpRequest(req: Request, res: Response): Promise<void> {
|
|
249
|
+
// The protocol version of this server, on each response (roadmap R25)
|
|
250
|
+
res.setHeader(PROTOCOL_HEADER, String(PROTOCOL_VERSION));
|
|
251
|
+
const version = protocolVersionOf(req.headers[PROTOCOL_HEADER.toLowerCase()]);
|
|
252
|
+
if (version !== undefined && version > PROTOCOL_VERSION) {
|
|
253
|
+
res.status(400).json({
|
|
254
|
+
error: `This server speaks the protocol ${PROTOCOL_VERSION}, and the request uses ${version}`,
|
|
255
|
+
code: "PROTOCOL_VERSION",
|
|
256
|
+
retryable: false,
|
|
257
|
+
protocol: PROTOCOL_VERSION,
|
|
258
|
+
});
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
259
261
|
if (this.stopped) {
|
|
260
262
|
res.status(503).json({ error: "Server stopped", code: "SERVER_STOPPED", retryable: true });
|
|
261
263
|
return;
|
|
@@ -360,6 +362,26 @@ export class HttpServerTransport implements ServerTransport {
|
|
|
360
362
|
): Promise<void> {
|
|
361
363
|
const ndjson = String(req.headers.accept ?? "").includes(NDJSON);
|
|
362
364
|
|
|
365
|
+
if (!ndjson && isUnboundedStream(response)) {
|
|
366
|
+
// The last item of a stream without an end never comes: refuse the call, and end the stream
|
|
367
|
+
// (roadmap R15: before, the request waited until a timeout)
|
|
368
|
+
void Promise.resolve(stream[Symbol.asyncIterator]().return?.()).catch(() => undefined);
|
|
369
|
+
this.serverResponseToHttp(
|
|
370
|
+
{
|
|
371
|
+
id: response.id,
|
|
372
|
+
status: {
|
|
373
|
+
type: "error",
|
|
374
|
+
code: "NOT_ACCEPTABLE",
|
|
375
|
+
message: `This procedure gives a stream that can run without an end. Send "Accept: ${NDJSON}" to read its items.`,
|
|
376
|
+
retryable: false,
|
|
377
|
+
},
|
|
378
|
+
metadata: {},
|
|
379
|
+
},
|
|
380
|
+
res
|
|
381
|
+
);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
|
|
363
385
|
if (!ndjson) {
|
|
364
386
|
let last: { value: unknown } | undefined;
|
|
365
387
|
try {
|
|
@@ -424,11 +446,14 @@ export class HttpServerTransport implements ServerTransport {
|
|
|
424
446
|
const id =
|
|
425
447
|
(req.headers["x-request-id"] as string) ??
|
|
426
448
|
`req-${Date.now()}-${Math.random()}`;
|
|
449
|
+
const bearer = bearerOf(req.headers["authorization"]);
|
|
427
450
|
|
|
428
451
|
// Build metadata from headers and query params. The flattened query parameters cannot
|
|
429
452
|
// replace headers, query or params (before, "?headers=x" replaced metadata.headers).
|
|
453
|
+
// The headers without the secrets: the credentials go in request.credentials, not in the
|
|
454
|
+
// metadata, so a handler and a nested call do not see them (roadmap R18)
|
|
430
455
|
const metadata: Metadata = {
|
|
431
|
-
headers: req.headers as Record<string, string
|
|
456
|
+
headers: withoutSecretHeaders(req.headers as Record<string, string>),
|
|
432
457
|
query: req.query,
|
|
433
458
|
params: req.params,
|
|
434
459
|
};
|
|
@@ -505,6 +530,8 @@ export class HttpServerTransport implements ServerTransport {
|
|
|
505
530
|
method,
|
|
506
531
|
payload,
|
|
507
532
|
metadata,
|
|
533
|
+
// A network request: ProcedureServer examines the token (roadmap R18)
|
|
534
|
+
credentials: bearer === undefined ? {} : { bearer },
|
|
508
535
|
};
|
|
509
536
|
}
|
|
510
537
|
|
|
@@ -524,7 +551,7 @@ export class HttpServerTransport implements ServerTransport {
|
|
|
524
551
|
// HTTP status via the error registry rather than parseInt (which yields NaN -> 500, so every
|
|
525
552
|
// error became a 500). See documentation/BUGS-2026-07.md (M7).
|
|
526
553
|
const mapped =
|
|
527
|
-
|
|
554
|
+
httpStatusOf(status.code) ??
|
|
528
555
|
(ERROR_REGISTRY as Record<string, { httpStatus?: number }>)[status.code]?.httpStatus;
|
|
529
556
|
httpStatusCode = mapped ?? (status.type === "success" ? 200 : 500);
|
|
530
557
|
} else if (typeof status.code === "number") {
|
|
@@ -551,10 +578,19 @@ export class HttpServerTransport implements ServerTransport {
|
|
|
551
578
|
}
|
|
552
579
|
res.json(payload);
|
|
553
580
|
} else {
|
|
581
|
+
// The wait that the error asks for: a Retry-After header in seconds, and the exact
|
|
582
|
+
// milliseconds in the body (R12)
|
|
583
|
+
const retryAfterMs = metadata["retry-after"];
|
|
584
|
+
const hasRetryAfter = typeof retryAfterMs === "number" && Number.isFinite(retryAfterMs) && retryAfterMs >= 0;
|
|
585
|
+
if (hasRetryAfter) res.setHeader("Retry-After", String(Math.ceil(retryAfterMs / 1000)));
|
|
586
|
+
// The reason of a FORBIDDEN error travels on each face (roadmap R25)
|
|
587
|
+
const reason = typeof metadata["reason"] === "string" ? metadata["reason"] : undefined;
|
|
554
588
|
res.json({
|
|
555
589
|
error: status.message,
|
|
556
590
|
code: status.code,
|
|
557
591
|
retryable: status.retryable,
|
|
592
|
+
...(hasRetryAfter ? { retryAfterMs } : {}),
|
|
593
|
+
...(reason !== undefined ? { reason } : {}),
|
|
558
594
|
});
|
|
559
595
|
}
|
|
560
596
|
}
|
|
@@ -95,7 +95,7 @@ export interface HeaderConverter {
|
|
|
95
95
|
* Implements standard conventions for converting metadata to headers:
|
|
96
96
|
* - Auth: Bearer token, API key
|
|
97
97
|
* - Tracing: X-Trace-Id, X-Span-Id format
|
|
98
|
-
* -
|
|
98
|
+
* - The deadline: `procedure-timeout` in X-Metadata (no X-Timeout header)
|
|
99
99
|
* - Custom fields: Pass through as-is
|
|
100
100
|
*/
|
|
101
101
|
export class DefaultHeaderConverter implements HeaderConverter {
|
|
@@ -120,7 +120,10 @@ export class DefaultHeaderConverter implements HeaderConverter {
|
|
|
120
120
|
headers[HTTPHeaders.API_KEY] = metadata.auth.apiKey;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
// Distributed Tracing
|
|
123
|
+
// Distributed Tracing: the W3C trace context, and the older headers (roadmap R16)
|
|
124
|
+
if (typeof metadata["traceparent"] === "string") {
|
|
125
|
+
headers["traceparent"] = metadata["traceparent"];
|
|
126
|
+
}
|
|
124
127
|
if (metadata.tracing?.traceId) {
|
|
125
128
|
headers[HTTPHeaders.TRACE_ID] = metadata.tracing.traceId;
|
|
126
129
|
}
|
|
@@ -131,10 +134,8 @@ export class DefaultHeaderConverter implements HeaderConverter {
|
|
|
131
134
|
headers[HTTPHeaders.PARENT_SPAN_ID] = metadata.tracing.parentSpanId;
|
|
132
135
|
}
|
|
133
136
|
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
headers[HTTPHeaders.TIMEOUT] = String(metadata.timeout.overall);
|
|
137
|
-
}
|
|
137
|
+
// No X-Timeout: the remaining time of the deadline goes as procedure-timeout in X-Metadata,
|
|
138
|
+
// which the server uses (roadmap R10). The server ignored X-Timeout.
|
|
138
139
|
|
|
139
140
|
const custom: Record<string, unknown> = {};
|
|
140
141
|
for (const [key, value] of Object.entries(metadata)) {
|
|
@@ -9,9 +9,41 @@ import type { Handler, LocalTransportOptions } from "./types.js";
|
|
|
9
9
|
import { methodKey } from "./types.js";
|
|
10
10
|
import type { ProcedureRegistry } from "../../../procedures/registry.js";
|
|
11
11
|
import type { AnyProcedure } from "../../../procedures/types.js";
|
|
12
|
-
import { InvocationError, invokeProcedure, isAsyncIterable } from "../../../procedures/invoke.js";
|
|
12
|
+
import { InvocationError, invokeProcedure, isAsyncIterable, type Interceptor } from "../../../procedures/invoke.js";
|
|
13
|
+
import { TIMEOUT_METADATA_KEY } from "../../../client/deadline.js";
|
|
14
|
+
import { narrowCapability } from "../../../procedures/capability.js";
|
|
15
|
+
import type { Capability, ClearanceOrder } from "../../../procedures/types.js";
|
|
13
16
|
import { methodToPath } from "../../../server/method.js";
|
|
14
17
|
|
|
18
|
+
/** The value of `untilAborted` when the signal aborts first. */
|
|
19
|
+
const ABORTED: unique symbol = Symbol("aborted");
|
|
20
|
+
|
|
21
|
+
/** The value of a promise, or ABORTED when the signal aborts first. The promise runs on. */
|
|
22
|
+
function untilAborted<T>(promise: Promise<T>, signal: AbortSignal | undefined): Promise<T | typeof ABORTED> {
|
|
23
|
+
if (!signal) return promise;
|
|
24
|
+
if (signal.aborted) {
|
|
25
|
+
promise.catch(() => undefined);
|
|
26
|
+
return Promise.resolve(ABORTED);
|
|
27
|
+
}
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
const onAbort = (): void => {
|
|
30
|
+
promise.catch(() => undefined);
|
|
31
|
+
resolve(ABORTED);
|
|
32
|
+
};
|
|
33
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
34
|
+
promise.then(
|
|
35
|
+
(value) => {
|
|
36
|
+
signal.removeEventListener("abort", onAbort);
|
|
37
|
+
resolve(value);
|
|
38
|
+
},
|
|
39
|
+
(error: unknown) => {
|
|
40
|
+
signal.removeEventListener("abort", onAbort);
|
|
41
|
+
reject(error);
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
15
47
|
function successItem<TRes>(id: string, payload: unknown): ResponseItem<TRes> {
|
|
16
48
|
return { id, status: { type: "success", code: 200 }, payload: payload as TRes, metadata: {} };
|
|
17
49
|
}
|
|
@@ -53,10 +85,16 @@ export class LocalTransport implements Transport {
|
|
|
53
85
|
private readonly handlers = new Map<string, Handler>();
|
|
54
86
|
private readonly throwOnMissing: boolean;
|
|
55
87
|
private readonly registry: ProcedureRegistry | undefined;
|
|
88
|
+
private readonly interceptors: readonly Interceptor[] | undefined;
|
|
89
|
+
private readonly capability: Capability | undefined;
|
|
90
|
+
private readonly clearanceOrder: ClearanceOrder | undefined;
|
|
56
91
|
|
|
57
92
|
constructor(options: LocalTransportOptions = {}) {
|
|
58
93
|
this.throwOnMissing = options.throwOnMissing !== false;
|
|
59
94
|
this.registry = options.registry;
|
|
95
|
+
this.interceptors = options.interceptors;
|
|
96
|
+
this.capability = options.capability;
|
|
97
|
+
this.clearanceOrder = options.clearanceOrder;
|
|
60
98
|
|
|
61
99
|
// Register pre-configured handlers. A key without a version ("users.get") gets the
|
|
62
100
|
// empty version, as methodKey() makes it: before, such a key was never found.
|
|
@@ -136,21 +174,44 @@ export class LocalTransport implements Transport {
|
|
|
136
174
|
let values: AsyncIterable<unknown> | undefined;
|
|
137
175
|
let value: unknown;
|
|
138
176
|
if (procedure) {
|
|
139
|
-
|
|
177
|
+
// The interceptors, the capability and the deadline of the call, as on a server (R14)
|
|
178
|
+
const timeoutMs = message.metadata[TIMEOUT_METADATA_KEY];
|
|
179
|
+
const capability =
|
|
180
|
+
typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs >= 0
|
|
181
|
+
? narrowCapability(this.capability, { timeoutMs }, this.clearanceOrder)
|
|
182
|
+
: this.capability;
|
|
183
|
+
const output = await untilAborted(invokeProcedure(procedure, message.payload, {
|
|
140
184
|
registry: this.registry,
|
|
141
185
|
metadata: message.metadata,
|
|
142
186
|
signal: message.signal,
|
|
143
|
-
|
|
187
|
+
...(this.interceptors ? { interceptors: this.interceptors } : {}),
|
|
188
|
+
...(capability ? { capability } : {}),
|
|
189
|
+
...(this.clearanceOrder ? { clearanceOrder: this.clearanceOrder } : {}),
|
|
190
|
+
}), message.signal);
|
|
191
|
+
if (output === ABORTED) {
|
|
192
|
+
// The caller stops waiting at once, as over HTTP and WebSocket (the conformance suite
|
|
193
|
+
// of roadmap R18 found this: before, a local call waited for its handler)
|
|
194
|
+
yield errorItem<TRes>(message.id, "ABORTED", "Request was aborted");
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
144
197
|
if (output.kind === "stream") values = output.items;
|
|
145
198
|
else value = output.value;
|
|
146
199
|
} else {
|
|
147
200
|
// Execute handler (may be sync, async or an async generator)
|
|
148
|
-
const result = await handler!(message.payload, message);
|
|
201
|
+
const result = await untilAborted(Promise.resolve(handler!(message.payload, message)), message.signal);
|
|
202
|
+
if (result === ABORTED) {
|
|
203
|
+
yield errorItem<TRes>(message.id, "ABORTED", "Request was aborted");
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
149
206
|
if (isAsyncIterable(result)) values = result;
|
|
150
207
|
else value = result;
|
|
151
208
|
}
|
|
152
209
|
|
|
153
210
|
if (values === undefined) {
|
|
211
|
+
if (message.signal?.aborted) {
|
|
212
|
+
yield errorItem<TRes>(message.id, "ABORTED", "Request was aborted");
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
154
215
|
yield successItem<TRes>(message.id, value);
|
|
155
216
|
return;
|
|
156
217
|
}
|
|
@@ -164,7 +225,10 @@ export class LocalTransport implements Transport {
|
|
|
164
225
|
} catch (error) {
|
|
165
226
|
// The handler threw, or the invocation failed (its code: VALIDATION_ERROR, ABORTED and so on)
|
|
166
227
|
const code = error instanceof InvocationError ? error.code : "HANDLER_ERROR";
|
|
167
|
-
|
|
228
|
+
const item = errorItem<TRes>(message.id, code, error instanceof Error ? error.message : "Unknown error");
|
|
229
|
+
// The reason of a FORBIDDEN error travels on each face (roadmap R25)
|
|
230
|
+
if (error instanceof InvocationError && error.reason) item.metadata = { reason: error.reason };
|
|
231
|
+
yield item;
|
|
168
232
|
}
|
|
169
233
|
}
|
|
170
234
|
|
|
@@ -172,6 +236,7 @@ export class LocalTransport implements Transport {
|
|
|
172
236
|
// No resources to clean up
|
|
173
237
|
}
|
|
174
238
|
|
|
239
|
+
|
|
175
240
|
/**
|
|
176
241
|
* Clear all handlers.
|
|
177
242
|
*/
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
import type { Message, Method } from "../../../client/types.js";
|
|
8
8
|
import type { ProcedureRegistry } from "../../../procedures/registry.js";
|
|
9
|
+
import type { Interceptor } from "../../../procedures/invoke.js";
|
|
10
|
+
import type { Capability, ClearanceOrder } from "../../../procedures/types.js";
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* Handler function for a method.
|
|
@@ -47,4 +49,20 @@ export interface LocalTransportOptions {
|
|
|
47
49
|
* The procedure runs through `invokeProcedure()`, as on a server.
|
|
48
50
|
*/
|
|
49
51
|
registry?: ProcedureRegistry;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The interceptors of each procedure that the transport runs, as on a server: policy, tracing,
|
|
55
|
+
* timeouts, metrics (roadmap R14). Before, a local call had none.
|
|
56
|
+
*/
|
|
57
|
+
interceptors?: readonly Interceptor[] | undefined;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The capability of each call. The remaining time that a call sends (`procedure-timeout`)
|
|
61
|
+
* makes its deadline earlier, as on a server: a `Client` with a timeout gives a local call a
|
|
62
|
+
* deadline too (roadmap R14).
|
|
63
|
+
*/
|
|
64
|
+
capability?: Capability | undefined;
|
|
65
|
+
|
|
66
|
+
/** The order of the clearance values of the capability. */
|
|
67
|
+
clearanceOrder?: ClearanceOrder | undefined;
|
|
50
68
|
}
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
MockTransportOptions,
|
|
12
12
|
CallHistoryEntry,
|
|
13
13
|
} from "./types.js";
|
|
14
|
+
import { optionalMs } from "../../../duration.js";
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Mock transport implementation for testing.
|
|
@@ -26,8 +27,8 @@ import type {
|
|
|
26
27
|
* ```typescript
|
|
27
28
|
* // Create mock transport with latency
|
|
28
29
|
* const mock = new MockTransport({
|
|
29
|
-
* minLatency: 10,
|
|
30
|
-
* maxLatency: 50,
|
|
30
|
+
* minLatency: ms(10),
|
|
31
|
+
* maxLatency: ms(50),
|
|
31
32
|
* });
|
|
32
33
|
*
|
|
33
34
|
* // Mock a specific response
|
|
@@ -57,14 +58,15 @@ import type {
|
|
|
57
58
|
export class MockTransport implements Transport {
|
|
58
59
|
readonly name = "mock";
|
|
59
60
|
|
|
60
|
-
|
|
61
|
+
/** The options, with the latencies in milliseconds */
|
|
62
|
+
private options: Required<Omit<MockTransportOptions, "minLatency" | "maxLatency">> & { minLatency: number; maxLatency: number };
|
|
61
63
|
private responses = new Map<ResponseMatcher, MockResponse>();
|
|
62
64
|
private history: CallHistoryEntry[] = [];
|
|
63
65
|
|
|
64
66
|
constructor(options: MockTransportOptions = {}) {
|
|
65
67
|
this.options = {
|
|
66
|
-
minLatency: options.minLatency ?? 0,
|
|
67
|
-
maxLatency: options.maxLatency ?? 0,
|
|
68
|
+
minLatency: optionalMs(options.minLatency, "minLatency") ?? 0,
|
|
69
|
+
maxLatency: optionalMs(options.maxLatency, "maxLatency") ?? 0,
|
|
68
70
|
failureRate: options.failureRate ?? 0,
|
|
69
71
|
defaultErrorMessage: options.defaultErrorMessage ?? "Mock transport error",
|
|
70
72
|
trackHistory: options.trackHistory ?? true,
|
|
@@ -247,7 +249,8 @@ export class MockTransport implements Transport {
|
|
|
247
249
|
|
|
248
250
|
// Apply custom delay if specified
|
|
249
251
|
if (mockResponse.delay !== undefined) {
|
|
250
|
-
|
|
252
|
+
const delay = optionalMs(mockResponse.delay, "delay");
|
|
253
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
251
254
|
}
|
|
252
255
|
|
|
253
256
|
// Handle error response
|