@zudojs/rpc 0.0.1 → 1.0.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 (115) hide show
  1. package/LICENSE +21 -0
  2. package/dist/index.d.ts +8 -4
  3. package/dist/index.js +3 -4
  4. package/dist/rpc/client/index.d.ts +1 -1
  5. package/dist/rpc/client/rpcClient.core.d.ts +57 -3
  6. package/dist/rpc/client/rpcClient.core.js +172 -32
  7. package/dist/rpc/constants/index.d.ts +1 -1
  8. package/dist/rpc/constants/index.js +1 -1
  9. package/dist/rpc/constants/rpcConstants.core.d.ts +18 -0
  10. package/dist/rpc/constants/rpcConstants.core.js +18 -0
  11. package/dist/rpc/dispatcher/index.d.ts +1 -0
  12. package/dist/rpc/dispatcher/rpcDispatcher.core.d.ts +48 -1
  13. package/dist/rpc/dispatcher/rpcDispatcher.core.js +104 -13
  14. package/dist/rpc/middleware/rpcMiddleware.core.d.ts +12 -0
  15. package/dist/rpc/middleware/rpcMiddleware.core.js +29 -6
  16. package/dist/rpc/procedure/rpcProcedure.type.d.ts +25 -3
  17. package/dist/rpc/procedure/rpcProcedure.type.js +9 -0
  18. package/dist/rpc/procedure/rpcProcedureRegistry.core.d.ts +15 -1
  19. package/dist/rpc/procedure/rpcProcedureRegistry.core.js +26 -1
  20. package/dist/rpc/procedure/rpcProcedureRouter.core.d.ts +1 -1
  21. package/dist/rpc/procedure/rpcProcedureRouter.core.js +5 -0
  22. package/dist/rpc/reliability/cancellation/rpcCancellation.helper.d.ts +30 -3
  23. package/dist/rpc/reliability/cancellation/rpcCancellation.helper.js +60 -4
  24. package/dist/rpc/reliability/deadline/rpcDeadline.helper.d.ts +14 -1
  25. package/dist/rpc/reliability/deadline/rpcDeadline.helper.js +23 -2
  26. package/dist/rpc/reliability/index.d.ts +5 -4
  27. package/dist/rpc/reliability/index.js +3 -3
  28. package/dist/rpc/reliability/retry/rpcRetry.helper.d.ts +22 -0
  29. package/dist/rpc/reliability/retry/rpcRetry.helper.js +80 -8
  30. package/dist/rpc/reliability/timeout/rpcTimeout.helper.d.ts +18 -2
  31. package/dist/rpc/reliability/timeout/rpcTimeout.helper.js +71 -7
  32. package/dist/rpc/server/index.d.ts +1 -0
  33. package/dist/rpc/server/rpcServer.core.d.ts +41 -2
  34. package/dist/rpc/server/rpcServer.core.js +82 -18
  35. package/dist/rpc/streaming/rpcStreaming.type.d.ts +9 -0
  36. package/dist/rpc/streaming/rpcStreaming.type.js +9 -0
  37. package/dist/rpc/transport/rpcTransport.type.d.ts +9 -1
  38. package/dist/rpc/validation/index.d.ts +3 -0
  39. package/dist/rpc/validation/index.js +2 -0
  40. package/dist/rpc/validation/rpcValidation.core.d.ts +81 -0
  41. package/dist/rpc/validation/rpcValidation.core.js +125 -0
  42. package/package.json +23 -16
  43. package/dist/.tsbuildinfo +0 -1
  44. package/dist/index.d.ts.map +0 -1
  45. package/dist/index.js.map +0 -1
  46. package/dist/rpc/client/index.d.ts.map +0 -1
  47. package/dist/rpc/client/index.js.map +0 -1
  48. package/dist/rpc/client/rpcClient.core.d.ts.map +0 -1
  49. package/dist/rpc/client/rpcClient.core.js.map +0 -1
  50. package/dist/rpc/constants/index.d.ts.map +0 -1
  51. package/dist/rpc/constants/index.js.map +0 -1
  52. package/dist/rpc/constants/rpcConstants.core.d.ts.map +0 -1
  53. package/dist/rpc/constants/rpcConstants.core.js.map +0 -1
  54. package/dist/rpc/context/index.d.ts.map +0 -1
  55. package/dist/rpc/context/index.js.map +0 -1
  56. package/dist/rpc/context/rpcContext.type.d.ts.map +0 -1
  57. package/dist/rpc/context/rpcContext.type.js.map +0 -1
  58. package/dist/rpc/dispatcher/index.d.ts.map +0 -1
  59. package/dist/rpc/dispatcher/index.js.map +0 -1
  60. package/dist/rpc/dispatcher/rpcDispatcher.core.d.ts.map +0 -1
  61. package/dist/rpc/dispatcher/rpcDispatcher.core.js.map +0 -1
  62. package/dist/rpc/errors/index.d.ts.map +0 -1
  63. package/dist/rpc/errors/index.js.map +0 -1
  64. package/dist/rpc/errors/rpc.errors.d.ts.map +0 -1
  65. package/dist/rpc/errors/rpc.errors.js.map +0 -1
  66. package/dist/rpc/interceptor/index.d.ts.map +0 -1
  67. package/dist/rpc/interceptor/index.js.map +0 -1
  68. package/dist/rpc/interceptor/rpcInterceptor.type.d.ts.map +0 -1
  69. package/dist/rpc/interceptor/rpcInterceptor.type.js.map +0 -1
  70. package/dist/rpc/middleware/index.d.ts.map +0 -1
  71. package/dist/rpc/middleware/index.js.map +0 -1
  72. package/dist/rpc/middleware/rpcMiddleware.core.d.ts.map +0 -1
  73. package/dist/rpc/middleware/rpcMiddleware.core.js.map +0 -1
  74. package/dist/rpc/procedure/index.d.ts.map +0 -1
  75. package/dist/rpc/procedure/index.js.map +0 -1
  76. package/dist/rpc/procedure/rpcProcedure.type.d.ts.map +0 -1
  77. package/dist/rpc/procedure/rpcProcedure.type.js.map +0 -1
  78. package/dist/rpc/procedure/rpcProcedureRegistry.core.d.ts.map +0 -1
  79. package/dist/rpc/procedure/rpcProcedureRegistry.core.js.map +0 -1
  80. package/dist/rpc/procedure/rpcProcedureRouter.core.d.ts.map +0 -1
  81. package/dist/rpc/procedure/rpcProcedureRouter.core.js.map +0 -1
  82. package/dist/rpc/reliability/cancellation/rpcCancellation.helper.d.ts.map +0 -1
  83. package/dist/rpc/reliability/cancellation/rpcCancellation.helper.js.map +0 -1
  84. package/dist/rpc/reliability/deadline/rpcDeadline.helper.d.ts.map +0 -1
  85. package/dist/rpc/reliability/deadline/rpcDeadline.helper.js.map +0 -1
  86. package/dist/rpc/reliability/index.d.ts.map +0 -1
  87. package/dist/rpc/reliability/index.js.map +0 -1
  88. package/dist/rpc/reliability/retry/rpcRetry.helper.d.ts.map +0 -1
  89. package/dist/rpc/reliability/retry/rpcRetry.helper.js.map +0 -1
  90. package/dist/rpc/reliability/timeout/rpcTimeout.helper.d.ts.map +0 -1
  91. package/dist/rpc/reliability/timeout/rpcTimeout.helper.js.map +0 -1
  92. package/dist/rpc/server/index.d.ts.map +0 -1
  93. package/dist/rpc/server/index.js.map +0 -1
  94. package/dist/rpc/server/rpcServer.core.d.ts.map +0 -1
  95. package/dist/rpc/server/rpcServer.core.js.map +0 -1
  96. package/dist/rpc/streaming/index.d.ts.map +0 -1
  97. package/dist/rpc/streaming/index.js.map +0 -1
  98. package/dist/rpc/streaming/rpcStreaming.type.d.ts.map +0 -1
  99. package/dist/rpc/streaming/rpcStreaming.type.js.map +0 -1
  100. package/dist/rpc/transport/index.d.ts.map +0 -1
  101. package/dist/rpc/transport/index.js.map +0 -1
  102. package/dist/rpc/transport/rpcTransport.type.d.ts.map +0 -1
  103. package/dist/rpc/transport/rpcTransport.type.js.map +0 -1
  104. package/dist/rpc/types/index.d.ts.map +0 -1
  105. package/dist/rpc/types/index.js.map +0 -1
  106. package/dist/rpc/types/rpcMetadata.type.d.ts.map +0 -1
  107. package/dist/rpc/types/rpcMetadata.type.js.map +0 -1
  108. package/dist/rpc/types/rpcProcedureName.type.d.ts.map +0 -1
  109. package/dist/rpc/types/rpcProcedureName.type.js.map +0 -1
  110. package/dist/rpc/types/rpcRequest.type.d.ts.map +0 -1
  111. package/dist/rpc/types/rpcRequest.type.js.map +0 -1
  112. package/dist/rpc/types/rpcResponse.type.d.ts.map +0 -1
  113. package/dist/rpc/types/rpcResponse.type.js.map +0 -1
  114. package/dist/testing/index.d.ts.map +0 -1
  115. package/dist/testing/index.js.map +0 -1
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Zudojs Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.d.ts CHANGED
@@ -21,7 +21,9 @@
21
21
  */
