@kubb/plugin-msw 5.1.0 → 5.1.3

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.
@@ -0,0 +1,141 @@
1
+ import { Exclude, Group, Include, Output, OutputOptions, Override, PluginType, Resolver, ResolverPatch, ast } from "kubb/kit";
2
+ //#region src/types.d.ts
3
+ /**
4
+ * Resolver for MSW that provides naming methods for handler functions.
5
+ */
6
+ type ResolverMsw = Resolver & {
7
+ /**
8
+ * Naming for generated MSW handlers.
9
+ */
10
+ handler: {
11
+ /**
12
+ * Resolves the handler function name for an operation.
13
+ */
14
+ name(node: ast.OperationNode): string;
15
+ /**
16
+ * Resolves the exported handlers collection name.
17
+ */
18
+ listName(): string;
19
+ };
20
+ };
21
+ /**
22
+ * Where the generated MSW handlers are written and how they are exported, plus the optional
23
+ * `group` strategy. The `group` option organizes `output.mode: 'directory'` output into per-tag or per-path subdirectories.
24
+ *
25
+ * @default { path: 'handlers', barrel: { type: 'named' } }
26
+ */
27
+ type Options = OutputOptions & {
28
+ /**
29
+ * Base URL prepended to every handler's request URL. When omitted, handlers match the
30
+ * operation path only (for example `/pets/:petId`). The adapter's server URL is not used.
31
+ */
32
+ baseURL?: string;
33
+ /**
34
+ * Skip operations matching at least one entry in the list.
35
+ */
36
+ exclude?: Array<Exclude>;
37
+ /**
38
+ * Restrict generation to operations matching at least one entry in the list.
39
+ */
40
+ include?: Array<Include>;
41
+ /**
42
+ * Apply a different options object to operations matching a pattern.
43
+ */
44
+ override?: Array<Override<ResolvedOptions>>;
45
+ /**
46
+ * Override how handler names and file paths are built.
47
+ */
48
+ resolver?: ResolverPatch<ResolverMsw>;
49
+ /**
50
+ * Macros applied to operation nodes before printing.
51
+ */
52
+ macros?: Array<ast.Macro>;
53
+ /**
54
+ * Emit a `handlers.ts` file that re-exports every handler in operation order.
55
+ * Drop the file into `setupServer(...handlers)` or `setupWorker(...handlers)`.
56
+ *
57
+ * @default false
58
+ */
59
+ handlers?: boolean;
60
+ /**
61
+ * Source of the response body returned by each generated handler.
62
+ * - `'data'`: typed empty/example payload, ready for you to fill in from tests.
63
+ * - `'faker'`: value produced by `@kubb/plugin-faker`.
64
+ *
65
+ * @default 'data'
66
+ */
67
+ parser?: 'data' | 'faker';
68
+ };
69
+ type ResolvedOptions = {
70
+ output: Output;
71
+ group: Group | null;
72
+ exclude: NonNullable<Options['exclude']>;
73
+ include: Options['include'];
74
+ override: NonNullable<Options['override']>;
75
+ parser: NonNullable<Options['parser']>;
76
+ baseURL: Options['baseURL'] | undefined;
77
+ handlers: boolean;
78
+ };
79
+ type PluginMsw = PluginType<'plugin-msw', Options, ResolvedOptions, ResolverMsw>;
80
+ declare global {
81
+ namespace Kubb {
82
+ interface PluginRegistry {
83
+ 'plugin-msw': PluginMsw;
84
+ }
85
+ }
86
+ }
87
+ //#endregion
88
+ //#region src/plugin.d.ts
89
+ /**
90
+ * Canonical plugin name for `@kubb/plugin-msw`. Used for driver lookups and
91
+ * cross-plugin dependency references.
92
+ */
93
+ export declare const pluginMswName = "plugin-msw";
94
+ /**
95
+ * Generates MSW request handlers from an OpenAPI spec. Drop them into your
96
+ * test setup or service worker to mock the API end-to-end. Request path,
97
+ * method, status, and response body all stay in sync with the spec. Combine
98
+ * with `@kubb/plugin-faker` (via `parser: 'faker'`) to seed handlers with
99
+ * realistic data.
100
+ *
101
+ * @example
102
+ * ```ts
103
+ * import { defineConfig } from 'kubb/config'
104
+ * import { pluginTs } from '@kubb/plugin-ts'
105
+ * import { pluginMsw } from '@kubb/plugin-msw'
106
+ *
107
+ * export default defineConfig({
108
+ * input: './petStore.yaml',
109
+ * output: { path: './src/gen' },
110
+ * plugins: [
111
+ * pluginTs(),
112
+ * pluginMsw({
113
+ * output: { path: './handlers' },
114
+ * handlers: true,
115
+ * }),
116
+ * ],
117
+ * })
118
+ * ```
119
+ */
120
+ export declare const pluginMsw: import("kubb/kit").PluginFactory<PluginMsw>;
121
+ //#endregion
122
+ //#region src/resolvers/resolverMsw.d.ts
123
+ /**
124
+ * Default resolver used by `@kubb/plugin-msw`. Decides the names and file
125
+ * paths for every generated MSW handler. Function names get a `Handler`
126
+ * suffix; the aggregate export is always `handlers`.
127
+ *
128
+ * The top-level `name` applies the `handler` suffix, and `file` falls back to the built-in
129
+ * `toFilePath` casing. Operation-specific naming is grouped under the `handler` namespace.
130
+ *
131
+ * @example Resolve a handler name
132
+ * ```ts
133
+ * import { resolverMsw } from '@kubb/plugin-msw'
134
+ *
135
+ * resolverMsw.name('addPet') // 'addPetHandler'
136
+ * ```
137
+ */
138
+ export declare const resolverMsw: ResolverMsw;
139
+ //#endregion
140
+ export { type PluginMsw, type ResolverMsw, pluginMsw as default };
141
+ //# sourceMappingURL=index.d.cts.map
package/dist/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { t as __name } from "./rolldown-runtime-C0LytTxp.js";
2
1
  import { Exclude, Group, Include, Output, OutputOptions, Override, PluginType, Resolver, ResolverPatch, ast } from "kubb/kit";
