@kubb/plugin-msw 6.2.0-canary.20261111T122007 → 6.2.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/dist/index.js CHANGED
@@ -1,8 +1,240 @@
1
- import { Resolver, Url, addGenerator, ast, camelCase, createResolver, defineExtension, defineGenerator, setMacros, setResolver } from "@kubb/kit";
1
+ import { Resolver, Url, addGenerator, ast, casing, createResolver, defineExtension, defineGenerator, setMacros, setResolver, text } from "@kubb/kit";
2
2
  import { pluginFakerName } from "@kubb/plugin-faker";
3
3
  import { enumTypeName, pluginTsName } from "@kubb/plugin-ts";
4
4
  import { File, Function, jsxRenderer } from "@kubb/renderer-jsx";
5
5
  import { jsx, jsxs } from "@kubb/renderer-jsx/jsx-runtime";
6
+ // kubb-extension.json
7
+ var compatibility = {
8
+ "kubb": ">=5.18.4",
9
+ "node": ">=22.22"
10
+ };
11
+ // package.json
12
+ var version = "6.2.0";
13
+ // src/generators/handlersGenerator.ts
14
+ /**
15
+ * Aggregate generator enabled by `pluginMsw({ handlers: true })`. Emits a
16
+ * `handlers.ts` file that re-exports every generated handler in operation
17
+ * order, ready to spread into `setupServer(...handlers)` or
18
+ * `setupWorker(...handlers)`.
19
+ */
20
+ const handlersGenerator = defineGenerator({
21
+ name: "plugin-msw",
22
+ operations(nodes, ctx) {
23
+ const { resolver, config, root } = ctx;
24
+ const { output, group } = ctx.options;
25
+ const handlersName = resolver.handler.listName();
26
+ const createHandlersName = resolver.resolveName({
27
+ key: ["handlers", "create"],
28
+ name: casing.camel(handlersName, { prefix: "create" })
29
+ });
30
+ const file = resolver.file({
31
+ name: handlersName,
32
+ extname: ".ts",
33
+ root,
34
+ output,
35
+ group: group ?? void 0
36
+ });
37
+ const handlerNames = nodes.map((node) => resolver.handler.name(node));
38
+ const imports = nodes.map((node, index) => {
39
+ const operationName = handlerNames[index];
40
+ const operationFile = resolver.file({
41
+ name: resolver.name(node.operationId),
42
+ extname: ".ts",
43
+ tag: node.tags[0] ?? "default",
44
+ path: node.path,
45
+ root,
46
+ output,
47
+ group: group ?? void 0
48
+ });
49
+ return ast.factory.createImport({
50
+ name: [operationName],
51
+ root: file.path,
52
+ path: operationFile.path
53
+ });
54
+ });
55
+ const handlers = handlerNames.map((name) => `${name}(undefined, options)`);
56
+ return [ast.factory.createFile({
57
+ baseName: file.baseName,
58
+ path: file.path,
59
+ meta: file.meta,
60
+ banner: resolver.default.banner(ctx.meta, {
61
+ output,
62
+ config,
63
+ file: {
64
+ path: file.path,
65
+ baseName: file.baseName
66
+ }
67
+ }),
68
+ footer: resolver.default.footer(ctx.meta, {
69
+ output,
70
+ config,
71
+ file: {
72
+ path: file.path,
73
+ baseName: file.baseName
74
+ }
75
+ }),
76
+ imports,
77
+ sources: [ast.factory.createSource({
78
+ name: createHandlersName,
79
+ isIndexable: true,
80
+ isExportable: true,
81
+ nodes: [ast.factory.createText(`export function ${createHandlersName}(options?: { baseURL?: string }) {
82
+ return [${handlers.join(", ")}] as const
83
+ }`)]
84
+ }), ast.factory.createSource({
85
+ name: handlersName,
86
+ isIndexable: true,
87
+ isExportable: true,
88
+ nodes: [ast.factory.createText(`export const ${handlersName} = ${createHandlersName}()`)]
89
+ })]
90
+ })];
91
+ }
92
+ });
93
+ // ../../internals/utils/src/reserved.ts
94
+ /**
95
+ * JavaScript and Java reserved words.
96
+ * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
97
+ */
98
+ const reservedWords = /* @__PURE__ */ new Set([
99
+ "abstract",
100
+ "arguments",
101
+ "await",
102
+ "boolean",
103
+ "break",
104
+ "byte",
105
+ "case",
106
+ "catch",
107
+ "char",
108
+ "class",
109
+ "const",
110
+ "continue",
111
+ "debugger",
112
+ "default",
113
+ "delete",
114
+ "do",
115
+ "double",
116
+ "else",
117
+ "enum",
118
+ "eval",
119
+ "export",
120
+ "extends",
121
+ "false",
122
+ "final",
123
+ "finally",
124
+ "float",
125
+ "for",
126
+ "function",
127
+ "goto",
128
+ "if",
129
+ "implements",
130
+ "import",
131
+ "in",
132
+ "instanceof",
133
+ "int",
134
+ "interface",
135
+ "let",
136
+ "long",
137
+ "native",
138
+ "new",
139
+ "null",
140
+ "package",
141
+ "private",
142
+ "protected",
143
+ "public",
144
+ "return",
145
+ "short",
146
+ "static",
147
+ "super",
148
+ "switch",
149
+ "synchronized",
150
+ "this",
151
+ "throw",
152
+ "throws",
153
+ "transient",
154
+ "true",
155
+ "try",
156
+ "typeof",
157
+ "var",
158
+ "void",
159
+ "volatile",
160
+ "while",
161
+ "with",
162
+ "yield",
163
+ "Array",
164
+ "Date",
165
+ "hasOwnProperty",
166
+ "Infinity",
167
+ "isFinite",
168
+ "isNaN",
169
+ "isPrototypeOf",
170
+ "length",
171
+ "Math",
172
+ "name",
173
+ "NaN",
174
+ "Number",
175
+ "Object",
176
+ "prototype",
177
+ "String",
178
+ "toString",
179
+ "undefined",
180
+ "valueOf"
181
+ ]);
182
+ /**
183
+ * Returns `true` when `name` is a syntactically valid JavaScript variable name.
184
+ *
185
+ * @example
186
+ * ```ts
187
+ * isValidVarName('status') // true
188
+ * isValidVarName('class') // false (reserved word)
189
+ * isValidVarName('42foo') // false (starts with digit)
190
+ * ```
191
+ */
192
+ function isValidVarName(name) {
193
+ if (!name || reservedWords.has(name)) return false;
194
+ return isIdentifier(name);
195
+ }
196
+ /**
197
+ * Returns `true` when `name` is syntactically a valid identifier, ignoring reserved words.
198
+ *
199
+ * Reserved words and globals (`class`, `name`, `Date`, …) are valid as bare object-literal keys
200
+ * even though they are not valid variable names, so use this (not {@link isValidVarName}) when
201
+ * deciding whether an object key needs quoting.
202
+ *
203
+ * @example
204
+ * ```ts
205
+ * isIdentifier('name') // true
206
+ * isIdentifier('x-total')// false
207
+ * ```
208
+ */
209
+ function isIdentifier(name) {
210
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
211
+ }
212
+ // ../../internals/utils/src/codegen.ts
213
+ /**
214
+ * An object key, quoted only when it is not identifier-shaped. Reserved words such as `class` stay bare.
215
+ *
216
+ * @example
217
+ * ```ts
218
+ * objectKey('x-total') // "'x-total'"
219
+ * ```
220
+ */
221
+ function objectKey(name) {
222
+ return isIdentifier(name) ? name : text.quote(name);
223
+ }
224
+ /**
225
+ * A JSON value as a JavaScript literal, with single-quoted strings and identifier keys left unquoted.
226
+ *
227
+ * @example
228
+ * ```ts
229
+ * toLiteral({ id: 1, 'x-tag': ['a'] }) // "{ id: 1, 'x-tag': ['a'] }"
230
+ * ```
231
+ */
232
+ function toLiteral(value) {
233
+ if (typeof value === "string") return text.quote(value);
234
+ if (Array.isArray(value)) return `[${value.map(toLiteral).join(", ")}]`;
235
+ if (value && typeof value === "object") return `{ ${Object.entries(value).map(([key, item]) => `${objectKey(key)}: ${toLiteral(item)}`).join(", ")} }`;
236
+ return String(value);
237
+ }
6
238
  // ../../internals/shared/src/operation.ts
