@kubb/plugin-msw 6.2.0-canary.20261111T122007 → 6.2.1

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.1";
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,49 +445,109 @@ 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
  }
451
+ const bigintReplacer = `(_key, value) => {
452
+ if (typeof value !== 'bigint') return value
453
+ if (value > Number.MAX_SAFE_INTEGER || value < Number.MIN_SAFE_INTEGER) throw new TypeError(\`Cannot serialize \${value}n as JSON without losing precision.\`)
454
+ return Number(value)
455
+ }`;
456
+ /** The `JSON.stringify` call for `body`, with a replacer when the response holds an `int64`, which `JSON.stringify` throws on. */
457
+ function stringify(body, response) {
458
+ return (response?.content ?? []).some((entry) => entry.schema && ast.schema.find(entry.schema, {
459
+ predicate: (schema) => schema.type === "bigint",
460
+ followRefs: true
461
+ }) !== void 0) ? `JSON.stringify(${body}, ${bigintReplacer})` : `JSON.stringify(${body})`;
462
+ }
356
463
  /**
357
464
  * The statements that return a mocked `Response` for `body`. A string body for a response the client reads as text goes
358
465
  * out as raw text with that content type, so an XML mock is not sent as a quoted JSON string. Any other body is JSON.
359
466
  */
360
467
  function responseReturn(body, status, response) {
361
468
  const contentType = getContentType(response);
469
+ const headers = getMockHeaders(response);
470
+ const name = isValidVarName(body) ? body : "body";
471
+ const prelude = name === body ? "" : `const body = ${body}\n `;
472
+ if (contentType && ast.mediaType.getBase(contentType) === "text/event-stream") {
473
+ const entry = getResponseEntry(response);
474
+ const serialized = entry?.examples?.find((example) => example.serializedValue !== void 0)?.serializedValue;
475
+ const fallback = serialized === void 0 ? "''" : toLiteral(serialized);
476
+ if (entry?.stream?.shape !== "event") return `${prelude}return ${responseLiteral(`${name} === undefined ? ${fallback} : \`\${${eventDataLines(name, response)}}\\n\\n\``, status, contentType, headers)}`;
477
+ return `${prelude}const event = ${name} as { event?: unknown; id?: unknown; retry?: unknown; data?: unknown } | undefined
478
+ const lines = [
479
+ ...(['event', 'id', 'retry'] as const).filter((field) => event?.[field] !== undefined).map((field) => \`\${field}: \${event?.[field]}\`),
480
+ // A client dispatches an event only once it has a \`data\` line, so an event without data still sends an empty one.
481
+ event?.data === undefined ? 'data:' : ${eventDataLines("event.data", response)},
482
+ ]
483
+
484
+ return ${responseLiteral(`event === undefined ? ${fallback} : \`\${lines.join('\\n')}\\n\\n\``, status, contentType, headers)}`;
485
+ }
362
486
  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)}
487
+ if (!textContentType) return `return ${responseLiteral(stringify(body, response), status, contentType, headers)}`;
488
+ return `${prelude}if (typeof ${name} === 'string') return ${responseLiteral(name, status, textContentType, headers)}
366
489
 
367
- return ${responseLiteral(`JSON.stringify(${name})`, status, contentType)}`;
490
+ return ${responseLiteral(stringify(name, response), status, contentType, headers)}`;
491
+ }
492
+ /**
493
+ * The `data:` lines of one Server-Sent Event. A string goes out as it is, since a client keeps string `data` as text.
494
+ */
495
+ function eventDataLines(body, response) {
496
+ return `(typeof ${body} === 'string' ? ${body} : ${stringify(body, response)}).split('\\n').map((line) => \`data: \${line}\`).join('\\n')`;
368
497
  }
369
498
  /**
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.
499
+ * The spec example of the response entry the mock sends, as source code, or `null` when that entry has none.
500
+ */
501
+ function getExampleBody(response) {
502
+ const entry = getResponseEntry(response);
503
+ const example = entry?.examples?.find((item) => item.value !== void 0 || item.serializedValue !== void 0);
504
+ if (!entry || !example) return null;
505
+ if (example.value !== void 0) return toLiteral(example.value);
506
+ if (!ast.mediaType.isJson(entry)) return ast.mediaType.getBase(entry) !== "text/event-stream" && getTextContentType(response) === entry.contentType ? toLiteral(example.serializedValue) : null;
507
+ try {
508
+ return toLiteral(JSON.parse(example.serializedValue));
509
+ } catch {
510
+ return null;
511
+ }
512
+ }
513
+ const mswMethods = /* @__PURE__ */ new Set([
514
+ "GET",
515
+ "POST",
516
+ "PUT",
517
+ "PATCH",
518
+ "DELETE",
519
+ "HEAD",
520
+ "OPTIONS"
521
+ ]);
522
+ /**
523
+ * Converts an HTTP method to its lowercase MSW equivalent (e.g., 'POST' → 'post'). MSW has no `http.query`, `http.trace` or
524
+ * handler for a custom `additionalOperations` method, so such an operation maps to `http.all` and its handler checks the
525
+ * method itself.
372
526
  */