3
2
  //#region src/types.d.ts
4
3
  /**
@@ -76,7 +75,6 @@ type ResolvedOptions = {
76
75
  parser: NonNullable<Options['parser']>;
77
76
  baseURL: Options['baseURL'] | undefined;
78
77
  handlers: boolean;
79
- resolver: ResolverMsw;
80
78
  };
81
79
  type PluginMsw = PluginType<'plugin-msw', Options, ResolvedOptions, ResolverMsw>;
82
80
  declare global {
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
- import "./rolldown-runtime-C0LytTxp.js";
2
- import { Resolver, Url, addGenerator, ast, createResolver, defineExtension, defineGenerator, setMacros, setResolver } from "kubb/kit";
1
+ import { Resolver, Url, addGenerator, assertPluginOption, ast, createResolver, defineExtension, defineGenerator, setMacros, setResolver } from "kubb/kit";
3
2
  import { pluginFakerName } from "@kubb/plugin-faker";
4
- import { createFunctionParameter, createFunctionParameters, functionPrinter, pluginTsName } from "@kubb/plugin-ts";
3
+ import { enumTypeName, pluginTsName } from "@kubb/plugin-ts";
5
4
  import { File, Function, jsxRenderer } from "kubb/jsx";
6
5
  import { jsx, jsxs } from "kubb/jsx/jsx-runtime";
6
+ //#endregion
7
7
  //#region ../../internals/shared/src/operation.ts
8
8
  /**
9
9
  * Builds the `ResolverFileParams` every operation generator passes to
@@ -102,20 +102,6 @@ function camelCase(text, { prefix = "", suffix = "" } = {}) {
102
102
  return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
103
103
  }
104
104
  //#endregion
105
- //#region ../../internals/shared/src/refs.ts
106
- /**
107
- * Collects the resolved target name of every pointer-carrying ref in a schema tree, in
108
- * first-occurrence order. Use this for name-only checks (e.g. redeclaration detection) where
109
- * `resolver.imports` would resolve file paths that are then discarded.
110
- */
111
- function collectRefNames(schema) {
112
- return ast.collectSync(schema, { schema: (node) => {
113
- const refNode = ast.narrowSchema(node, "ref");
114
- if (!refNode?.ref) return null;
115
- return ast.resolveRefName(refNode);
116
- } });
117
- }
118
- //#endregion
119
105
  //#region ../../internals/shared/src/group.ts
