ts-procedures 5.16.0 → 6.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 (146) hide show
  1. package/README.md +2 -0
  2. package/agent_config/claude-code/agents/ts-procedures-architect.md +13 -6
  3. package/agent_config/claude-code/skills/ts-procedures/SKILL.md +26 -4
  4. package/agent_config/claude-code/skills/ts-procedures/anti-patterns.md +85 -17
  5. package/agent_config/claude-code/skills/ts-procedures/api-reference.md +163 -5
  6. package/agent_config/claude-code/skills/ts-procedures/patterns.md +169 -13
  7. package/agent_config/claude-code/skills/ts-procedures-review/SKILL.md +1 -1
  8. package/agent_config/claude-code/skills/ts-procedures-review/checklist.md +20 -12
  9. package/agent_config/claude-code/skills/ts-procedures-scaffold/SKILL.md +2 -1
  10. package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/client.md +22 -15
  11. package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/express-rpc.md +20 -17
  12. package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/hono-api.md +20 -16
  13. package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/hono-rpc.md +20 -17
  14. package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/hono-stream.md +16 -3
  15. package/agent_config/copilot/copilot-instructions.md +77 -12
  16. package/agent_config/cursor/cursorrules +77 -12
  17. package/build/client/call.d.ts +2 -1
  18. package/build/client/call.js +9 -1
  19. package/build/client/call.js.map +1 -1
  20. package/build/client/error-dispatch.d.ts +13 -0
  21. package/build/client/error-dispatch.js +26 -0
  22. package/build/client/error-dispatch.js.map +1 -0
  23. package/build/client/error-dispatch.test.d.ts +1 -0
  24. package/build/client/error-dispatch.test.js +56 -0
  25. package/build/client/error-dispatch.test.js.map +1 -0
  26. package/build/client/fetch-adapter.js +10 -4
  27. package/build/client/fetch-adapter.js.map +1 -1
  28. package/build/client/index.d.ts +2 -1
  29. package/build/client/index.js +5 -1
  30. package/build/client/index.js.map +1 -1
  31. package/build/client/stream.d.ts +2 -1
  32. package/build/client/stream.js +13 -3
  33. package/build/client/stream.js.map +1 -1
  34. package/build/client/typed-error-dispatch.test.d.ts +1 -0
  35. package/build/client/typed-error-dispatch.test.js +168 -0
  36. package/build/client/typed-error-dispatch.test.js.map +1 -0
  37. package/build/client/types.d.ts +37 -0
  38. package/build/codegen/e2e.test.js +9 -4
  39. package/build/codegen/e2e.test.js.map +1 -1
  40. package/build/codegen/emit-client-runtime.js +4 -0
  41. package/build/codegen/emit-client-runtime.js.map +1 -1
  42. package/build/codegen/emit-errors.d.ts +17 -6
  43. package/build/codegen/emit-errors.integration.test.d.ts +1 -0
  44. package/build/codegen/emit-errors.integration.test.js +162 -0
  45. package/build/codegen/emit-errors.integration.test.js.map +1 -0
  46. package/build/codegen/emit-errors.js +50 -39
  47. package/build/codegen/emit-errors.js.map +1 -1
  48. package/build/codegen/emit-errors.test.js +75 -78
  49. package/build/codegen/emit-errors.test.js.map +1 -1
  50. package/build/codegen/emit-index.d.ts +7 -0
  51. package/build/codegen/emit-index.js +26 -4
  52. package/build/codegen/emit-index.js.map +1 -1
  53. package/build/codegen/emit-index.test.js +55 -23
  54. package/build/codegen/emit-index.test.js.map +1 -1
  55. package/build/codegen/emit-scope.d.ts +8 -0
  56. package/build/codegen/emit-scope.js +82 -7
  57. package/build/codegen/emit-scope.js.map +1 -1
  58. package/build/codegen/pipeline.js +22 -2
  59. package/build/codegen/pipeline.js.map +1 -1
  60. package/build/implementations/http/doc-registry.d.ts +21 -0
  61. package/build/implementations/http/doc-registry.js +51 -78
  62. package/build/implementations/http/doc-registry.js.map +1 -1
  63. package/build/implementations/http/doc-registry.test.js +8 -6
  64. package/build/implementations/http/doc-registry.test.js.map +1 -1
  65. package/build/implementations/http/error-taxonomy.d.ts +240 -0
  66. package/build/implementations/http/error-taxonomy.js +230 -0
  67. package/build/implementations/http/error-taxonomy.js.map +1 -0
  68. package/build/implementations/http/error-taxonomy.test.d.ts +1 -0
  69. package/build/implementations/http/error-taxonomy.test.js +399 -0
  70. package/build/implementations/http/error-taxonomy.test.js.map +1 -0
  71. package/build/implementations/http/express-rpc/error-taxonomy.test.d.ts +1 -0
  72. package/build/implementations/http/express-rpc/error-taxonomy.test.js +83 -0
  73. package/build/implementations/http/express-rpc/error-taxonomy.test.js.map +1 -0
  74. package/build/implementations/http/express-rpc/index.d.ts +39 -8
  75. package/build/implementations/http/express-rpc/index.js +39 -8
  76. package/build/implementations/http/express-rpc/index.js.map +1 -1
  77. package/build/implementations/http/hono-api/error-taxonomy.test.d.ts +1 -0
  78. package/build/implementations/http/hono-api/error-taxonomy.test.js +137 -0
  79. package/build/implementations/http/hono-api/error-taxonomy.test.js.map +1 -0
  80. package/build/implementations/http/hono-api/index.d.ts +38 -1
  81. package/build/implementations/http/hono-api/index.js +32 -0
  82. package/build/implementations/http/hono-api/index.js.map +1 -1
  83. package/build/implementations/http/hono-rpc/error-taxonomy.test.d.ts +1 -0
  84. package/build/implementations/http/hono-rpc/error-taxonomy.test.js +64 -0
  85. package/build/implementations/http/hono-rpc/error-taxonomy.test.js.map +1 -0
  86. package/build/implementations/http/hono-rpc/index.d.ts +34 -7
  87. package/build/implementations/http/hono-rpc/index.js +31 -4
  88. package/build/implementations/http/hono-rpc/index.js.map +1 -1
  89. package/build/implementations/http/hono-stream/error-taxonomy.test.d.ts +1 -0
  90. package/build/implementations/http/hono-stream/error-taxonomy.test.js +87 -0
  91. package/build/implementations/http/hono-stream/error-taxonomy.test.js.map +1 -0
  92. package/build/implementations/http/hono-stream/index.d.ts +40 -3
  93. package/build/implementations/http/hono-stream/index.js +37 -10
  94. package/build/implementations/http/hono-stream/index.js.map +1 -1
  95. package/build/implementations/http/hono-stream/index.test.js +45 -18
  96. package/build/implementations/http/hono-stream/index.test.js.map +1 -1
  97. package/build/implementations/http/on-request-error.test.d.ts +1 -0
  98. package/build/implementations/http/on-request-error.test.js +173 -0
  99. package/build/implementations/http/on-request-error.test.js.map +1 -0
  100. package/build/implementations/http/route-errors.test.d.ts +1 -0
  101. package/build/implementations/http/route-errors.test.js +140 -0
  102. package/build/implementations/http/route-errors.test.js.map +1 -0
  103. package/build/implementations/types.d.ts +30 -2
  104. package/docs/client-and-codegen.md +105 -12
  105. package/docs/core.md +14 -5
  106. package/docs/http-integrations.md +135 -4
  107. package/docs/streaming.md +3 -1
  108. package/package.json +7 -2
  109. package/src/client/call.ts +10 -1
  110. package/src/client/error-dispatch.test.ts +72 -0
  111. package/src/client/error-dispatch.ts +27 -0
  112. package/src/client/fetch-adapter.ts +11 -5
  113. package/src/client/index.ts +9 -0
  114. package/src/client/stream.ts +14 -3
  115. package/src/client/typed-error-dispatch.test.ts +211 -0
  116. package/src/client/types.ts +42 -0
  117. package/src/codegen/e2e.test.ts +9 -4
  118. package/src/codegen/emit-client-runtime.ts +4 -0
  119. package/src/codegen/emit-errors.integration.test.ts +183 -0
  120. package/src/codegen/emit-errors.test.ts +91 -87
  121. package/src/codegen/emit-errors.ts +123 -41
  122. package/src/codegen/emit-index.test.ts +68 -24
  123. package/src/codegen/emit-index.ts +66 -4
  124. package/src/codegen/emit-scope.ts +124 -7
  125. package/src/codegen/pipeline.ts +25 -2
  126. package/src/implementations/http/README.md +19 -4
  127. package/src/implementations/http/doc-registry.test.ts +10 -6
  128. package/src/implementations/http/doc-registry.ts +63 -80
  129. package/src/implementations/http/error-taxonomy.test.ts +438 -0
  130. package/src/implementations/http/error-taxonomy.ts +337 -0
  131. package/src/implementations/http/express-rpc/README.md +21 -22
  132. package/src/implementations/http/express-rpc/error-taxonomy.test.ts +103 -0
  133. package/src/implementations/http/express-rpc/index.ts +75 -14
  134. package/src/implementations/http/hono-api/README.md +284 -0
  135. package/src/implementations/http/hono-api/error-taxonomy.test.ts +179 -0
  136. package/src/implementations/http/hono-api/index.ts +76 -1
  137. package/src/implementations/http/hono-rpc/README.md +18 -19
  138. package/src/implementations/http/hono-rpc/error-taxonomy.test.ts +82 -0
  139. package/src/implementations/http/hono-rpc/index.ts +65 -9
  140. package/src/implementations/http/hono-stream/README.md +44 -25
  141. package/src/implementations/http/hono-stream/error-taxonomy.test.ts +98 -0
  142. package/src/implementations/http/hono-stream/index.test.ts +54 -18
  143. package/src/implementations/http/hono-stream/index.ts +83 -13
  144. package/src/implementations/http/on-request-error.test.ts +201 -0
  145. package/src/implementations/http/route-errors.test.ts +177 -0
  146. package/src/implementations/types.ts +30 -2
