@kubb/plugin-msw 6.2.0-canary.20261111T115959 → 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,42 +359,15 @@ 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
170
365
  * it is, so two of them never share an allocation even when a custom resolver gives both the same
171
366
  * name. Leave `scope` empty for the operation's own declaration.
172
367
  *
368
+ * The key starts from the operation's input `pointer`, which macros keep, so a plugin whose macro
369
+ * rewrites the method or path still resolves another plugin's names for the same operation.
370
+ *
173
371
  * @example
174
372
  * ```ts
175
373
  * resolver.resolveName({ key: operationKey(node, 'response', 'status', 404), name })
@@ -178,8 +376,7 @@ function resolveResponseTypes(node, resolver) {
178
376
  function operationKey(node, ...scope) {
179
377
  return [
180
378
  "operation",
181
- node.method,
182
- node.path,
379
+ ...node.pointer ? [node.pointer] : [node.method, node.path],
183
380
  ...scope.map(String)
184
381
  ];
185
382
  }
@@ -189,124 +386,25 @@ function operationKey(node, ...scope) {
189
386
  function getEnumNames(ctx) {
190
387
  return ctx.memo("enumNames:set", () => new Set((ctx.driver.inputNode?.schemas ?? []).flatMap((schema) => schema.type === "enum" && schema.name ? [schema.name] : [])));
191
388
  }
192
- // ../../internals/shared/src/group.ts
193
- /**
194
- * Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
195
- * shared default naming so every plugin groups output consistently:
196
- *
197
- * - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
198
- * - other groups use the camelCased group (`pet store` → `petStore`).
199
- *
200
- * A user-provided `group.name` always wins over the default namer, so callers stay in
201
- * control of their output folders. Returns `null` when grouping is disabled, matching the
202
- * per-plugin convention.
203
- *
204
- * @param group - The user-supplied group option, or `undefined` to disable grouping.
205
- *
206
- * @example
207
- * ```ts
208
- * createGroupConfig(group) // shared across every plugin
209
- * ```
210
- */
211
- function createGroupConfig(group) {
212
- if (!group) return null;
213
- const defaultName = (ctx) => {
214
- if (group.type === "path") return `${ctx.group.split("/")[1]}`;
215
- return camelCase(ctx.group);
216
- };
217
- return {
218
- ...group,
219
- name: group.name ? group.name : defaultName
220
- };
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);
221
394
  }
222
- // kubb-extension.json
223
- var compatibility = {
224
- "kubb": ">=5.16.0",
225
- "node": ">=22.22"
226
- };
227
- // package.json
228
- var version = "6.2.0-canary.20261111T115959";
229
- // src/generators/handlersGenerator.ts
230
395
  /**
231
- * Aggregate generator enabled by `pluginMsw({ handlers: true })`. Emits a
232
- * `handlers.ts` file that re-exports every generated handler in operation
233
- * order, ready to spread into `setupServer(...handlers)` or
234
- * `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.
235
397
  */
236
- const handlersGenerator = defineGenerator({
237
- name: "plugin-msw",
238
- operations(nodes, ctx) {
239
- const { resolver, config, root } = ctx;
240
- const { output, group } = ctx.options;
241
- const handlersName = resolver.handler.listName();
242
- const createHandlersName = resolver.resolveName({
243
- key: ["handlers", "create"],
244
- name: camelCase(handlersName, { prefix: "create" })
245
- });
246
- const file = resolver.file({
247
- name: handlersName,
248
- extname: ".ts",
249
- root,
250
- output,
251
- group: group ?? void 0
252
- });
253
- const handlerNames = nodes.map((node) => resolver.handler.name(node));
254
- const imports = nodes.map((node, index) => {
255
- const operationName = handlerNames[index];
256
- const operationFile = resolver.file({
257
- name: resolver.name(node.operationId),
258
- extname: ".ts",
259
- tag: node.tags[0] ?? "default",
260
- path: node.path,
261
- root,
262
- output,
263
- group: group ?? void 0
264
- });
265
- return ast.factory.createImport({
266
- name: [operationName],
267
- root: file.path,
268
- path: operationFile.path
269
- });
270
- });
271
- const handlers = handlerNames.map((name) => `${name}(undefined, options)`);
272
- return [ast.factory.createFile({
273
- baseName: file.baseName,
274
- path: file.path,
275
- meta: file.meta,
276
- banner: resolver.default.banner(ctx.meta, {
277
- output,
278
- config,
279
- file: {
280
- path: file.path,
281
- baseName: file.baseName
282
- }
283
- }),
284
- footer: resolver.default.footer(ctx.meta, {
285
- output,
286
- config,
287
- file: {
288
- path: file.path,
289
- baseName: file.baseName
290
- }
291
- }),
292
- imports,
293
- sources: [ast.factory.createSource({
294
- name: createHandlersName,
295
- isIndexable: true,
296
- isExportable: true,
297
- nodes: [ast.factory.createText(`export function ${createHandlersName}(options?: { baseURL?: string }) {
298
- return [${handlers.join(", ")}] as const
299
- }`)]
300
- }), ast.factory.createSource({
301
- name: handlersName,
302
- isIndexable: true,
303
- isExportable: true,
304
- nodes: [ast.factory.createText(`export const ${handlersName} = ${createHandlersName}()`)]
305
- })]
306
- })];
307
- }
308
- });
309
- // 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
+ }
310
408
  /**
311
409
  * Returns the base URL as source code. A `${...}` value stays a template literal.
312
410
  */
