ts-procedures 8.2.1 → 8.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent_config/claude-code/skills/ts-procedures/SKILL.md +31 -9
- package/agent_config/claude-code/skills/ts-procedures/api-reference.md +3 -1
- package/agent_config/claude-code/skills/ts-procedures/patterns.md +30 -6
- package/agent_config/claude-code/skills/ts-procedures/templates/client.md +3 -3
- package/agent_config/claude-code/skills/ts-procedures/templates/hono.md +3 -3
- package/agent_config/claude-code/skills/ts-procedures/templates/procedure.md +3 -3
- package/agent_config/claude-code/skills/ts-procedures/templates/stream-procedure.md +3 -3
- package/agent_config/copilot/copilot-instructions.md +10 -6
- package/agent_config/cursor/cursorrules +10 -6
- package/build/client/call.js +1 -1
- package/build/client/call.js.map +1 -1
- package/build/client/index.d.ts +1 -1
- package/build/client/index.js +23 -1
- package/build/client/index.js.map +1 -1
- package/build/client/index.test.js +87 -0
- package/build/client/index.test.js.map +1 -1
- package/build/client/resolve-options.d.ts +5 -4
- package/build/client/resolve-options.js +18 -7
- package/build/client/resolve-options.js.map +1 -1
- package/build/client/resolve-options.test.js +53 -24
- package/build/client/resolve-options.test.js.map +1 -1
- package/build/client/stream.js +1 -1
- package/build/client/stream.js.map +1 -1
- package/build/client/types.d.ts +31 -3
- package/build/codegen/__fixtures__/make-envelope.d.ts +41 -0
- package/build/codegen/__fixtures__/make-envelope.js +38 -0
- package/build/codegen/__fixtures__/make-envelope.js.map +1 -0
- package/build/codegen/bin/cli.d.ts +11 -0
- package/build/codegen/bin/cli.js +30 -21
- package/build/codegen/bin/cli.js.map +1 -1
- package/build/codegen/bin/cli.test.js +36 -1
- package/build/codegen/bin/cli.test.js.map +1 -1
- package/build/codegen/bin/flag-specs.d.ts +10 -0
- package/build/codegen/bin/flag-specs.js +60 -0
- package/build/codegen/bin/flag-specs.js.map +1 -0
- package/build/codegen/bin/flag-specs.test.d.ts +1 -0
- package/build/codegen/bin/flag-specs.test.js +26 -0
- package/build/codegen/bin/flag-specs.test.js.map +1 -0
- package/build/codegen/collect-models.d.ts +37 -0
- package/build/codegen/collect-models.js +74 -0
- package/build/codegen/collect-models.js.map +1 -0
- package/build/codegen/collect-models.test.d.ts +1 -0
- package/build/codegen/collect-models.test.js +40 -0
- package/build/codegen/collect-models.test.js.map +1 -0
- package/build/codegen/emit-client-runtime.js +1 -0
- package/build/codegen/emit-client-runtime.js.map +1 -1
- package/build/codegen/emit-errors.integration.test.js +22 -0
- package/build/codegen/emit-errors.integration.test.js.map +1 -1
- package/build/codegen/emit-models.d.ts +26 -0
- package/build/codegen/emit-models.js +53 -0
- package/build/codegen/emit-models.js.map +1 -0
- package/build/codegen/emit-models.test.d.ts +1 -0
- package/build/codegen/emit-models.test.js +42 -0
- package/build/codegen/emit-models.test.js.map +1 -0
- package/build/codegen/emit-scope.d.ts +10 -0
- package/build/codegen/emit-scope.js +119 -34
- package/build/codegen/emit-scope.js.map +1 -1
- package/build/codegen/emit-types.d.ts +26 -1
- package/build/codegen/emit-types.js +27 -5
- package/build/codegen/emit-types.js.map +1 -1
- package/build/codegen/index.d.ts +5 -0
- package/build/codegen/index.js +2 -0
- package/build/codegen/index.js.map +1 -1
- package/build/codegen/model-refs.d.ts +27 -0
- package/build/codegen/model-refs.js +49 -0
- package/build/codegen/model-refs.js.map +1 -0
- package/build/codegen/model-refs.test.d.ts +1 -0
- package/build/codegen/model-refs.test.js +33 -0
- package/build/codegen/model-refs.test.js.map +1 -0
- package/build/codegen/pipeline.d.ts +3 -0
- package/build/codegen/pipeline.js +3 -1
- package/build/codegen/pipeline.js.map +1 -1
- package/build/codegen/schema-walk.d.ts +13 -0
- package/build/codegen/schema-walk.js +26 -0
- package/build/codegen/schema-walk.js.map +1 -0
- package/build/codegen/schema-walk.test.d.ts +1 -0
- package/build/codegen/schema-walk.test.js +35 -0
- package/build/codegen/schema-walk.test.js.map +1 -0
- package/build/codegen/targets/_shared/target-run.d.ts +5 -0
- package/build/codegen/targets/ts/run.js +28 -1
- package/build/codegen/targets/ts/run.js.map +1 -1
- package/build/codegen/targets/ts/shared-models.test.d.ts +1 -0
- package/build/codegen/targets/ts/shared-models.test.js +258 -0
- package/build/codegen/targets/ts/shared-models.test.js.map +1 -0
- package/build/doc-envelope.d.ts +13 -0
- package/build/doc-envelope.js +23 -0
- package/build/doc-envelope.js.map +1 -0
- package/build/doc-envelope.test.d.ts +1 -0
- package/build/doc-envelope.test.js +31 -0
- package/build/doc-envelope.test.js.map +1 -0
- package/build/exports.d.ts +2 -0
- package/build/exports.js +1 -0
- package/build/exports.js.map +1 -1
- package/build/implementations/http/error-taxonomy.d.ts +40 -0
- package/build/implementations/http/error-taxonomy.js +57 -5
- package/build/implementations/http/error-taxonomy.js.map +1 -1
- package/build/implementations/http/error-taxonomy.test.js +95 -1
- package/build/implementations/http/error-taxonomy.test.js.map +1 -1
- package/build/implementations/http/hono/handlers/http.js +19 -24
- package/build/implementations/http/hono/handlers/http.js.map +1 -1
- package/build/implementations/http/hono/handlers/http.test.js +64 -1
- package/build/implementations/http/hono/handlers/http.test.js.map +1 -1
- package/docs/client-and-codegen.md +109 -0
- package/docs/core.md +2 -0
- package/docs/handoffs/ajsc-named-type-collision.md +134 -0
- package/docs/handoffs/ajsc-named-type-support.md +181 -0
- package/docs/http-integrations.md +4 -0
- package/docs/superpowers/plans/2026-06-05-dx-feedback-round.md +1292 -0
- package/docs/superpowers/specs/2026-06-05-dx-feedback-round-design.md +285 -0
- package/package.json +2 -2
- package/src/client/call.ts +1 -1
- package/src/client/index.test.ts +98 -0
- package/src/client/index.ts +32 -1
- package/src/client/resolve-options.test.ts +73 -26
- package/src/client/resolve-options.ts +23 -9
- package/src/client/stream.ts +1 -1
- package/src/client/types.ts +34 -3
- package/src/codegen/__fixtures__/make-envelope.ts +89 -0
- package/src/codegen/bin/cli.test.ts +38 -1
- package/src/codegen/bin/cli.ts +33 -22
- package/src/codegen/bin/flag-specs.test.ts +27 -0
- package/src/codegen/bin/flag-specs.ts +69 -0
- package/src/codegen/collect-models.test.ts +46 -0
- package/src/codegen/collect-models.ts +108 -0
- package/src/codegen/emit-client-runtime.ts +1 -0
- package/src/codegen/emit-errors.integration.test.ts +26 -0
- package/src/codegen/emit-models.test.ts +48 -0
- package/src/codegen/emit-models.ts +63 -0
- package/src/codegen/emit-scope.ts +145 -33
- package/src/codegen/emit-types.ts +48 -7
- package/src/codegen/index.ts +7 -0
- package/src/codegen/model-refs.test.ts +37 -0
- package/src/codegen/model-refs.ts +57 -0
- package/src/codegen/pipeline.ts +6 -1
- package/src/codegen/schema-walk.test.ts +37 -0
- package/src/codegen/schema-walk.ts +23 -0
- package/src/codegen/targets/_shared/target-run.ts +5 -0
- package/src/codegen/targets/ts/run.ts +33 -0
- package/src/codegen/targets/ts/shared-models.test.ts +283 -0
- package/src/doc-envelope.test.ts +35 -0
- package/src/doc-envelope.ts +30 -0
- package/src/exports.ts +2 -0
- package/src/implementations/http/error-taxonomy.test.ts +111 -0
- package/src/implementations/http/error-taxonomy.ts +60 -5
- package/src/implementations/http/hono/handlers/http.test.ts +69 -1
- package/src/implementations/http/hono/handlers/http.ts +19 -21
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { runPipeline } from '../../pipeline.js';
|
|
3
|
+
import { makeApiRoute, makeEnvelope } from '../../__fixtures__/make-envelope.js';
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Fixtures
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
/** A reusable `$id`-bearing schema — appears verbatim in multiple routes/scopes. */
|
|
8
|
+
const messageModel = {
|
|
9
|
+
$id: 'urn:msg',
|
|
10
|
+
title: 'Message',
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
id: { type: 'string' },
|
|
14
|
+
body: { type: 'string' },
|
|
15
|
+
},
|
|
16
|
+
required: ['id', 'body'],
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* The same `urn:msg` model appears as a nested property in two scopes
|
|
20
|
+
* (`messages`, `threads`), and once as the WHOLE response body of a route
|
|
21
|
+
* (top-level `$id`).
|
|
22
|
+
*/
|
|
23
|
+
function modelEnvelope() {
|
|
24
|
+
return makeEnvelope([
|
|
25
|
+
makeApiRoute({
|
|
26
|
+
name: 'GetMessage',
|
|
27
|
+
scope: 'messages',
|
|
28
|
+
fullPath: '/messages/:id',
|
|
29
|
+
jsonSchema: {
|
|
30
|
+
req: {
|
|
31
|
+
pathParams: {
|
|
32
|
+
type: 'object',
|
|
33
|
+
properties: { id: { type: 'string' } },
|
|
34
|
+
required: ['id'],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
// Whole response body IS the model (top-level $id) → `Response = Message`.
|
|
38
|
+
res: { body: { ...messageModel } },
|
|
39
|
+
},
|
|
40
|
+
}),
|
|
41
|
+
makeApiRoute({
|
|
42
|
+
name: 'ListMessages',
|
|
43
|
+
scope: 'messages',
|
|
44
|
+
fullPath: '/messages',
|
|
45
|
+
jsonSchema: {
|
|
46
|
+
res: {
|
|
47
|
+
body: {
|
|
48
|
+
type: 'object',
|
|
49
|
+
properties: {
|
|
50
|
+
// Model nested inside an array → referenced, not inlined.
|
|
51
|
+
items: { type: 'array', items: { ...messageModel } },
|
|
52
|
+
},
|
|
53
|
+
required: ['items'],
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
}),
|
|
58
|
+
makeApiRoute({
|
|
59
|
+
name: 'GetThread',
|
|
60
|
+
scope: 'threads',
|
|
61
|
+
fullPath: '/threads/:id',
|
|
62
|
+
jsonSchema: {
|
|
63
|
+
res: {
|
|
64
|
+
body: {
|
|
65
|
+
type: 'object',
|
|
66
|
+
properties: {
|
|
67
|
+
// Same model in a SECOND scope.
|
|
68
|
+
latest: { ...messageModel },
|
|
69
|
+
},
|
|
70
|
+
required: ['latest'],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
}),
|
|
75
|
+
]);
|
|
76
|
+
}
|
|
77
|
+
/** Identical to {@link modelEnvelope} but with every `$id`/`title` stripped. */
|
|
78
|
+
function noModelEnvelope() {
|
|
79
|
+
return makeEnvelope([
|
|
80
|
+
makeApiRoute({
|
|
81
|
+
name: 'GetMessage',
|
|
82
|
+
scope: 'messages',
|
|
83
|
+
fullPath: '/messages/:id',
|
|
84
|
+
jsonSchema: {
|
|
85
|
+
req: {
|
|
86
|
+
pathParams: {
|
|
87
|
+
type: 'object',
|
|
88
|
+
properties: { id: { type: 'string' } },
|
|
89
|
+
required: ['id'],
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
res: {
|
|
93
|
+
body: {
|
|
94
|
+
type: 'object',
|
|
95
|
+
properties: { id: { type: 'string' }, body: { type: 'string' } },
|
|
96
|
+
required: ['id', 'body'],
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
}),
|
|
101
|
+
]);
|
|
102
|
+
}
|
|
103
|
+
function findFile(files, suffix) {
|
|
104
|
+
return files.find((f) => f.path.endsWith(suffix));
|
|
105
|
+
}
|
|
106
|
+
const countMatches = (haystack, re) => (haystack.match(re) ?? []).length;
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// Tests
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
describe('shared models (TS target, ajsc x-named-type)', () => {
|
|
111
|
+
it('shareModels:true emits _models.ts with exactly one Message type; scopes reference it', async () => {
|
|
112
|
+
const files = await runPipeline({
|
|
113
|
+
envelope: modelEnvelope(),
|
|
114
|
+
outDir: 'out',
|
|
115
|
+
dryRun: true,
|
|
116
|
+
shareModels: true,
|
|
117
|
+
namespaceTypes: true,
|
|
118
|
+
selfContained: false,
|
|
119
|
+
});
|
|
120
|
+
const modelsFile = findFile(files, '_models.ts');
|
|
121
|
+
expect(modelsFile).toBeDefined();
|
|
122
|
+
expect(countMatches(modelsFile.code, /export type Message =/g)).toBe(1);
|
|
123
|
+
// Both scopes that use the model import it and reference Message by name.
|
|
124
|
+
const messages = findFile(files, 'messages.ts');
|
|
125
|
+
const threads = findFile(files, 'threads.ts');
|
|
126
|
+
for (const scope of [messages, threads]) {
|
|
127
|
+
expect(scope.code).toContain("from './_models'");
|
|
128
|
+
expect(scope.code).toContain('Message');
|
|
129
|
+
// No inlined model object literal — the body shape lives only in _models.ts.
|
|
130
|
+
expect(scope.code).not.toMatch(/body:\s*string;[^}]*}\s*\)/);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
it('a route whose entire response IS the model emits `Response = Message`', async () => {
|
|
134
|
+
const files = await runPipeline({
|
|
135
|
+
envelope: modelEnvelope(),
|
|
136
|
+
outDir: 'out',
|
|
137
|
+
dryRun: true,
|
|
138
|
+
shareModels: true,
|
|
139
|
+
namespaceTypes: true,
|
|
140
|
+
});
|
|
141
|
+
const messages = findFile(files, 'messages.ts');
|
|
142
|
+
// GetMessage's response body IS the whole model (top-level $id) → the Body
|
|
143
|
+
// alias is exactly `Message`, the single token replacing the whole schema.
|
|
144
|
+
expect(messages.code).toMatch(/export type Body = Message\b/);
|
|
145
|
+
// And the nested-array case references the same model inside Array<…>.
|
|
146
|
+
expect(messages.code).toMatch(/Array<Message>/);
|
|
147
|
+
});
|
|
148
|
+
it('shareModels:false emits NO _models.ts and inlines as before', async () => {
|
|
149
|
+
const files = await runPipeline({
|
|
150
|
+
envelope: modelEnvelope(),
|
|
151
|
+
outDir: 'out',
|
|
152
|
+
dryRun: true,
|
|
153
|
+
shareModels: false,
|
|
154
|
+
namespaceTypes: true,
|
|
155
|
+
});
|
|
156
|
+
expect(findFile(files, '_models.ts')).toBeUndefined();
|
|
157
|
+
const messages = findFile(files, 'messages.ts');
|
|
158
|
+
// The model is inlined: its properties appear in the scope, no _models import.
|
|
159
|
+
expect(messages.code).not.toContain("from './_models'");
|
|
160
|
+
expect(messages.code).toContain('body');
|
|
161
|
+
});
|
|
162
|
+
it('regression: a no-$id envelope produces byte-identical scope output for true vs false', async () => {
|
|
163
|
+
const on = await runPipeline({
|
|
164
|
+
envelope: noModelEnvelope(),
|
|
165
|
+
outDir: 'out',
|
|
166
|
+
dryRun: true,
|
|
167
|
+
shareModels: true,
|
|
168
|
+
namespaceTypes: true,
|
|
169
|
+
});
|
|
170
|
+
const off = await runPipeline({
|
|
171
|
+
envelope: noModelEnvelope(),
|
|
172
|
+
outDir: 'out',
|
|
173
|
+
dryRun: true,
|
|
174
|
+
shareModels: false,
|
|
175
|
+
namespaceTypes: true,
|
|
176
|
+
});
|
|
177
|
+
// No models exist, so neither run emits _models.ts.
|
|
178
|
+
expect(findFile(on, '_models.ts')).toBeUndefined();
|
|
179
|
+
expect(findFile(off, '_models.ts')).toBeUndefined();
|
|
180
|
+
const onScope = findFile(on, 'messages.ts');
|
|
181
|
+
const offScope = findFile(off, 'messages.ts');
|
|
182
|
+
expect(onScope.code).toBe(offScope.code);
|
|
183
|
+
});
|
|
184
|
+
it('sharedTypesImport re-exports the model and skips generating its body', async () => {
|
|
185
|
+
const files = await runPipeline({
|
|
186
|
+
envelope: modelEnvelope(),
|
|
187
|
+
outDir: 'out',
|
|
188
|
+
dryRun: true,
|
|
189
|
+
shareModels: true,
|
|
190
|
+
namespaceTypes: true,
|
|
191
|
+
sharedTypesImport: { 'urn:msg': { module: '@shared/schemas', name: 'Message' } },
|
|
192
|
+
});
|
|
193
|
+
const modelsFile = findFile(files, '_models.ts');
|
|
194
|
+
expect(modelsFile.code).toContain("export { Message } from '@shared/schemas'");
|
|
195
|
+
// No generated body when the model is externally imported.
|
|
196
|
+
expect(modelsFile.code).not.toMatch(/export type Message =/);
|
|
197
|
+
// Scopes still import Message from ./_models (the single hub).
|
|
198
|
+
const messages = findFile(files, 'messages.ts');
|
|
199
|
+
expect(messages.code).toContain("from './_models'");
|
|
200
|
+
expect(messages.code).toContain('Message');
|
|
201
|
+
});
|
|
202
|
+
it('flat mode (namespaceTypes:false) imports Message from ./_models', async () => {
|
|
203
|
+
const files = await runPipeline({
|
|
204
|
+
envelope: modelEnvelope(),
|
|
205
|
+
outDir: 'out',
|
|
206
|
+
dryRun: true,
|
|
207
|
+
shareModels: true,
|
|
208
|
+
namespaceTypes: false,
|
|
209
|
+
});
|
|
210
|
+
const messages = findFile(files, 'messages.ts');
|
|
211
|
+
expect(messages.code).toContain("import type { Message } from './_models'");
|
|
212
|
+
});
|
|
213
|
+
it('fails loudly when a shared model name collides with a property-derived sub-type', async () => {
|
|
214
|
+
// `latest` references the Message model; a sibling property literally named
|
|
215
|
+
// `message` would make ajsc extract a structural sub-type ALSO named
|
|
216
|
+
// `Message`. ajsc silently merges the two (the model reference resolves to
|
|
217
|
+
// the unrelated structural type), so codegen must reject this rather than
|
|
218
|
+
// emit a silently-wrong type. See assertNoModelNameCollision in emit-scope.
|
|
219
|
+
const collision = makeEnvelope([
|
|
220
|
+
makeApiRoute({
|
|
221
|
+
name: 'GetThread',
|
|
222
|
+
scope: 'chat',
|
|
223
|
+
fullPath: '/thread',
|
|
224
|
+
jsonSchema: {
|
|
225
|
+
res: {
|
|
226
|
+
body: {
|
|
227
|
+
type: 'object',
|
|
228
|
+
required: ['latest', 'message'],
|
|
229
|
+
properties: {
|
|
230
|
+
latest: {
|
|
231
|
+
type: 'object',
|
|
232
|
+
$id: 'urn:msg',
|
|
233
|
+
title: 'Message',
|
|
234
|
+
required: ['id'],
|
|
235
|
+
properties: { id: { type: 'string' } },
|
|
236
|
+
},
|
|
237
|
+
message: {
|
|
238
|
+
type: 'object',
|
|
239
|
+
required: ['unread'],
|
|
240
|
+
properties: { unread: { type: 'boolean' } },
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
}),
|
|
247
|
+
]);
|
|
248
|
+
await expect(runPipeline({
|
|
249
|
+
envelope: collision,
|
|
250
|
+
outDir: 'out',
|
|
251
|
+
dryRun: true,
|
|
252
|
+
shareModels: true,
|
|
253
|
+
namespaceTypes: true,
|
|
254
|
+
selfContained: false,
|
|
255
|
+
})).rejects.toThrow(/shared model 'Message' collides/);
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
//# sourceMappingURL=shared-models.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared-models.test.js","sourceRoot":"","sources":["../../../../src/codegen/targets/ts/shared-models.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { DocEnvelope } from './implementations/types.js';
|
|
2
|
+
export type DocEnvelopeSource = DocEnvelope | {
|
|
3
|
+
toDocEnvelope(): DocEnvelope;
|
|
4
|
+
} | {
|
|
5
|
+
toJSON(): DocEnvelope;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Serializes a doc envelope to disk as pretty JSON so codegen can run offline
|
|
9
|
+
* via `--file <path>` without a running server. Accepts a plain `DocEnvelope`,
|
|
10
|
+
* a builder exposing `toDocEnvelope()`, or a `DocRegistry` exposing `toJSON()`.
|
|
11
|
+
* Parent directories are created as needed.
|
|
12
|
+
*/
|
|
13
|
+
export declare function writeDocEnvelope(source: DocEnvelopeSource, path: string): Promise<void>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
function coerceToEnvelope(source) {
|
|
4
|
+
if (typeof source.toDocEnvelope === 'function') {
|
|
5
|
+
return source.toDocEnvelope();
|
|
6
|
+
}
|
|
7
|
+
if (typeof source.toJSON === 'function') {
|
|
8
|
+
return source.toJSON();
|
|
9
|
+
}
|
|
10
|
+
return source;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Serializes a doc envelope to disk as pretty JSON so codegen can run offline
|
|
14
|
+
* via `--file <path>` without a running server. Accepts a plain `DocEnvelope`,
|
|
15
|
+
* a builder exposing `toDocEnvelope()`, or a `DocRegistry` exposing `toJSON()`.
|
|
16
|
+
* Parent directories are created as needed.
|
|
17
|
+
*/
|
|
18
|
+
export async function writeDocEnvelope(source, path) {
|
|
19
|
+
const envelope = coerceToEnvelope(source);
|
|
20
|
+
await mkdir(dirname(path), { recursive: true });
|
|
21
|
+
await writeFile(path, JSON.stringify(envelope, null, 2), 'utf8');
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=doc-envelope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doc-envelope.js","sourceRoot":"","sources":["../src/doc-envelope.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAQnC,SAAS,gBAAgB,CAAC,MAAyB;IACjD,IAAI,OAAQ,MAAsC,CAAC,aAAa,KAAK,UAAU,EAAE,CAAC;QAChF,OAAQ,MAA2C,CAAC,aAAa,EAAE,CAAA;IACrE,CAAC;IACD,IAAI,OAAQ,MAA+B,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;QAClE,OAAQ,MAAoC,CAAC,MAAM,EAAE,CAAA;IACvD,CAAC;IACD,OAAO,MAAqB,CAAA;AAC9B,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,MAAyB,EAAE,IAAY;IAC5E,MAAM,QAAQ,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAA;IACzC,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAC/C,MAAM,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAA;AAClE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
+
import { readFile, rm, mkdtemp } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { writeDocEnvelope } from './doc-envelope.js';
|
|
6
|
+
const envelope = { basePath: '', headers: [], errors: [], routes: [] };
|
|
7
|
+
describe('writeDocEnvelope', () => {
|
|
8
|
+
let dir;
|
|
9
|
+
afterEach(async () => { if (dir)
|
|
10
|
+
await rm(dir, { recursive: true, force: true }); });
|
|
11
|
+
it('writes a plain DocEnvelope as pretty JSON', async () => {
|
|
12
|
+
dir = await mkdtemp(join(tmpdir(), 'tsp-'));
|
|
13
|
+
const out = join(dir, 'nested', 'docs.json');
|
|
14
|
+
await writeDocEnvelope(envelope, out);
|
|
15
|
+
const parsed = JSON.parse(await readFile(out, 'utf8'));
|
|
16
|
+
expect(parsed).toEqual(envelope);
|
|
17
|
+
});
|
|
18
|
+
it('accepts a builder-like object with toDocEnvelope()', async () => {
|
|
19
|
+
dir = await mkdtemp(join(tmpdir(), 'tsp-'));
|
|
20
|
+
const out = join(dir, 'docs.json');
|
|
21
|
+
await writeDocEnvelope({ toDocEnvelope: () => envelope }, out);
|
|
22
|
+
expect(JSON.parse(await readFile(out, 'utf8'))).toEqual(envelope);
|
|
23
|
+
});
|
|
24
|
+
it('accepts a DocRegistry-like object (toJSON)', async () => {
|
|
25
|
+
dir = await mkdtemp(join(tmpdir(), 'tsp-'));
|
|
26
|
+
const out = join(dir, 'docs.json');
|
|
27
|
+
await writeDocEnvelope({ toJSON: () => envelope }, out);
|
|
28
|
+
expect(JSON.parse(await readFile(out, 'utf8'))).toEqual(envelope);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
//# sourceMappingURL=doc-envelope.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doc-envelope.test.js","sourceRoot":"","sources":["../src/doc-envelope.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AACxD,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAGpD,MAAM,QAAQ,GAAgB,EAAE,QAAQ,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAA;AAEnF,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,IAAI,GAAW,CAAA;IACf,SAAS,CAAC,KAAK,IAAI,EAAE,GAAG,IAAI,GAAG;QAAE,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA,CAAC,CAAC,CAAC,CAAA;IAEnF,EAAE,CAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;QACzD,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,MAAM,CAAC,CAAC,CAAA;QAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAA;QAC5C,MAAM,gBAAgB,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAA;QACrC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAA;QACtD,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;IAClC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;QAClE,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,MAAM,CAAC,CAAC,CAAA;QAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAA;QAClC,MAAM,gBAAgB,CAAC,EAAE,aAAa,EAAE,GAAG,EAAE,CAAC,QAAQ,EAAE,EAAE,GAAG,CAAC,CAAA;QAC9D,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;IACnE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;QAC1D,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,MAAM,CAAC,CAAC,CAAA;QAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAA;QAClC,MAAM,gBAAgB,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,EAAE,EAAE,GAAG,CAAC,CAAA;QACvD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;IACnE,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
package/build/exports.d.ts
CHANGED
|
@@ -7,3 +7,5 @@ export * from './schema/resolve-schema-lib.js';
|
|
|
7
7
|
export * from './schema/types.js';
|
|
8
8
|
export type { HttpReturn } from './create-http.js';
|
|
9
9
|
export type { TCreateHttpConfig } from './types.js';
|
|
10
|
+
export { writeDocEnvelope, type DocEnvelopeSource } from './doc-envelope.js';
|
|
11
|
+
export type { DocEnvelope } from './implementations/types.js';
|
package/build/exports.js
CHANGED
package/build/exports.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exports.js","sourceRoot":"","sources":["../src/exports.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,kBAAkB,CAAA;AAChC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,oBAAoB,CAAA;AAClC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,mBAAmB,CAAA"}
|
|
1
|
+
{"version":3,"file":"exports.js","sourceRoot":"","sources":["../src/exports.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,kBAAkB,CAAA;AAChC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,oBAAoB,CAAA;AAClC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,mBAAmB,CAAA;AAGjC,OAAO,EAAE,gBAAgB,EAA0B,MAAM,mBAAmB,CAAA"}
|
|
@@ -189,9 +189,49 @@ export declare const defaultErrorTaxonomy: {
|
|
|
189
189
|
* via `DocRegistry.defaultErrors()`.
|
|
190
190
|
*/
|
|
191
191
|
export declare const PROCEDURE_REGISTRATION_ERROR_DOC: ErrorDoc;
|
|
192
|
+
/**
|
|
193
|
+
* The default response body for an entry without a custom `toResponse`:
|
|
194
|
+
* `{ name: <key>, message }`. This is the single source of truth for the default
|
|
195
|
+
* wire shape — `resolveErrorResponse` serializes with it and
|
|
196
|
+
* {@link defaultErrorSchema} describes it. Keeping both derived from one place
|
|
197
|
+
* means the synthesized schema can never drift from what the runtime emits.
|
|
198
|
+
*/
|
|
199
|
+
export declare function defaultErrorBody(key: string, err: unknown): {
|
|
200
|
+
name: string;
|
|
201
|
+
message: string;
|
|
202
|
+
};
|
|
203
|
+
/**
|
|
204
|
+
* Synthesizes the response-body JSON Schema for a taxonomy entry that ships
|
|
205
|
+
* neither an explicit `schema` nor a custom `toResponse`.
|
|
206
|
+
*
|
|
207
|
+
* The common case for `defineErrorTaxonomy` is `{ class, statusCode }` only.
|
|
208
|
+
* For those entries the default `toResponse` (see `resolveErrorResponse`) emits
|
|
209
|
+
* exactly `{ name: <key>, message }`. Without a schema, `taxonomyToErrorDocs`
|
|
210
|
+
* produced a schema-less `ErrorDoc`, and codegen (`emit-errors.ts`) only emits a
|
|
211
|
+
* typed client error class for docs that carry a schema — so these entries
|
|
212
|
+
* silently fell back to the untyped `ClientHttpError`, while framework errors
|
|
213
|
+
* (which ship schemas) worked. That mismatch was confusing.
|
|
214
|
+
*
|
|
215
|
+
* By describing the default envelope here, the entry becomes self-describing and
|
|
216
|
+
* codegen emits a typed client error class with zero ceremony from the consumer.
|
|
217
|
+
*
|
|
218
|
+
* Rules:
|
|
219
|
+
* - Entry has an explicit `schema` → caller keeps it (this is not consulted).
|
|
220
|
+
* - Entry has a custom `toResponse` but no `schema` → returns `undefined`; the
|
|
221
|
+
* body shape is unknown and we never guess it.
|
|
222
|
+
* - Entry has neither → returns the `{ name: const <key>, message }` schema that
|
|
223
|
+
* describes {@link defaultErrorBody} — the exact body the runtime serializes.
|
|
224
|
+
* (An invariant test keeps the two from drifting.)
|
|
225
|
+
*/
|
|
226
|
+
export declare function defaultErrorSchema(key: string, entry: ErrorTaxonomyEntry): Record<string, unknown> | undefined;
|
|
192
227
|
/**
|
|
193
228
|
* Converts a taxonomy into {@link ErrorDoc} objects suitable for a DocEnvelope.
|
|
194
229
|
*
|
|
230
|
+
* For entries that supply neither a `schema` nor a custom `toResponse`, the
|
|
231
|
+
* schema of the default `{ name, message }` envelope is synthesized (see
|
|
232
|
+
* {@link defaultErrorSchema}) so client codegen emits a typed error class for
|
|
233
|
+
* them too — matching the behavior of the schema-carrying framework defaults.
|
|
234
|
+
*
|
|
195
235
|
* @internal Used by `DocRegistry` to merge taxonomy entries into the envelope.
|
|
196
236
|
* Consumers should pass their taxonomy directly to `new DocRegistry({ errors: taxonomy })`
|
|
197
237
|
* rather than calling this helper — the constructor handles the conversion.
|
|
@@ -145,9 +145,64 @@ export const PROCEDURE_REGISTRATION_ERROR_DOC = {
|
|
|
145
145
|
required: ['name', 'procedureName', 'message'],
|
|
146
146
|
},
|
|
147
147
|
};
|
|
148
|
+
/**
|
|
149
|
+
* The default response body for an entry without a custom `toResponse`:
|
|
150
|
+
* `{ name: <key>, message }`. This is the single source of truth for the default
|
|
151
|
+
* wire shape — `resolveErrorResponse` serializes with it and
|
|
152
|
+
* {@link defaultErrorSchema} describes it. Keeping both derived from one place
|
|
153
|
+
* means the synthesized schema can never drift from what the runtime emits.
|
|
154
|
+
*/
|
|
155
|
+
export function defaultErrorBody(key, err) {
|
|
156
|
+
return {
|
|
157
|
+
name: key,
|
|
158
|
+
message: err instanceof Error ? err.message : String(err),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Synthesizes the response-body JSON Schema for a taxonomy entry that ships
|
|
163
|
+
* neither an explicit `schema` nor a custom `toResponse`.
|
|
164
|
+
*
|
|
165
|
+
* The common case for `defineErrorTaxonomy` is `{ class, statusCode }` only.
|
|
166
|
+
* For those entries the default `toResponse` (see `resolveErrorResponse`) emits
|
|
167
|
+
* exactly `{ name: <key>, message }`. Without a schema, `taxonomyToErrorDocs`
|
|
168
|
+
* produced a schema-less `ErrorDoc`, and codegen (`emit-errors.ts`) only emits a
|
|
169
|
+
* typed client error class for docs that carry a schema — so these entries
|
|
170
|
+
* silently fell back to the untyped `ClientHttpError`, while framework errors
|
|
171
|
+
* (which ship schemas) worked. That mismatch was confusing.
|
|
172
|
+
*
|
|
173
|
+
* By describing the default envelope here, the entry becomes self-describing and
|
|
174
|
+
* codegen emits a typed client error class with zero ceremony from the consumer.
|
|
175
|
+
*
|
|
176
|
+
* Rules:
|
|
177
|
+
* - Entry has an explicit `schema` → caller keeps it (this is not consulted).
|
|
178
|
+
* - Entry has a custom `toResponse` but no `schema` → returns `undefined`; the
|
|
179
|
+
* body shape is unknown and we never guess it.
|
|
180
|
+
* - Entry has neither → returns the `{ name: const <key>, message }` schema that
|
|
181
|
+
* describes {@link defaultErrorBody} — the exact body the runtime serializes.
|
|
182
|
+
* (An invariant test keeps the two from drifting.)
|
|
183
|
+
*/
|
|
184
|
+
export function defaultErrorSchema(key, entry) {
|
|
185
|
+
if (entry.schema)
|
|
186
|
+
return entry.schema;
|
|
187
|
+
if (entry.toResponse)
|
|
188
|
+
return undefined;
|
|
189
|
+
return {
|
|
190
|
+
type: 'object',
|
|
191
|
+
properties: {
|
|
192
|
+
name: { type: 'string', const: key },
|
|
193
|
+
message: { type: 'string' },
|
|
194
|
+
},
|
|
195
|
+
required: ['name', 'message'],
|
|
196
|
+
};
|
|
197
|
+
}
|
|
148
198
|
/**
|
|
149
199
|
* Converts a taxonomy into {@link ErrorDoc} objects suitable for a DocEnvelope.
|
|
150
200
|
*
|
|
201
|
+
* For entries that supply neither a `schema` nor a custom `toResponse`, the
|
|
202
|
+
* schema of the default `{ name, message }` envelope is synthesized (see
|
|
203
|
+
* {@link defaultErrorSchema}) so client codegen emits a typed error class for
|
|
204
|
+
* them too — matching the behavior of the schema-carrying framework defaults.
|
|
205
|
+
*
|
|
151
206
|
* @internal Used by `DocRegistry` to merge taxonomy entries into the envelope.
|
|
152
207
|
* Consumers should pass their taxonomy directly to `new DocRegistry({ errors: taxonomy })`
|
|
153
208
|
* rather than calling this helper — the constructor handles the conversion.
|
|
@@ -157,7 +212,7 @@ export function taxonomyToErrorDocs(taxonomy) {
|
|
|
157
212
|
name: key,
|
|
158
213
|
statusCode: entry.statusCode,
|
|
159
214
|
description: entry.description ?? '',
|
|
160
|
-
schema: entry
|
|
215
|
+
schema: defaultErrorSchema(key, entry),
|
|
161
216
|
}));
|
|
162
217
|
}
|
|
163
218
|
/**
|
|
@@ -207,10 +262,7 @@ export function resolveErrorResponse(params) {
|
|
|
207
262
|
continue;
|
|
208
263
|
const rawBody = entry.toResponse
|
|
209
264
|
? entry.toResponse(candidate, { key })
|
|
210
|
-
:
|
|
211
|
-
name: key,
|
|
212
|
-
message: candidate instanceof Error ? candidate.message : String(candidate),
|
|
213
|
-
};
|
|
265
|
+
: defaultErrorBody(key, candidate);
|
|
214
266
|
const body = ensureName(rawBody, key);
|
|
215
267
|
return {
|
|
216
268
|
statusCode: entry.statusCode,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-taxonomy.js","sourceRoot":"","sources":["../../../src/implementations/http/error-taxonomy.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"error-taxonomy.js","sourceRoot":"","sources":["../../../src/implementations/http/error-taxonomy.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,wBAAwB,EACxB,6BAA6B,GAC9B,MAAM,iBAAiB,CAAA;AAkDxB;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CAA0B,OAAU;IACrE,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;IAErC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,KAAK,EAAE,CAAC;QACjC,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,KAAK,SAAS,CAAA;QAC1C,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,KAAK,SAAS,CAAA;QAC1C,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CACb,yBAAyB,GAAG,gDAAgD,CAC7E,CAAA;QACH,CAAC;IACH,CAAC;IAED,+EAA+E;IAC/E,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE;QAC1B,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK;YAAE,OAAO,CAAC,CAAA;QAClC,IAAI,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK;YAAE,OAAO,CAAC,CAAA;QACjC,IAAI,CAAC,CAAC,KAAK,CAAC,SAAS,YAAY,CAAC,CAAC,KAAK;YAAE,OAAO,CAAC,CAAC,CAAA;QACnD,IAAI,CAAC,CAAC,KAAK,CAAC,SAAS,YAAY,CAAC,CAAC,KAAK;YAAE,OAAO,CAAC,CAAA;QAClD,OAAO,CAAC,CAAA;IACV,CAAC,CAAC,CAAA;IAEF,OAAO,MAAM,CAAC,WAAW,CAAC,KAAK,CAAM,CAAA;AACvC,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,mBAAmB,CAAC;IACtD,wBAAwB,EAAE;QACxB,KAAK,EAAE,wBAAwB;QAC/B,UAAU,EAAE,GAAG;QACf,WAAW,EAAE,8DAA8D;QAC3E,UAAU,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YACpB,IAAI,EAAE,0BAAmC;YACzC,aAAa,EAAE,GAAG,CAAC,aAAa;YAChC,OAAO,EAAE,GAAG,CAAC,OAAO;YACpB,MAAM,EAAE,GAAG,CAAC,MAAM;SACnB,CAAC;QACF,MAAM,EAAE;YACN,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,0BAA0B,EAAE;gBAC3D,aAAa,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBACjC,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBAC3B,MAAM,EAAE;oBACN,IAAI,EAAE,OAAO;oBACb,KAAK,EAAE;wBACL,IAAI,EAAE,QAAQ;wBACd,UAAU,EAAE;4BACV,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;4BAChC,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;yBAC5B;qBACF;iBACF;aACF;YACD,QAAQ,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,SAAS,CAAC;SAC/C;KACF;IACD,6BAA6B,EAAE;QAC7B,KAAK,EAAE,6BAA6B;QACpC,UAAU,EAAE,GAAG;QACf,WAAW,EAAE,wEAAwE;QACrF,UAAU,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YACpB,IAAI,EAAE,+BAAwC;YAC9C,aAAa,EAAE,GAAG,CAAC,aAAa;YAChC,OAAO,EAAE,GAAG,CAAC,OAAO;YACpB,MAAM,EAAE,GAAG,CAAC,MAAM;SACnB,CAAC;QACF,MAAM,EAAE;YACN,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,+BAA+B,EAAE;gBAChE,aAAa,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBACjC,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBAC3B,MAAM,EAAE;oBACN,IAAI,EAAE,OAAO;oBACb,KAAK,EAAE;wBACL,IAAI,EAAE,QAAQ;wBACd,UAAU,EAAE;4BACV,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;4BAChC,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;yBAC5B;qBACF;iBACF;aACF;YACD,QAAQ,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,SAAS,CAAC;SAC/C;KACF;IACD,cAAc,EAAE;QACd,KAAK,EAAE,CAAC,GAAG,EAAyB,EAAE,CACpC,GAAG,YAAY,cAAc,IAAK,GAA2B,CAAC,KAAK,KAAK,SAAS;QACnF,UAAU,EAAE,GAAG;QACf,WAAW,EAAE,kEAAkE;QAC/E,UAAU,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YACpB,IAAI,EAAE,gBAAyB;YAC/B,aAAa,EAAE,GAAG,CAAC,aAAa;YAChC,OAAO,EAAE,GAAG,CAAC,OAAO;YACpB,IAAI,EAAE,GAAG,CAAC,IAAI;SACf,CAAC;QACF,MAAM,EAAE;YACN,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,gBAAgB,EAAE;gBACjD,aAAa,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBACjC,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBAC3B,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;aACzB;YACD,QAAQ,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,SAAS,CAAC;SAC/C;KACF;CACF,CAAC,CAAA;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gCAAgC,GAAa;IACxD,IAAI,EAAE,4BAA4B;IAClC,UAAU,EAAE,GAAG;IACf,WAAW,EACT,iFAAiF;IACnF,MAAM,EAAE;QACN,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,4BAA4B,EAAE;YAC7D,aAAa,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YACjC,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;SAC5B;QACD,QAAQ,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,SAAS,CAAC;KAC/C;CACF,CAAA;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAW,EAAE,GAAY;IACxD,OAAO;QACL,IAAI,EAAE,GAAG;QACT,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;KAC1D,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,kBAAkB,CAChC,GAAW,EACX,KAAyB;IAEzB,IAAI,KAAK,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC,MAAM,CAAA;IACrC,IAAI,KAAK,CAAC,UAAU;QAAE,OAAO,SAAS,CAAA;IACtC,OAAO;QACL,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE;YACpC,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;SAC5B;QACD,QAAQ,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC;KAC9B,CAAA;AACH,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,mBAAmB,CAAC,QAAuB;IACzD,OAAO,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;QACrD,IAAI,EAAE,GAAG;QACT,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,WAAW,EAAE,KAAK,CAAC,WAAW,IAAI,EAAE;QACpC,MAAM,EAAE,kBAAkB,CAAC,GAAG,EAAE,KAAK,CAAC;KACvC,CAAC,CAAC,CAAA;AACL,CAAC;AA6BD;;;;GAIG;AACH,SAAS,UAAU,CAAC,OAAgB,EAAE,GAAW;IAC/C,IAAI,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,IAAK,OAAkB,CAAC,EAAE,CAAC;QAC/E,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,GAAI,OAAkB,EAAE,CAAA;IAC9C,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAQpC;IACC,MAAM,EACJ,GAAG,EACH,YAAY,EACZ,eAAe,GAAG,IAAI,EACtB,YAAY,EACZ,SAAS,EACT,GAAG,GACJ,GAAG,MAAM,CAAA;IAEV,MAAM,YAAY,GAChB,GAAG,YAAY,cAAc,IAAK,GAA2B,CAAC,KAAK,KAAK,SAAS;QAC/E,CAAC,CAAE,GAA2B,CAAC,KAAK;QACpC,CAAC,CAAC,SAAS,CAAA;IACf,8EAA8E;IAC9E,yCAAyC;IACzC,MAAM,UAAU,GACd,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IAE1D,MAAM,QAAQ,GAAG,CAAC,GAAkB,EAAgC,EAAE;QACpE,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YAC/C,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;gBACnC,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK;oBACzB,CAAC,CAAC,SAAS,YAAY,KAAK,CAAC,KAAK;oBAClC,CAAC,CAAC,KAAK,CAAC,KAAK;wBACX,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC;wBACxB,CAAC,CAAC,KAAK,CAAA;gBACX,IAAI,CAAC,OAAO;oBAAE,SAAQ;gBAEtB,MAAM,OAAO,GAAG,KAAK,CAAC,UAAU;oBAC9B,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,SAAgB,EAAE,EAAE,GAAG,EAAE,CAAC;oBAC7C,CAAC,CAAC,gBAAgB,CAAC,GAAG,EAAE,SAAS,CAAC,CAAA;gBACpC,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;gBAErC,OAAO;oBACL,UAAU,EAAE,KAAK,CAAC,UAAU;oBAC5B,IAAI;oBACJ,UAAU,EAAE,KAAK,IAAI,EAAE;wBACrB,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;4BAClB,MAAM,KAAK,CAAC,OAAO,CAAC,SAAgB,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAA;wBAChE,CAAC;oBACH,CAAC;iBACF,CAAA;YACH,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC,CAAA;IAED,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,GAAG,GAAG,QAAQ,CAAC,YAAY,CAAC,CAAA;QAClC,IAAI,GAAG;YAAE,OAAO,GAAG,CAAA;IACrB,CAAC;IAED,IAAI,eAAe,EAAE,CAAC;QACpB,MAAM,GAAG,GAAG,QAAQ,CAAC,oBAAoB,CAAC,CAAA;QAC1C,IAAI,GAAG;YAAE,OAAO,GAAG,CAAA;IACrB,CAAC;IAED,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,YAAY,GAAG,UAAU,CAAC,CAAC,CAAC,CAAA;QAClC,OAAO;YACL,UAAU,EAAE,YAAY,CAAC,UAAU,IAAI,GAAG;YAC1C,IAAI,EAAE,YAAY,CAAC,UAAU,CAAC,YAAY,CAAC;YAC3C,UAAU,EAAE,KAAK,IAAI,EAAE;gBACrB,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;oBACzB,MAAM,YAAY,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAA;gBAC9D,CAAC;YACH,CAAC;SACF,CAAA;IACH,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, expect, test } from 'vitest';
|
|
2
2
|
import { ProcedureError, ProcedureValidationError, ProcedureYieldValidationError, } from '../../errors.js';
|
|
3
|
-
import
|
|
3
|
+
import * as AJV from 'ajv';
|
|
4
|
+
import { defineErrorTaxonomy, resolveErrorResponse, defaultErrorTaxonomy, taxonomyToErrorDocs, defaultErrorSchema, defaultErrorBody, } from './error-taxonomy.js';
|
|
4
5
|
class UseCaseError extends Error {
|
|
5
6
|
externalMsg;
|
|
6
7
|
internalMsg;
|
|
@@ -396,4 +397,97 @@ describe('resolveErrorResponse', () => {
|
|
|
396
397
|
expect(Object.keys(taxonomy)).toEqual(['B', 'A']);
|
|
397
398
|
});
|
|
398
399
|
});
|
|
400
|
+
describe('taxonomyToErrorDocs', () => {
|
|
401
|
+
test('synthesizes a { name const, message } schema for class+statusCode-only entries', () => {
|
|
402
|
+
const taxonomy = defineErrorTaxonomy({
|
|
403
|
+
AuthError: { class: AuthError, statusCode: 401 },
|
|
404
|
+
});
|
|
405
|
+
const docs = taxonomyToErrorDocs(taxonomy);
|
|
406
|
+
const auth = docs.find((d) => d.name === 'AuthError');
|
|
407
|
+
expect(auth?.statusCode).toBe(401);
|
|
408
|
+
expect(auth?.schema).toEqual({
|
|
409
|
+
type: 'object',
|
|
410
|
+
properties: {
|
|
411
|
+
name: { type: 'string', const: 'AuthError' },
|
|
412
|
+
message: { type: 'string' },
|
|
413
|
+
},
|
|
414
|
+
required: ['name', 'message'],
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
test('does NOT synthesize a schema when a custom toResponse is present', () => {
|
|
418
|
+
const taxonomy = defineErrorTaxonomy({
|
|
419
|
+
UseCaseError: {
|
|
420
|
+
class: UseCaseError,
|
|
421
|
+
statusCode: 422,
|
|
422
|
+
toResponse: (err) => ({ name: 'UseCaseError', message: err.externalMsg }),
|
|
423
|
+
},
|
|
424
|
+
});
|
|
425
|
+
const docs = taxonomyToErrorDocs(taxonomy);
|
|
426
|
+
const useCase = docs.find((d) => d.name === 'UseCaseError');
|
|
427
|
+
expect(useCase?.schema).toBeUndefined();
|
|
428
|
+
});
|
|
429
|
+
test('preserves an explicit schema untouched', () => {
|
|
430
|
+
const explicitSchema = {
|
|
431
|
+
type: 'object',
|
|
432
|
+
properties: {
|
|
433
|
+
name: { type: 'string', const: 'UseCaseError' },
|
|
434
|
+
reason: { type: 'string' },
|
|
435
|
+
},
|
|
436
|
+
required: ['name', 'reason'],
|
|
437
|
+
};
|
|
438
|
+
const taxonomy = defineErrorTaxonomy({
|
|
439
|
+
UseCaseError: {
|
|
440
|
+
class: UseCaseError,
|
|
441
|
+
statusCode: 422,
|
|
442
|
+
schema: explicitSchema,
|
|
443
|
+
},
|
|
444
|
+
});
|
|
445
|
+
const docs = taxonomyToErrorDocs(taxonomy);
|
|
446
|
+
const useCase = docs.find((d) => d.name === 'UseCaseError');
|
|
447
|
+
expect(useCase?.schema).toBe(explicitSchema);
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
describe('defaultErrorSchema', () => {
|
|
451
|
+
test('synthesizes the { name, message } envelope for a bare entry', () => {
|
|
452
|
+
expect(defaultErrorSchema('AuthError', { class: AuthError, statusCode: 401 })).toEqual({
|
|
453
|
+
type: 'object',
|
|
454
|
+
properties: {
|
|
455
|
+
name: { type: 'string', const: 'AuthError' },
|
|
456
|
+
message: { type: 'string' },
|
|
457
|
+
},
|
|
458
|
+
required: ['name', 'message'],
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
test('returns undefined when a custom toResponse is present (shape unknown)', () => {
|
|
462
|
+
expect(defaultErrorSchema('UseCaseError', {
|
|
463
|
+
class: UseCaseError,
|
|
464
|
+
statusCode: 422,
|
|
465
|
+
toResponse: () => ({ name: 'UseCaseError' }),
|
|
466
|
+
})).toBeUndefined();
|
|
467
|
+
});
|
|
468
|
+
test('returns the explicit schema when one is set', () => {
|
|
469
|
+
const schema = { type: 'object', properties: {} };
|
|
470
|
+
expect(defaultErrorSchema('UseCaseError', { class: UseCaseError, statusCode: 422, schema })).toBe(schema);
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
// The synthesized schema and the runtime body share one source (defaultErrorBody).
|
|
474
|
+
// This locks the invariant: whatever the default branch serializes must validate
|
|
475
|
+
// against the schema codegen turns into the client error class. If either side
|
|
476
|
+
// changes shape, this fails before consumers see a mismatch.
|
|
477
|
+
describe('defaultErrorBody / defaultErrorSchema invariant', () => {
|
|
478
|
+
const ajv = new AJV.Ajv();
|
|
479
|
+
test('default body validates against the synthesized schema', () => {
|
|
480
|
+
const schema = defaultErrorSchema('AuthError', { class: AuthError, statusCode: 401 });
|
|
481
|
+
const validate = ajv.compile(schema);
|
|
482
|
+
expect(validate(defaultErrorBody('AuthError', new Error('nope')))).toBe(true);
|
|
483
|
+
// A non-Error throw stringifies to a message — still valid.
|
|
484
|
+
expect(validate(defaultErrorBody('AuthError', 'plain string'))).toBe(true);
|
|
485
|
+
// Wrong discriminator name is rejected by the `const` — proves the schema
|
|
486
|
+
// actually constrains the wire shape the client dispatcher keys on.
|
|
487
|
+
expect(validate({ name: 'SomethingElse', message: 'x' })).toBe(false);
|
|
488
|
+
});
|
|
489
|
+
test('default body carries exactly the schema-described keys', () => {
|
|
490
|
+
expect(Object.keys(defaultErrorBody('X', new Error('m'))).sort()).toEqual(['message', 'name']);
|
|
491
|
+
});
|
|
492
|
+
});
|
|
399
493
|
//# sourceMappingURL=error-taxonomy.test.js.map
|