120
106
  /**
121
107
  * Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
@@ -150,11 +136,11 @@ function createGroupConfig(group) {
150
136
  //#region kubb-extension.json
151
137
  var compatibility = {
152
138
  "kubb": ">=5.8.3",
153
- "node": ">=22"
139
+ "node": ">=22.12"
154
140
  };
155
141
  //#endregion
156
142
  //#region package.json
157
- var version = "5.1.0";
143
+ var version = "5.1.3";
158
144
  //#endregion
159
145
  //#region src/generators/handlersGenerator.ts
160
146
  /**
@@ -176,8 +162,9 @@ const handlersGenerator = defineGenerator({
176
162
  output,
177
163
  group: group ?? void 0
178
164
  });
179
- const imports = nodes.map((node) => {
180
- const operationName = resolver.handler.name(node);
165
+ const handlerNames = nodes.map((node) => resolver.handler.name(node));
166
+ const imports = nodes.map((node, index) => {
167
+ const operationName = handlerNames[index];
181
168
  const operationFile = resolver.file({
182
169
  name: resolver.name(node.operationId),
183
170
  extname: ".ts",
@@ -193,7 +180,7 @@ const handlersGenerator = defineGenerator({
193
180
  path: operationFile.path
194
181
  });
195
182
  });
196
- const handlers = nodes.map((node) => `${resolver.handler.name(node)}()`);
183
+ const handlers = handlerNames.map((name) => `${name}()`);
197
184
  return [ast.factory.createFile({
198
185
  baseName: file.baseName,
199
186
  path: file.path,
@@ -253,62 +240,41 @@ function getResponseContentType(response) {
253
240
  return typeof value === "string" && value.length > 0 ? value : null;
254
241
  }
255
242
  /**
256
- * Whether the operation uses the HTTP QUERY method (RFC 10008).
257
- */
258
- function isQueryOperation(node) {
259
- return ast.isHttpOperationNode(node) && node.method === "QUERY";
260
- }
261
- /**
262
243
  * Converts an HTTP method to its lowercase MSW equivalent (e.g., 'POST' → 'post'). MSW has no
263
244
  * `http.query`, so a QUERY operation maps to `http.all` and its handler checks the method itself.
264
245
  */
265
246
  function getMswMethod(node) {
266
- if (!ast.isHttpOperationNode(node)) return "";
267
- return isQueryOperation(node) ? "all" : node.method.toLowerCase();
247
+ return node.method === "QUERY" ? "all" : node.method.toLowerCase();
268
248
  }
269
249
  /**
270
- * Resolves faker metadata for an MSW operation, including response name and file path.
250
+ * The `headers` entry of a mocked `Response` init, or an empty string when there is no content type.
271
251
  */