@@ -334,13 +432,12 @@ function hasResponseSchema(response) {
334
432
  * content types, JSON is preferred (the faker mock body is JSON), otherwise the first declared type.
335
433
  */
336
434
  function getResponseContentType(response) {
337
- const contents = response?.content ?? [];
338
- const value = (contents.find((entry) => isJson(entry.contentType)) ?? contents[0])?.contentType;
339
- 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;
340
437
  }
341
- function isJson(contentType) {
342
- const baseType = contentType?.split(";")[0]?.trim().toLowerCase();
343
- 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];
344
441
  }
345
442
  /**
346
443
  * The first content type of a response that the client reads as text, such as `application/xml`, or `null` when none is.
@@ -348,7 +445,7 @@ function isJson(contentType) {
348
445
  function getTextContentType(response) {
349
446
  return (response?.content ?? []).find((entry) => {
350
447
  const schema = toResponseContent(entry, response?.content).schema;
351
- return !isJson(entry.contentType) && schema && ast.resolveRef(schema).primitive === "string";
448
+ return !ast.mediaType.isJson(entry) && schema && ast.ref.getSchema(schema).primitive === "string";
352
449
  })?.contentType ?? null;
353
450
  }
354
451
  /**
@@ -357,40 +454,88 @@ function getTextContentType(response) {
357
454
  */
358
455
  function responseReturn(body, status, response) {
359
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
+ }
360
474
  const textContentType = hasResponseSchema(response) ? getTextContentType(response) : null;
361
- if (!textContentType) return `return ${responseLiteral(`JSON.stringify(${body})`, status, contentType)}`;
362
- const name = /^\w+$/.test(body) ? body : "body";
363
- 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)}
364
477
 
365
- 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')`;
366
485
  }
367
486
  /**
368
- * Converts an HTTP method to its lowercase MSW equivalent (e.g., 'POST' → 'post'). MSW has no
369
- * `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.
370
514
  */
371
515
  function getMswMethod(node) {
372
- return node.method === "QUERY" ? "all" : node.method.toLowerCase();
516
+ return mswMethods.has(node.method) ? node.method.toLowerCase() : "all";
373
517
  }
374
518
  /**
375
- * 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.
376
520
  */
377
- function headersBlock(contentType) {
378
- 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 },` : "";
379
524
  }
380
525
  /**
381
526
  * The `new HttpResponse<string>(...)` expression a mocked handler returns.
382
527
  */
383
- function responseLiteral(body, status, contentType) {
528
+ function responseLiteral(body, status, contentType, headers) {
384
529
  return `new HttpResponse<string>(${body}, {
385
- status: ${status},${headersBlock(contentType)}
530
+ status: ${status},${headersBlock(contentType, headers)}
386
531
  })`;
387
532
  }
388
533
  // src/components/Mock.tsx
389
- function Mock({ baseURL = "", name, fakerName, typeName, requestTypeName, node }) {
534
+ function Mock({ baseURL = "", name, fakerName, responseName, typeName, requestTypeName, node }) {
390
535
  const method = getMswMethod(node);
391
- const methodGuard = node.method === "QUERY" ? `if (info.request.method !== 'QUERY') return\n\n ` : "";
392
- const successResponse = getPrimarySuccessResponse(node);
393
- 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;
394
539
  const url = Url.toPath(node.path);
395
540
  const dataType = hasResponseSchema(successResponse) ? typeName : "string | number | boolean | null | object";
396
541
  const paramType = fakerName ? typeName : dataType;
@@ -398,7 +543,10 @@ function Mock({ baseURL = "", name, fakerName, typeName, requestTypeName, node }
398
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 }`;
399
544
  const httpCall = requestBodyType ? `http.${method}<Record<string, string>, ${requestBodyType}>` : `http.${method}`;
400
545
  const urlLiteral = `\`\${options?.baseURL ?? ${resolveDefaultBaseURL(baseURL)}}${url.replace(/([^/]):/g, "$1\\\\:")}\``;
401
- 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);
402
550
  return /* @__PURE__ */ jsx(File.Source, {
403
551
  name,
404
552
  isIndexable: true,
@@ -410,16 +558,15 @@ function Mock({ baseURL = "", name, fakerName, typeName, requestTypeName, node }
410
558
  children: `return ${httpCall}(${urlLiteral}, function handler(info) {
411
559
  ${methodGuard}if(typeof data === 'function') return data(info)
412
560
 
413
- ${responseReturn(responseBody, statusCode, successResponse)}
561
+ ${responseStatements}
414
562
  })`
415
563
  })
416
564
  });
