@procedures/client 0.35.0 → 0.36.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 (120) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/adapters/http/server/handler.d.ts.map +1 -1
  4. package/dist/adapters/http/server/handler.js +17 -3
  5. package/dist/adapters/http/server/handler.js.map +1 -1
  6. package/dist/adapters/port/transport.d.ts.map +1 -1
  7. package/dist/adapters/port/transport.js +2 -0
  8. package/dist/adapters/port/transport.js.map +1 -1
  9. package/dist/adapters/websocket/server/transport.d.ts.map +1 -1
  10. package/dist/adapters/websocket/server/transport.js +2 -0
  11. package/dist/adapters/websocket/server/transport.js.map +1 -1
  12. package/dist/adapters/websocket/shared/rpc.d.ts.map +1 -1
  13. package/dist/adapters/websocket/shared/rpc.js +14 -5
  14. package/dist/adapters/websocket/shared/rpc.js.map +1 -1
  15. package/dist/browser.d.ts +6 -4
  16. package/dist/browser.d.ts.map +1 -1
  17. package/dist/browser.js +4 -1
  18. package/dist/browser.js.map +1 -1
  19. package/dist/client/errors/index.d.ts +1 -1
  20. package/dist/client/errors/index.d.ts.map +1 -1
  21. package/dist/client/errors/index.js +1 -1
  22. package/dist/client/errors/index.js.map +1 -1
  23. package/dist/client/errors/registry.d.ts +10 -0
  24. package/dist/client/errors/registry.d.ts.map +1 -1
  25. package/dist/client/errors/registry.js +12 -0
  26. package/dist/client/errors/registry.js.map +1 -1
  27. package/dist/client/index.d.ts +1 -1
  28. package/dist/client/index.d.ts.map +1 -1
  29. package/dist/client/index.js +1 -1
  30. package/dist/client/index.js.map +1 -1
  31. package/dist/events/bus.d.ts.map +1 -1
  32. package/dist/events/bus.js +5 -1
  33. package/dist/events/bus.js.map +1 -1
  34. package/dist/events/index.d.ts +1 -1
  35. package/dist/events/index.d.ts.map +1 -1
  36. package/dist/events/publisher.d.ts +18 -2
  37. package/dist/events/publisher.d.ts.map +1 -1
  38. package/dist/events/publisher.js +142 -25
  39. package/dist/events/publisher.js.map +1 -1
  40. package/dist/events/recheck.d.ts +27 -5
  41. package/dist/events/recheck.d.ts.map +1 -1
  42. package/dist/events/recheck.js +11 -10
  43. package/dist/events/recheck.js.map +1 -1
  44. package/dist/procedures/collection/admission.d.ts +26 -7
  45. package/dist/procedures/collection/admission.d.ts.map +1 -1
  46. package/dist/procedures/collection/admission.js +39 -7
  47. package/dist/procedures/collection/admission.js.map +1 -1
  48. package/dist/procedures/collection/changes.d.ts +28 -4
  49. package/dist/procedures/collection/changes.d.ts.map +1 -1
  50. package/dist/procedures/collection/changes.js +50 -6
  51. package/dist/procedures/collection/changes.js.map +1 -1
  52. package/dist/procedures/collection/hint-filter.d.ts +143 -0
  53. package/dist/procedures/collection/hint-filter.d.ts.map +1 -0
  54. package/dist/procedures/collection/hint-filter.js +311 -0
  55. package/dist/procedures/collection/hint-filter.js.map +1 -0
  56. package/dist/procedures/collection/index.d.ts +2 -0
  57. package/dist/procedures/collection/index.d.ts.map +1 -1
  58. package/dist/procedures/collection/index.js +2 -0
  59. package/dist/procedures/collection/index.js.map +1 -1
  60. package/dist/procedures/collection/procedures.d.ts +2 -2
  61. package/dist/procedures/collection/procedures.d.ts.map +1 -1
  62. package/dist/procedures/collection/procedures.js +6 -4
  63. package/dist/procedures/collection/procedures.js.map +1 -1
  64. package/dist/procedures/index.d.ts +2 -2
  65. package/dist/procedures/index.d.ts.map +1 -1
  66. package/dist/procedures/index.js +3 -1
  67. package/dist/procedures/index.js.map +1 -1
  68. package/dist/server/error-policy.d.ts +96 -0
  69. package/dist/server/error-policy.d.ts.map +1 -0
  70. package/dist/server/error-policy.js +196 -0
  71. package/dist/server/error-policy.js.map +1 -0
  72. package/dist/server/error-response.d.ts +30 -0
  73. package/dist/server/error-response.d.ts.map +1 -0
  74. package/dist/server/error-response.js +90 -0
  75. package/dist/server/error-response.js.map +1 -0
  76. package/dist/server/index.d.ts +2 -0
  77. package/dist/server/index.d.ts.map +1 -1
  78. package/dist/server/index.js +1 -0
  79. package/dist/server/index.js.map +1 -1
  80. package/dist/server/last-resort.d.ts +17 -0
  81. package/dist/server/last-resort.d.ts.map +1 -0
  82. package/dist/server/last-resort.js +19 -0
  83. package/dist/server/last-resort.js.map +1 -0
  84. package/dist/server/procedure-server.d.ts +3 -11
  85. package/dist/server/procedure-server.d.ts.map +1 -1
  86. package/dist/server/procedure-server.js +7 -42
  87. package/dist/server/procedure-server.js.map +1 -1
  88. package/dist/server/server.d.ts +43 -1
  89. package/dist/server/server.d.ts.map +1 -1
  90. package/dist/server/server.js +39 -2
  91. package/dist/server/server.js.map +1 -1
  92. package/dist/server/types.d.ts +12 -0
  93. package/dist/server/types.d.ts.map +1 -1
  94. package/dist/server/types.js.map +1 -1
  95. package/package.json +1 -1
  96. package/src/adapters/http/server/handler.ts +18 -3
  97. package/src/adapters/port/transport.ts +2 -0
  98. package/src/adapters/websocket/server/transport.ts +2 -0
  99. package/src/adapters/websocket/shared/rpc.ts +14 -5
  100. package/src/browser.ts +11 -0
  101. package/src/client/errors/index.ts +1 -0
  102. package/src/client/errors/registry.ts +13 -0
  103. package/src/client/index.ts +1 -0
  104. package/src/events/bus.ts +6 -1
  105. package/src/events/index.ts +1 -1
  106. package/src/events/publisher.ts +150 -26
  107. package/src/events/recheck.ts +38 -13
  108. package/src/procedures/collection/admission.ts +50 -9
  109. package/src/procedures/collection/changes.ts +68 -7
  110. package/src/procedures/collection/hint-filter.ts +376 -0
  111. package/src/procedures/collection/index.ts +3 -0
  112. package/src/procedures/collection/procedures.ts +8 -6
  113. package/src/procedures/index.ts +5 -0
  114. package/src/server/error-policy.ts +263 -0
  115. package/src/server/error-response.ts +91 -0
  116. package/src/server/index.ts +2 -0
  117. package/src/server/last-resort.ts +23 -0
  118. package/src/server/procedure-server.ts +8 -41
  119. package/src/server/server.ts +73 -3
  120. package/src/server/types.ts +13 -0
