@kubb/plugin-msw 5.0.0-beta.84 → 5.0.0-beta.86

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,5 +1,5 @@
1
1
  import "./rolldown-runtime-C0LytTxp.js";
2
- import { ast, defineGenerator, definePlugin, defineResolver } from "kubb/kit";
2
+ import { Resolver, ast, createResolver, defineGenerator, definePlugin } from "kubb/kit";
3
3
  import { pluginFakerName } from "@kubb/plugin-faker";
4
4
  import { createFunctionParameter, createFunctionParameters, functionPrinter, pluginTsName } from "@kubb/plugin-ts";
5
5
  import { File, Function, jsxRenderer } from "kubb/jsx";
@@ -31,31 +31,6 @@ function camelCase(text, { prefix = "", suffix = "" } = {}) {
31
31
  return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
32
32
  }
33
33
  //#endregion
34
- //#region ../../internals/utils/src/fs.ts
35
- /**
36
- * Builds a nested file path from a dotted name. Splits on dots that precede a letter
37
- * (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
38
- * every earlier segment, applies `caseLast` to the final segment, and joins with `/`.
39
- *
40
- * Empty segments are dropped before joining. They arise when the name starts with a dot
41
- * followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
42
- * an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
43
- * absolute path, letting generated files escape the configured output directory.
44
- *
45
- * @example Nested path from a dotted name
46
- * `toFilePath('pet.petId') // 'pet/petId'`
47
- *
48
- * @example PascalCase the final segment
49
- * `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
50
- *
51
- * @example Suffix applied to the final segment only
52
- * `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
53
- */
54
- function toFilePath(name, caseLast = camelCase) {
55
- const parts = name.split(/\.(?=[a-zA-Z])/);
56
- return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
57
- }
58
- //#endregion
59
34
  //#region ../../internals/utils/src/reserved.ts