@@ -0,0 +1,399 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { ProcedureError, ProcedureValidationError, ProcedureYieldValidationError, } from '../../errors.js';
3
+ import { defineErrorTaxonomy, resolveErrorResponse, defaultErrorTaxonomy, } from './error-taxonomy.js';
4
+ class UseCaseError extends Error {
5
+ externalMsg;
6
+ internalMsg;
7
+ constructor(externalMsg, internalMsg) {
8
+ super(externalMsg);
9
+ this.externalMsg = externalMsg;
10
+ this.internalMsg = internalMsg;
11
+ this.name = 'UseCaseError';
12
+ Object.setPrototypeOf(this, UseCaseError.prototype);
13
+ }
14
+ }
15
+ class AuthError extends Error {
16
+ reason;
17
+ constructor(reason) {
18
+ super(reason);
19
+ this.reason = reason;
20
+ this.name = 'AuthError';
21
+ Object.setPrototypeOf(this, AuthError.prototype);
22
+ }
23
+ }
24
+ const fakeProcedure = { name: 'Test', config: {}, handler: async () => { } };
25
+ describe('defineErrorTaxonomy', () => {
26
+ test('validates exactly one discriminator per entry', () => {
27
+ expect(() => defineErrorTaxonomy({
28
+ Bad: { statusCode: 400 },
29
+ })).toThrow(/exactly one of/);
30
+ expect(() => defineErrorTaxonomy({
31
+ Bad: {
32
+ class: Error,
33
+ match: (e) => e instanceof Error,
34
+ statusCode: 400,
35
+ },
36
+ })).toThrow(/exactly one of/);
37
+ });
38
+ test('accepts a valid entry', () => {
39
+ const t = defineErrorTaxonomy({
40
+ UseCaseError: { class: UseCaseError, statusCode: 422 },
41
+ });
42
+ expect(t.UseCaseError.statusCode).toBe(422);
43
+ });
44
+ });
45
+ describe('resolveErrorResponse', () => {
46
+ test('class match uses toResponse output', () => {
47
+ const taxonomy = defineErrorTaxonomy({
48
+ UseCaseError: {
49
+ class: UseCaseError,
50
+ statusCode: 422,
51
+ toResponse: (err) => ({ name: 'UseCaseError', message: err.externalMsg }),
52
+ },
53
+ });
54
+ const resolved = resolveErrorResponse({
55
+ err: new UseCaseError('external', 'internal'),
56
+ userTaxonomy: taxonomy,
57
+ procedure: fakeProcedure,
58
+ raw: {},
59
+ });
60
+ expect(resolved?.statusCode).toBe(422);
61
+ expect(resolved?.body).toEqual({ name: 'UseCaseError', message: 'external' });
62
+ });
63
+ test('match predicate catches 3rd-party errors', () => {
64
+ const mongoLike = Object.assign(new Error('dup'), { name: 'MongoServerError', code: 11000 });
65
+ const taxonomy = defineErrorTaxonomy({
66
+ MongoDuplicateKey: {
67
+ match: (e) => e instanceof Error && e.code === 11000,
68
+ statusCode: 409,
69
+ toResponse: () => ({ name: 'Conflict' }),
70
+ },
71
+ });
72
+ const resolved = resolveErrorResponse({
73
+ err: mongoLike,
74
+ userTaxonomy: taxonomy,
75
+ procedure: fakeProcedure,
76
+ raw: {},
77
+ });
78
+ expect(resolved?.statusCode).toBe(409);
79
+ expect(resolved?.body).toEqual({ name: 'Conflict' });
80
+ });
81
+ test('default toResponse emits { name, message } from entry key', () => {
82
+ const taxonomy = defineErrorTaxonomy({
83
+ AuthError: { class: AuthError, statusCode: 401 },
84
+ });
85
+ const resolved = resolveErrorResponse({
86
+ err: new AuthError('unauthenticated'),
87
+ userTaxonomy: taxonomy,
88
+ procedure: fakeProcedure,
89
+ raw: {},
90
+ });
91
+ expect(resolved?.body).toEqual({ name: 'AuthError', message: 'unauthenticated' });
92
+ });
93
+ test('first matching entry wins — subclass declared before base', () => {
94
+ const taxonomy = defineErrorTaxonomy({
95
+ ProcedureValidationError: {
96
+ class: ProcedureValidationError,
97
+ statusCode: 400,
98
+ toResponse: () => ({ kind: 'validation' }),
99
+ },
100
+ ProcedureError: {
101
+ class: ProcedureError,
102
+ statusCode: 500,
103
+ toResponse: () => ({ kind: 'base' }),
104
+ },
105
+ });
106
+ const resolved = resolveErrorResponse({
107
+ err: new ProcedureValidationError('Test', 'bad', []),
108
+ userTaxonomy: taxonomy,
109
+ procedure: fakeProcedure,
110
+ includeDefaults: false,
111
+ raw: {},
112
+ });
113
+ expect(resolved?.statusCode).toBe(400);
114
+ expect(resolved?.body).toEqual({ name: 'ProcedureValidationError', kind: 'validation' });
115
+ });
116
+ test('topological sort fixes a subclass that was declared after its base', () => {
117
+ // Under first-match-wins without sorting, the base would catch the
118
+ // subclass. defineErrorTaxonomy reorders to keep the subclass entry first.
119
+ const taxonomy = defineErrorTaxonomy({
120
+ ProcedureError: {
121
+ class: ProcedureError,
122
+ statusCode: 500,
123
+ toResponse: () => ({ kind: 'base' }),
124
+ },
125
+ ProcedureValidationError: {
126
+ class: ProcedureValidationError,
127
+ statusCode: 400,
128
+ toResponse: () => ({ kind: 'validation' }),
129
+ },
130
+ });
131
+ const resolved = resolveErrorResponse({
132
+ err: new ProcedureValidationError('Test', 'bad', []),
133
+ userTaxonomy: taxonomy,
134
+ procedure: fakeProcedure,
135
+ includeDefaults: false,
136
+ raw: {},
137
+ });
138
+ expect(resolved?.statusCode).toBe(400);
139
+ expect(resolved?.body).toEqual({ name: 'ProcedureValidationError', kind: 'validation' });
140
+ });
141
+ test('falls through to unknownError when nothing matches', () => {
142
+ const resolved = resolveErrorResponse({
143
+ err: new Error('boom'),
144
+ userTaxonomy: defineErrorTaxonomy({
145
+ AuthError: { class: AuthError, statusCode: 401 },
146
+ }),
147
+ includeDefaults: false,
148
+ unknownError: {
149
+ statusCode: 500,
150
+ toResponse: () => ({ name: 'InternalServerError' }),
151
+ },
152
+ procedure: fakeProcedure,
153
+ raw: {},
154
+ });
155
+ expect(resolved?.statusCode).toBe(500);
156
+ expect(resolved?.body).toEqual({ name: 'InternalServerError' });
157
+ });
158
+ test('returns null when nothing matches and no unknownError', () => {
159
+ const resolved = resolveErrorResponse({
160
+ err: new Error('boom'),
161
+ userTaxonomy: defineErrorTaxonomy({
162
+ AuthError: { class: AuthError, statusCode: 401 },
163
+ }),
164
+ includeDefaults: false,
165
+ procedure: fakeProcedure,
166
+ raw: {},
167
+ });
168
+ expect(resolved).toBeNull();
169
+ });
170
+ test('default taxonomy catches ProcedureValidationError with status 400', () => {
171
+ const resolved = resolveErrorResponse({
172
+ err: new ProcedureValidationError('Test', 'bad', []),
173
+ procedure: fakeProcedure,
174
+ raw: {},
175
+ });
176
+ expect(resolved?.statusCode).toBe(400);
177
+ expect((resolved?.body).name).toBe('ProcedureValidationError');
178
+ });
179
+ test('default taxonomy catches ProcedureYieldValidationError with status 500', () => {
180
+ const resolved = resolveErrorResponse({
181
+ err: new ProcedureYieldValidationError('Test', 'bad yield', []),
182
+ procedure: fakeProcedure,
183
+ raw: {},
184
+ });
185
+ expect(resolved?.statusCode).toBe(500);
186
+ expect((resolved?.body).name).toBe('ProcedureYieldValidationError');
187
+ });
188
+ test('includeDefaults: false disables the default taxonomy', () => {
189
+ const resolved = resolveErrorResponse({
190
+ err: new ProcedureValidationError('Test', 'bad', []),
191
+ includeDefaults: false,
192
+ procedure: fakeProcedure,
193
+ raw: {},
194
+ });
195
+ expect(resolved).toBeNull();
196
+ });
197
+ test('user entry overrides default for the same class', () => {
198
+ const resolved = resolveErrorResponse({
199
+ err: new ProcedureValidationError('Test', 'bad', []),
200
+ userTaxonomy: defineErrorTaxonomy({
201
+ ProcedureValidationError: {
202
+ class: ProcedureValidationError,
203
+ statusCode: 418,
204
+ toResponse: () => ({ overridden: true }),
205
+ },
206
+ }),
207
+ procedure: fakeProcedure,
208
+ raw: {},
209
+ });
210
+ expect(resolved?.statusCode).toBe(418);
211
+ expect(resolved?.body).toEqual({ name: 'ProcedureValidationError', overridden: true });
212
+ });
213
+ test('onCatch is awaited via runOnCatch', async () => {
214
+ const calls = [];
215
+ const taxonomy = defineErrorTaxonomy({
216
+ UseCaseError: {
217
+ class: UseCaseError,
218
+ statusCode: 422,
219
+ onCatch: async (err) => {
220
+ await new Promise((r) => setTimeout(r, 5));
221
+ calls.push(err.internalMsg);
222
+ },
223
+ },
224
+ });
225
+ const resolved = resolveErrorResponse({
226
+ err: new UseCaseError('ext', 'internal-log'),
227
+ userTaxonomy: taxonomy,
228
+ procedure: fakeProcedure,
229
+ raw: {},
230
+ });
231
+ expect(calls).toEqual([]);
232
+ await resolved.runOnCatch();
233
+ expect(calls).toEqual(['internal-log']);
234
+ });
235
+ test('unknownError onCatch is awaited', async () => {
236
+ const calls = [];
237
+ const resolved = resolveErrorResponse({
238
+ err: new Error('boom'),
239
+ includeDefaults: false,
240
+ unknownError: {
241
+ toResponse: () => ({}),
242
+ onCatch: async (err) => {
243
+ await Promise.resolve();
244
+ calls.push(err);
245
+ },
246
+ },
247
+ procedure: fakeProcedure,
248
+ raw: {},
249
+ });
250
+ await resolved.runOnCatch();
251
+ expect(calls).toHaveLength(1);
252
+ expect(calls[0].message).toBe('boom');
253
+ });
254
+ test('onCatch receives procedure, key and raw', async () => {
255
+ let received;
256
+ const taxonomy = defineErrorTaxonomy({
257
+ AuthError: {
258
+ class: AuthError,
259
+ statusCode: 401,
260
+ onCatch: (_err, ctx) => {
261
+ received = ctx;
262
+ },
263
+ },
264
+ });
265
+ const resolved = resolveErrorResponse({
266
+ err: new AuthError('forbidden'),
267
+ userTaxonomy: taxonomy,
268
+ procedure: fakeProcedure,
269
+ raw: { marker: 'hono-context' },
270
+ });
271
+ await resolved.runOnCatch();
272
+ expect(received.procedure).toBe(fakeProcedure);
273
+ expect(received.key).toBe('AuthError');
274
+ expect(received.raw).toEqual({ marker: 'hono-context' });
275
+ });
276
+ test('defaultErrorTaxonomy exposes all four framework error mappings', () => {
277
+ expect(defaultErrorTaxonomy.ProcedureValidationError.statusCode).toBe(400);
278
+ expect(defaultErrorTaxonomy.ProcedureYieldValidationError.statusCode).toBe(500);
279
+ expect(defaultErrorTaxonomy.ProcedureError.statusCode).toBe(500);
280
+ });
281
+ test('user taxonomy matches cause inside a ProcedureError wrapper', () => {
282
+ // Simulates what the core does when a non-ProcedureError is thrown: wraps
283
+ // into ProcedureError with `cause` preserved.
284
+ const original = new UseCaseError('public', 'private');
285
+ const wrapped = new ProcedureError('Test', 'wrapped');
286
+ wrapped.cause = original;
287
+ const taxonomy = defineErrorTaxonomy({
288
+ UseCaseError: {
289
+ class: UseCaseError,
290
+ statusCode: 422,
291
+ toResponse: (err) => ({ name: 'UseCaseError', message: err.externalMsg }),
292
+ },
293
+ });
294
+ const resolved = resolveErrorResponse({
295
+ err: wrapped,
296
+ userTaxonomy: taxonomy,
297
+ procedure: fakeProcedure,
298
+ raw: {},
299
+ });
300
+ expect(resolved?.statusCode).toBe(422);
301
+ expect(resolved?.body).toEqual({ name: 'UseCaseError', message: 'public' });
302
+ });
303
+ test('wrapped ProcedureError falls through default taxonomy to unknownError', () => {
304
+ const original = new TypeError('db-broke');
305
+ const wrapped = new ProcedureError('Test', 'wrapped');
306
+ wrapped.cause = original;
307
+ const resolved = resolveErrorResponse({
308
+ err: wrapped,
309
+ unknownError: {
310
+ statusCode: 500,
311
+ toResponse: (err) => ({ name: 'InternalServerError', type: err.constructor.name }),
312
+ },
313
+ procedure: fakeProcedure,
314
+ raw: {},
315
+ });
316
+ expect(resolved?.statusCode).toBe(500);
317
+ expect(resolved?.body).toEqual({ name: 'InternalServerError', type: 'TypeError' });
318
+ });
319
+ test('direct ProcedureError (no cause) still caught by default entry', () => {
320
+ const direct = new ProcedureError('Test', 'from ctx.error');
321
+ const resolved = resolveErrorResponse({
322
+ err: direct,
323
+ procedure: fakeProcedure,
324
+ raw: {},
325
+ });
326
+ expect(resolved?.statusCode).toBe(500);
327
+ expect((resolved?.body).name).toBe('ProcedureError');
328
+ });
329
+ test('auto-injects name when toResponse omits it', () => {
330
+ const taxonomy = defineErrorTaxonomy({
331
+ UseCaseError: {
332
+ class: UseCaseError,
333
+ statusCode: 422,
334
+ // Returns a body without `name` — resolver should add one.
335
+ toResponse: (err) => ({ message: err.externalMsg, detail: err.internalMsg }),
336
+ },
337
+ });
338
+ const resolved = resolveErrorResponse({
339
+ err: new UseCaseError('ext', 'int'),
340
+ userTaxonomy: taxonomy,
341
+ procedure: fakeProcedure,
342
+ raw: {},
343
+ });
344
+ expect(resolved?.body).toEqual({ name: 'UseCaseError', message: 'ext', detail: 'int' });
345
+ });
346
+ test('preserves explicit name in toResponse output', () => {
347
+ const taxonomy = defineErrorTaxonomy({
348
+ UseCaseError: {
349
+ class: UseCaseError,
350
+ statusCode: 422,
351
+ toResponse: () => ({ name: 'CustomAlias', reason: 'x' }),
352
+ },
353
+ });
354
+ const resolved = resolveErrorResponse({
355
+ err: new UseCaseError('ext', 'int'),
356
+ userTaxonomy: taxonomy,
357
+ procedure: fakeProcedure,
358
+ raw: {},
359
+ });
360
+ expect((resolved?.body).name).toBe('CustomAlias');
361
+ });
362
+ test('defineErrorTaxonomy topologically sorts class entries (subclass before base)', () => {
363
+ // Declared base-before-subclass on purpose — sort must swap them.
364
+ const taxonomy = defineErrorTaxonomy({
365
+ ProcedureError: {
366
+ class: ProcedureError,
367
+ statusCode: 500,
368
+ toResponse: () => ({ kind: 'base' }),
369
+ },
370
+ ProcedureValidationError: {
371
+ class: ProcedureValidationError,
372
+ statusCode: 400,
373
+ toResponse: () => ({ kind: 'validation' }),
374
+ },
375
+ });
376
+ const keys = Object.keys(taxonomy);
377
+ expect(keys).toEqual(['ProcedureValidationError', 'ProcedureError']);
378
+ const resolved = resolveErrorResponse({
379
+ err: new ProcedureValidationError('Test', 'bad', []),
380
+ userTaxonomy: taxonomy,
381
+ includeDefaults: false,
382
+ procedure: fakeProcedure,
383
+ raw: {},
384
+ });
385
+ expect(resolved?.statusCode).toBe(400);
386
+ });
387
+ test('topological sort preserves declared order for unrelated classes', () => {
388
+ class A extends Error {
389
+ }
390
+ class B extends Error {
391
+ }
392
+ const taxonomy = defineErrorTaxonomy({
393
+ B: { class: B, statusCode: 400 },
394
+ A: { class: A, statusCode: 400 },
395
+ });
396
+ expect(Object.keys(taxonomy)).toEqual(['B', 'A']);
397
+ });
398
+ });
399
+ //# sourceMappingURL=error-taxonomy.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-taxonomy.test.js","sourceRoot":"","sources":["../../../src/implementations/http/error-taxonomy.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ import { describe, expect, test, vi } from 'vitest';
2
+ import request from 'supertest';
3
+ import { Type } from 'typebox';
4
+ import { Procedures } from '../../../index.js';
5
+ import { ExpressRPCAppBuilder, defineErrorTaxonomy } from './index.js';
6
+ class UseCaseError extends Error {
7
+ externalMsg;
8
+ internalMsg;
9
+ constructor(externalMsg, internalMsg) {
10
+ super(externalMsg);
11
+ this.externalMsg = externalMsg;
12
+ this.internalMsg = internalMsg;
13
+ this.name = 'UseCaseError';
14
+ Object.setPrototypeOf(this, UseCaseError.prototype);
15
+ }
16
+ }
17
+ describe('ExpressRPCAppBuilder — error taxonomy', () => {
18
+ test('taxonomy catches user error thrown from RPC handler', async () => {
19
+ const errors = defineErrorTaxonomy({
20
+ UseCaseError: {
21
+ class: UseCaseError,
22
+ statusCode: 422,
23
+ toResponse: (err) => ({ name: 'UseCaseError', message: err.externalMsg }),
24
+ },
25
+ });
26
+ const RPC = Procedures();
27
+ RPC.Create('Boom', { scope: 'test', version: 1, schema: { params: Type.Object({}) } }, async () => {
28
+ throw new UseCaseError('ext', 'int');
29
+ });
30
+ const app = new ExpressRPCAppBuilder({ errors }).register(RPC, () => ({})).build();
31
+ const res = await request(app).post('/test/boom/1').send({});
32
+ expect(res.status).toBe(422);
33
+ expect(res.body).toEqual({ name: 'UseCaseError', message: 'ext' });
34
+ });
35
+ test('unknownError catches unmapped errors', async () => {
36
+ const RPC = Procedures();
37
+ RPC.Create('Boom', { scope: 'test', version: 1, schema: { params: Type.Object({}) } }, async () => {
38
+ throw new TypeError('db down');
39
+ });
40
+ const app = new ExpressRPCAppBuilder({
41
+ unknownError: { statusCode: 503, toResponse: () => ({ name: 'ServiceUnavailable' }) },
42
+ })
43
+ .register(RPC, () => ({}))
44
+ .build();
45
+ const res = await request(app).post('/test/boom/1').send({});
46
+ expect(res.status).toBe(503);
47
+ expect(res.body).toEqual({ name: 'ServiceUnavailable' });
48
+ });
49
+ test('onCatch receives { req, res } as raw context', async () => {
50
+ let rawSeen;
51
+ const errors = defineErrorTaxonomy({
52
+ UseCaseError: {
53
+ class: UseCaseError,
54
+ statusCode: 422,
55
+ onCatch: (_err, ctx) => {
56
+ rawSeen = ctx.raw;
57
+ },
58
+ },
59
+ });
60
+ const RPC = Procedures();
61
+ RPC.Create('Boom', { scope: 'test', version: 1, schema: { params: Type.Object({}) } }, async () => {
62
+ throw new UseCaseError('ext', 'int');
63
+ });
64
+ const app = new ExpressRPCAppBuilder({ errors }).register(RPC, () => ({})).build();
65
+ await request(app).post('/test/boom/1').send({});
66
+ expect(rawSeen.req).toBeDefined();
67
+ expect(rawSeen.res).toBeDefined();
68
+ });
69
+ test('onError callback handles errors not matched by the taxonomy', async () => {
70
+ const onError = vi.fn((_p, _req, res) => {
71
+ res.status(418).json({ legacy: true });
72
+ });
73
+ const RPC = Procedures();
74
+ RPC.Create('Boom', { scope: 'test', version: 1, schema: { params: Type.Object({}) } }, async () => {
75
+ throw new TypeError('legacy');
76
+ });
77
+ const app = new ExpressRPCAppBuilder({ onError }).register(RPC, () => ({})).build();
78
+ const res = await request(app).post('/test/boom/1').send({});
79
+ expect(res.status).toBe(418);
80
+ expect(onError).toHaveBeenCalledOnce();
81
+ });
82
+ });
83
+ //# sourceMappingURL=error-taxonomy.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-taxonomy.test.js","sourceRoot":"","sources":["../../../../src/implementations/http/express-rpc/error-taxonomy.test.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,10 @@
1
1
  import express from 'express';