7
239
  /**
8
240
  * Builds the `ResolverFileParams` every operation generator passes to
@@ -68,19 +300,12 @@ function resolveOperationFile(options) {
68
300
  return file;
69
301
  });
70
302
  }
71
- function baseContentType(contentType) {
72
- return contentType.split(";")[0].trim().toLowerCase();
73
- }
74
303
  /**
75
- * Whether a content entry is a sequence of JSON values the adapter marks as a stream, such as `application/jsonl`,
76
- * `application/x-ndjson`, `application/json-seq`, or any media type with an OpenAPI 3.2 `itemSchema`.
304
+ * How a content entry is framed as a stream: `'events'` for Server-Sent Events, `'lines'` for JSON Lines.
77
305
  */
78
- function isJsonLines(entry) {
79
- const baseType = baseContentType(entry.contentType);
80
- return Boolean(entry.stream) && baseType !== "text/event-stream" && !baseType.startsWith("multipart/");
81
- }
82
- function isJsonContentType(baseType) {
83
- return baseType === "application/json" || baseType.endsWith("+json") || baseType === "text/json";
306
+ function getStreamFraming(entry) {
307
+ if (ast.mediaType.getBase(entry) === "text/event-stream") return "events";
308
+ return entry.stream && ast.mediaType.isStream(entry) ? "lines" : void 0;
84
309
  }
85
310
  /**
86
311
  * Parser for one response content entry.
@@ -91,10 +316,10 @@ function isJsonContentType(baseType) {
91
316
  * `image/*`, `audio/*`, and `video/*` are blobs.
92
317
  */
93
318
  function responseTypeForContent(entry) {
94
- const baseType = baseContentType(entry.contentType);
319
+ const baseType = ast.mediaType.getBase(entry);
95
320
  if (!baseType) return void 0;
96
- if (baseType === "text/event-stream" || isJsonLines(entry)) return "stream";
97
- if (isJsonContentType(baseType)) return void 0;
321
+ if (getStreamFraming(entry)) return "stream";
322
+ if (ast.mediaType.isJson(baseType)) return void 0;
98
323
  if (entry.schema?.type === "blob") return "blob";
99
324
  if (baseType.startsWith("text/")) return "text";
100
325
  if (isBinaryContentType(baseType)) return "blob";
@@ -111,14 +336,14 @@ function isBinaryContentType(baseType) {
111
336
  function toResponseContent(entry, entries = [entry]) {
112
337
  const { contentType, schema } = entry;
113
338
  if (!schema || entry.stream) return entry;
114
- const baseType = baseContentType(contentType);
115
- const resolved = ast.resolveRef(schema);
339
+ const baseType = ast.mediaType.getBase(contentType);
340
+ const resolved = ast.ref.getSchema(schema);
116
341
  if (isBinaryContentType(baseType)) return resolved.type === "blob" ? entry : {
117
342
  ...entry,
118
343
  schema: ast.factory.createSchema({ type: "blob" }),
119
344
  keysToOmit: null
120
345
  };
121
- const isPlainMediaType = Boolean(baseType) && !isJsonContentType(baseType) && !baseType.includes("*");
346
+ const isPlainMediaType = Boolean(baseType) && !ast.mediaType.isJson(baseType) && !baseType.includes("*");
122
347
  if (resolved.type === "blob") return isPlainMediaType && !entries.every((item) => item.schema?.type === "blob") ? {
123
348
  ...entry,
124
349
  schema: ast.factory.createSchema({ type: "string" }),
@@ -134,36 +359,6 @@ function toResponseContent(entry, entries = [entry]) {
134
359
  keysToOmit: null
135
360
  } : entry;
136
361
  }
137
- function getStatusCodeNumber(statusCode) {
138
- const code = Number(statusCode);
139
- return Number.isNaN(code) ? null : code;
140
- }
141
- function isSuccessStatusCode(statusCode) {
142
- const code = getStatusCodeNumber(statusCode);
143
- return code !== null && code >= 200 && code < 300;
144
- }
145
- function getSuccessResponses(responses) {
146
- return responses.filter((response) => isSuccessStatusCode(response.statusCode));
147
- }
148
- function getOperationSuccessResponses(node) {
149
- return getSuccessResponses(node.responses);
150
- }
151
- function getPrimarySuccessResponse(node) {
152
- return getOperationSuccessResponses(node)[0] ?? null;
153
- }
154
- function resolveResponseTypes(node, resolver) {
155
- const types = [];
156
- for (const response of node.responses) {
157
- if (response.statusCode === "default") {
158
- types.push(["default", resolver.response.response(node)]);
159
- continue;
160
- }
161
- const code = getStatusCodeNumber(response.statusCode);
162
- if (code === null) continue;
163
- types.push([code, isSuccessStatusCode(code) ? resolver.response.response(node) : resolver.response.status(node, response.statusCode)]);
164
- }
165
- return types;
166
- }
167
362
  // ../../internals/shared/src/resolver.ts
168
363
  /**
169
364
  * Builds the allocation key for a declaration an operation owns. `scope` names which declaration
@@ -191,124 +386,25 @@ function operationKey(node, ...scope) {
191
386
  function getEnumNames(ctx) {
192
387
  return ctx.memo("enumNames:set", () => new Set((ctx.driver.inputNode?.schemas ?? []).flatMap((schema) => schema.type === "enum" && schema.name ? [schema.name] : [])));
193
388
  }
194
- // ../../internals/shared/src/group.ts
195
- /**
196
- * Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
197
- * shared default naming so every plugin groups output consistently:
198
- *
199
- * - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
200
- * - other groups use the camelCased group (`pet store` → `petStore`).
201
- *
202
- * A user-provided `group.name` always wins over the default namer, so callers stay in
203
- * control of their output folders. Returns `null` when grouping is disabled, matching the
204
- * per-plugin convention.
205
- *
206
- * @param group - The user-supplied group option, or `undefined` to disable grouping.
207
- *
208
- * @example
209
- * ```ts
210
- * createGroupConfig(group) // shared across every plugin
211
- * ```
212
- */
213
- function createGroupConfig(group) {
214
- if (!group) return null;
215
- const defaultName = (ctx) => {
216
- if (group.type === "path") return `${ctx.group.split("/")[1]}`;
217
- return camelCase(ctx.group);
218
- };
219
- return {
220
- ...group,
221
- name: group.name ? group.name : defaultName
222
- };
389
+ // src/utils.ts
390
+ function toSimpleStyle(value) {
391
+ if (Array.isArray(value)) return value.join(",");
392
+ if (typeof value === "object" && value !== null) return Object.entries(value).flat().join(",");
393
+ return String(value);
223
394
  }
224
- // kubb-extension.json
225
- var compatibility = {
226
- "kubb": ">=5.16.0",
227
- "node": ">=22.22"
228
- };
229
- // package.json
230
- var version = "6.2.0-canary.20261111T122007";
231
- // src/generators/handlersGenerator.ts
232
395
  /**
233
- * Aggregate generator enabled by `pluginMsw({ handlers: true })`. Emits a
234
- * `handlers.ts` file that re-exports every generated handler in operation
235
- * order, ready to spread into `setupServer(...handlers)` or
236
- * `setupWorker(...handlers)`.
396
+ * The declared headers a mock sends, each with its example's `serializedValue` or `value`, else its schema's `default` or first example.
237
397
  */
238
- const handlersGenerator = defineGenerator({
239
- name: "plugin-msw",
240
- operations(nodes, ctx) {
241
- const { resolver, config, root } = ctx;
242
- const { output, group } = ctx.options;
243
- const handlersName = resolver.handler.listName();
244
- const createHandlersName = resolver.resolveName({
245
- key: ["handlers", "create"],
246
- name: camelCase(handlersName, { prefix: "create" })
247
- });
248
- const file = resolver.file({
249
- name: handlersName,
250
- extname: ".ts",
251
- root,
252
- output,
253
- group: group ?? void 0
254
- });
255
- const handlerNames = nodes.map((node) => resolver.handler.name(node));
256
- const imports = nodes.map((node, index) => {
257
- const operationName = handlerNames[index];
258
- const operationFile = resolver.file({
259
- name: resolver.name(node.operationId),
260
- extname: ".ts",
261
- tag: node.tags[0] ?? "default",
262
- path: node.path,
263
- root,
264
- output,
265
- group: group ?? void 0
266
- });
267
- return ast.factory.createImport({
268
- name: [operationName],
269
- root: file.path,
270
- path: operationFile.path
271
- });
272
- });
273
- const handlers = handlerNames.map((name) => `${name}(undefined, options)`);
274
- return [ast.factory.createFile({
275
- baseName: file.baseName,
276
- path: file.path,
277
- meta: file.meta,
278
- banner: resolver.default.banner(ctx.meta, {
279
- output,
280
- config,
281
- file: {
282
- path: file.path,
283
- baseName: file.baseName
284
- }
285
- }),
286
- footer: resolver.default.footer(ctx.meta, {
287
- output,
288
- config,
289
- file: {
290
- path: file.path,
291
- baseName: file.baseName
292
- }
293
- }),
294
- imports,
295
- sources: [ast.factory.createSource({
296
- name: createHandlersName,
297
- isIndexable: true,
298
- isExportable: true,
299
- nodes: [ast.factory.createText(`export function ${createHandlersName}(options?: { baseURL?: string }) {
300
- return [${handlers.join(", ")}] as const
301
- }`)]
302
- }), ast.factory.createSource({
303
- name: handlersName,
304
- isIndexable: true,
305
- isExportable: true,
306
- nodes: [ast.factory.createText(`export const ${handlersName} = ${createHandlersName}()`)]
307
- })]
308
- })];
309
- }
310
- });
311
- // src/utils.ts
398
+ function getMockHeaders(response) {
399
+ return (response?.headers ?? []).flatMap((header) => {
400
+ if (header.name.toLowerCase() === "content-type") return [];
401
+ const example = header.examples?.find((entry) => entry.serializedValue !== void 0 || entry.value !== void 0);
402
+ const headerValue = example?.serializedValue ?? example?.value ?? header.schema.default ?? header.schema.examples?.[0];
403
+ if (headerValue === void 0 || headerValue === null) return [];
404
+ const headerText = toSimpleStyle(headerValue);
405
+ return /^[\t\x20-\x7E\x80-\xFF]*$/.test(headerText) ? [[header.name, headerText]] : [];
406
+ });
407
+ }
312
408
  /**
313
409
  * Returns the base URL as source code. A `${...}` value stays a template literal.
314
410
  */
@@ -336,13 +432,12 @@ function hasResponseSchema(response) {
336
432
  * content types, JSON is preferred (the faker mock body is JSON), otherwise the first declared type.
337
433
  */
338
434
  function getResponseContentType(response) {
339
- const contents = response?.content ?? [];
340
- const value = (contents.find((entry) => isJson(entry.contentType)) ?? contents[0])?.contentType;
341
- return typeof value === "string" && value.length > 0 ? value : null;
435
+ const contentType = getResponseEntry(response)?.contentType;
436
+ return typeof contentType === "string" && contentType.length > 0 ? contentType : null;
342
437
  }
343
- function isJson(contentType) {
344
- const baseType = contentType?.split(";")[0]?.trim().toLowerCase();
345
- return baseType === "application/json" || Boolean(baseType?.endsWith("+json"));
438
+ function getResponseEntry(response) {
439
+ const contents = response?.content ?? [];
440
+ return contents.find((entry) => ast.mediaType.isJson(entry)) ?? contents[0];
346
441
  }
347
442
  /**
348
443
  * The first content type of a response that the client reads as text, such as `application/xml`, or `null` when none is.
@@ -350,7 +445,7 @@ function isJson(contentType) {
350
445
  function getTextContentType(response) {
351
446
  return (response?.content ?? []).find((entry) => {
352
447
  const schema = toResponseContent(entry, response?.content).schema;
353
- return !isJson(entry.contentType) && schema && ast.resolveRef(schema).primitive === "string";
448
+ return !ast.mediaType.isJson(entry) && schema && ast.ref.getSchema(schema).primitive === "string";
354
449
  })?.contentType ?? null;
355
450
  }
356
451
  /**
@@ -359,40 +454,88 @@ function getTextContentType(response) {
359
454
  */
360
455
  function responseReturn(body, status, response) {
361
456
  const contentType = getContentType(response);
457
+ const headers = getMockHeaders(response);
458
+ const name = isValidVarName(body) ? body : "body";
459
+ const prelude = name === body ? "" : `const body = ${body}\n `;
460
+ if (contentType && ast.mediaType.getBase(contentType) === "text/event-stream") {
461
+ const entry = getResponseEntry(response);
462
+ const serialized = entry?.examples?.find((example) => example.serializedValue !== void 0)?.serializedValue;
463
+ const fallback = serialized === void 0 ? "''" : toLiteral(serialized);
464
+ if (entry?.stream?.shape !== "event") return `${prelude}return ${responseLiteral(`${name} === undefined ? ${fallback} : \`\${${eventDataLines(name)}}\\n\\n\``, status, contentType, headers)}`;
465
+ return `${prelude}const event = ${name} as { event?: unknown; id?: unknown; retry?: unknown; data?: unknown } | undefined
466
+ const lines = [
467
+ ...(['event', 'id', 'retry'] as const).filter((field) => event?.[field] !== undefined).map((field) => \`\${field}: \${event?.[field]}\`),
468
+ // A client dispatches an event only once it has a \`data\` line, so an event without data still sends an empty one.
469
+ event?.data === undefined ? 'data:' : ${eventDataLines("event.data")},
470
+ ]
471
+
472
+ return ${responseLiteral(`event === undefined ? ${fallback} : \`\${lines.join('\\n')}\\n\\n\``, status, contentType, headers)}`;
473
+ }
362
474
  const textContentType = hasResponseSchema(response) ? getTextContentType(response) : null;
363
- if (!textContentType) return `return ${responseLiteral(`JSON.stringify(${body})`, status, contentType)}`;
364
- const name = /^\w+$/.test(body) ? body : "body";
365
- return `${name === body ? "" : `const body = ${body}\n `}if (typeof ${name} === 'string') return ${responseLiteral(name, status, textContentType)}
475
+ if (!textContentType) return `return ${responseLiteral(`JSON.stringify(${body})`, status, contentType, headers)}`;
476
+ return `${prelude}if (typeof ${name} === 'string') return ${responseLiteral(name, status, textContentType, headers)}
366
477
 
367
- return ${responseLiteral(`JSON.stringify(${name})`, status, contentType)}`;
478
+ return ${responseLiteral(`JSON.stringify(${name})`, status, contentType, headers)}`;
479
+ }
480
+ /**
481
+ * The `data:` lines of one Server-Sent Event. A string goes out as it is, since a client keeps string `data` as text.
482
+ */
483
+ function eventDataLines(body) {
484
+ return `(typeof ${body} === 'string' ? ${body} : JSON.stringify(${body})).split('\\n').map((line) => \`data: \${line}\`).join('\\n')`;
368
485
  }
369
486
  /**
370
- * Converts an HTTP method to its lowercase MSW equivalent (e.g., 'POST' → 'post'). MSW has no
371
- * `http.query`, so a QUERY operation maps to `http.all` and its handler checks the method itself.
487
+ * The spec example of the response entry the mock sends, as source code, or `null` when that entry has none.
488
+ */
489
+ function getExampleBody(response) {
490
+ const entry = getResponseEntry(response);
491
+ const example = entry?.examples?.find((item) => item.value !== void 0 || item.serializedValue !== void 0);
492
+ if (!entry || !example) return null;
493
+ if (example.value !== void 0) return toLiteral(example.value);
494
+ if (!ast.mediaType.isJson(entry)) return ast.mediaType.getBase(entry) !== "text/event-stream" && getTextContentType(response) === entry.contentType ? toLiteral(example.serializedValue) : null;
495
+ try {
496
+ return toLiteral(JSON.parse(example.serializedValue));
497
+ } catch {
498
+ return null;
499
+ }
500
+ }
501
+ const mswMethods = /* @__PURE__ */ new Set([
502
+ "GET",
503
+ "POST",
504
+ "PUT",
505
+ "PATCH",
506
+ "DELETE",
507
+ "HEAD",
508
+ "OPTIONS"
509
+ ]);
510
+ /**
511
+ * Converts an HTTP method to its lowercase MSW equivalent (e.g., 'POST' → 'post'). MSW has no `http.query`, `http.trace` or
512
+ * handler for a custom `additionalOperations` method, so such an operation maps to `http.all` and its handler checks the
513
+ * method itself.
372
514
  */
373
515
  function getMswMethod(node) {
374
- return node.method === "QUERY" ? "all" : node.method.toLowerCase();
516
+ return mswMethods.has(node.method) ? node.method.toLowerCase() : "all";
375
517
  }
376
518
  /**
377
- * The `headers` entry of a mocked `Response` init, or an empty string when there is no content type.
519
+ * The `headers` entry of a mocked `Response` init, or an empty string when there is no header to send.
378
520
  */
379
- function headersBlock(contentType) {
380
- return contentType ? `\n headers: {\n 'Content-Type': '${contentType}'\n },` : "";
521
+ function headersBlock(contentType, headers) {
522
+ const entries = [...contentType ? [`'Content-Type': ${text.quote(contentType)}`] : [], ...headers.map(([name, value]) => `${text.quote(name)}: ${text.quote(value)}`)];
523
+ return entries.length ? `\n headers: {\n ${entries.join(",\n ")}\n },` : "";
381
524
  }
382
525
  /**
383
526
  * The `new HttpResponse<string>(...)` expression a mocked handler returns.
384
527
  */
385
- function responseLiteral(body, status, contentType) {
528
+ function responseLiteral(body, status, contentType, headers) {
386
529
  return `new HttpResponse<string>(${body}, {
387
- status: ${status},${headersBlock(contentType)}
530
+ status: ${status},${headersBlock(contentType, headers)}
388
531
  })`;
389
532
  }
390
533
  // src/components/Mock.tsx
391
- function Mock({ baseURL = "", name, fakerName, typeName, requestTypeName, node }) {
534
+ function Mock({ baseURL = "", name, fakerName, responseName, typeName, requestTypeName, node }) {
392
535
  const method = getMswMethod(node);
393
- const methodGuard = node.method === "QUERY" ? `if (info.request.method !== 'QUERY') return\n\n ` : "";
394
- const successResponse = getPrimarySuccessResponse(node);
395
- const statusCode = successResponse ? Number(successResponse.statusCode) : 200;
536
+ const methodGuard = method === "all" ? `if (info.request.method !== ${text.quote(node.method)}) return\n\n ` : "";
537
+ const successResponse = ast.http.responses.findPrimary(node);
538
+ const statusCode = successResponse ? ast.http.status.toCode(successResponse) : 200;
396
539
  const url = Url.toPath(node.path);
397
540
  const dataType = hasResponseSchema(successResponse) ? typeName : "string | number | boolean | null | object";
398
541
  const paramType = fakerName ? typeName : dataType;
@@ -400,7 +543,10 @@ function Mock({ baseURL = "", name, fakerName, typeName, requestTypeName, node }
400
543
  const params = `data?: ${paramType} | ${requestBodyType ? `HttpResponseResolver<Record<string, string>, ${requestBodyType}>` : `((info: Parameters<Parameters<typeof http.${method}>[1]>[0]) => Response | Promise<Response>)`}, options?: { baseURL?: string }`;
401
544
  const httpCall = requestBodyType ? `http.${method}<Record<string, string>, ${requestBodyType}>` : `http.${method}`;
402
545
  const urlLiteral = `\`\${options?.baseURL ?? ${resolveDefaultBaseURL(baseURL)}}${url.replace(/([^/]):/g, "$1\\\\:")}\``;
403
- const responseBody = fakerName ? `data ?? ${fakerName}(data)` : "data";
546
+ const example = fakerName ? null : getExampleBody(successResponse);
547
+ const exampleBody = example ? `data === undefined ? ${example} : data` : "data";
548
+ const responseBody = fakerName ? `data ?? ${fakerName}()` : exampleBody;
549
+ const responseStatements = responseName && !example ? `return ${responseName}(${responseBody})` : responseReturn(responseBody, statusCode, successResponse);
404
550
  return /* @__PURE__ */ jsx(File.Source, {
405
551
  name,
406
552
  isIndexable: true,
@@ -412,16 +558,15 @@ function Mock({ baseURL = "", name, fakerName, typeName, requestTypeName, node }
412
558
  children: `return ${httpCall}(${urlLiteral}, function handler(info) {
413
559
  ${methodGuard}if(typeof data === 'function') return data(info)
414
560
 
415
- ${responseReturn(responseBody, statusCode, successResponse)}
561
+ ${responseStatements}
416
562
  })`
417
563
  })
418
564
  });
419
565
  }
420
566
  // src/components/Response.tsx
421
567
  function Response({ name, typeName, response }) {
422
- const statusCode = Number(response.statusCode);
423
- const params = `data${hasResponseSchema(response) ? "" : "?"}: ${typeName}`;
424
- const responseName = `${name}Response${statusCode}`;
568
+ const params = `data?: ${typeName}`;
569
+ const responseName = `${name}Response${response.statusCode}`;
425
570
  return /* @__PURE__ */ jsx(File.Source, {
426
571
  name: responseName,
427
572
  isIndexable: true,
@@ -431,7 +576,7 @@ function Response({ name, typeName, response }) {
431
576
  export: true,
432
577
  params,
433
578
  children: `
434
- ${responseReturn("data", statusCode, response)}`
579
+ ${responseReturn("data", ast.http.status.toCode(response), response)}`
435
580
  })
436
581
  });
437
582
  }
@@ -446,7 +591,7 @@ const mswGenerator = defineGenerator({
446
591
  name: "msw",
447
592
  renderer: jsxRenderer,
448
593
  operation(node, ctx) {
449
- if (!ast.isHttpOperationNode(node)) return null;
594
+ if (!ast.http.isOperation(node)) return null;
450
595
  const { driver, resolver, config, root } = ctx;
451
596
  const { output, parser, baseURL, group } = ctx.options;
452
597
  const fileName = resolver.name(node.operationId);
@@ -488,8 +633,8 @@ const mswGenerator = defineGenerator({
488
633
  }),
489
634
  responseName: tsResolver.response.response(node)
490
635
  };
491
- const successResponses = getOperationSuccessResponses(node);
492
- const referencedNames = node.responses.flatMap((response) => (response.content ?? []).flatMap((entry) => entry.schema ? ast.collectImportedRefNames(entry.schema) : []));
636
+ const successResponses = ast.http.responses.filterSuccess(node);
637
+ const referencedNames = ast.http.responses.getImportedNames(node.responses);
493
638
  const enumOptions = pluginTs.options?.enum;
494
639
  const enumNames = getEnumNames(ctx);
495
640
  const hasResponseNameCollision = referencedNames.some((name) => {
@@ -500,14 +645,15 @@ const mswGenerator = defineGenerator({
500
645
  isEnum: enumNames.has(name)
501
646
  }) === type.responseName;
502
647
  });
503
- const types = resolveResponseTypes(node, tsResolver).map(([code, typeName]) => {
504
- const successResponse = successResponses.find((response) => response.statusCode === String(code));
505
- return [code, hasResponseNameCollision && successResponse ? tsResolver.response.status(node, successResponse.statusCode) : typeName];
648
+ const types = node.responses.filter((response) => response.statusCode !== "default" && ast.http.status.getClass(response) !== 1).map((response) => {
649
+ return [response, ast.http.status.isSuccess(response) && !hasResponseNameCollision ? type.responseName : tsResolver.response.status(node, response.statusCode)];
506
650
  });
507
- const responseTypeNames = types.map(([, typeName]) => typeName).filter((name) => !hasResponseNameCollision || name !== type.responseName);
651
+ const responseTypeNames = types.map(([, typeName]) => typeName);
508
652
  const mockResponseName = hasResponseNameCollision && successResponses[0] ? tsResolver.response.status(node, successResponses[0].statusCode) : type.responseName;
509
653
  const hasSuccessSchema = successResponses.some((response) => hasResponseSchema(response));
510
- const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.response.body(node) : null;
654
+ const primaryResponse = ast.http.responses.findPrimary(node);
655
+ const responseHelperName = primaryResponse && hasResponseSchema(primaryResponse) && types.some(([response, typeName]) => response.statusCode === primaryResponse.statusCode && typeName === mockResponseName) ? `${mock.name}Response${primaryResponse.statusCode}` : void 0;
656
+ const requestName = ast.http.requestBody.getSchema(node) ? tsResolver.response.body(node) : null;
511
657
  return /* @__PURE__ */ jsxs(File, {
512
658
  baseName: mock.file.baseName,
513
659
  path: mock.file.path,
@@ -558,20 +704,17 @@ const mswGenerator = defineGenerator({
558
704
  root: mock.file.path,
559
705
  path: faker.file.path
560
706
  }),
561
- types.filter(([code]) => code !== "default").map(([code, typeName]) => {
562
- const response = node.responses.find((item) => item.statusCode === String(code));
563
- if (!response) return null;
564
- return /* @__PURE__ */ jsx(Response, {
565
- typeName,
566
- response,
567
- name: mock.name
568
- }, typeName);
569
- }),
707
+ types.map(([response, typeName]) => /* @__PURE__ */ jsx(Response, {
708
+ typeName,
709
+ response,
710
+ name: mock.name
711
+ }, response.statusCode)),
570
712
  /* @__PURE__ */ jsx(Mock, {
571
713
  name: mock.name,
572
714
  typeName: mockResponseName,
573
715
  requestTypeName: requestName,
574
716
  fakerName: faker && hasSuccessSchema ? faker.name : void 0,
717
+ responseName: responseHelperName,
575
718
  node,
576
719
  baseURL
577
720
  })
@@ -598,7 +741,7 @@ const mswGenerator = defineGenerator({
598
741
  const resolverMsw = createResolver({
599
742
  pluginName: "plugin-msw",
600
743
  name(name) {
601
- return this.memo(`name:${name}`, () => camelCase(name, { suffix: "handler" }));
744
+ return this.memo(`name:${name}`, () => casing.camel(name, { suffix: "handler" }));
602
745
  },
603
746
  handler: {
604
747
  name(node) {
@@ -673,7 +816,7 @@ const pluginMsw = defineExtension({
673
816
  output,
674
817
  parser,
675
818
  baseURL,
676
- group: createGroupConfig(group),
819
+ group: group ?? null,
677
820
  exclude,
678
821
  include,
679
822
  override,