22
22
  export type { RPCProcedureName, RPCMetadata, RPCMetadataOptions, RPCRequest, RPCRequestOptions, RPCErrorPayload, RPCResponse, } from "./rpc/types/index.js";
23
23
  export { createRPCMetadata, createRPCRequest, createRPCResponse, createRPCErrorResponse, } from "./rpc/types/index.js";
24
- export { DEFAULT_RPC_TIMEOUT, MAX_RPC_PAYLOAD_SIZE, MAX_PENDING_REQUESTS, MAX_MIDDLEWARE, MAX_PROCEDURES, PROCEDURE_NAME_PATTERN, } from "./rpc/constants/index.js";
24
+ export { DEFAULT_RPC_TIMEOUT, MAX_RPC_PAYLOAD_SIZE, MAX_PENDING_REQUESTS, MAX_MIDDLEWARE, MAX_PROCEDURES, MAX_PROCEDURE_NAME_LENGTH, MAX_TIMER_DELAY, PROCEDURE_NAME_PATTERN, INTERNAL_ERROR_MESSAGE, } from "./rpc/constants/index.js";
25
+ export type { RPCRequestLimits, RPCSchema } from "./rpc/validation/index.js";
26
+ export { assertValidProcedureName, assertValidRequest, measurePayloadBytes, toValidationIssues, parseInput, parseOutput, } from "./rpc/validation/index.js";
25
27
  export type { RPCErrorOptions } from "./rpc/errors/index.js";