272
- function resolveFakerMeta(node, options) {
273
- const { root, fakerResolver, fakerOutput, fakerGroup } = options;
274
- const tag = node.tags[0] ?? "default";
275
- return {
276
- name: fakerResolver.response.response(node),
277
- file: fakerResolver.file({
278
- name: node.operationId,
279
- extname: ".ts",
280
- tag,
281
- path: node.path,
282
- root,
283
- output: fakerOutput,
284
- group: fakerGroup ?? void 0
285
- })
286
- };
252
+ function headersBlock(contentType) {
253
+ return contentType ? `\n headers: {\n 'Content-Type': '${contentType}'\n },` : "";
254
+ }
255
+ /**
256
+ * The `new Response(...)` expression a mocked handler returns.
257
+ */
258
+ function responseLiteral(body, status, contentType) {
259
+ return `new Response(${body}, {
260
+ status: ${status},${headersBlock(contentType)}
261
+ })`;
287
262
  }
288
263
  //#endregion
289
264
  //#region src/components/Mock.tsx
290
- const declarationPrinter$1 = functionPrinter({ mode: "declaration" });
291
265
  function Mock({ baseURL = "", name, fakerName, typeName, requestTypeName, node }) {
292
266
  const method = getMswMethod(node);
293
- const methodGuard = isQueryOperation(node) ? `if (info.request.method !== 'QUERY') return\n\n ` : "";
267
+ const methodGuard = node.method === "QUERY" ? `if (info.request.method !== 'QUERY') return\n\n ` : "";
294
268
  const successResponse = getPrimarySuccessResponse(node);
295
269
  const statusCode = successResponse ? Number(successResponse.statusCode) : 200;
296
270
  const contentType = getContentType(successResponse);
297
- const url = ast.isHttpOperationNode(node) ? Url.toPath(node.path) : "";
298
- const headers = [contentType ? `'Content-Type': '${contentType}'` : null].filter(Boolean);
271
+ const url = Url.toPath(node.path);
299
272
  const dataType = hasResponseSchema(successResponse) ? typeName : "string | number | boolean | null | object";
300
- const paramType = fakerName ? typeName : dataType;
301
- const callbackType = requestTypeName ? `HttpResponseResolver<Record<string, string>, ${requestTypeName}>` : `((info: Parameters<Parameters<typeof http.${method}>[1]>[0]) => Response | Promise<Response>)`;
302
- const params = declarationPrinter$1.print(createFunctionParameters({ params: [createFunctionParameter({
303
- name: "data",
304
- type: `${paramType} | ${callbackType}`,
305
- optional: true
306
- })] }));
273
+ const params = `data?: ${fakerName ? typeName : dataType} | ${requestTypeName ? `HttpResponseResolver<Record<string, string>, ${requestTypeName}>` : `((info: Parameters<Parameters<typeof http.${method}>[1]>[0]) => Response | Promise<Response>)`}`;
307
274
  const httpCall = requestTypeName ? `http.${method}<Record<string, string>, ${requestTypeName}>` : `http.${method}`;
308
275
  const requestUrl = `${baseURL}${url.replace(/([^/]):/g, "$1\\\\:")}`;
309
276
  const urlLiteral = fakerName ? `'${requestUrl}'` : `\`${requestUrl}\``;
310
277
  const responseBody = fakerName ? `JSON.stringify(data ?? ${fakerName}(data))` : "JSON.stringify(data)";
311
- const headersBlock = headers.length ? `\n headers: {\n ${headers.join(", \n")}\n },` : "";
312
278
  return /* @__PURE__ */ jsx(File.Source, {
313
279
  name,
314
280
  isIndexable: true,
@@ -316,31 +282,22 @@ function Mock({ baseURL = "", name, fakerName, typeName, requestTypeName, node }
316
282
  children: /* @__PURE__ */ jsx(Function, {
317
283
  name,
318
284
  export: true,
319
- params: params ?? "",
285
+ params,
320
286
  children: `return ${httpCall}(${urlLiteral}, function handler(info) {
321
287
  ${methodGuard}if(typeof data === 'function') return data(info)
322
288
 
323
- return new Response(${responseBody}, {
324
- status: ${statusCode},${headersBlock}
325
- })
289
+ return ${responseLiteral(responseBody, statusCode, contentType)}
326
290
  })`
327
291
  })
328
292
  });
329
293
  }
330
294
  //#endregion
331
295
  //#region src/components/Response.tsx
332
- const declarationPrinter = functionPrinter({ mode: "declaration" });
333
296
  function Response({ name, typeName, response }) {
334
297
  const statusCode = Number(response.statusCode);
335
298
  const contentType = getContentType(response);
336
- const headers = [contentType ? `'Content-Type': '${contentType}'` : null].filter(Boolean);
337
- const params = declarationPrinter.print(createFunctionParameters({ params: [createFunctionParameter({
338
- name: "data",
339
- type: typeName,
340
- optional: !hasResponseSchema(response)
341
- })] }));
299
+ const params = `data${hasResponseSchema(response) ? "" : "?"}: ${typeName}`;
342
300
  const responseName = `${name}Response${statusCode}`;
343
- const headersBlock = headers.length ? `\n headers: {\n ${headers.join(", \n")}\n },` : "";
344
301
  return /* @__PURE__ */ jsx(File.Source, {
345
302
  name: responseName,
346
303
  isIndexable: true,
@@ -348,11 +305,9 @@ function Response({ name, typeName, response }) {
348
305
  children: /* @__PURE__ */ jsx(Function, {
349
306
  name: responseName,
350
307
  export: true,
351
- params: params ?? "",
308
+ params,
352
309
  children: `
353
- return new Response(JSON.stringify(data), {
354
- status: ${statusCode},${headersBlock}
355
- })`
310
+ return ${responseLiteral("JSON.stringify(data)", statusCode, contentType)}`
356
311
  })
357
312
  });
358
313
  }
@@ -385,12 +340,19 @@ const mswGenerator = defineGenerator({
385
340
  })
386
341
  };
387
342
  const fakerPlugin = parser === "faker" ? ctx.requirePlugin(pluginFakerName) : null;
388
- const faker = fakerPlugin ? resolveFakerMeta(node, {
389
- root,
390
- fakerResolver: driver.getResolver(pluginFakerName),
391
- fakerOutput: fakerPlugin.options?.output ?? output,
392
- fakerGroup: fakerPlugin.options?.group ?? null
393
- }) : null;
343
+ const fakerResolver = fakerPlugin ? driver.getResolver(pluginFakerName) : null;
344
+ const faker = fakerPlugin && fakerResolver ? {
345
+ name: fakerResolver.response.response(node),
346
+ file: fakerResolver.file({
347
+ name: node.operationId,
348
+ extname: ".ts",
349
+ tag: node.tags[0] ?? "default",
350
+ path: node.path,
351
+ root,
352
+ output: fakerPlugin.options?.output ?? output,
353
+ group: fakerPlugin.options?.group ?? void 0
354
+ })
355
+ } : null;
394
356
  const pluginTs = ctx.requirePlugin(pluginTsName);
395
357
  const tsResolver = driver.getResolver(pluginTsName);
396
358
  const type = {
@@ -405,11 +367,16 @@ const mswGenerator = defineGenerator({
405
367
  responseName: tsResolver.response.response(node)
406
368
  };
407
369
  const successResponses = getOperationSuccessResponses(node);
408
- const referencedNames = node.responses.flatMap((response) => (response.content ?? []).flatMap((entry) => entry.schema ? collectRefNames(entry.schema) : []));
370
+ const referencedNames = node.responses.flatMap((response) => (response.content ?? []).flatMap((entry) => entry.schema ? ast.collectImportedRefNames(entry.schema) : []));
409
371
  const enumOptions = pluginTs.options?.enum;
410
372
  const enumNames = new Set(ctx.meta.enumNames);
411
373
  const hasResponseNameCollision = referencedNames.some((name) => {
412
- return (enumOptions?.type === "asConst" && enumOptions.typeSuffix && enumNames.has(name) ? tsResolver.enum.keyName({ name }, enumOptions.typeSuffix) : tsResolver.name(name)) === type.responseName;
374
+ return enumTypeName({
375
+ resolver: tsResolver,
376
+ name,
377
+ enumOptions,
378
+ isEnum: enumNames.has(name)
379
+ }) === type.responseName;
413
380
  });
414
381
  const types = resolveResponseTypes(node, tsResolver).map(([code, typeName]) => {
415
382
  const successResponse = successResponses.find((response) => response.statusCode === String(code));
@@ -459,7 +426,7 @@ const mswGenerator = defineGenerator({
459
426
  root: mock.file.path,
460
427
  isTypeOnly: true
461
428
  }),
462
- parser === "faker" && faker && hasSuccessSchema && /* @__PURE__ */ jsx(File.Import, {
429
+ faker && hasSuccessSchema && /* @__PURE__ */ jsx(File.Import, {
463
430
  name: [faker.name],
464
431
  root: mock.file.path,
465
432
  path: faker.file.path
@@ -473,17 +440,11 @@ const mswGenerator = defineGenerator({
473
440
  name: mock.name
474
441
  }, typeName);
475
442
  }),
476
- parser === "faker" && faker && hasSuccessSchema ? /* @__PURE__ */ jsx(Mock, {
477
- name: mock.name,
478
- typeName: mockResponseName,
479
- requestTypeName: requestName,
480
- fakerName: faker.name,
481
- node,
482
- baseURL
483
- }) : /* @__PURE__ */ jsx(Mock, {
443
+ /* @__PURE__ */ jsx(Mock, {
484
444
  name: mock.name,
485
445
  typeName: mockResponseName,
486
446
  requestTypeName: requestName,
447
+ fakerName: faker && hasSuccessSchema ? faker.name : void 0,
487
448
  node,
488
449
  baseURL
489
450
  })
@@ -571,6 +532,12 @@ const pluginMsw = defineExtension({
571
532
  path: "handlers",
572
533
  barrel: { type: "named" }
573
534
  }, group, exclude = [], include, override = [], handlers = false, parser = "data", baseURL, resolver: userResolver, macros: userMacros } = options;
535
+ assertPluginOption({
536
+ plugin: pluginMswName,
537
+ option: "parser",
538
+ value: parser,
539
+ allowed: ["data", "faker"]
540
+ });
574
541
  const resolver = userResolver ? Resolver.merge(resolverMsw, userResolver) : resolverMsw;
575
542
  setResolver(resolver);
576
543
  if (userMacros?.length) setMacros(userMacros);
@@ -584,8 +551,7 @@ const pluginMsw = defineExtension({
584
551
  exclude,
585
552
  include,
586
553
  override,
587
- handlers,
588
- resolver
554
+ handlers
589
555
  };
590
556
  }
591
557
  });