2
2
  import { TProcedureRegistration } from '../../../index.js';
3
3
  import { ExtractConfig, ExtractContext, ProceduresFactory, RPCConfig, RPCHttpRouteDoc } from '../../types.js';
4
+ import { ErrorTaxonomy, ErrorTaxonomyEntry, UnknownErrorConfig, defineErrorTaxonomy } from '../error-taxonomy.js';
4
5
  export type { RPCConfig, RPCHttpRouteDoc };
6
+ export { defineErrorTaxonomy };
7
+ export type { ErrorTaxonomy, ErrorTaxonomyEntry, UnknownErrorConfig };
5
8
  export type ExpressRPCAppBuilderConfig = {
6
9
  /**
7
10
  * An existing Express application instance to use.
@@ -15,13 +18,39 @@ export type ExpressRPCAppBuilderConfig = {
15
18
  onRequestEnd?: (req: express.Request, res: express.Response) => void;
16
19
  onSuccess?: (procedure: TProcedureRegistration, req: express.Request, res: express.Response) => void;
17
20
  /**
18
- * Error handler called when a procedure throws an error.
19
- * @param procedure
20
- * @param req
21
- * @param res
22
- * @param error
21
+ * Declarative error-to-response mapping (one of the two peer error modes).
22
+ * See hono-api for the full taxonomy contract. The `raw` field passed to
23
+ * taxonomy callbacks is `{ req, res }`.
24
+ */
25
+ errors?: ErrorTaxonomy;
26
+ /** Fallback serializer for errors not matched by the taxonomy. */
27
+ unknownError?: UnknownErrorConfig;
28
+ /**
29
+ * Imperative error callback — the other peer error mode. Receives every
30
+ * error directly and writes the response via `res`. Use this when you want
31
+ * full control, or alongside `errors` for the tail of errors the taxonomy
32
+ * doesn't cover.
23
33
  */
24
34
  onError?: (procedure: TProcedureRegistration, req: express.Request, res: express.Response, error: Error) => void;
35
+ /**
36
+ * Cross-cutting observer — fires for every caught error, BEFORE dispatch.
37
+ * Awaited. Cannot write to `res` (observer only — check `res.headersSent`
38
+ * if you must touch it). Thrown errors inside the observer are swallowed
39
+ * and logged.
40
+ */
41
+ onRequestError?: (ctx: OnRequestErrorContext) => void | Promise<void>;
42
+ };
43
+ /**
44
+ * Context passed to the `onRequestError` observer. `raw` is `{ req, res }`
45
+ * for the in-flight request.
46
+ */
47
+ export type OnRequestErrorContext = {
48
+ err: unknown;
49
+ procedure: TProcedureRegistration;
50
+ raw: {
51
+ req: express.Request;
52
+ res: express.Response;
53
+ };
25
54
  };
26
55
  /**
27
56
  * Builder class for creating an Express application with RPC routes.
@@ -48,11 +77,13 @@ export declare class ExpressRPCAppBuilder {
48
77
  constructor(config?: ExpressRPCAppBuilderConfig | undefined);
49
78
  /**
50
79
  * Generates the RPC route path based on the RPC configuration.
51
- * The RPCConfig name can be a string or an array of strings to form nested paths.
80
+ * `RPCConfig.scope` can be a string or an array of strings to form nested paths.
52
81
  *
53
82
  * Example
54
- * name: ['string', 'string-string', 'string']
55
- * path: /string/string-string/string/version
83
+ * name: 'GetUser'
84
+ * scope: ['users', 'profile']
85
+ * version: 1
86
+ * path: /users/profile/get-user/1
56
87
  * @param config
57
88
  */
58
89
  static makeRPCHttpRoutePath({ name, config, prefix, }: {