26
28
  export { RPCError, RPCProcedureNotFoundError, RPCInvalidRequestError, RPCValidationError, RPCAuthenticationError, RPCForbiddenError, RPCTimeoutError, RPCCancelledError, RPCInternalError, RPCTransportError, RPCSerializationError, RPCDeserializationError, RPCUnavailableError, RPCRateLimitedError, RPCDeadlineExceededError, RPCDuplicateProcedureError, createRPCError, isRPCError, } from "./rpc/errors/index.js";
27
29
  export type { RPCHandler, RPCProcedure, RPCProcedureOptions, } from "./rpc/procedure/index.js";
@@ -31,13 +33,15 @@ export type { RPCContext } from "./rpc/context/index.js";
31
33
  export { createRPCContext } from "./rpc/context/index.js";
32
34
  export type { RPCMiddleware } from "./rpc/middleware/index.js";
33
35
  export { RPCMiddlewareStack } from "./rpc/middleware/index.js";
36
+ export type { RPCDispatcherOptions } from "./rpc/dispatcher/index.js";
34
37
  export { RPCDispatcher } from "./rpc/dispatcher/index.js";
38
+ export type { RPCServerOptions } from "./rpc/server/index.js";
35
39
  export { RPCServer } from "./rpc/server/index.js";
36
40
  export type { RPCTransport, RPCTransportRequestOptions, } from "./rpc/transport/index.js";
37
- export type { RPCCallOptions } from "./rpc/client/index.js";
41
+ export type { RPCCallOptions, RPCClientOptions } from "./rpc/client/index.js";
38
42
  export { RPCClient } from "./rpc/client/index.js";
39
- export { createTimeout, withTimeout, getRemainingTime, isDeadlineExceeded, throwIfDeadlineExceeded, createCancellableSignal, cancelSignal, DEFAULT_RETRY_OPTIONS, calculateRetryDelay, retry, } from "./rpc/reliability/index.js";
40
- export type { RPCBackoff, RPCRetryOptions } from "./rpc/reliability/index.js";
43
+ export { createTimeout, withTimeout, runWithTimeout, getRemainingTime, isDeadlineExceeded, throwIfDeadlineExceeded, readDeadline, createCancellableSignal, cancelSignal, throwIfCancelled, combineSignals, DEFAULT_RETRY_OPTIONS, calculateRetryDelay, retry, } from "./rpc/reliability/index.js";
44
+ export type { RPCBackoff, RPCJitter, RPCRetryOptions, CancellableSignal, } from "./rpc/reliability/index.js";
41
45
  export type { RPCInterceptor } from "./rpc/interceptor/index.js";
42
46
  export { createNoopRPCInterceptor } from "./rpc/interceptor/index.js";
43
47
  export type { RPCStreamingHandler, RPCStreamingProcedure, } from "./rpc/streaming/index.js";
package/dist/index.js CHANGED
@@ -21,19 +21,18 @@
21
21
  */