60
35
  /**
61
36
  * JavaScript and Java reserved words.
@@ -156,6 +131,22 @@ const reservedWords = /* @__PURE__ */ new Set([
156
131
  */
157
132
  function isValidVarName(name) {
158
133
  if (!name || reservedWords.has(name)) return false;
134
+ return isIdentifier(name);
135
+ }
136
+ /**
137
+ * Returns `true` when `name` is syntactically a valid identifier, ignoring reserved words.
138
+ *
139
+ * Reserved words and globals (`class`, `name`, `Date`, …) are valid as bare object-literal keys
140
+ * even though they are not valid variable names, so use this (not {@link isValidVarName}) when
141
+ * deciding whether an object key needs quoting.
142
+ *
143
+ * @example
144
+ * ```ts
145
+ * isIdentifier('name') // true
146
+ * isIdentifier('x-total')// false
147
+ * ```
148
+ */
149
+ function isIdentifier(name) {
159
150
  return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
160
151
  }
161
152
  //#endregion
@@ -292,12 +283,12 @@ function resolveResponseTypes(node, resolver) {
292
283
  const types = [];
293
284
  for (const response of node.responses) {
294
285
  if (response.statusCode === "default") {
295
- types.push(["default", resolver.resolveResponseName(node)]);
286
+ types.push(["default", resolver.response.response(node)]);
296
287
  continue;
297
288
  }
298
289
  const code = getStatusCodeNumber(response.statusCode);
299
290
  if (code === null) continue;
300
- types.push([code, isSuccessStatusCode(code) ? resolver.resolveResponseName(node) : resolver.resolveResponseStatusName(node, response.statusCode)]);
291
+ types.push([code, isSuccessStatusCode(code) ? resolver.response.response(node) : resolver.response.status(node, response.statusCode)]);
301
292
  }
302
293
  return types;
303
294
  }
@@ -345,23 +336,21 @@ const handlersGenerator = defineGenerator({
345
336
  operations(nodes, ctx) {
346
337
  const { resolver, config, root } = ctx;
347
338
  const { output, group } = ctx.options;
348
- const handlersName = resolver.resolveHandlersName();
349
- const file = resolver.resolveFile({
350
- name: resolver.resolvePathName(handlersName, "file"),
351
- extname: ".ts"
352
- }, {
339
+ const handlersName = resolver.handler.listName();
340
+ const file = resolver.file({
341
+ name: handlersName,
342
+ extname: ".ts",
353
343
  root,
354
344
  output,
355
345
  group: group ?? void 0
356
346
  });
357
347
  const imports = nodes.map((node) => {
358
- const operationName = resolver.resolveHandlerName(node);
359
- const operationFile = resolver.resolveFile({
360
- name: resolver.resolveName(node.operationId),
348
+ const operationName = resolver.handler.name(node);
349
+ const operationFile = resolver.file({
350
+ name: resolver.name(node.operationId),
361
351
  extname: ".ts",
362
352
  tag: node.tags[0] ?? "default",
363
- path: node.path
364
- }, {
353
+ path: node.path,
365
354
  root,
366
355
  output,
367
356
  group: group ?? void 0
@@ -372,12 +361,12 @@ const handlersGenerator = defineGenerator({
372
361
  path: operationFile.path
373
362
  });
374
363
  });
375
- const handlers = nodes.map((node) => `${resolver.resolveHandlerName(node)}()`);
364
+ const handlers = nodes.map((node) => `${resolver.handler.name(node)}()`);
376
365
  return [ast.factory.createFile({
377
366
  baseName: file.baseName,
378
367
  path: file.path,
379
368
  meta: file.meta,
380
- banner: resolver.resolveBanner(ctx.meta, {
369
+ banner: resolver.default.banner(ctx.meta, {
381
370
  output,
382
371
  config,
383
372
  file: {
@@ -385,7 +374,7 @@ const handlersGenerator = defineGenerator({
385
374
  baseName: file.baseName
386
375
  }
387
376
  }),
388
- footer: resolver.resolveFooter(ctx.meta, {
377
+ footer: resolver.default.footer(ctx.meta, {
389
378
  output,
390
379
  config,
391
380
  file: {
@@ -450,13 +439,12 @@ function resolveFakerMeta(node, options) {
450
439
  const { root, fakerResolver, fakerOutput, fakerGroup } = options;
451
440
  const tag = node.tags[0] ?? "default";
452
441
  return {
453
- name: fakerResolver.resolveResponseName(node),
454
- file: fakerResolver.resolveFile({
442
+ name: fakerResolver.response.response(node),
443
+ file: fakerResolver.file({
455
444
  name: node.operationId,
456
445
  extname: ".ts",
457
446
  tag,
458
- path: node.path
459
- }, {
447
+ path: node.path,
460
448
  root,
461
449
  output: fakerOutput,
462
450
  group: fakerGroup ?? void 0
@@ -465,60 +453,26 @@ function resolveFakerMeta(node, options) {
465
453
  }
466
454
  //#endregion
467
455
  //#region src/components/Mock.tsx
468
- const declarationPrinter$2 = functionPrinter({ mode: "declaration" });
469
- function Mock({ baseURL = "", name, typeName, requestTypeName, node }) {
470
- const method = getMswMethod(node);
471
- const successResponse = getPrimarySuccessResponse(node);
472
- const statusCode = successResponse ? Number(successResponse.statusCode) : 200;
473
- const contentType = getContentType(successResponse);
474
- const url = Url.toPath(getMswUrl(node));
475
- const headers = [contentType ? `'Content-Type': '${contentType}'` : null].filter(Boolean);
476
- const dataType = hasResponseSchema(successResponse) ? typeName : "string | number | boolean | null | object";
477
- const callbackType = requestTypeName ? `HttpResponseResolver<Record<string, string>, ${requestTypeName}>` : `((info: Parameters<Parameters<typeof http.${method}>[1]>[0]) => Response | Promise<Response>)`;
478
- const params = declarationPrinter$2.print(createFunctionParameters({ params: [createFunctionParameter({
479
- name: "data",
480
- type: `${dataType} | ${callbackType}`,
481
- optional: true
482
- })] }));
483
- const httpCall = requestTypeName ? `http.${method}<Record<string, string>, ${requestTypeName}>` : `http.${method}`;
484
- return /* @__PURE__ */ jsx(File.Source, {
485
- name,
486
- isIndexable: true,
487
- isExportable: true,
488
- children: /* @__PURE__ */ jsx(Function, {
489
- name,
490
- export: true,
491
- params: params ?? "",
492
- children: `return ${httpCall}(\`${baseURL}${url.replace(/([^/]):/g, "$1\\\\:")}\`, function handler(info) {
493
- if(typeof data === 'function') return data(info)
494
-
495
- return new Response(JSON.stringify(data), {
496
- status: ${statusCode},
497
- ${headers.length ? ` headers: {
498
- ${headers.join(", \n")}
499
- },` : ""}
500
- })
501
- })`
502
- })
503
- });
504
- }
505
- //#endregion
506
- //#region src/components/MockWithFaker.tsx
507
456
  const declarationPrinter$1 = functionPrinter({ mode: "declaration" });
508
- function MockWithFaker({ baseURL = "", name, fakerName, typeName, requestTypeName, node }) {
457
+ function Mock({ baseURL = "", name, fakerName, typeName, requestTypeName, node }) {
509
458
  const method = getMswMethod(node);
510
459
  const successResponse = getPrimarySuccessResponse(node);
511
460
  const statusCode = successResponse ? Number(successResponse.statusCode) : 200;
512
461
  const contentType = getContentType(successResponse);
513
462
  const url = Url.toPath(getMswUrl(node));
514
463
  const headers = [contentType ? `'Content-Type': '${contentType}'` : null].filter(Boolean);
464
+ const dataType = hasResponseSchema(successResponse) ? typeName : "string | number | boolean | null | object";
465
+ const paramType = fakerName ? typeName : dataType;
515
466
  const callbackType = requestTypeName ? `HttpResponseResolver<Record<string, string>, ${requestTypeName}>` : `((info: Parameters<Parameters<typeof http.${method}>[1]>[0]) => Response | Promise<Response>)`;
516
467
  const params = declarationPrinter$1.print(createFunctionParameters({ params: [createFunctionParameter({
517
468
  name: "data",
518
- type: `${typeName} | ${callbackType}`,
469
+ type: `${paramType} | ${callbackType}`,
519
470
  optional: true
520
471
  })] }));
521
472
  const httpCall = requestTypeName ? `http.${method}<Record<string, string>, ${requestTypeName}>` : `http.${method}`;
473
+ const requestUrl = `${baseURL}${url.replace(/([^/]):/g, "$1\\\\:")}`;
474
+ const urlLiteral = fakerName ? `'${requestUrl}'` : `\`${requestUrl}\``;
475
+ const responseBody = fakerName ? `JSON.stringify(data || ${fakerName}(data))` : "JSON.stringify(data)";
522
476
  return /* @__PURE__ */ jsx(File.Source, {
523
477
  name,
524
478
  isIndexable: true,
@@ -527,10 +481,10 @@ function MockWithFaker({ baseURL = "", name, fakerName, typeName, requestTypeNam
527
481
  name,
528
482
  export: true,
529
483
  params: params ?? "",
530
- children: `return ${httpCall}('${baseURL}${url.replace(/([^/]):/g, "$1\\\\:")}', function handler(info) {
484
+ children: `return ${httpCall}(${urlLiteral}, function handler(info) {
531
485
  if(typeof data === 'function') return data(info)
532
486
 
533
- return new Response(JSON.stringify(data || ${fakerName}(data)), {
487
+ return new Response(${responseBody}, {
534
488
  status: ${statusCode},
535
489
  ${headers.length ? ` headers: {
536
490
  ${headers.join(", \n")}
@@ -586,15 +540,14 @@ const mswGenerator = defineGenerator({
586
540
  if (!ast.isHttpOperationNode(node)) return null;
587
541
  const { driver, resolver, config, root } = ctx;
588
542
  const { output, parser, baseURL, group } = ctx.options;
589
- const fileName = resolver.resolveName(node.operationId);
543
+ const fileName = resolver.name(node.operationId);
590
544
  const mock = {
591
- name: resolver.resolveHandlerName(node),
592
- file: resolver.resolveFile({
545
+ name: resolver.handler.name(node),
546
+ file: resolver.file({
593
547
  name: fileName,
594
548
  extname: ".ts",
595
549
  tag: node.tags[0] ?? "default",
596
- path: node.path
597
- }, {
550
+ path: node.path,
598
551
  root,
599
552
  output,
600
553
  group: group ?? void 0
@@ -611,26 +564,25 @@ const mswGenerator = defineGenerator({
611
564
  if (!pluginTs) return null;
612
565
  const tsResolver = driver.getResolver(pluginTsName);
613
566
  const type = {
614
- file: tsResolver.resolveFile({
567
+ file: tsResolver.file({
615
568
  name: node.operationId,
616
569
  extname: ".ts",
617
570
  tag: node.tags[0] ?? "default",
618
- path: node.path
619
- }, {
571
+ path: node.path,
620
572
  root,
621
573
  output: pluginTs.options?.output ?? output,
622
574
  group: pluginTs.options?.group ?? void 0
623
575
  }),
624
- responseName: tsResolver.resolveResponseName(node)
576
+ responseName: tsResolver.response.response(node)
625
577
  };
626
578
  const types = resolveResponseTypes(node, tsResolver);
627
579
  const hasSuccessSchema = getOperationSuccessResponses(node).some((response) => !!response.content?.[0]?.schema);
628
- const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : null;
580
+ const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.response.body(node) : null;
629
581
  return /* @__PURE__ */ jsxs(File, {
630
582
  baseName: mock.file.baseName,
631
583
  path: mock.file.path,
632
584
  meta: mock.file.meta,
633
- banner: resolver.resolveBanner(ctx.meta, {
585
+ banner: resolver.default.banner(ctx.meta, {
634
586
  output,
635
587
  config,
636
588
  file: {
@@ -638,7 +590,7 @@ const mswGenerator = defineGenerator({
638
590
  baseName: mock.file.baseName
639
591
  }
640
592
  }),
641
- footer: resolver.resolveFooter(ctx.meta, {
593
+ footer: resolver.default.footer(ctx.meta, {
642
594
  output,
643
595
  config,
644
596
  file: {
@@ -680,7 +632,7 @@ const mswGenerator = defineGenerator({
680
632
  name: mock.name
681
633
  }, typeName);
682
634
  }),
683
- parser === "faker" && faker && hasSuccessSchema ? /* @__PURE__ */ jsx(MockWithFaker, {
635
+ parser === "faker" && faker && hasSuccessSchema ? /* @__PURE__ */ jsx(Mock, {
684
636
  name: mock.name,
685
637
  typeName: type.responseName,
686
638
  requestTypeName: requestName,
@@ -705,32 +657,30 @@ const mswGenerator = defineGenerator({
705
657
  * paths for every generated MSW handler. Function names get a `Handler`
706
658
  * suffix; the aggregate export is always `handlers`.
707
659
  *
660
+ * The top-level `name` applies the `handler` suffix, and `file` falls back to the built-in
661
+ * `toFilePath` casing. Operation-specific naming is grouped under the `handler` namespace.
662
+ *
708
663
  * @example Resolve a handler name
709
664
  * ```ts
710
665
  * import { resolverMsw } from '@kubb/plugin-msw'
711
666
  *
712
- * resolverMsw.resolveName('addPet') // 'addPetHandler'
667
+ * resolverMsw.name('addPet') // 'addPetHandler'
713
668
  * ```
714
669
  */
715
- const resolverMsw = defineResolver(() => ({
716
- name: "default",
670
+ const resolverMsw = createResolver({
717
671
  pluginName: "plugin-msw",
718
- default(name, type) {
719
- return type === "file" ? toFilePath(name) : camelCase(name);
720
- },
721
- resolveName(name) {
672
+ name(name) {
722
673
  return camelCase(name, { suffix: "handler" });
723
674
  },
724
- resolvePathName(name, type) {
725
- return this.default(name, type);
726
- },
727
- resolveHandlerName(node) {
728
- return this.resolveName(node.operationId);
729
- },
730
- resolveHandlersName() {
731
- return "handlers";
675
+ handler: {
676
+ name(node) {
677
+ return this.name(node.operationId);
678
+ },
679
+ listName() {
680
+ return "handlers";
681
+ }
732
682
  }
733
- }));
683
+ });
734
684
  //#endregion
735
685
  //#region src/plugin.ts
736
686
  /**
@@ -775,10 +725,7 @@ const pluginMsw = definePlugin((options) => {
775
725
  options,
776
726
  dependencies: [pluginTsName, parser === "faker" ? pluginFakerName : null].filter((dependency) => Boolean(dependency)),
777
727
  hooks: { "kubb:plugin:setup"(ctx) {
778
- const resolver = userResolver ? {
779
- ...resolverMsw,
780
- ...userResolver
781
- } : resolverMsw;
728
+ const resolver = userResolver ? Resolver.merge(resolverMsw, userResolver) : resolverMsw;
782
729
  ctx.setOptions({
783
730
  output,
784
731
  parser,