@procedures/client 0.47.1 → 0.48.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 (139) hide show
  1. package/CHANGELOG.md +92 -68
  2. package/README.md +3 -3
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/adapters/http/client/batch.d.ts.map +1 -1
  5. package/dist/adapters/http/client/batch.js.map +1 -1
  6. package/dist/adapters/http/server/body.d.ts.map +1 -1
  7. package/dist/adapters/http/server/body.js.map +1 -1
  8. package/dist/adapters/http/shared/constants.d.ts +82 -78
  9. package/dist/adapters/http/shared/constants.d.ts.map +1 -1
  10. package/dist/adapters/http/shared/constants.js +78 -80
  11. package/dist/adapters/http/shared/constants.js.map +1 -1
  12. package/dist/adapters/http/shared/errors.d.ts +15 -13
  13. package/dist/adapters/http/shared/errors.d.ts.map +1 -1
  14. package/dist/adapters/http/shared/errors.js +13 -14
  15. package/dist/adapters/http/shared/errors.js.map +1 -1
  16. package/dist/adapters/port/transport.d.ts.map +1 -1
  17. package/dist/adapters/port/transport.js.map +1 -1
  18. package/dist/adapters/sequence.d.ts.map +1 -1
  19. package/dist/adapters/sequence.js.map +1 -1
  20. package/dist/adapters/websocket/client/types.d.ts +16 -6
  21. package/dist/adapters/websocket/client/types.d.ts.map +1 -1
  22. package/dist/adapters/websocket/client/types.js +7 -8
  23. package/dist/adapters/websocket/client/types.js.map +1 -1
  24. package/dist/adapters/websocket/shared/rpc.d.ts.map +1 -1
  25. package/dist/adapters/websocket/shared/rpc.js.map +1 -1
  26. package/dist/browser.d.ts +1 -1
  27. package/dist/browser.d.ts.map +1 -1
  28. package/dist/browser.js.map +1 -1
  29. package/dist/client/batch-executor.d.ts.map +1 -1
  30. package/dist/client/batch-executor.js.map +1 -1
  31. package/dist/client/client.d.ts.map +1 -1
  32. package/dist/client/client.js +3 -4
  33. package/dist/client/client.js.map +1 -1
  34. package/dist/client/errors/codes.d.ts +66 -64
  35. package/dist/client/errors/codes.d.ts.map +1 -1
  36. package/dist/client/errors/codes.js +64 -65
  37. package/dist/client/errors/codes.js.map +1 -1
  38. package/dist/client/errors/http-status.d.ts +69 -67
  39. package/dist/client/errors/http-status.d.ts.map +1 -1
  40. package/dist/client/errors/http-status.js +67 -68
  41. package/dist/client/errors/http-status.js.map +1 -1
  42. package/dist/client/errors/types.d.ts +38 -15
  43. package/dist/client/errors/types.d.ts.map +1 -1
  44. package/dist/client/errors/types.js +17 -19
  45. package/dist/client/errors/types.js.map +1 -1
  46. package/dist/client/middleware/circuit-breaker.d.ts.map +1 -1
  47. package/dist/client/middleware/circuit-breaker.js.map +1 -1
  48. package/dist/client/middleware/lru.d.ts +1 -1
  49. package/dist/client/middleware/lru.d.ts.map +1 -1
  50. package/dist/client/middleware/lru.js.map +1 -1
  51. package/dist/client/middleware/rate-limit.d.ts.map +1 -1
  52. package/dist/client/middleware/rate-limit.js.map +1 -1
  53. package/dist/client/middleware/resilience.d.ts.map +1 -1
  54. package/dist/client/middleware/resilience.js +2 -1
  55. package/dist/client/middleware/resilience.js.map +1 -1
  56. package/dist/client/route-resolver.d.ts.map +1 -1
  57. package/dist/client/route-resolver.js.map +1 -1
  58. package/dist/client/types.d.ts.map +1 -1
  59. package/dist/client/types.js.map +1 -1
  60. package/dist/client/validation/types.d.ts +1 -7
  61. package/dist/client/validation/types.d.ts.map +1 -1
  62. package/dist/client/validation/types.js +4 -7
  63. package/dist/client/validation/types.js.map +1 -1
  64. package/dist/events/bus.d.ts +35 -0
  65. package/dist/events/bus.d.ts.map +1 -1
  66. package/dist/events/bus.js +135 -22
  67. package/dist/events/bus.js.map +1 -1
  68. package/dist/events/index.d.ts +1 -1
  69. package/dist/events/index.d.ts.map +1 -1
  70. package/dist/events/index.js.map +1 -1
  71. package/dist/events/publisher.d.ts.map +1 -1
  72. package/dist/events/publisher.js +4 -7
  73. package/dist/events/publisher.js.map +1 -1
  74. package/dist/events/recheck.d.ts.map +1 -1
  75. package/dist/events/recheck.js.map +1 -1
  76. package/dist/events/remote.d.ts +1 -7
  77. package/dist/events/remote.d.ts.map +1 -1
  78. package/dist/events/remote.js +4 -7
  79. package/dist/events/remote.js.map +1 -1
  80. package/dist/events/types.d.ts +30 -0
  81. package/dist/events/types.d.ts.map +1 -1
  82. package/dist/middleware/compose.d.ts.map +1 -1
  83. package/dist/middleware/compose.js +6 -10
  84. package/dist/middleware/compose.js.map +1 -1
  85. package/dist/procedures/caller.d.ts +1 -1
  86. package/dist/procedures/caller.js +1 -1
  87. package/dist/procedures/collection/admission.d.ts +1 -1
  88. package/dist/procedures/collection/admission.js +1 -1
  89. package/dist/procedures/collection/hint-filter.d.ts +1 -7
  90. package/dist/procedures/collection/hint-filter.d.ts.map +1 -1
  91. package/dist/procedures/collection/hint-filter.js +4 -7
  92. package/dist/procedures/collection/hint-filter.js.map +1 -1
  93. package/dist/server/face-rules.d.ts +1 -1
  94. package/dist/server/face-rules.js +1 -1
  95. package/dist/server/procedure-server.d.ts +7 -5
  96. package/dist/server/procedure-server.d.ts.map +1 -1
  97. package/dist/server/procedure-server.js +10 -8
  98. package/dist/server/procedure-server.js.map +1 -1
  99. package/dist/server/types.d.ts.map +1 -1
  100. package/dist/server/types.js.map +1 -1
  101. package/dist/testing/transport-contract.d.ts +1 -1
  102. package/dist/testing/transport-contract.js +1 -1
  103. package/package.json +8 -4
  104. package/src/adapters/http/client/batch.ts +4 -1
  105. package/src/adapters/http/server/body.ts +4 -1
  106. package/src/adapters/http/shared/constants.ts +84 -78
  107. package/src/adapters/http/shared/errors.ts +16 -13
  108. package/src/adapters/port/transport.ts +4 -1
  109. package/src/adapters/sequence.ts +5 -1
  110. package/src/adapters/websocket/client/types.ts +19 -6
  111. package/src/adapters/websocket/shared/rpc.ts +15 -3
  112. package/src/browser.ts +1 -0
  113. package/src/client/batch-executor.ts +5 -1
  114. package/src/client/client.ts +3 -4
  115. package/src/client/errors/README.md +12 -12
  116. package/src/client/errors/codes.ts +67 -64
  117. package/src/client/errors/http-status.ts +70 -67
  118. package/src/client/errors/types.ts +44 -15
  119. package/src/client/middleware/circuit-breaker.ts +7 -2
  120. package/src/client/middleware/lru.ts +7 -2
  121. package/src/client/middleware/rate-limit.ts +4 -1
  122. package/src/client/middleware/resilience.ts +2 -1
  123. package/src/client/route-resolver.ts +5 -1
  124. package/src/client/types.ts +7 -2
  125. package/src/client/validation/types.ts +13 -6
  126. package/src/events/bus.ts +152 -23
  127. package/src/events/index.ts +1 -0
  128. package/src/events/publisher.ts +22 -9
  129. package/src/events/recheck.ts +4 -1
  130. package/src/events/remote.ts +33 -12
  131. package/src/events/types.ts +34 -0
  132. package/src/middleware/compose.ts +6 -22
  133. package/src/procedures/caller.ts +1 -1
  134. package/src/procedures/collection/admission.ts +1 -1
  135. package/src/procedures/collection/hint-filter.ts +13 -6
  136. package/src/server/face-rules.ts +1 -1
  137. package/src/server/procedure-server.ts +11 -8
  138. package/src/server/types.ts +7 -2
  139. package/src/testing/transport-contract.ts +1 -1