22
22
  export { createRPCMetadata, createRPCRequest, createRPCResponse, createRPCErrorResponse, } from "./rpc/types/index.js";
23
23
  // Constants
24
- export { DEFAULT_RPC_TIMEOUT, MAX_RPC_PAYLOAD_SIZE, MAX_PENDING_REQUESTS, MAX_MIDDLEWARE, MAX_PROCEDURES, PROCEDURE_NAME_PATTERN, } from "./rpc/constants/index.js";
24
+ export { DEFAULT_RPC_TIMEOUT, MAX_RPC_PAYLOAD_SIZE, MAX_PENDING_REQUESTS, MAX_MIDDLEWARE, MAX_PROCEDURES, MAX_PROCEDURE_NAME_LENGTH, MAX_TIMER_DELAY, PROCEDURE_NAME_PATTERN, INTERNAL_ERROR_MESSAGE, } from "./rpc/constants/index.js";
25
+ export { assertValidProcedureName, assertValidRequest, measurePayloadBytes, toValidationIssues, parseInput, parseOutput, } from "./rpc/validation/index.js";
25
26
  export { RPCError, RPCProcedureNotFoundError, RPCInvalidRequestError, RPCValidationError, RPCAuthenticationError, RPCForbiddenError, RPCTimeoutError, RPCCancelledError, RPCInternalError, RPCTransportError, RPCSerializationError, RPCDeserializationError, RPCUnavailableError, RPCRateLimitedError, RPCDeadlineExceededError, RPCDuplicateProcedureError, createRPCError, isRPCError, } from "./rpc/errors/index.js";
26
27
  export { createRPCProcedure } from "./rpc/procedure/index.js";
27
28
  export { RPCProcedureRegistry, RPCProcedureRouter, } from "./rpc/procedure/index.js";
28
29
  export { createRPCContext } from "./rpc/context/index.js";
29
30
  export { RPCMiddlewareStack } from "./rpc/middleware/index.js";
30
- // Dispatcher
31
31
  export { RPCDispatcher } from "./rpc/dispatcher/index.js";
32
- // Server
33
32
  export { RPCServer } from "./rpc/server/index.js";
34
33
  export { RPCClient } from "./rpc/client/index.js";
35
34
  // Reliability
36
- export { createTimeout, withTimeout, getRemainingTime, isDeadlineExceeded, throwIfDeadlineExceeded, createCancellableSignal, cancelSignal, DEFAULT_RETRY_OPTIONS, calculateRetryDelay, retry, } from "./rpc/reliability/index.js";
35
+ export { createTimeout, withTimeout, runWithTimeout, getRemainingTime, isDeadlineExceeded, throwIfDeadlineExceeded, readDeadline, createCancellableSignal, cancelSignal, throwIfCancelled, combineSignals, DEFAULT_RETRY_OPTIONS, calculateRetryDelay, retry, } from "./rpc/reliability/index.js";
37
36
  export { createNoopRPCInterceptor } from "./rpc/interceptor/index.js";
38
37
  export { createRPCStreamingProcedure } from "./rpc/streaming/index.js";
39
38
  //# sourceMappingURL=index.js.map
@@ -1,3 +1,3 @@
1
- export type { RPCCallOptions } from "./rpcClient.core.js";
1
+ export type { RPCCallOptions, RPCClientOptions } from "./rpcClient.core.js";
2
2
  export { RPCClient } from "./rpcClient.core.js";
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -4,23 +4,77 @@ import type { RPCTransport } from "../transport/rpcTransport.type.js";
4
4
  */
5
5
  export interface RPCCallOptions {
6
6
  readonly timeout?: number;
7
+ /** Cancels the call. An already-aborted signal fails immediately. */
7
8
  readonly signal?: AbortSignal;
8
9
  readonly metadata?: Record<string, string | number | boolean | undefined>;
9
10
  }
11
+ /**
12
+ * Options for constructing an RPC client.
13
+ */
14
+ export interface RPCClientOptions {
15
+ /** Default timeout for calls that do not specify one. */
16
+ readonly timeout?: number;
17
+ /**
18
+ * Maximum number of calls in flight at once. Defaults to
19
+ * {@link MAX_PENDING_REQUESTS}. Additional calls fail fast rather than
20
+ * queueing without bound.
21
+ */
22
+ readonly maxPending?: number;
23
+ }
10
24
  /**
11
25
  * RPC client for invoking remote procedures.
26
+ *
27
+ * Every call is tracked while it is genuinely in flight and removed the
28
+ * moment it settles, so the concurrency limit reflects real in-flight
29
+ * work rather than accumulating entries from calls that already
30
+ * succeeded.
12
31
  */