@@ -17,6 +17,8 @@ import type {
17
17
  } from "./types.js";
18
18
  import { HandlerNotFoundError, ServerError } from "./types.js";
19
19
  import { compose } from "../middleware/index.js";
20
+ import { callOf, exposeError, exposedResponse, markLogged, policyActs, type ExposedError, type HiddenErrorLog, type ServerErrorPolicy } from "./error-policy.js";
21
+ import { withRealError } from "./error-response.js";
20
22
 
21
23
  /**
22
24
  * Server configuration options.
@@ -30,7 +32,9 @@ export interface ServerOptions {
30
32
 
31
33
  /**
32
34
  * Global error handler.
33
- * Called when a handler throws an error.
35
+ * Called when a handler throws an error. The error policy (`errors`) also gives it each hidden
36
+ * error one time. A last-resort error of a face that failed before it knew the call comes with
37
+ * `UNKNOWN_REQUEST`: a request with the id `"unknown"`.
34
38
  */
35
39
  onError?: (error: Error, request: ServerRequest) => void;
36
40
 
@@ -43,6 +47,33 @@ export interface ServerOptions {
43
47
  * Response logger.
44
48
  */
45
49
  onResponse?: (response: ServerResponse) => void;
50
+
51
+ /**
52
+ * The error policy (roadmap CLI-80): what an error tells the caller when it leaves the server.
53
+ * `handle()` applies it, so each face (HTTP, WebSocket, MCP, a message port) sends the same
54
+ * error. A nested call of `ctx.client` keeps the real error.
55
+ *
56
+ * - `expose: "all"` (the default): each error goes to the caller as before.
57
+ * - `expose: "coded"`: an error with a known code (`isKnownErrorCode`) goes to the caller as it
58
+ * is, and so does an error with a code of `codes`. Each other error goes as `INTERNAL_ERROR`
59
+ * with the message `"internal error"` and its real retryable flag. The server gives the real
60
+ * error to `onError` one time, or to `console.error` without `onError`.
61
+ * - `map` runs first, with the real error of its own class. Its result goes to the caller as it
62
+ * is, with its `retryAfterMs`. `undefined` gives the error to `expose`.
63
+ * - The last-resort catch of each face applies the same policy (`exposeError`).
64
+ *
65
+ * @example
66
+ * ```typescript
67
+ * new ProcedureServer({
68
+ * errors: {
69
+ * expose: "coded",
70
+ * map: (error) => (isDuplicateKey(error) ? new ServerError("The item exists", "CONFLICT") : undefined),
71
+ * },
72
+ * onError: (error, request) => logger.error({ err: error, id: request.id }),
73
+ * });
74
+ * ```
75
+ */
76
+ errors?: ServerErrorPolicy | undefined;
46
77
  }