@@ -110,7 +110,11 @@ function cancelledResult(path: ProcedurePath, message: string): ProcedureCallRes
110
110
  * ```
111
111
  */
112
112
  export class BatchExecutor {
113
- constructor(private readonly execute: ProcedureExecutor) {}
113
+ private readonly execute: ProcedureExecutor;
114
+
115
+ constructor(execute: ProcedureExecutor) {
116
+ this.execute = execute;
117
+ }
114
118
 
115
119
  /**
116
120
  * Execute resolved routes with the specified strategy.
@@ -38,7 +38,7 @@ import type {
38
38
  TypedClientMiddleware,
39
39
  } from "./types.js";
40
40
  import { ClientError } from "./types.js";
41
- import { compose } from "../middleware/compose.js";
41
+ import { compose } from "@mark1russell7/collections/layers";
42
42
  import { pathToMethod } from "../server/method.js";
43
43
  import type { CallOptions, ClientContextInput } from "./context.js";
44
44
  import { mergeContext, normalizeCallOptions } from "./context.js";
@@ -920,9 +920,8 @@ export class Client<TContext = {}> {
920
920
  yield* self.transport.send<TReq, TRes>(context.message);
921
921
  };
922
922
 
923
- // Use universal middleware composition
924
- // compose(...middleware, finalRunner) applies middleware in order (onion model)
925
- return compose<false>(...(this.middleware as any[]), transportRunner) as ClientRunner<TReq, TRes>;
923
+ // The first middleware is the outermost one (onion model)
924
+ return compose(...(this.middleware as ClientMiddleware<TReq, TRes>[]))(transportRunner);
926
925
  }
927
926
 
928
927
  /**
@@ -4,8 +4,8 @@ Protocol-agnostic error handling with rich metadata and bidirectional HTTP statu
4
4
 
5
5
  ## Zero Magic Strings & Numbers
6
6
 
7
- - **ErrorCode enum** - All error codes use enum values (no string literals)
8
- - **HTTPStatus enum** - All HTTP status codes use enum values (no magic numbers)
7
+ - **ErrorCode** - All error codes use the values of this object (no string literals)
8
+ - **HTTPStatus** - All HTTP status codes use the values of this object (no magic numbers)
9
9
  - **Single source of truth** - `HTTP_STATUS_ERROR_MAP` defines bidirectional mapping
10
10
 
11
11
  ## Architecture
@@ -67,7 +67,7 @@ import { errorCodeToHTTPStatus, ErrorCode, HTTPStatus } from "@common/client/err
67
67
  // Map error code to HTTP status
68
68
  function handleError(code: ErrorCode): number {
69
69
  const status = errorCodeToHTTPStatus(code);
70
- return status; // Returns HTTPStatus enum value (404, 500, etc.)
70
+ return status; // Returns a value of HTTPStatus (404, 500, etc.)
71
71
  }
72
72
 
73
73
  // Example
@@ -100,8 +100,8 @@ try {
100
100
  common/src/client/errors/
101
101
  ├── README.md ← You are here
102
102
  ├── index.ts ← Public API exports
103
- ├── codes.ts ← ErrorCode enum (70+ codes)
104
- ├── http-status.ts ← HTTPStatus enum (all HTTP status codes)
103
+ ├── codes.ts ← ErrorCode (70+ codes)
104
+ ├── http-status.ts ← HTTPStatus (all HTTP status codes)
105
105
  ├── types.ts ← TypeScript interfaces
106
106
  ├── registry.ts ← ERROR_REGISTRY with full metadata
107
107
  ├── http-errors.ts ← HTTP-specific errors + bidirectional mapping
@@ -135,7 +135,7 @@ Works across all transports:
135
135
 
136
136
  Full TypeScript support:
137
137
  ```typescript