13
32
  export declare class RPCClient {
14
33
  private readonly transport;
34
+ private readonly options;
15
35
  private readonly pending;
16
- constructor(transport: RPCTransport);
36
+ private closed;
37
+ constructor(transport: RPCTransport, options?: RPCClientOptions);
38
+ /**
39
+ * Number of calls currently in flight.
40
+ */
41
+ get pendingCount(): number;
17
42
  /**
18
43
  * Calls a remote procedure.
19
44
  */
20
45
  call<TInput = unknown, TOutput = unknown>(procedure: string, input: TInput, options?: RPCCallOptions): Promise<TOutput>;
21
46
  /**
22
- * Creates a timeout promise for an RPC call.
47
+ * Cancels every in-flight call and stops accepting new ones.
48
+ */
49
+ close(reason?: string): Promise<void>;
50
+ /**
51
+ * Describes the calls currently in flight, for diagnostics.
52
+ */
53
+ inspectPending(): readonly {
54
+ procedure: string;
55
+ elapsedMs: number;
56
+ }[];
57
+ /**
58
+ * A promise that rejects when the signal aborts.
59
+ */
60
+ private abortPromise;
61
+ private abortReason;
62
+ private cancellationError;
63
+ /**
64
+ * Normalises an abort reason into an `RPCCancelledError`.
65
+ *
66
+ * A bare `controller.abort()` yields a `DOMException`, so callers that
67
+ * branch on error type would never see a cancellation. The original
68
+ * reason is preserved as `cause`; a reason that is already an RPC
69
+ * error is passed through untouched.
70
+ */
71
+ private toCancellation;
72
+ /**
73
+ * Reconstructs a typed error from an error response.
74
+ *
75
+ * The wire code drives the type, so a caller can tell an
76
+ * authentication failure from a timeout without string matching.
23
77
  */
24
- private createTimeout;
78
+ private toError;
25
79
  }
26
80
  //# sourceMappingURL=rpcClient.core.d.ts.map
@@ -1,60 +1,200 @@
1
+ import { randomUUID } from "node:crypto";
1
2
  import { createRPCRequest } from "../types/rpcRequest.type.js";
2
- import { RPCProcedureNotFoundError } from "../errors/rpc.errors.js";
3
+ import { isRPCError, RPCCancelledError, RPCError, RPCTimeoutError, RPCUnavailableError, } from "../errors/rpc.errors.js";
3
4
  import { DEFAULT_RPC_TIMEOUT, MAX_PENDING_REQUESTS, } from "../constants/rpcConstants.core.js";
5
+ import { createTimeout } from "../reliability/timeout/rpcTimeout.helper.js";
4
6
  /**
5
7
  * RPC client for invoking remote procedures.
8
+ *
9
+ * Every call is tracked while it is genuinely in flight and removed the
10
+ * moment it settles, so the concurrency limit reflects real in-flight
11
+ * work rather than accumulating entries from calls that already
12
+ * succeeded.
6
13
  */