373
527
  function getMswMethod(node) {
374
- return node.method === "QUERY" ? "all" : node.method.toLowerCase();
528
+ return mswMethods.has(node.method) ? node.method.toLowerCase() : "all";
375
529
  }
376
530
  /**
377
- * The `headers` entry of a mocked `Response` init, or an empty string when there is no content type.
531
+ * The `headers` entry of a mocked `Response` init, or an empty string when there is no header to send.
378
532
  */
379
- function headersBlock(contentType) {
380
- return contentType ? `\n headers: {\n 'Content-Type': '${contentType}'\n },` : "";
533
+ function headersBlock(contentType, headers) {
534
+ const entries = [...contentType ? [`'Content-Type': ${text.quote(contentType)}`] : [], ...headers.map(([name, value]) => `${text.quote(name)}: ${text.quote(value)}`)];
535
+ return entries.length ? `\n headers: {\n ${entries.join(",\n ")}\n },` : "";
381
536
  }
382
537
  /**
383
538
  * The `new HttpResponse<string>(...)` expression a mocked handler returns.
384
539
  */
385
- function responseLiteral(body, status, contentType) {
540
+ function responseLiteral(body, status, contentType, headers) {
386
541
  return `new HttpResponse<string>(${body}, {
387
- status: ${status},${headersBlock(contentType)}
542
+ status: ${status},${headersBlock(contentType, headers)}
388
543
  })`;
389
544
  }
390
545
  // src/components/Mock.tsx
391
- function Mock({ baseURL = "", name, fakerName, typeName, requestTypeName, node }) {
546
+ function Mock({ baseURL = "", name, fakerName, responseName, typeName, requestTypeName, node }) {
392
547
  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;
548
+ const methodGuard = method === "all" ? `if (info.request.method !== ${text.quote(node.method)}) return\n\n ` : "";
549
+ const successResponse = ast.http.responses.findPrimary(node);
550
+ const statusCode = successResponse ? ast.http.status.toCode(successResponse) : 200;
396
551
  const url = Url.toPath(node.path);
397
552
  const dataType = hasResponseSchema(successResponse) ? typeName : "string | number | boolean | null | object";
398
553
  const paramType = fakerName ? typeName : dataType;
@@ -400,7 +555,10 @@ function Mock({ baseURL = "", name, fakerName, typeName, requestTypeName, node }
400
555
  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
556
  const httpCall = requestBodyType ? `http.${method}<Record<string, string>, ${requestBodyType}>` : `http.${method}`;
402
557
  const urlLiteral = `\`\${options?.baseURL ?? ${resolveDefaultBaseURL(baseURL)}}${url.replace(/([^/]):/g, "$1\\\\:")}\``;
403
- const responseBody = fakerName ? `data ?? ${fakerName}(data)` : "data";
558
+ const example = fakerName ? null : getExampleBody(successResponse);
559
+ const exampleBody = example ? `data === undefined ? ${example} : data` : "data";
560
+ const responseBody = fakerName ? `data ?? ${fakerName}()` : exampleBody;
561
+ const responseStatements = responseName && !example ? `return ${responseName}(${responseBody})` : responseReturn(responseBody, statusCode, successResponse);
404
562
  return /* @__PURE__ */ jsx(File.Source, {
405
563
  name,
406
564
  isIndexable: true,
@@ -412,16 +570,15 @@ function Mock({ baseURL = "", name, fakerName, typeName, requestTypeName, node }
412
570
  children: `return ${httpCall}(${urlLiteral}, function handler(info) {
413
571
  ${methodGuard}if(typeof data === 'function') return data(info)
414
572
 
415
- ${responseReturn(responseBody, statusCode, successResponse)}
573
+ ${responseStatements}
416
574
  })`
417
575
  })
418
576
  });
419
577
  }
420
578
  // src/components/Response.tsx
421
579
  function Response({ name, typeName, response }) {
422
- const statusCode = Number(response.statusCode);
423
- const params = `data${hasResponseSchema(response) ? "" : "?"}: ${typeName}`;
424
- const responseName = `${name}Response${statusCode}`;
580
+ const params = `data?: ${typeName}`;
581
+ const responseName = `${name}Response${response.statusCode}`;
425
582
  return /* @__PURE__ */ jsx(File.Source, {
426
583
  name: responseName,
427
584
  isIndexable: true,
@@ -431,7 +588,7 @@ function Response({ name, typeName, response }) {
431
588
  export: true,
432
589
  params,
433
590
  children: `
434
- ${responseReturn("data", statusCode, response)}`
591
+ ${responseReturn("data", ast.http.status.toCode(response), response)}`
435
592
  })
436
593
  });
437
594
  }
@@ -446,7 +603,7 @@ const mswGenerator = defineGenerator({
446
603
  name: "msw",
447
604
  renderer: jsxRenderer,
448
605
  operation(node, ctx) {
449
- if (!ast.isHttpOperationNode(node)) return null;
606
+ if (!ast.http.isOperation(node)) return null;
450
607
  const { driver, resolver, config, root } = ctx;
451
608
  const { output, parser, baseURL, group } = ctx.options;
452
609
  const fileName = resolver.name(node.operationId);
@@ -488,8 +645,8 @@ const mswGenerator = defineGenerator({
488
645
  }),
489
646
  responseName: tsResolver.response.response(node)
490
647
  };
491
- const successResponses = getOperationSuccessResponses(node);
492
- const referencedNames = node.responses.flatMap((response) => (response.content ?? []).flatMap((entry) => entry.schema ? ast.collectImportedRefNames(entry.schema) : []));
648
+ const successResponses = ast.http.responses.filterSuccess(node);
649
+ const referencedNames = ast.http.responses.getImportedNames(node.responses);
493
650
  const enumOptions = pluginTs.options?.enum;
494
651
  const enumNames = getEnumNames(ctx);
495
652
  const hasResponseNameCollision = referencedNames.some((name) => {
@@ -500,14 +657,15 @@ const mswGenerator = defineGenerator({
500
657
  isEnum: enumNames.has(name)
501
658
  }) === type.responseName;
502
659
  });
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];
660
+ const types = node.responses.filter((response) => response.statusCode !== "default" && ast.http.status.getClass(response) !== 1).map((response) => {
661
+ return [response, ast.http.status.isSuccess(response) && !hasResponseNameCollision ? type.responseName : tsResolver.response.status(node, response.statusCode)];
506
662
  });
507
- const responseTypeNames = types.map(([, typeName]) => typeName).filter((name) => !hasResponseNameCollision || name !== type.responseName);
663
+ const responseTypeNames = types.map(([, typeName]) => typeName);
508
664
  const mockResponseName = hasResponseNameCollision && successResponses[0] ? tsResolver.response.status(node, successResponses[0].statusCode) : type.responseName;
509
665
  const hasSuccessSchema = successResponses.some((response) => hasResponseSchema(response));
510
- const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.response.body(node) : null;
666
+ const primaryResponse = ast.http.responses.findPrimary(node);
667
+ const responseHelperName = primaryResponse && hasResponseSchema(primaryResponse) && types.some(([response, typeName]) => response.statusCode === primaryResponse.statusCode && typeName === mockResponseName) ? `${mock.name}Response${primaryResponse.statusCode}` : void 0;
668
+ const requestName = ast.http.requestBody.getSchema(node) ? tsResolver.response.body(node) : null;
511
669
  return /* @__PURE__ */ jsxs(File, {
512
670
  baseName: mock.file.baseName,
513
671
  path: mock.file.path,
@@ -558,20 +716,17 @@ const mswGenerator = defineGenerator({
558
716
  root: mock.file.path,
559
717
  path: faker.file.path
560
718
  }),
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
- }),
719
+ types.map(([response, typeName]) => /* @__PURE__ */ jsx(Response, {
720
+ typeName,
721
+ response,
722
+ name: mock.name
723
+ }, response.statusCode)),
570
724
  /* @__PURE__ */ jsx(Mock, {
571
725
  name: mock.name,
572
726
  typeName: mockResponseName,
573
727
  requestTypeName: requestName,
574
728
  fakerName: faker && hasSuccessSchema ? faker.name : void 0,
729
+ responseName: responseHelperName,
575
730
  node,
576
731
  baseURL
577
732
  })
@@ -598,7 +753,7 @@ const mswGenerator = defineGenerator({
598
753
  const resolverMsw = createResolver({
599
754
  pluginName: "plugin-msw",
600
755
  name(name) {
601
- return this.memo(`name:${name}`, () => camelCase(name, { suffix: "handler" }));
756
+ return this.memo(`name:${name}`, () => casing.camel(name, { suffix: "handler" }));
602
757
  },
603
758
  handler: {
604
759
  name(node) {
@@ -673,7 +828,7 @@ const pluginMsw = defineExtension({
673
828
  output,
674
829
  parser,
675
830
  baseURL,
676
- group: createGroupConfig(group),
831
+ group: group ?? null,
677
832
  exclude,
678
833
  include,
679
834
  override,