138
- // All enums are type-safe
138
+ // All values are type-safe
139
139
  const code: ErrorCode = ErrorCode.TIMEOUT;
140
140
  const status: HTTPStatus = HTTPStatus.NOT_FOUND;
141
141
 
@@ -201,7 +201,7 @@ console.error(formatError(error, true)); // include stack trace
201
201
 
202
202
  ## Best Practices
203
203
 
204
- 1. **Always use enum values** - Never use string literals or magic numbers
204
+ 1. **Always use the named values** - Never use string literals or magic numbers
205
205
  2. **Use the registry** - Don't create error metadata manually
206
206
  3. **Provide context** - Include requestId, transport, method when creating errors
207
207
  4. **Check retryable flag** - Respect the metadata when implementing retry logic
@@ -209,7 +209,7 @@ console.error(formatError(error, true)); // include stack trace
209
209
 
210
210
  ## Adding New Errors
211
211
 
212
- 1. Add to `ErrorCode` enum in `codes.ts`
212
+ 1. Add to `ErrorCode` in `codes.ts`
213
213
  2. Add metadata to `ERROR_REGISTRY` in `registry.ts` or `HTTP_ERROR_REGISTRY` in `http-errors.ts`
214
214
  3. If HTTP-related, add to `HTTP_STATUS_ERROR_MAP` in `http-errors.ts`
215
215
 
@@ -217,11 +217,11 @@ console.error(formatError(error, true)); // include stack trace
217
217
 
218
218
  Example:
219
219
  ```typescript
220
- // 1. Add to ErrorCode enum
221
- export enum ErrorCode {
220
+ // 1. Add to ErrorCode
221
+ export const ErrorCode = {
222
222
  // ... existing codes
223
- MY_NEW_ERROR = "MY_NEW_ERROR",
224
- }
223
+ MY_NEW_ERROR: "MY_NEW_ERROR",
224
+ } as const;
225
225
 
226
226
  // 2. Add to registry
227
227
  export const ERROR_REGISTRY: ErrorRegistry = {
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Error Code Enums
2
+ * Error Codes
3
3
  *
4
- * Comprehensive enum of ALL error codes.
5
- * NO MAGIC STRINGS - use these enums everywhere!
4
+ * ALL error codes.
5
+ * NO MAGIC STRINGS - use these values everywhere!
6
6
  */
7
7
 
8
8
  /**
@@ -10,81 +10,84 @@
10
10
  *
11
11
  * Protocol-agnostic error codes that work across all transports.
12
12
  */
13
- export enum ErrorCode {
13
+ export const ErrorCode = {
14
14
  // Network errors
15
- NETWORK_ERROR = "NETWORK_ERROR",
16
- CONNECTION_REFUSED = "CONNECTION_REFUSED",
17
- DNS_ERROR = "DNS_ERROR",
18
- STREAM_ENDED = "STREAM_ENDED",
15
+ NETWORK_ERROR: "NETWORK_ERROR",
16
+ CONNECTION_REFUSED: "CONNECTION_REFUSED",
17
+ DNS_ERROR: "DNS_ERROR",
18
+ STREAM_ENDED: "STREAM_ENDED",
19
19
 
20
20
  // Timeout errors
21
- TIMEOUT = "TIMEOUT",
22
- CONNECTION_TIMEOUT = "CONNECTION_TIMEOUT",
23
- READ_TIMEOUT = "READ_TIMEOUT",
24
- DEADLINE_EXCEEDED = "DEADLINE_EXCEEDED",
21
+ TIMEOUT: "TIMEOUT",
22
+ CONNECTION_TIMEOUT: "CONNECTION_TIMEOUT",
23
+ READ_TIMEOUT: "READ_TIMEOUT",
24
+ DEADLINE_EXCEEDED: "DEADLINE_EXCEEDED",
25
25
 
26
26
  // Cancellation
27
- ABORTED = "ABORTED",
27
+ ABORTED: "ABORTED",
28
28
 
29
29
  // Protocol/parsing errors
30
- PARSE_ERROR = "PARSE_ERROR",
31
- SERIALIZE_ERROR = "SERIALIZE_ERROR",
32
- INVALID_RESPONSE = "INVALID_RESPONSE",
33
- PROTOCOL_ERROR = "PROTOCOL_ERROR",
30
+ PARSE_ERROR: "PARSE_ERROR",
31
+ SERIALIZE_ERROR: "SERIALIZE_ERROR",
32
+ INVALID_RESPONSE: "INVALID_RESPONSE",
33
+ PROTOCOL_ERROR: "PROTOCOL_ERROR",
34
34
 
35
35
  // Client errors (4xx equivalent)
36
- BAD_REQUEST = "BAD_REQUEST",
37
- UNAUTHORIZED = "UNAUTHORIZED",
38
- FORBIDDEN = "FORBIDDEN",
39
- NOT_FOUND = "NOT_FOUND",
40
- METHOD_NOT_ALLOWED = "METHOD_NOT_ALLOWED",
41
- NOT_ACCEPTABLE = "NOT_ACCEPTABLE",
42
- PROXY_AUTHENTICATION_REQUIRED = "PROXY_AUTHENTICATION_REQUIRED",
43
- CONFLICT = "CONFLICT",
44
- GONE = "GONE",
45
- LENGTH_REQUIRED = "LENGTH_REQUIRED",
46
- PRECONDITION_FAILED = "PRECONDITION_FAILED",
47
- PAYLOAD_TOO_LARGE = "PAYLOAD_TOO_LARGE",
48
- URI_TOO_LONG = "URI_TOO_LONG",
49
- UNSUPPORTED_MEDIA_TYPE = "UNSUPPORTED_MEDIA_TYPE",
50
- RANGE_NOT_SATISFIABLE = "RANGE_NOT_SATISFIABLE",
51
- EXPECTATION_FAILED = "EXPECTATION_FAILED",
52
- IM_A_TEAPOT = "IM_A_TEAPOT",
53
- MISDIRECTED_REQUEST = "MISDIRECTED_REQUEST",
54
- UNPROCESSABLE_ENTITY = "UNPROCESSABLE_ENTITY",
55
- LOCKED = "LOCKED",
56
- FAILED_DEPENDENCY = "FAILED_DEPENDENCY",
57
- TOO_EARLY = "TOO_EARLY",
58
- UPGRADE_REQUIRED = "UPGRADE_REQUIRED",
59
- PRECONDITION_REQUIRED = "PRECONDITION_REQUIRED",
60
- TOO_MANY_REQUESTS = "TOO_MANY_REQUESTS",
61
- REQUEST_HEADER_FIELDS_TOO_LARGE = "REQUEST_HEADER_FIELDS_TOO_LARGE",
62
- UNAVAILABLE_FOR_LEGAL_REASONS = "UNAVAILABLE_FOR_LEGAL_REASONS",
63
- CLIENT_CLOSED_REQUEST = "CLIENT_CLOSED_REQUEST",
64
- METADATA_TOO_LARGE = "METADATA_TOO_LARGE",
36
+ BAD_REQUEST: "BAD_REQUEST",
37
+ UNAUTHORIZED: "UNAUTHORIZED",
38
+ FORBIDDEN: "FORBIDDEN",
39
+ NOT_FOUND: "NOT_FOUND",
40
+ METHOD_NOT_ALLOWED: "METHOD_NOT_ALLOWED",
41
+ NOT_ACCEPTABLE: "NOT_ACCEPTABLE",
42
+ PROXY_AUTHENTICATION_REQUIRED: "PROXY_AUTHENTICATION_REQUIRED",
43
+ CONFLICT: "CONFLICT",
44
+ GONE: "GONE",
45
+ LENGTH_REQUIRED: "LENGTH_REQUIRED",
46
+ PRECONDITION_FAILED: "PRECONDITION_FAILED",
47
+ PAYLOAD_TOO_LARGE: "PAYLOAD_TOO_LARGE",
48
+ URI_TOO_LONG: "URI_TOO_LONG",
49
+ UNSUPPORTED_MEDIA_TYPE: "UNSUPPORTED_MEDIA_TYPE",
50
+ RANGE_NOT_SATISFIABLE: "RANGE_NOT_SATISFIABLE",
51
+ EXPECTATION_FAILED: "EXPECTATION_FAILED",
52
+ IM_A_TEAPOT: "IM_A_TEAPOT",
53
+ MISDIRECTED_REQUEST: "MISDIRECTED_REQUEST",
54
+ UNPROCESSABLE_ENTITY: "UNPROCESSABLE_ENTITY",
55
+ LOCKED: "LOCKED",
56
+ FAILED_DEPENDENCY: "FAILED_DEPENDENCY",
57
+ TOO_EARLY: "TOO_EARLY",
58
+ UPGRADE_REQUIRED: "UPGRADE_REQUIRED",
59
+ PRECONDITION_REQUIRED: "PRECONDITION_REQUIRED",
60
+ TOO_MANY_REQUESTS: "TOO_MANY_REQUESTS",
61
+ REQUEST_HEADER_FIELDS_TOO_LARGE: "REQUEST_HEADER_FIELDS_TOO_LARGE",
62
+ UNAVAILABLE_FOR_LEGAL_REASONS: "UNAVAILABLE_FOR_LEGAL_REASONS",
63
+ CLIENT_CLOSED_REQUEST: "CLIENT_CLOSED_REQUEST",
64
+ METADATA_TOO_LARGE: "METADATA_TOO_LARGE",
65
65
 
66
66
  // Server errors (5xx equivalent)
67
- INTERNAL_ERROR = "INTERNAL_ERROR",
68
- NOT_IMPLEMENTED = "NOT_IMPLEMENTED",
69
- BAD_GATEWAY = "BAD_GATEWAY",
70
- SERVICE_UNAVAILABLE = "SERVICE_UNAVAILABLE",
71
- GATEWAY_TIMEOUT = "GATEWAY_TIMEOUT",
72
- HTTP_VERSION_NOT_SUPPORTED = "HTTP_VERSION_NOT_SUPPORTED",
73
- VARIANT_ALSO_NEGOTIATES = "VARIANT_ALSO_NEGOTIATES",
74
- INSUFFICIENT_STORAGE = "INSUFFICIENT_STORAGE",
75
- LOOP_DETECTED = "LOOP_DETECTED",
76
- NOT_EXTENDED = "NOT_EXTENDED",
77
- NETWORK_AUTHENTICATION_REQUIRED = "NETWORK_AUTHENTICATION_REQUIRED",
78
- HTTP_NETWORK_CONNECT_TIMEOUT = "HTTP_NETWORK_CONNECT_TIMEOUT",
67
+ INTERNAL_ERROR: "INTERNAL_ERROR",
68
+ NOT_IMPLEMENTED: "NOT_IMPLEMENTED",
69
+ BAD_GATEWAY: "BAD_GATEWAY",
70
+ SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE",
71
+ GATEWAY_TIMEOUT: "GATEWAY_TIMEOUT",
72
+ HTTP_VERSION_NOT_SUPPORTED: "HTTP_VERSION_NOT_SUPPORTED",
73
+ VARIANT_ALSO_NEGOTIATES: "VARIANT_ALSO_NEGOTIATES",
74
+ INSUFFICIENT_STORAGE: "INSUFFICIENT_STORAGE",
75
+ LOOP_DETECTED: "LOOP_DETECTED",
76
+ NOT_EXTENDED: "NOT_EXTENDED",
77
+ NETWORK_AUTHENTICATION_REQUIRED: "NETWORK_AUTHENTICATION_REQUIRED",
78
+ HTTP_NETWORK_CONNECT_TIMEOUT: "HTTP_NETWORK_CONNECT_TIMEOUT",
79
79
 
80
80
  // Configuration errors
81
- INVALID_CONFIG = "INVALID_CONFIG",
82
- INVALID_URL = "INVALID_URL",
83
- INVALID_METHOD = "INVALID_METHOD",
81
+ INVALID_CONFIG: "INVALID_CONFIG",
82
+ INVALID_URL: "INVALID_URL",
83
+ INVALID_METHOD: "INVALID_METHOD",
84
84
 
85
85
  // HTTP-specific
86
- HTTP_ERROR = "HTTP_ERROR",
86
+ HTTP_ERROR: "HTTP_ERROR",
87
87
 
88
88
  // Unknown
89
- UNKNOWN = "UNKNOWN",
90
- }
89
+ UNKNOWN: "UNKNOWN",
90
+ } as const;
91
+
92
+ /** This type is the union of the values of `ErrorCode`. */
93
+ export type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
@@ -1,82 +1,85 @@
1
1
  /**
2
2
  * HTTP Status Code Constants
3
3
  *
4
- * Comprehensive enum of HTTP status codes.
4
+ * All HTTP status codes.
5
5
  * NO MAGIC NUMBERS - use these constants everywhere!
6
6
  */
7
7
 
8
- export enum HTTPStatus {
8
+ export const HTTPStatus = {
9
9
  // 1xx Informational
10
- CONTINUE = 100,
11
- SWITCHING_PROTOCOLS = 101,
12
- PROCESSING = 102,
13
- EARLY_HINTS = 103,
10
+ CONTINUE: 100,
11
+ SWITCHING_PROTOCOLS: 101,
12
+ PROCESSING: 102,
13
+ EARLY_HINTS: 103,
14
14
 
15
15
  // 2xx Success
16
- OK = 200,
17
- CREATED = 201,
18
- ACCEPTED = 202,
19
- NON_AUTHORITATIVE_INFORMATION = 203,
20
- NO_CONTENT = 204,
21
- RESET_CONTENT = 205,
22
- PARTIAL_CONTENT = 206,
23
- MULTI_STATUS = 207,
24
- ALREADY_REPORTED = 208,
25
- IM_USED = 226,
16
+ OK: 200,
17
+ CREATED: 201,
18
+ ACCEPTED: 202,
19
+ NON_AUTHORITATIVE_INFORMATION: 203,
20
+ NO_CONTENT: 204,
21
+ RESET_CONTENT: 205,
22
+ PARTIAL_CONTENT: 206,
23
+ MULTI_STATUS: 207,
24
+ ALREADY_REPORTED: 208,
25
+ IM_USED: 226,
26
26
 
27
27
  // 3xx Redirection
28
- MULTIPLE_CHOICES = 300,
29
- MOVED_PERMANENTLY = 301,
30
- FOUND = 302,
31
- SEE_OTHER = 303,
32
- NOT_MODIFIED = 304,
33
- USE_PROXY = 305,
34
- TEMPORARY_REDIRECT = 307,
35
- PERMANENT_REDIRECT = 308,
28
+ MULTIPLE_CHOICES: 300,
29
+ MOVED_PERMANENTLY: 301,
30
+ FOUND: 302,
31
+ SEE_OTHER: 303,
32
+ NOT_MODIFIED: 304,
33
+ USE_PROXY: 305,
34
+ TEMPORARY_REDIRECT: 307,
35
+ PERMANENT_REDIRECT: 308,
36
36
 
37
37
  // 4xx Client Errors
38
- BAD_REQUEST = 400,
39
- UNAUTHORIZED = 401,
40
- PAYMENT_REQUIRED = 402,
41
- FORBIDDEN = 403,
42
- NOT_FOUND = 404,
43
- METHOD_NOT_ALLOWED = 405,
44
- NOT_ACCEPTABLE = 406,
45
- PROXY_AUTHENTICATION_REQUIRED = 407,
46
- REQUEST_TIMEOUT = 408,
47
- CONFLICT = 409,
48
- GONE = 410,
49
- LENGTH_REQUIRED = 411,
50
- PRECONDITION_FAILED = 412,
51
- PAYLOAD_TOO_LARGE = 413,
52
- URI_TOO_LONG = 414,
53
- UNSUPPORTED_MEDIA_TYPE = 415,
54
- RANGE_NOT_SATISFIABLE = 416,
55
- EXPECTATION_FAILED = 417,
56
- IM_A_TEAPOT = 418,
57
- MISDIRECTED_REQUEST = 421,
58
- UNPROCESSABLE_ENTITY = 422,
59
- LOCKED = 423,
60
- FAILED_DEPENDENCY = 424,
61
- TOO_EARLY = 425,
62
- UPGRADE_REQUIRED = 426,
63
- PRECONDITION_REQUIRED = 428,
64
- TOO_MANY_REQUESTS = 429,
65
- REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
66
- UNAVAILABLE_FOR_LEGAL_REASONS = 451,
67
- CLIENT_CLOSED_REQUEST = 499, // nginx-specific
38
+ BAD_REQUEST: 400,
39
+ UNAUTHORIZED: 401,
40
+ PAYMENT_REQUIRED: 402,
41
+ FORBIDDEN: 403,
42
+ NOT_FOUND: 404,
43
+ METHOD_NOT_ALLOWED: 405,
44
+ NOT_ACCEPTABLE: 406,
45
+ PROXY_AUTHENTICATION_REQUIRED: 407,
46
+ REQUEST_TIMEOUT: 408,
47
+ CONFLICT: 409,
48
+ GONE: 410,
49
+ LENGTH_REQUIRED: 411,
50
+ PRECONDITION_FAILED: 412,
51
+ PAYLOAD_TOO_LARGE: 413,
52
+ URI_TOO_LONG: 414,
53
+ UNSUPPORTED_MEDIA_TYPE: 415,
54
+ RANGE_NOT_SATISFIABLE: 416,
55
+ EXPECTATION_FAILED: 417,
56
+ IM_A_TEAPOT: 418,
57
+ MISDIRECTED_REQUEST: 421,
58
+ UNPROCESSABLE_ENTITY: 422,
59
+ LOCKED: 423,
60
+ FAILED_DEPENDENCY: 424,
61
+ TOO_EARLY: 425,
62
+ UPGRADE_REQUIRED: 426,
63
+ PRECONDITION_REQUIRED: 428,
64
+ TOO_MANY_REQUESTS: 429,
65
+ REQUEST_HEADER_FIELDS_TOO_LARGE: 431,
66
+ UNAVAILABLE_FOR_LEGAL_REASONS: 451,
67
+ CLIENT_CLOSED_REQUEST: 499, // nginx-specific
68
68
 
69
69
  // 5xx Server Errors
70
- INTERNAL_SERVER_ERROR = 500,
71
- NOT_IMPLEMENTED = 501,
72
- BAD_GATEWAY = 502,
73
- SERVICE_UNAVAILABLE = 503,
74
- GATEWAY_TIMEOUT = 504,
75
- HTTP_VERSION_NOT_SUPPORTED = 505,
76
- VARIANT_ALSO_NEGOTIATES = 506,
77
- INSUFFICIENT_STORAGE = 507,
78
- LOOP_DETECTED = 508,
79
- NOT_EXTENDED = 510,
80
- NETWORK_AUTHENTICATION_REQUIRED = 511,
81
- NETWORK_CONNECT_TIMEOUT = 599, // non-standard
82
- }
70
+ INTERNAL_SERVER_ERROR: 500,
71
+ NOT_IMPLEMENTED: 501,
72
+ BAD_GATEWAY: 502,
73
+ SERVICE_UNAVAILABLE: 503,
74
+ GATEWAY_TIMEOUT: 504,
75
+ HTTP_VERSION_NOT_SUPPORTED: 505,
76
+ VARIANT_ALSO_NEGOTIATES: 506,
77
+ INSUFFICIENT_STORAGE: 507,
78
+ LOOP_DETECTED: 508,
79
+ NOT_EXTENDED: 510,
80
+ NETWORK_AUTHENTICATION_REQUIRED: 511,
81
+ NETWORK_CONNECT_TIMEOUT: 599, // non-standard
82
+ } as const;
83
+
84
+ /** This type is the union of the values of `HTTPStatus`. */
85
+ export type HTTPStatus = (typeof HTTPStatus)[keyof typeof HTTPStatus];
@@ -8,39 +8,68 @@
8
8
  /**
9
9
  * Error severity levels.
10
10
  */
11
- export enum ErrorSeverity {
11
+ export const ErrorSeverity = {
12
12
  /** Informational - not an actual error */
13
- INFO = "info",
13
+ INFO: "info",
14
14
  /** Warning - degraded but functional */
15
- WARNING = "warning",
15
+ WARNING: "warning",
16
16
  /** Error - operation failed */
17
- ERROR = "error",
17
+ ERROR: "error",
18
18
  /** Critical - system-level failure */
19
- CRITICAL = "critical",
19
+ CRITICAL: "critical",
20
+ } as const;
21
+
22
+ /** This type is the union of the values of `ErrorSeverity`. */
23
+ export type ErrorSeverity = (typeof ErrorSeverity)[keyof typeof ErrorSeverity];
24
+
25
+ /** These types keep each member of `ErrorSeverity` usable as a type. */
26
+ // eslint-disable-next-line @typescript-eslint/no-namespace
27
+ export declare namespace ErrorSeverity {
28
+ export type INFO = typeof ErrorSeverity.INFO;
29
+ export type WARNING = typeof ErrorSeverity.WARNING;
30
+ export type ERROR = typeof ErrorSeverity.ERROR;
31
+ export type CRITICAL = typeof ErrorSeverity.CRITICAL;
20
32
  }
21
33
 
22
34
  /**
23
35
  * Error categories for classification.
24
36
  */
25
- export enum ErrorCategory {
37
+ export const ErrorCategory = {
26
38
  /** Network-level errors (connectivity, DNS, etc.) */
27
- NETWORK = "network",
39
+ NETWORK: "network",
28
40
  /** Client-side errors (validation, configuration) */
29
- CLIENT = "client",
41
+ CLIENT: "client",
30
42
  /** Server-side errors (5xx, internal errors) */
31
- SERVER = "server",
43
+ SERVER: "server",
32
44
  /** Protocol-level errors (parsing, encoding) */
33
- PROTOCOL = "protocol",
45
+ PROTOCOL: "protocol",
34
46
  /** Authentication/authorization errors */
35
- AUTH = "auth",
47
+ AUTH: "auth",
36
48
  /** Rate limiting and throttling */
37
- RATE_LIMIT = "rate_limit",
49
+ RATE_LIMIT: "rate_limit",
38
50
  /** Timeout errors */
39
- TIMEOUT = "timeout",
51
+ TIMEOUT: "timeout",
40
52
  /** Cancellation errors */
41
- CANCELLED = "cancelled",
53
+ CANCELLED: "cancelled",
42
54
  /** Unknown/uncategorized errors */
43
- UNKNOWN = "unknown",
55
+ UNKNOWN: "unknown",
56
+ } as const;
57
+
58
+ /** This type is the union of the values of `ErrorCategory`. */
59
+ export type ErrorCategory = (typeof ErrorCategory)[keyof typeof ErrorCategory];
60
+
61
+ /** These types keep each member of `ErrorCategory` usable as a type. */
62
+ // eslint-disable-next-line @typescript-eslint/no-namespace
63
+ export declare namespace ErrorCategory {
64
+ export type NETWORK = typeof ErrorCategory.NETWORK;
65
+ export type CLIENT = typeof ErrorCategory.CLIENT;
66
+ export type SERVER = typeof ErrorCategory.SERVER;
67
+ export type PROTOCOL = typeof ErrorCategory.PROTOCOL;
68
+ export type AUTH = typeof ErrorCategory.AUTH;
69
+ export type RATE_LIMIT = typeof ErrorCategory.RATE_LIMIT;
70
+ export type TIMEOUT = typeof ErrorCategory.TIMEOUT;
71
+ export type CANCELLED = typeof ErrorCategory.CANCELLED;
72
+ export type UNKNOWN = typeof ErrorCategory.UNKNOWN;
44
73
  }
45
74
 
46
75
  /**
@@ -116,13 +116,18 @@ export interface CircuitBreakerStats {
116
116
  * Circuit breaker error thrown when circuit is open.
117
117
  */
118
118
  export class CircuitBreakerError extends Error {
119
+ public readonly state: CircuitState;
120
+ public readonly lastError: Error | null;
121
+
119
122
  constructor(
120
- public readonly state: CircuitState,
121
- public readonly lastError: Error | null
123
+ state: CircuitState,
124
+ lastError: Error | null
122
125
  ) {
123
126
  super(
124
127
  `Circuit breaker is ${state}${lastError ? `: ${lastError.message}` : ""}`
125
128
  );
129
+ this.state = state;
130
+ this.lastError = lastError;
126
131
  this.name = "CircuitBreakerError";
127
132
  }
128
133
  }
@@ -8,6 +8,9 @@
8
8
  * `hashMap` of that package).
9
9
  */
10
10
  export class LruMap<K, V> {
11
+ private readonly capacity: number;
12
+ private readonly onEvict?: ((key: K, value: V) => void) | undefined;
13
+
11
14
  private readonly entries = new Map<K, V>();
12
15
 
13
16
  /**
@@ -15,9 +18,11 @@ export class LruMap<K, V> {
15
18
  * @param onEvict - Called for each key that a write removes to keep the capacity.
16
19
  */
17
20
  constructor(
18
- private readonly capacity: number,
19
- private readonly onEvict?: (key: K, value: V) => void
21
+ capacity: number,
22
+ onEvict?: (key: K, value: V) => void
20
23
  ) {
24
+ this.capacity = capacity;
25
+ this.onEvict = onEvict;
21
26
  if (!Number.isInteger(capacity) || capacity < 1) {
22
27
  throw new RangeError(`The capacity of an LRU map must be a positive whole number, not ${capacity}`);
23
28
  }
@@ -89,11 +89,14 @@ export interface RateLimitStats {
89
89
  * Rate limit error thrown when limit exceeded.
90
90
  */
91
91
  export class RateLimitError extends Error {
92
+ public readonly stats: RateLimitStats;
93
+
92
94
  constructor(
93
95
  message: string,
94
- public readonly stats: RateLimitStats
96
+ stats: RateLimitStats
95
97
  ) {
96
98
  super(message);
99
+ this.stats = stats;
97
100
  this.name = "RateLimitError";
98
101
  }
99
102
  }
@@ -16,6 +16,7 @@
16
16
  * call goes to the server as `procedure-timeout` with each try (`client/deadline.ts`).
17
17
  */
18
18
 
19
+ import { compose } from "@mark1russell7/collections/layers";
19
20
  import type { ClientMiddleware, ClientRunner, TypedClientMiddleware } from "../types.js";
20
21
  import type { CacheContext, CircuitBreakerContext, RateLimitContext, RetryContext, TimeoutContext } from "./contexts.js";
21
22
  import { createCacheMiddleware, type CacheOptions } from "./cache.js";
@@ -76,6 +77,6 @@ export function resilience(options: ResilienceOptions): TypedClientMiddleware<Re
76
77
  layers.push(createTimeoutMiddleware({ perAttempt: options.perAttempt, ...(message ? { message: `${message} (per-attempt)` } : {}) }) as ClientMiddleware);
77
78
  }
78
79
  const composed = <TReq, TRes>(next: ClientRunner<TReq, TRes>): ClientRunner<TReq, TRes> =>
79
- layers.reduceRight<ClientRunner<TReq, TRes>>((inner, layer) => (layer as ClientMiddleware<TReq, TRes>)(inner), next);
80
+ compose(...(layers as ClientMiddleware<TReq, TRes>[]))(next);
80
81
  return composed as TypedClientMiddleware<ResilienceContext, {}>;
81
82
  }
@@ -99,7 +99,11 @@ export interface RouteResolutionResult {
99
99
  * ```