7
14
  export class RPCClient {
8
15
  transport;
16
+ options;
9
17
  pending = new Map();
10
- constructor(transport) {
18
+ closed = false;
19
+ constructor(transport, options = {}) {
11
20
  this.transport = transport;
21
+ this.options = options;
22
+ }
23
+ /**
24
+ * Number of calls currently in flight.
25
+ */
26
+ get pendingCount() {
27
+ return this.pending.size;
12
28
  }
13
29
  /**
14
30
  * Calls a remote procedure.
15
31
  */
16
32
  async call(procedure, input, options = {}) {
17
- const id = crypto.randomUUID();
18
- const timeout = options.timeout ?? DEFAULT_RPC_TIMEOUT;
19
- if (this.pending.size >= MAX_PENDING_REQUESTS) {
20
- throw new Error("Too many pending RPC requests.");
33
+ if (this.closed) {
34
+ throw new RPCUnavailableError("RPC client has been closed.", procedure);
35
+ }
36
+ const maxPending = this.options.maxPending ?? MAX_PENDING_REQUESTS;
37
+ if (this.pending.size >= maxPending) {
38
+ throw new RPCUnavailableError(`Too many RPC requests in flight (${this.pending.size}/${maxPending}).`, procedure);
21
39
  }
22
- const response = await Promise.race([
23
- this.transport.send(createRPCRequest({
40
+ if (options.signal?.aborted) {
41
+ throw this.cancellationError(options.signal, procedure);
42
+ }
43
+ const id = randomUUID();
44
+ const timeoutMs = options.timeout ?? this.options.timeout ?? DEFAULT_RPC_TIMEOUT;
45
+ const controller = new AbortController();
46
+ const timeout = timeoutMs > 0 ? createTimeout(timeoutMs, procedure) : undefined;
47
+ const onCallerAbort = () => {
48
+ controller.abort(this.cancellationError(options.signal, procedure));
49
+ };
50
+ options.signal?.addEventListener("abort", onCallerAbort, { once: true });
51
+ this.pending.set(id, {
52
+ procedure,
53
+ startedAt: Date.now(),
54
+ cancel: (reason) => {
55
+ if (!controller.signal.aborted) {
56
+ controller.abort(reason);
57
+ }
58
+ },
59
+ });
60
+ try {
61
+ const request = createRPCRequest({
24
62
  id,
25
63
  procedure,
26
64
  payload: input,
27
65
  metadata: options.metadata,
28
- })),
29
- this.createTimeout(id, timeout),
30
- ]);
31
- if (!response.success) {
32
- const error = new Error(response.error?.message ?? "RPC call failed.");
33
- error.code = response.error?.code;
34
- throw error;
66
+ });
67
+ const races = [
68
+ this.transport.send(request, { signal: controller.signal }),
69
+ this.abortPromise(controller.signal, procedure),
70
+ ];
71
+ if (timeout) {
72
+ races.push(timeout.promise.catch((error) => {
73
+ if (!controller.signal.aborted) {
74
+ controller.abort(error);
75
+ }
76
+ throw error;
77
+ }));
78
+ }
79
+ const response = await Promise.race(races);
80
+ if (!response.success) {
81
+ throw this.toError(response, procedure);
82
+ }
83
+ return response.result;
84
+ }
85
+ finally {
86
+ // Removing the entry here — rather than leaving it for a timer to
87
+ // reap — is what keeps `pendingCount` equal to the number of calls
88
+ // actually in flight.
89
+ this.pending.delete(id);
90
+ timeout?.cancel();
91
+ options.signal?.removeEventListener("abort", onCallerAbort);
92
+ if (!controller.signal.aborted) {
93
+ controller.abort(new RPCCancelledError("Call settled.", procedure));
94
+ }
35
95
  }
36
- return response.result;
37
96
  }
38
97
  /**
39
- * Creates a timeout promise for an RPC call.
98
+ * Cancels every in-flight call and stops accepting new ones.
99
+ */
100
+ async close(reason) {
101
+ this.closed = true;
102
+ const error = new RPCCancelledError(reason ?? "RPC client closed.");
103
+ for (const call of this.pending.values()) {
104
+ call.cancel(error);
105
+ }
106
+ this.pending.clear();
107
+ await this.transport.close?.();
108
+ }
109
+ /**
110
+ * Describes the calls currently in flight, for diagnostics.
111
+ */
112
+ inspectPending() {
113
+ const now = Date.now();
114
+ return [...this.pending.values()].map((call) => ({
115
+ procedure: call.procedure,
116
+ elapsedMs: now - call.startedAt,
117
+ }));
118
+ }
119
+ /**
120
+ * A promise that rejects when the signal aborts.
40
121
  */
41
- createTimeout(id, timeout) {
42
- return new Promise((resolve, reject) => {
43
- const timer = setTimeout(() => {
44
- this.pending.delete(id);
45
- reject(new Error(`RPC call timed out after ${timeout}ms.`));
46
- }, timeout);
47
- this.pending.set(id, {
48
- resolve: (response) => {
49
- clearTimeout(timer);
50
- resolve(response);
51
- },
52
- reject: (error) => {
53
- clearTimeout(timer);
54
- reject(error);
55
- },
122
+ abortPromise(signal, procedure) {
123
+ return new Promise((_, reject) => {
124
+ if (signal.aborted) {
125
+ reject(this.abortReason(signal, procedure));
126
+ return;
127
+ }
128
+ signal.addEventListener("abort", () => reject(this.abortReason(signal, procedure)), { once: true });
129
+ });
130
+ }
131
+ abortReason(signal, procedure) {
132
+ return this.toCancellation(signal.reason, procedure);
133
+ }
134
+ cancellationError(signal, procedure) {
135
+ return this.toCancellation(signal?.reason, procedure);
136
+ }
137
+ /**
138
+ * Normalises an abort reason into an `RPCCancelledError`.
139
+ *
140
+ * A bare `controller.abort()` yields a `DOMException`, so callers that
141
+ * branch on error type would never see a cancellation. The original
142
+ * reason is preserved as `cause`; a reason that is already an RPC
143
+ * error is passed through untouched.
144
+ */
145
+ toCancellation(reason, procedure) {
146
+ if (isRPCError(reason)) {
147
+ return reason;
148
+ }
149
+ const message = reason instanceof Error && reason.name !== "AbortError"
150
+ ? reason.message
151
+ : "Call cancelled by caller.";
152
+ const error = new RPCCancelledError(message, procedure);
153
+ if (reason !== undefined) {
154
+ Object.defineProperty(error, "cause", {
155
+ value: reason,
156
+ enumerable: false,
157
+ configurable: true,
158
+ writable: true,
56
159
  });
160
+ }
161
+ return error;
162
+ }
163
+ /**
164
+ * Reconstructs a typed error from an error response.
165
+ *
166
+ * The wire code drives the type, so a caller can tell an
167
+ * authentication failure from a timeout without string matching.
168
+ */
169
+ toError(response, procedure) {
170
+ const message = response.error?.message ?? "RPC call failed.";
171
+ const code = response.error?.code;
172
+ switch (code) {
173
+ case "RPC_TIMEOUT":
174
+ return new RPCTimeoutError(0, procedure);
175
+ case "RPC_CANCELLED":
176
+ return new RPCCancelledError(message, procedure);
177
+ case "RPC_UNAVAILABLE":
178
+ return new RPCUnavailableError(message, procedure);
179
+ default:
180
+ break;
181
+ }
182
+ const error = new RPCError(message, { procedureName: procedure });
183
+ // Preserve the server's code and any details for callers that
184
+ // branch on them.
185
+ Object.defineProperty(error, "code", {
186
+ value: code ?? error.code,
187
+ enumerable: true,
188
+ configurable: true,
57
189
  });
190
+ if (response.error?.details !== undefined) {
191
+ Object.defineProperty(error, "details", {
192
+ value: response.error.details,
193
+ enumerable: true,
194
+ configurable: true,
195
+ });
196
+ }
197
+ return error;
58
198
  }
59
199
  }
60
200
  //# sourceMappingURL=rpcClient.core.js.map
@@ -1,2 +1,2 @@
1
- export { DEFAULT_RPC_TIMEOUT, MAX_RPC_PAYLOAD_SIZE, MAX_PENDING_REQUESTS, MAX_MIDDLEWARE, MAX_PROCEDURES, PROCEDURE_NAME_PATTERN, } from "./rpcConstants.core.js";
1
+ export { DEFAULT_RPC_TIMEOUT, MAX_RPC_PAYLOAD_SIZE, MAX_PENDING_REQUESTS, MAX_MIDDLEWARE, MAX_PROCEDURES, MAX_PROCEDURE_NAME_LENGTH, MAX_TIMER_DELAY, PROCEDURE_NAME_PATTERN, INTERNAL_ERROR_MESSAGE, } from "./rpcConstants.core.js";
2
2
  //# sourceMappingURL=index.d.ts.map
@@ -1,2 +1,2 @@
1
- export { DEFAULT_RPC_TIMEOUT, MAX_RPC_PAYLOAD_SIZE, MAX_PENDING_REQUESTS, MAX_MIDDLEWARE, MAX_PROCEDURES, PROCEDURE_NAME_PATTERN, } from "./rpcConstants.core.js";
1
+ export { DEFAULT_RPC_TIMEOUT, MAX_RPC_PAYLOAD_SIZE, MAX_PENDING_REQUESTS, MAX_MIDDLEWARE, MAX_PROCEDURES, MAX_PROCEDURE_NAME_LENGTH, MAX_TIMER_DELAY, PROCEDURE_NAME_PATTERN, INTERNAL_ERROR_MESSAGE, } from "./rpcConstants.core.js";
2
2
  //# sourceMappingURL=index.js.map
@@ -27,4 +27,22 @@ export declare const MAX_PROCEDURES = 4096;
27
27
  * Procedure name pattern: dot-separated lowercase identifiers.
28
28
  */
29
29
  export declare const PROCEDURE_NAME_PATTERN: RegExp;
30
+ /**
31
+ * Maximum length of a procedure name, checked before the pattern so a
32
+ * pathological name cannot drive regex backtracking.
33
+ */
34
+ export declare const MAX_PROCEDURE_NAME_LENGTH = 256;
35
+ /**
36
+ * Largest delay Node's timer subsystem accepts. A larger delay overflows
37
+ * a signed 32-bit integer and is silently clamped to `1`.
38
+ */
39
+ export declare const MAX_TIMER_DELAY = 2147483647;
40
+ /**
41
+ * Message returned to callers for an unexpected server-side failure.
42
+ *
43
+ * Internal exception text may name hosts, paths, credentials or queries,
44
+ * so it is logged rather than returned. Callers correlate with the
45
+ * request id instead.
46
+ */
47
+ export declare const INTERNAL_ERROR_MESSAGE = "The server encountered an internal error while handling this request.";
30
48
  //# sourceMappingURL=rpcConstants.core.d.ts.map
@@ -27,4 +27,22 @@ export const MAX_PROCEDURES = 4096;
27
27
  * Procedure name pattern: dot-separated lowercase identifiers.
28
28
  */
29
29
  export const PROCEDURE_NAME_PATTERN = /^[a-z][a-zA-Z0-9]*(\.[a-z][a-zA-Z0-9]*)+$/;
30
+ /**
31
+ * Maximum length of a procedure name, checked before the pattern so a
32
+ * pathological name cannot drive regex backtracking.
33
+ */
34
+ export const MAX_PROCEDURE_NAME_LENGTH = 256;
35
+ /**
36
+ * Largest delay Node's timer subsystem accepts. A larger delay overflows
37
+ * a signed 32-bit integer and is silently clamped to `1`.
38
+ */
39
+ export const MAX_TIMER_DELAY = 2_147_483_647;
40
+ /**
41
+ * Message returned to callers for an unexpected server-side failure.
42
+ *
43
+ * Internal exception text may name hosts, paths, credentials or queries,
44
+ * so it is logged rather than returned. Callers correlate with the
45
+ * request id instead.
46
+ */
47
+ export const INTERNAL_ERROR_MESSAGE = "The server encountered an internal error while handling this request.";
30
48
  //# sourceMappingURL=rpcConstants.core.js.map
@@ -1,2 +1,3 @@
1
+ export type { RPCDispatcherOptions } from "./rpcDispatcher.core.js";
1
2
  export { RPCDispatcher } from "./rpcDispatcher.core.js";
2
3
  //# sourceMappingURL=index.d.ts.map
@@ -2,18 +2,65 @@ import type { RPCRequest } from "../types/rpcRequest.type.js";
2
2
  import type { RPCResponse } from "../types/rpcResponse.type.js";
3
3
  import type { RPCProcedure } from "../procedure/rpcProcedure.type.js";
4
4
  import type { RPCMiddlewareStack } from "../middleware/rpcMiddleware.core.js";
5
+ import type { RPCInterceptor } from "../interceptor/rpcInterceptor.type.js";
6
+ /**
7
+ * Options controlling dispatch.
8
+ */
9
+ export interface RPCDispatcherOptions {
10
+ /**
11
+ * Timeout applied to a procedure that declares none, in milliseconds.
12
+ * Defaults to {@link DEFAULT_RPC_TIMEOUT}. Set to `0` to leave
13
+ * procedures without their own timeout unbounded.
14
+ */
15
+ readonly defaultTimeout?: number;
16
+ /**
17
+ * Whether a `deadline` in request metadata caps the effective timeout.
18
+ * Defaults to `true`.
19
+ */
20
+ readonly honourDeadline?: boolean;
21
+ /**
22
+ * Interceptors wrapping every dispatch, outermost first.
23
+ *
24
+ * They run around the middleware stack and the handler, so an
25
+ * interceptor observes validation and middleware as part of the call
26
+ * it wraps.
27
+ */
28
+ readonly interceptors?: readonly RPCInterceptor[];
29
+ }
5
30
  /**
6
31
  * Dispatches RPC requests to registered procedures.
32
+ *
33
+ * Every dispatch runs under a real `AbortController`, a bounded timeout,
34
+ * and — when the procedure declares schemas — input and output
35
+ * validation. Errors keep their identity: an `RPCError` thrown by a
36
+ * middleware or handler propagates unchanged so the server can map it to
37
+ * the right response code, and only genuinely unexpected errors are
38
+ * wrapped.
7
39
  */
8
40
  export declare class RPCDispatcher {
9
41
  private readonly registry;
10
42
  private readonly middleware;
43
+ private readonly options;
11
44
  constructor(registry: {
12
45
  require(name: string): RPCProcedure;
13
- }, middleware: RPCMiddlewareStack);
46
+ }, middleware: RPCMiddlewareStack, options?: RPCDispatcherOptions);
14
47
  /**
15
48
  * Dispatches an RPC request.
16
49
  */
17
50
  dispatch(request: RPCRequest): Promise<RPCResponse>;
51
+ /**
52
+ * Runs the interceptor chain around the dispatch.
53
+ *
54
+ * Each interceptor may call `next()` once; a second call throws rather
55
+ * than re-running the handler.
56
+ */
57
+ private applyInterceptors;
58
+ /**
59
+ * Resolves the timeout for a dispatch.
60
+ *
61
+ * A `deadline` in request metadata can only shorten the timeout, never
62
+ * extend it past what the procedure allows.
63
+ */
64
+ private resolveTimeout;
18
65
  }
19
66
  //# sourceMappingURL=rpcDispatcher.core.d.ts.map