417
565
  }
418
566
  // src/components/Response.tsx
419
567
  function Response({ name, typeName, response }) {
420
- const statusCode = Number(response.statusCode);
421
- const params = `data${hasResponseSchema(response) ? "" : "?"}: ${typeName}`;
422
- const responseName = `${name}Response${statusCode}`;
568
+ const params = `data?: ${typeName}`;
569
+ const responseName = `${name}Response${response.statusCode}`;
423
570
  return /* @__PURE__ */ jsx(File.Source, {
424
571
  name: responseName,
425
572
  isIndexable: true,
@@ -429,7 +576,7 @@ function Response({ name, typeName, response }) {
429
576
  export: true,
430
577
  params,
431
578
  children: `
432
- ${responseReturn("data", statusCode, response)}`
579
+ ${responseReturn("data", ast.http.status.toCode(response), response)}`
433
580
  })
434
581
  });
435
582
  }
@@ -444,7 +591,7 @@ const mswGenerator = defineGenerator({
444
591
  name: "msw",
445
592
  renderer: jsxRenderer,
446
593
  operation(node, ctx) {
447
- if (!ast.isHttpOperationNode(node)) return null;
594
+ if (!ast.http.isOperation(node)) return null;
448
595
  const { driver, resolver, config, root } = ctx;
449
596
  const { output, parser, baseURL, group } = ctx.options;
450
597
  const fileName = resolver.name(node.operationId);
@@ -486,8 +633,8 @@ const mswGenerator = defineGenerator({
486
633
  }),
487
634
  responseName: tsResolver.response.response(node)
488
635
  };
489
- const successResponses = getOperationSuccessResponses(node);
490
- 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);
491
638
  const enumOptions = pluginTs.options?.enum;
492
639
  const enumNames = getEnumNames(ctx);
493
640
  const hasResponseNameCollision = referencedNames.some((name) => {
@@ -498,14 +645,15 @@ const mswGenerator = defineGenerator({
498
645
  isEnum: enumNames.has(name)
499
646
  }) === type.responseName;
500
647
  });
501
- const types = resolveResponseTypes(node, tsResolver).map(([code, typeName]) => {
502
- const successResponse = successResponses.find((response) => response.statusCode === String(code));
503
- 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)];
504
650
  });
505
- const responseTypeNames = types.map(([, typeName]) => typeName).filter((name) => !hasResponseNameCollision || name !== type.responseName);
651
+ const responseTypeNames = types.map(([, typeName]) => typeName);
506
652
  const mockResponseName = hasResponseNameCollision && successResponses[0] ? tsResolver.response.status(node, successResponses[0].statusCode) : type.responseName;
507
653
  const hasSuccessSchema = successResponses.some((response) => hasResponseSchema(response));
508
- 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;
509
657
  return /* @__PURE__ */ jsxs(File, {
510
658
  baseName: mock.file.baseName,
511
659
  path: mock.file.path,
@@ -556,20 +704,17 @@ const mswGenerator = defineGenerator({
556
704
  root: mock.file.path,
557
705
  path: faker.file.path
558
706
  }),
559
- types.filter(([code]) => code !== "default").map(([code, typeName]) => {
560
- const response = node.responses.find((item) => item.statusCode === String(code));
561
- if (!response) return null;
562
- return /* @__PURE__ */ jsx(Response, {
563
- typeName,
564
- response,
565
- name: mock.name
566
- }, typeName);
567
- }),
707
+ types.map(([response, typeName]) => /* @__PURE__ */ jsx(Response, {
708
+ typeName,
709
+ response,
710
+ name: mock.name
711
+ }, response.statusCode)),
568
712
  /* @__PURE__ */ jsx(Mock, {
569
713
  name: mock.name,
570
714
  typeName: mockResponseName,
571
715
  requestTypeName: requestName,
572
716
  fakerName: faker && hasSuccessSchema ? faker.name : void 0,
717
+ responseName: responseHelperName,
573
718
  node,
574
719
  baseURL
575
720
  })
@@ -596,7 +741,7 @@ const mswGenerator = defineGenerator({
596
741
  const resolverMsw = createResolver({
597
742
  pluginName: "plugin-msw",
598
743
  name(name) {
599
- return this.memo(`name:${name}`, () => camelCase(name, { suffix: "handler" }));
744
+ return this.memo(`name:${name}`, () => casing.camel(name, { suffix: "handler" }));
600
745
  },
601
746
  handler: {
602
747
  name(node) {
@@ -671,7 +816,7 @@ const pluginMsw = defineExtension({
671
816
  output,
672
817
  parser,
673
818
  baseURL,
674
- group: createGroupConfig(group),
819
+ group: group ?? null,
675
820
  exclude,
676
821
  include,
677
822
  override,