100
100
  */
101
101
  export class RouteResolver {
102
- constructor(private readonly registry: ProcedureRegistry) {}
102
+ private readonly registry: ProcedureRegistry;
103
+
104
+ constructor(registry: ProcedureRegistry) {
105
+ this.registry = registry;
106
+ }
103
107
 
104
108
  /**
105
109
  * Resolve a route structure to procedures.
@@ -326,6 +326,9 @@ export type AccumulateContext<
326
326
  * Client error thrown when response has error status.
327
327
  */
328
328
  export class ClientError extends Error {
329
+ public readonly status: Extract<Status, { type: "error" }>;
330
+ public readonly responseId: string;
331
+
329
332
  /**
330
333
  * The metadata of the error response, for example `reason` of a `FORBIDDEN` error, or
331
334
  * `conflicts` of a `VERSION_CONFLICT` error (roadmap CLI-16). Empty when the response has none.
@@ -333,11 +336,13 @@ export class ClientError extends Error {
333
336
  public readonly metadata: Metadata;
334
337
 
335
338
  constructor(
336
- public readonly status: Extract<Status, { type: "error" }>,
337
- public readonly responseId: string,
339
+ status: Extract<Status, { type: "error" }>,
340
+ responseId: string,
338
341
  metadata: Metadata = {},
339
342
  ) {
340
343
  super(status.message);
344
+ this.status = status;
345
+ this.responseId = responseId;
341
346
  this.name = "ClientError";
342
347
  this.metadata = metadata;
343
348
  }
@@ -114,18 +114,25 @@ export type ValidationPhase = "input" | "output";
114
114
  * Error thrown when validation fails.
115
115
  */
116
116
  export class ValidationError extends Error {
117
+ /** Which phase of validation failed */
118
+ public readonly phase: ValidationPhase;
119
+ /** The underlying Zod error */
120
+ public readonly zodError: ZodErrorLike;
121
+ /** The method being called */
122
+ public readonly method: Method;
123
+
117
124
  override readonly name = "ValidationError";
118
125
 
119
126
  constructor(
120
127
  message: string,
121
- /** Which phase of validation failed */
122
- public readonly phase: ValidationPhase,
123
- /** The underlying Zod error */
124
- public readonly zodError: ZodErrorLike,
125
- /** The method being called */
126
- public readonly method: Method,
128
+ phase: ValidationPhase,
129
+ zodError: ZodErrorLike,
130
+ method: Method,
127
131
  ) {
128
132
  super(message);
133
+ this.phase = phase;
134
+ this.zodError = zodError;
135
+ this.method = method;
129
136
 
130
137
  // Maintain proper stack trace in V8 environments
131
138
  if (Error.captureStackTrace) {