47
78
 
48
79
  /**
@@ -63,6 +94,9 @@ interface HandlerEntry {
63
94
  handler: ServerHandler;
64
95
  }
65
96
 
97
+ /** The request that `onError` gets for an error of a face that failed before it knew the call (CLI-80). */
98
+ const UNKNOWN_REQUEST: ServerRequest = { id: "unknown", method: { service: "", operation: "" }, payload: undefined, metadata: {} };
99
+
66
100
  /**
67
101
  * Universal RPC server.
68
102
  *
@@ -196,6 +230,35 @@ export class Server {
196
230
  */
197
231
  async handle<TReq, TRes>(
198
232
  request: ServerRequest<TReq>
233
+ ): Promise<ServerResponse<TRes>> {
234
+ const policy = this.options.errors;
235
+ if (!policyActs(policy)) return this.handleRaw(request);
236
+ const response = await this.handleRaw<TReq, TRes>(request);
237
+ return exposedResponse(policy, request, response, this.hiddenErrorLog);
238
+ }
239
+
240
+ /**
241
+ * The error that the last-resort catch of a face sends (roadmap CLI-80): when `handle()` throws,
242
+ * or the face fails after it. `raw` is the error that the face sends without a policy. Under the
243
+ * option `errors`, `map` runs first, and `"coded"` hides the message of `INTERNAL_ERROR`. The
244
+ * logger gets each hidden error one time. A face calls it through `lastResortError`.
245
+ */
246
+ exposeError(error: unknown, request: ServerRequest | undefined, raw: ExposedError): ExposedError {
247
+ return exposeError(this.options.errors, error, callOf(request), (real) => this.hiddenErrorLog(real, request), raw);
248
+ }
249
+
250
+ /** The logger of the errors that the error policy hides: `onError`, or `console.error`. */
251
+ private readonly hiddenErrorLog: HiddenErrorLog = (error, request) => {
252
+ const real = error instanceof Error ? error : new Error(String(error));
253
+ // A face that failed before it knew the call gives no request: onError gets a request of no call
254
+ if (this.options.onError) this.options.onError(real, request ?? UNKNOWN_REQUEST);
255
+ // A constant format: the request id comes from the caller (CodeQL js/tainted-format-string)
256
+ else console.error("[Server] hidden error of %s: %o", request?.id ?? "a request", real);
257
+ };
258
+
259
+ /** The response of a request, before the error policy. */
260
+ private async handleRaw<TReq, TRes>(
261
+ request: ServerRequest<TReq>
199
262
  ): Promise<ServerResponse<TRes>> {
200
263
  // Log request
201
264
  if (this.options.onRequest) {
@@ -235,10 +298,12 @@ export class Server {
235
298
  // Log error
236
299
  if (this.options.onError) {
237
300
  this.options.onError(error as Error, request);
301
+ // The error policy does not log this error again (CLI-80)
302
+ markLogged(error);
238
303
  }
239
304
 
240
305
  // Convert error to response
241
- return this.errorToResponse<TRes>(request.id, error as Error);
306
+ return this.errorToResponse<TRes>(request, error as Error);
242
307
  }
243
308
  }
244
309
 
@@ -320,7 +385,12 @@ export class Server {
320
385
  /**
321
386
  * Convert error to response.
322
387
  */
323
- private errorToResponse<TRes>(id: string, error: Error): ServerResponse<TRes> {
388
+ private errorToResponse<TRes>(request: ServerRequest<unknown>, error: Error): ServerResponse<TRes> {
389
+ return withRealError(this.errorToStatus<TRes>(request.id, error), error, request);
390
+ }
391
+
392
+ /** The response of an error, without its record for the error policy. */
393
+ private errorToStatus<TRes>(id: string, error: Error): ServerResponse<TRes> {
324
394
  if (error instanceof HandlerNotFoundError) {
325
395
  return {
326
396
  id,
@@ -9,6 +9,7 @@ import type { Elicit } from "../procedures/elicit.js";
9
9
  import type { ReportProgress } from "../procedures/progress.js";
10
10
  import type { Metadata, Status, Method } from "../client/types.js";
11
11
  import type { CapabilityRequest } from "../procedures/types.js";
12
+ import type { ExposedError } from "./error-policy.js";
12
13
  import { methodName } from "./method.js";
13
14
 
14
15
  // Re-export Method from client types
@@ -47,6 +48,12 @@ export interface ServerRequest<TReq = unknown> {
47
48
  /** Abort signal for cancellation */
48
49
  signal?: AbortSignal;
49
50
 
51
+ /**
52
+ * The face that took the request: `"http"`, `"websocket"`, `"port"` or `"mcp"` (roadmap CLI-80). The face
53
+ * sets it, not the caller. The `map` function of the error policy (`errors`) gets it.
54
+ */
55
+ transport?: string | undefined;
56
+
50
57
  /**
51
58
  * The credentials of the request (roadmap R18). A network face reads them from the request: the
52
59
  * bearer token of an `Authorization` header or of the `token` query of a WebSocket connection,
@@ -213,6 +220,12 @@ export interface ServerDebugReport {
213
220
  */
214
221
  export interface RequestHandler {
215
222
  handle(request: ServerRequest): Promise<ServerResponse>;
223
+ /**
224
+ * The error that the last-resort catch of a face sends, when `handle()` throws or the face fails
225
+ * after it (roadmap CLI-80): `raw` under the error policy of the handler. `Server` has it, and
226
+ * a face calls it through `lastResortError`. Absent: the face sends `raw`.
227
+ */
228
+ exposeError?(error: unknown, request: ServerRequest | undefined, raw: ExposedError): ExposedError;
216
229
  /**
217
230
  * How the handler can authenticate a network request with ambient credentials: the cookies that
218
231
  * a browser sends by itself, also for a request of another site (CSRF). A network face then