@kubb/plugin-faker 5.0.0-beta.98 → 5.0.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,6 +1,6 @@
1
1
  import "./rolldown-runtime-C0LytTxp.js";
2
2
  import { posix } from "node:path";
3
- import { Resolver, ast, createResolver, defineGenerator, definePlugin } from "kubb/kit";
3
+ import { Resolver, ast, containsCircularRef, createResolver, defineGenerator, definePlugin } from "kubb/kit";
4
4
  import { buildParams, createFunctionParameter, createFunctionParameters, functionPrinter, pluginTsName } from "@kubb/plugin-ts";
5
5
  import { File, Function, jsxRenderer } from "kubb/jsx";
6
6
  import { Fragment, jsx, jsxs } from "kubb/jsx/jsx-runtime";
@@ -576,8 +576,9 @@ function Faker({ node, description, name, typeName, printer, seed, canOverride }
576
576
  })();
577
577
  const { dataType, returnType: resolvedReturnType } = resolveFakerTypeUsage(node, typeName, canOverride);
578
578
  if (!useGenericOverride) {
579
+ const dataParamName = /\bdata\b/.test(fakerTextWithOverride) ? "data" : "_data";
579
580
  const params = createFunctionParameters({ params: [createFunctionParameter({
580
- name: /\bdata\b/.test(fakerTextWithOverride) ? "data" : "_data",
581
+ name: dataParamName,
581
582
  type: dataType,
582
583
  optional: true
583
584
  })] });
@@ -601,7 +602,7 @@ function Faker({ node, description, name, typeName, printer, seed, canOverride }
601
602
  const functionSignature = `${description ? `/**\n * @description ${jsStringEscape(description)}\n */\n ` : ""}export function ${name}<TData extends Partial<${typeName}> = object>(data?: TData)`;
602
603
  const seedCode = seed ? `faker.seed(${JSON.stringify(seed)})\n ` : "";
603
604
  const { cyclicSchemas, schemaName } = printer.options;
604
- const functionBody = node.type === "object" && !!cyclicSchemas && (node.properties ?? []).some((p) => ast.containsCircularRef(p.schema, {
605
+ const functionBody = node.type === "object" && !!cyclicSchemas && (node.properties ?? []).some((p) => containsCircularRef(p.schema, {
605
606
  circularSchemas: cyclicSchemas,
606
607
  excludeName: schemaName
607
608
  })) ? `{
@@ -628,45 +629,64 @@ function Faker({ node, description, name, typeName, printer, seed, canOverride }
628
629
  }
629
630
  //#endregion
630
631
  //#region ../../internals/shared/src/params.ts
631
- const caseParamsCache = /* @__PURE__ */ new WeakMap();
632
632
  /**
633
- * Applies camelCase to parameter names and returns a new array without mutating the input.
633
+ * Drops parameters that share the same name, keeping the first.
634
634
  *
635
- * Run it before handing parameters to schema builders so output property keys get the right casing
636
- * while `OperationNode.parameters` stays intact for other consumers. When `casing` is unset, the
637
- * original array is returned unchanged. Results are cached per input array.
635
+ * A malformed spec can declare the same parameter name twice within one `in` location. Both would
636
+ * resolve to the same output property, so emitting both would yield an object type with a duplicate
637
+ * member, which TypeScript rejects. This is a defensive guard against that case, not a casing guard:
638
+ * parameter names flow through unchanged, so no two distinct names ever collide here anymore.
638
639
  */
639
- function caseParams(params, casing) {
640
- if (!casing) return params;
641
- const cached = caseParamsCache.get(params);
642
- if (cached) return cached;
643
- const result = params.map((param) => ({
644
- ...param,
645
- name: camelCase(param.name)
646
- }));
647
- caseParamsCache.set(params, result);
648
- return result;
649
- }
650
- /**
651
- * Drops parameters that collapse to the same property identity once camelCased, keeping the first.
652
- *
653
- * Some specs declare the same parameter twice under different casings (for example AWS S3 lists both
654
- * `max-uploads` and `MaxUploads`). Both resolve to one output property, so emitting both would yield
655
- * an object type with a duplicate member, which TypeScript rejects. De-duplicate by the camelCased
656
- * identity so the resulting group is collision-free regardless of the names each caller carries.
657
- */
658
- function dedupeByCasedName(params) {
640
+ function dedupeParams(params) {
659
641
  const seen = /* @__PURE__ */ new Set();
660
642
  return params.filter((param) => {
661
- const key = camelCase(param.name);
662
- if (seen.has(key)) return false;
663
- seen.add(key);
643
+ if (seen.has(param.name)) return false;
644
+ seen.add(param.name);
664
645
  return true;
665
646
  });
666
647
  }
667
648
  //#endregion
668
649
  //#region ../../internals/shared/src/operation.ts
669
650
  /**
651
+ * Builds the `ResolverFileParams` every operation generator passes to
652
+ * `resolver.file`: a file named `name`, tagged by the operation's first
653
+ * tag (or `'default'`), at the operation's path. Centralizes the entry object
654
+ * that was repeated at dozens of call sites across the client and query plugins.
655
+ *
656
+ * @example
657
+ * ```ts
658
+ * resolver.file(operationFileEntry(node, node.operationId), { root, output, group })
659
+ * ```
660
+ */
661
+ function operationFileEntry(node, name, extname = ".ts") {
662
+ return {
663
+ name,
664
+ extname,
665
+ tag: node.tags[0] ?? "default",
666
+ path: node.path
667
+ };
668
+ }
669
+ /**
670
+ * Resolves a dependency plugin's generated file for `node.operationId`, cached in `cache` (the
671
+ * current node's `ctx.cache`) under the resolver's own plugin name. Several dependents reading the
672
+ * same dependency for the same operation in one pass (a query plugin's several hook generators, the
673
+ * MCP handler, ...) share one computed name and path instead of each calling `resolver.file` again.
674
+ *
675
+ * @example Cache `plugin-ts`'s file for the current operation
676
+ * ```ts
677
+ * const fileTs = resolveDependencyOperationFile({ cache: ctx.cache, node, resolver: tsResolver, root, output })
678
+ * ```
679
+ */
680
+ function resolveDependencyOperationFile(options) {
681
+ const { cache, node, resolver, root, output, group } = options;
682
+ return cache.ensureItem(`${resolver.pluginName}:operationFile`, () => resolver.file({
683
+ ...operationFileEntry(node, node.operationId),
684
+ root,
685
+ output,
686
+ group: group ?? void 0
687
+ }));
688
+ }
689
+ /**
670
690
  * Maps a content type to the PascalCase suffix used to name per-content-type variants
671
691
  * (e.g. `application/json` → `Json`, `application/xml` → `Xml`, `multipart/form-data` → `FormData`).
672
692
  */
@@ -714,14 +734,24 @@ function resolveContentTypeVariants(entries, baseName) {
714
734
  };
715
735
  });
716
736
  }
717
- function getOperationParameters(node, options = {}) {
718
- const params = caseParams(node.parameters, options.paramsCasing === "original" ? void 0 : "camelcase");
719
- return {
720
- path: dedupeByCasedName(params.filter((param) => param.in === "path")),
721
- query: dedupeByCasedName(params.filter((param) => param.in === "query")),
722
- header: dedupeByCasedName(params.filter((param) => param.in === "header")),
723
- cookie: dedupeByCasedName(params.filter((param) => param.in === "cookie"))
737
+ const operationParameterGroupsByNode = /* @__PURE__ */ new WeakMap();
738
+ /**
739
+ * Groups an operation's parameters by location (`path`/`query`/`header`/`cookie`), deduping each
740
+ * group by name. Every plugin generator visiting the same `OperationNode` shares one AST instance
741
+ * (see `KubbDriver`), so the result is cached per node to avoid re-filtering and re-deduping the
742
+ * same parameters once per plugin.
743
+ */
744
+ function getOperationParameters(node) {
745
+ const cached = operationParameterGroupsByNode.get(node);
746
+ if (cached) return cached;
747
+ const groups = {
748
+ path: dedupeParams(node.parameters.filter((param) => param.in === "path")),
749
+ query: dedupeParams(node.parameters.filter((param) => param.in === "query")),
750
+ header: dedupeParams(node.parameters.filter((param) => param.in === "header")),
751
+ cookie: dedupeParams(node.parameters.filter((param) => param.in === "cookie"))
724
752
  };
753
+ operationParameterGroupsByNode.set(node, groups);
754
+ return groups;
725
755
  }
726
756
  //#endregion
727
757
  //#region ../../internals/shared/src/resolver.ts
@@ -830,7 +860,41 @@ function createGroupConfig(group) {
830
860
  };
831
861
  }
832
862
  //#endregion
863
+ //#region ../../internals/shared/src/schemaTraversal.ts
864
+ /**
865
+ * Maps each member of a union or intersection schema to its transformed output, pairing every
866
+ * result with the original member.
867
+ */
868
+ function mapSchemaMembers(node, transform) {
869
+ return (node.members ?? []).map((schema) => ({
870
+ schema,
871
+ output: transform(schema)
872
+ }));
873
+ }
874
+ /**
875
+ * Maps each item of an array or tuple schema to its transformed output, pairing every result with
876
+ * the original item.
877
+ */
878
+ function mapSchemaItems(node, transform) {
879
+ return (node.items ?? []).map((schema) => ({
880
+ schema,
881
+ output: transform(schema)
882
+ }));
883
+ }
884
+ //#endregion
833
885
  //#region src/printers/printerFaker.ts
886
+ /**
887
+ * Formats that put a whole number inside a `type: 'string'` schema, the way ProtoJSON encodes
888
+ * 64-bit integers, so the mock has to be digits rather than letters.
889
+ *
890
+ * @see https://protobuf.dev/programming-guides/json/#int64-strings
891
+ */
892
+ const integerFormats = /* @__PURE__ */ new Set([
893
+ "int32",
894
+ "int64",
895
+ "uint64"
896
+ ]);
897
+ const maxInt32 = 2147483647;
834
898
  const fakerKeywordMapper = {
835
899
  any: () => "undefined",
836
900
  unknown: () => "undefined",
@@ -848,6 +912,10 @@ const fakerKeywordMapper = {
848
912
  return "faker.number.int()";
849
913
  },
850
914
  bigint: () => "faker.number.bigInt()",
915
+ integerString: (format) => {
916
+ if (format === "int32") return `faker.number.int({ max: ${maxInt32} }).toString()`;
917
+ return "faker.number.bigInt().toString()";
918
+ },
851
919
  string: (min, max) => {
852
920
  if (max !== void 0 && min !== void 0) return `faker.string.alpha({ length: { min: ${min}, max: ${max} } })`;
853
921
  if (max !== void 0) return `faker.string.alpha({ length: ${max} })`;
@@ -947,6 +1015,7 @@ const printerFaker = ast.createPrinter((options) => {
947
1015
  null: () => fakerKeywordMapper.null(),
948
1016
  string(node) {
949
1017
  if (node.pattern) return fakerKeywordMapper.matches(node.pattern, this.options.regexGenerator);
1018
+ if (node.format && integerFormats.has(node.format)) return fakerKeywordMapper.integerString(node.format);
950
1019
  return fakerKeywordMapper.string(node.min, node.max);
951
1020
  },
952
1021
  email: () => fakerKeywordMapper.email(),
@@ -981,7 +1050,7 @@ const printerFaker = ast.createPrinter((options) => {
981
1050
  union(node) {
982
1051
  const { discriminatorPropertyName } = node;
983
1052
  const baseTypeName = this.options.typeName;
984
- const items = ast.mapSchemaMembers(node, (member) => {
1053
+ const items = mapSchemaMembers(node, (member) => {
985
1054
  const value = discriminatorPropertyName ? getDiscriminatorValue(member, discriminatorPropertyName) : void 0;
986
1055
  if (baseTypeName && value !== void 0) {
987
1056
  const typeName = `Extract<NonNullable<${baseTypeName}>, { ${JSON.stringify(discriminatorPropertyName)}: ${parseEnumValue(value)} }>`;
@@ -999,11 +1068,11 @@ const printerFaker = ast.createPrinter((options) => {
999
1068
  return fakerKeywordMapper.union(items);
1000
1069
  },
1001
1070
  intersection(node) {
1002
- const items = ast.mapSchemaMembers(node, (member) => printNested(member, { nestedInObject: true })).map(({ output }) => output).filter((item) => Boolean(item) && item !== "undefined");
1071
+ const items = mapSchemaMembers(node, (member) => printNested(member, { nestedInObject: true })).map(({ output }) => output).filter((item) => Boolean(item) && item !== "undefined");
1003
1072
  return fakerKeywordMapper.and(items);
1004
1073
  },
1005
1074
  array(node) {
1006
- const items = ast.mapSchemaItems(node, (member) => printNested(member, {
1075
+ const items = mapSchemaItems(node, (member) => printNested(member, {
1007
1076
  typeName: this.options.typeName ? `NonNullable<${this.options.typeName}>[number]` : void 0,
1008
1077
  nestedInObject: true
1009
1078
  })).map(({ output }) => output).filter((item) => Boolean(item));
@@ -1023,7 +1092,7 @@ const printerFaker = ast.createPrinter((options) => {
1023
1092
  typeName: this.options.typeName ? indexedTypeName(this.options.typeName, property.name, this.options.nestedInUnion) : void 0,
1024
1093
  nestedInObject: true
1025
1094
  }) ?? "undefined";
1026
- if (cyclicSchemas && ast.containsCircularRef(property.schema, {
1095
+ if (cyclicSchemas && containsCircularRef(property.schema, {
1027
1096
  circularSchemas: cyclicSchemas,
1028
1097
  excludeName: this.options.schemaName
1029
1098
  })) return `get ${objectKey(property.name)}() { const _value = ${value}; Object.defineProperty(this, ${JSON.stringify(property.name)}, { value: _value, configurable: true, writable: true, enumerable: true }); return _value }`;
@@ -1172,11 +1241,7 @@ const fakerGenerator = defineGenerator({
1172
1241
  const pluginTs = ctx.driver.getPlugin(pluginTsName);
1173
1242
  if (!pluginTs) return;
1174
1243
  const tsResolver = ctx.driver.getResolver(pluginTsName);
1175
- const params = caseParams(node.parameters, "camelcase");
1176
- const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters({
1177
- ...node,
1178
- parameters: params
1179
- }, { paramsCasing: "original" });
1244
+ const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters(node);
1180
1245
  const paramGroups = [
1181
1246
  {
1182
1247
  params: pathParams,
@@ -1256,14 +1321,13 @@ const fakerGenerator = defineGenerator({
1256
1321
  output,
1257
1322
  group: group ?? void 0
1258
1323
  }),
1259
- typeFile: tsResolver.file({
1260
- name: node.operationId,
1261
- extname: ".ts",
1262
- tag: node.tags[0] ?? "default",
1263
- path: node.path,
1324
+ typeFile: resolveDependencyOperationFile({
1325
+ cache: ctx.cache,
1326
+ node,
1327
+ resolver: tsResolver,
1264
1328
  root,
1265
1329
  output: pluginTs.options?.output ?? output,
1266
- group: pluginTs.options?.group ?? void 0
1330
+ group: pluginTs.options?.group
1267
1331
  })
1268
1332
  };
1269
1333
  function resolveMockImports(schema) {