@kubb/plugin-faker 5.0.0-beta.81 → 5.0.0-beta.85

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,11 +1,379 @@
1
1
  import "./rolldown-runtime-C0LytTxp.js";
2
- import { buildObject, containsCircularRef, extractRefName, jsStringEscape, mapSchemaItems, mapSchemaMembers, objectKey, stringify, toRegExpString } from "@kubb/ast/utils";
3
- import { createFunctionParameter, createFunctionParameters, functionPrinter, pluginTsName } from "@kubb/plugin-ts";
4
- import { File, Function, jsxRenderer } from "@kubb/renderer-jsx";
5
- import path, { posix } from "node:path";
6
- import { ast, defineGenerator, definePlugin, defineResolver } from "@kubb/core";
7
- import { Fragment, jsx, jsxs } from "@kubb/renderer-jsx/jsx-runtime";
8
- import { createHash } from "node:crypto";
2
+ import { posix } from "node:path";
3
+ import { Resolver, ast, createResolver, defineGenerator, definePlugin } from "kubb/kit";
4
+ import { buildParams, createFunctionParameter, createFunctionParameters, functionPrinter, pluginTsName } from "@kubb/plugin-ts";
5
+ import { File, Function, jsxRenderer } from "kubb/jsx";
6
+ import { Fragment, jsx, jsxs } from "kubb/jsx/jsx-runtime";
7
+ //#region ../../internals/utils/src/casing.ts
8
+ /**
9
+ * Shared implementation for camelCase and PascalCase conversion.
10
+ * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
11
+ * and capitalizes each word according to `pascal`.
12
+ *
13
+ * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
14
+ */
15
+ function toCamelOrPascal(text, pascal) {
16
+ return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
17
+ if (word.length > 1 && word === word.toUpperCase()) return word;
18
+ return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
19
+ }).join("").replace(/[^a-zA-Z0-9]/g, "");
20
+ }
21
+ /**
22
+ * Converts `text` to camelCase.
23
+ *
24
+ * @example Word boundaries
25
+ * `camelCase('hello-world') // 'helloWorld'`
26
+ *
27
+ * @example With a prefix
28
+ * `camelCase('tag', { prefix: 'create' }) // 'createTag'`
29
+ */
30
+ function camelCase(text, { prefix = "", suffix = "" } = {}) {
31
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
32
+ }
33
+ //#endregion
34
+ //#region ../../internals/utils/src/reserved.ts
35
+ /**
36
+ * JavaScript and Java reserved words.
37
+ * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
38
+ */
39
+ const reservedWords = /* @__PURE__ */ new Set([
40
+ "abstract",
41
+ "arguments",
42
+ "boolean",
43
+ "break",
44
+ "byte",
45
+ "case",
46
+ "catch",
47
+ "char",
48
+ "class",
49
+ "const",
50
+ "continue",
51
+ "debugger",
52
+ "default",
53
+ "delete",
54
+ "do",
55
+ "double",
56
+ "else",
57
+ "enum",
58
+ "eval",
59
+ "export",
60
+ "extends",
61
+ "false",
62
+ "final",
63
+ "finally",
64
+ "float",
65
+ "for",
66
+ "function",
67
+ "goto",
68
+ "if",
69
+ "implements",
70
+ "import",
71
+ "in",
72
+ "instanceof",
73
+ "int",
74
+ "interface",
75
+ "let",
76
+ "long",
77
+ "native",
78
+ "new",
79
+ "null",
80
+ "package",
81
+ "private",
82
+ "protected",
83
+ "public",
84
+ "return",
85
+ "short",
86
+ "static",
87
+ "super",
88
+ "switch",
89
+ "synchronized",
90
+ "this",
91
+ "throw",
92
+ "throws",
93
+ "transient",
94
+ "true",
95
+ "try",
96
+ "typeof",
97
+ "var",
98
+ "void",
99
+ "volatile",
100
+ "while",
101
+ "with",
102
+ "yield",
103
+ "Array",
104
+ "Date",
105
+ "hasOwnProperty",
106
+ "Infinity",
107
+ "isFinite",
108
+ "isNaN",
109
+ "isPrototypeOf",
110
+ "length",
111
+ "Math",
112
+ "name",
113
+ "NaN",
114
+ "Number",
115
+ "Object",
116
+ "prototype",
117
+ "String",
118
+ "toString",
119
+ "undefined",
120
+ "valueOf"
121
+ ]);
122
+ /**
123
+ * Returns `true` when `name` is a syntactically valid JavaScript variable name.
124
+ *
125
+ * @example
126
+ * ```ts
127
+ * isValidVarName('status') // true
128
+ * isValidVarName('class') // false (reserved word)
129
+ * isValidVarName('42foo') // false (starts with digit)
130
+ * ```
131
+ */
132
+ function isValidVarName(name) {
133
+ if (!name || reservedWords.has(name)) return false;
134
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
135
+ }
136
+ /**
137
+ * Returns `name` when it's a syntactically valid JavaScript variable name,
138
+ * otherwise prefixes it with `_` so the result is a valid identifier.
139
+ *
140
+ * Useful for sanitizing OpenAPI schema names or operation IDs that start with
141
+ * a digit (e.g. `409`, `504AccountCancel`) before using them as exported
142
+ * variable, type, or function names.
143
+ *
144
+ * @example
145
+ * ```ts
146
+ * ensureValidVarName('409') // '_409'
147
+ * ensureValidVarName('504AccountCancel') // '_504AccountCancel'
148
+ * ensureValidVarName('Pet') // 'Pet'
149
+ * ensureValidVarName('class') // '_class'
150
+ * ```
151
+ */
152
+ function ensureValidVarName(name) {
153
+ if (!name || isValidVarName(name)) return name;
154
+ return `_${name}`;
155
+ }
156
+ /**
157
+ * Returns `true` when `name` is syntactically a valid identifier, ignoring reserved words.
158
+ *
159
+ * Reserved words and globals (`class`, `name`, `Date`, …) are valid as bare object-literal keys
160
+ * even though they are not valid variable names, so use this (not {@link isValidVarName}) when
161
+ * deciding whether an object key needs quoting.
162
+ *
163
+ * @example
164
+ * ```ts
165
+ * isIdentifier('name') // true
166
+ * isIdentifier('x-total')// false
167
+ * ```
168
+ */
169
+ function isIdentifier(name) {
170
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
171
+ }
172
+ //#endregion
173
+ //#region ../../internals/utils/src/strings.ts
174
+ /**
175
+ * Wraps a value in single quotes for emitting a single-quoted JavaScript string literal, escaping
176
+ * any backslash or single quote in the content.
177
+ *
178
+ * @example
179
+ * ```ts
180
+ * singleQuote('foo') // "'foo'"
181
+ * singleQuote("o'clock") // "'o\\'clock'"
182
+ * ```
183
+ */
184
+ function singleQuote(value) {
185
+ if (value === void 0 || value === null) return "''";
186
+ return `'${String(value).replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
187
+ }
188
+ /**
189
+ * Strips a single matching pair of `"..."`, `'...'`, or `` `...` `` from both ends of `text`.
190
+ * Returns the string unchanged when no balanced quote pair is found.
191
+ *
192
+ * @example
193
+ * ```ts
194
+ * trimQuotes('"hello"') // 'hello'
195
+ * trimQuotes('hello') // 'hello'
196
+ * ```
197
+ */
198
+ function trimQuotes(text) {
199
+ if (text.length >= 2) {
200
+ const first = text[0];
201
+ const last = text[text.length - 1];
202
+ if (first === "\"" && last === "\"" || first === "'" && last === "'" || first === "`" && last === "`") return text.slice(1, -1);
203
+ }
204
+ return text;
205
+ }
206
+ /**
207
+ * Serializes a primitive to a single-quoted string literal, stripping any surrounding quotes first.
208
+ *
209
+ * Escaping runs through `JSON.stringify`, then the result switches to single quotes so the generated
210
+ * code matches the repo style without a formatter.
211
+ *
212
+ * @example
213
+ * ```ts
214
+ * stringify('hello') // "'hello'"
215
+ * stringify('"hello"') // "'hello'"
216
+ * ```
217
+ */
218
+ function stringify(value) {
219
+ if (value === void 0 || value === null) return "''";
220
+ return `'${JSON.stringify(trimQuotes(value.toString())).slice(1, -1).replace(/\\"/g, "\"").replace(/'/g, "\\'")}'`;
221
+ }
222
+ /**
223
+ * Escapes characters that are not allowed inside JS string literals, covering quotes, backslashes,
224
+ * and the Unicode line terminators U+2028 and U+2029.
225
+ *
226
+ * @see http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.4
227
+ *
228
+ * @example
229
+ * ```ts
230
+ * jsStringEscape('say "hi"\nbye') // 'say \\"hi\\"\\nbye'
231
+ * ```
232
+ */
233
+ function jsStringEscape(input) {
234
+ return `${input}`.replace(/["'\\\n\r\u2028\u2029]/g, (character) => {
235
+ switch (character) {
236
+ case "\"":
237
+ case "'":
238
+ case "\\": return `\\${character}`;
239
+ case "\n": return "\\n";
240
+ case "\r": return "\\r";
241
+ case "\u2028": return "\\u2028";
242
+ case "\u2029": return "\\u2029";
243
+ default: return "";
244
+ }
245
+ });
246
+ }
247
+ /**
248
+ * Converts a pattern string into a `new RegExp(...)` constructor call or a regex literal string.
249
+ * Inline flags expressed as a `^(?im)` prefix are extracted and applied to the resulting expression.
250
+ * Pass `null` as the second argument to emit a `/pattern/flags` literal instead.
251
+ *
252
+ * @example
253
+ * ```ts
254
+ * toRegExpString('^(?im)foo') // 'new RegExp("^foo", "im")'
255
+ * toRegExpString('^(?im)foo', null) // '/^foo/im'
256
+ * ```
257
+ */
258
+ function toRegExpString(text, func = "RegExp") {
259
+ const raw = trimQuotes(text);
260
+ const match = raw.match(/^\^(\(\?([igmsuy]+)\))/i);
261
+ const replacementTarget = match?.[1] ?? "";
262
+ const matchedFlags = match?.[2];
263
+ const cleaned = raw.replace(/^\\?\//, "").replace(/\\?\/$/, "").replace(replacementTarget, "");
264
+ const { source, flags } = new RegExp(cleaned, matchedFlags);
265
+ if (func === null) return `/${source}/${flags}`;
266
+ return `new ${func}(${JSON.stringify(source)}${flags ? `, ${JSON.stringify(flags)}` : ""})`;
267
+ }
268
+ //#endregion
269
+ //#region ../../internals/utils/src/codegen.ts
270
+ const INDENT = " ";
271
+ /**
272
+ * Indents every non-empty line of `text` by one indent level, leaving blank lines empty.
273
+ */
274
+ function indentLines(text) {
275
+ if (!text) return "";
276
+ return text.split("\n").map((line) => line.trim() ? `${INDENT}${line}` : "").join("\n");
277
+ }
278
+ /**
279
+ * Renders an object key, quoting it with single quotes only when it is not a valid identifier.
280
+ * Reserved words and globals (`name`, `class`, …) are valid bare keys and stay unquoted.
281
+ *
282
+ * @example
283
+ * ```ts
284
+ * objectKey('name') // 'name'
285
+ * objectKey('x-total') // "'x-total'"
286
+ * ```
287
+ */
288
+ function objectKey(name) {
289
+ return isIdentifier(name) ? name : singleQuote(name);
290
+ }
291
+ /**
292
+ * Assembles a multi-line object literal from already-rendered `entries`, indenting each entry one
293
+ * level and closing the brace at column zero. Entries that are themselves multi-line objects indent
294
+ * cumulatively. Each entry ends with a trailing comma to match the formatter's multi-line style.
295
+ *
296
+ * @example
297
+ * ```ts
298
+ * buildObject(['id: z.number()', 'name: z.string()'])
299
+ * // '{\n id: z.number(),\n name: z.string(),\n}'
300
+ * ```
301
+ */
302
+ function buildObject(entries) {
303
+ if (entries.length === 0) return "{}";
304
+ return `{\n${entries.map((entry) => `${indentLines(entry)},`).join("\n")}\n}`;
305
+ }
306
+ //#endregion
307
+ //#region ../../internals/utils/src/fs.ts
308
+ /**
309
+ * Builds a nested file path from a dotted name. Splits on dots that precede a letter
310
+ * (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
311
+ * every earlier segment, applies `caseLast` to the final segment, and joins with `/`.
312
+ *
313
+ * Empty segments are dropped before joining. They arise when the name starts with a dot
314
+ * followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
315
+ * an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
316
+ * absolute path, letting generated files escape the configured output directory.
317
+ *
318
+ * @example Nested path from a dotted name
319
+ * `toFilePath('pet.petId') // 'pet/petId'`
320
+ *
321
+ * @example PascalCase the final segment
322
+ * `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
323
+ *
324
+ * @example Suffix applied to the final segment only
325
+ * `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
326
+ */
327
+ function toFilePath(name, caseLast = camelCase) {
328
+ const parts = name.split(/\.(?=[a-zA-Z])/);
329
+ return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
330
+ }
331
+ //#endregion
332
+ //#region ../../internals/utils/src/imports.ts
333
+ function escapeRegExp(value) {
334
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
335
+ }
336
+ function getImportNames(entry) {
337
+ return (Array.isArray(entry.name) ? entry.name : [entry.name]).map((name) => {
338
+ if (typeof name === "string") return name;
339
+ return name.name ?? name.propertyName;
340
+ }).filter((name) => Boolean(name));
341
+ }
342
+ function filterUsedImports(imports, text, skipImportNames = []) {
343
+ const skip = new Set(skipImportNames);
344
+ return imports.filter((entry) => {
345
+ return getImportNames(entry).some((name) => {
346
+ if (skip.has(name)) return false;
347
+ return new RegExp(`\\b${escapeRegExp(name)}\\b(?=\\s*\\()`).test(text);
348
+ });
349
+ });
350
+ }
351
+ function aliasConflictingImports(imports, reservedNames) {
352
+ const reservedNameSet = new Set(reservedNames);
353
+ const aliases = /* @__PURE__ */ new Map();
354
+ return {
355
+ imports: imports.map((entry) => {
356
+ const aliasedNames = (Array.isArray(entry.name) ? entry.name : [entry.name]).map((item) => {
357
+ if (typeof item !== "string" || !reservedNameSet.has(item)) return item;
358
+ const alias = `${item}Schema`;
359
+ aliases.set(item, alias);
360
+ return {
361
+ propertyName: item,
362
+ name: alias
363
+ };
364
+ });
365
+ return aliasedNames.some((item) => typeof item === "object" && item.name) ? {
366
+ ...entry,
367
+ name: aliasedNames
368
+ } : entry;
369
+ }),
370
+ aliases
371
+ };
372
+ }
373
+ function rewriteAliasedImports(text, aliases) {
374
+ return Array.from(aliases).reduce((acc, [name, alias]) => acc.replace(new RegExp(`\\b${escapeRegExp(name)}\\b`, "g"), alias), text);
375
+ }
376
+ //#endregion
9
377
  //#region src/utils.ts
10
378
  /**
11
379
  * Returns the `@faker-js/faker` named export for a locale code.
@@ -54,17 +422,6 @@ function canOverrideSchema(node) {
54
422
  "blob"
55
423
  ])).has(node.type);
56
424
  }
57
- /**
58
- * Resolves a parameter name based on its location (path, query, header, etc.) using the provided resolver.
59
- */
60
- function resolveParamNameByLocation(resolver, node, param) {
61
- switch (param.in) {
62
- case "path": return resolver.resolvePathParamsName(node, param);
63
- case "query": return resolver.resolveQueryParamsName(node, param);
64
- case "header": return resolver.resolveHeaderParamsName(node, param);
65
- default: return resolver.resolveParamName(node, param);
66
- }
67
- }
68
425
  function shouldInlineSingleResponseSchema(schema) {
69
426
  return (/* @__PURE__ */ new Set([
70
427
  "any",
@@ -101,14 +458,14 @@ function buildResponseUnionSchema(node, resolver) {
101
458
  if (schema && shouldInlineSingleResponseSchema(schema)) return schema;
102
459
  return ast.factory.createSchema({
103
460
  type: "ref",
104
- name: resolver.resolveResponseStatusName(node, responses[0].statusCode)
461
+ name: resolver.response.status(node, responses[0].statusCode)
105
462
  });
106
463
  }
107
464
  return ast.factory.createSchema({
108
465
  type: "union",
109
466
  members: responses.map((response) => ast.factory.createSchema({
110
467
  type: "ref",
111
- name: resolver.resolveResponseStatusName(node, response.statusCode)
468
+ name: resolver.response.status(node, response.statusCode)
112
469
  }))
113
470
  });
114
471
  }
@@ -246,7 +603,7 @@ function Faker({ node, description, name, typeName, printer, seed, canOverride }
246
603
  const functionSignature = `${description ? `/**\n * @description ${jsStringEscape(description)}\n */\n ` : ""}export function ${name}<TData extends Partial<${typeName}> = object>(data?: TData)`;
247
604
  const seedCode = seed ? `faker.seed(${JSON.stringify(seed)})\n ` : "";
248
605
  const { cyclicSchemas, schemaName } = printer.options;
249
- const functionBody = node.type === "object" && !!cyclicSchemas && (node.properties ?? []).some((p) => containsCircularRef(p.schema, {
606
+ const functionBody = node.type === "object" && !!cyclicSchemas && (node.properties ?? []).some((p) => ast.containsCircularRef(p.schema, {
250
607
  circularSchemas: cyclicSchemas,
251
608
  excludeName: schemaName
252
609
  })) ? `{
@@ -272,226 +629,6 @@ function Faker({ node, description, name, typeName, printer, seed, canOverride }
272
629
  });
273
630
  }
274
631
  //#endregion
275
- //#region ../../internals/utils/src/casing.ts
276
- /**
277
- * Shared implementation for camelCase and PascalCase conversion.
278
- * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
279
- * and capitalizes each word according to `pascal`.
280
- *
281
- * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
282
- */
283
- function toCamelOrPascal(text, pascal) {
284
- return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
285
- if (word.length > 1 && word === word.toUpperCase()) return word;
286
- return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
287
- }).join("").replace(/[^a-zA-Z0-9]/g, "");
288
- }
289
- /**
290
- * Converts `text` to camelCase.
291
- *
292
- * @example Word boundaries
293
- * `camelCase('hello-world') // 'helloWorld'`
294
- *
295
- * @example With a prefix
296
- * `camelCase('tag', { prefix: 'create' }) // 'createTag'`
297
- */
298
- function camelCase(text, { prefix = "", suffix = "" } = {}) {
299
- return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
300
- }
301
- //#endregion
302
- //#region ../../internals/utils/src/fs.ts
303
- /**
304
- * Builds a nested file path from a dotted name. Splits on dots that precede a letter
305
- * (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
306
- * every earlier segment, applies `caseLast` to the final segment, and joins with `/`.
307
- *
308
- * Empty segments are dropped before joining. They arise when the name starts with a dot
309
- * followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
310
- * an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
311
- * absolute path, letting generated files escape the configured output directory.
312
- *
313
- * @example Nested path from a dotted name
314
- * `toFilePath('pet.petId') // 'pet/petId'`
315
- *
316
- * @example PascalCase the final segment
317
- * `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
318
- *
319
- * @example Suffix applied to the final segment only
320
- * `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
321
- */
322
- function toFilePath(name, caseLast = camelCase) {
323
- const parts = name.split(/\.(?=[a-zA-Z])/);
324
- return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
325
- }
326
- //#endregion
327
- //#region ../../internals/utils/src/imports.ts
328
- function escapeRegExp(value) {
329
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
330
- }
331
- function getImportNames(entry) {
332
- return (Array.isArray(entry.name) ? entry.name : [entry.name]).map((name) => {
333
- if (typeof name === "string") return name;
334
- return name.name ?? name.propertyName;
335
- }).filter((name) => Boolean(name));
336
- }
337
- function filterUsedImports(imports, text, skipImportNames = []) {
338
- const skip = new Set(skipImportNames);
339
- return imports.filter((entry) => {
340
- return getImportNames(entry).some((name) => {
341
- if (skip.has(name)) return false;
342
- return new RegExp(`\\b${escapeRegExp(name)}\\b(?=\\s*\\()`).test(text);
343
- });
344
- });
345
- }
346
- function aliasConflictingImports(imports, reservedNames) {
347
- const reservedNameSet = new Set(reservedNames);
348
- const aliases = /* @__PURE__ */ new Map();
349
- return {
350
- imports: imports.map((entry) => {
351
- const aliasedNames = (Array.isArray(entry.name) ? entry.name : [entry.name]).map((item) => {
352
- if (typeof item !== "string" || !reservedNameSet.has(item)) return item;
353
- const alias = `${item}Schema`;
354
- aliases.set(item, alias);
355
- return {
356
- propertyName: item,
357
- name: alias
358
- };
359
- });
360
- return aliasedNames.some((item) => typeof item === "object" && item.name) ? {
361
- ...entry,
362
- name: aliasedNames
363
- } : entry;
364
- }),
365
- aliases
366
- };
367
- }
368
- function rewriteAliasedImports(text, aliases) {
369
- return Array.from(aliases).reduce((acc, [name, alias]) => acc.replace(new RegExp(`\\b${escapeRegExp(name)}\\b`, "g"), alias), text);
370
- }
371
- //#endregion
372
- //#region ../../internals/utils/src/reserved.ts
373
- /**
374
- * JavaScript and Java reserved words.
375
- * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
376
- */
377
- const reservedWords = /* @__PURE__ */ new Set([
378
- "abstract",
379
- "arguments",
380
- "boolean",
381
- "break",
382
- "byte",
383
- "case",
384
- "catch",
385
- "char",
386
- "class",
387
- "const",
388
- "continue",
389
- "debugger",
390
- "default",
391
- "delete",
392
- "do",
393
- "double",
394
- "else",
395
- "enum",
396
- "eval",
397
- "export",
398
- "extends",
399
- "false",
400
- "final",
401
- "finally",
402
- "float",
403
- "for",
404
- "function",
405
- "goto",
406
- "if",
407
- "implements",
408
- "import",
409
- "in",
410
- "instanceof",
411
- "int",
412
- "interface",
413
- "let",
414
- "long",
415
- "native",
416
- "new",
417
- "null",
418
- "package",
419
- "private",
420
- "protected",
421
- "public",
422
- "return",
423
- "short",
424
- "static",
425
- "super",
426
- "switch",
427
- "synchronized",
428
- "this",
429
- "throw",
430
- "throws",
431
- "transient",
432
- "true",
433
- "try",
434
- "typeof",
435
- "var",
436
- "void",
437
- "volatile",
438
- "while",
439
- "with",
440
- "yield",
441
- "Array",
442
- "Date",
443
- "hasOwnProperty",
444
- "Infinity",
445
- "isFinite",
446
- "isNaN",
447
- "isPrototypeOf",
448
- "length",
449
- "Math",
450
- "name",
451
- "NaN",
452
- "Number",
453
- "Object",
454
- "prototype",
455
- "String",
456
- "toString",
457
- "undefined",
458
- "valueOf"
459
- ]);
460
- /**
461
- * Returns `true` when `name` is a syntactically valid JavaScript variable name.
462
- *
463
- * @example
464
- * ```ts
465
- * isValidVarName('status') // true
466
- * isValidVarName('class') // false (reserved word)
467
- * isValidVarName('42foo') // false (starts with digit)
468
- * ```
469
- */
470
- function isValidVarName(name) {
471
- if (!name || reservedWords.has(name)) return false;
472
- return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
473
- }
474
- /**
475
- * Returns `name` when it's a syntactically valid JavaScript variable name,
476
- * otherwise prefixes it with `_` so the result is a valid identifier.
477
- *
478
- * Useful for sanitizing OpenAPI schema names or operation IDs that start with
479
- * a digit (e.g. `409`, `504AccountCancel`) before using them as exported
480
- * variable, type, or function names.
481
- *
482
- * @example
483
- * ```ts
484
- * ensureValidVarName('409') // '_409'
485
- * ensureValidVarName('504AccountCancel') // '_504AccountCancel'
486
- * ensureValidVarName('Pet') // 'Pet'
487
- * ensureValidVarName('class') // '_class'
488
- * ```
489
- */
490
- function ensureValidVarName(name) {
491
- if (!name || isValidVarName(name)) return name;
492
- return `_${name}`;
493
- }
494
- //#endregion
495
632
  //#region ../../internals/shared/src/params.ts
496
633
  const caseParamsCache = /* @__PURE__ */ new WeakMap();
497
634
  /**
@@ -512,6 +649,23 @@ function caseParams(params, casing) {
512
649
  caseParamsCache.set(params, result);
513
650
  return result;
514
651
  }
652
+ /**
653
+ * Drops parameters that collapse to the same property identity once camelCased, keeping the first.
654
+ *
655
+ * Some specs declare the same parameter twice under different casings (for example AWS S3 lists both
656
+ * `max-uploads` and `MaxUploads`). Both resolve to one output property, so emitting both would yield
657
+ * an object type with a duplicate member, which TypeScript rejects. De-duplicate by the camelCased
658
+ * identity so the resulting group is collision-free regardless of the names each caller carries.
659
+ */
660
+ function dedupeByCasedName(params) {
661
+ const seen = /* @__PURE__ */ new Set();
662
+ return params.filter((param) => {
663
+ const key = camelCase(param.name);
664
+ if (seen.has(key)) return false;
665
+ seen.add(key);
666
+ return true;
667
+ });
668
+ }
515
669
  //#endregion
516
670
  //#region ../../internals/shared/src/operation.ts
517
671
  /**
@@ -562,6 +716,15 @@ function resolveContentTypeVariants(entries, baseName) {
562
716
  };
563
717
  });
564
718
  }
719
+ function getOperationParameters(node, options = {}) {
720
+ const params = caseParams(node.parameters, options.paramsCasing === "original" ? void 0 : "camelcase");
721
+ return {
722
+ path: dedupeByCasedName(params.filter((param) => param.in === "path")),
723
+ query: dedupeByCasedName(params.filter((param) => param.in === "query")),
724
+ header: dedupeByCasedName(params.filter((param) => param.in === "header")),
725
+ cookie: dedupeByCasedName(params.filter((param) => param.in === "cookie"))
726
+ };
727
+ }
565
728
  //#endregion
566
729
  //#region ../../internals/shared/src/group.ts
567
730
  /**
@@ -732,10 +895,10 @@ const printerFaker = ast.createPrinter((options) => {
732
895
  return fakerKeywordMapper.time(node.representation ?? "string", this.options.dateParser);
733
896
  },
734
897
  ref(node) {
735
- const refName = node.ref ? this.options.nameMapping?.get(node.ref) ?? extractRefName(node.ref) ?? node.name ?? node.schema?.name : node.name ?? node.schema?.name;
898
+ const refName = node.ref ? this.options.nameMapping?.get(node.ref) ?? ast.extractRefName(node.ref) ?? node.name ?? node.schema?.name : node.name ?? node.schema?.name;
736
899
  if (!refName) throw new Error("Name not defined for ref node");
737
900
  if (this.options.schemaName && refName === this.options.schemaName) return this.options.typeName ? `undefined as unknown as ${this.options.typeName}` : "undefined as unknown";
738
- const resolvedName = node.ref ? this.options.resolver.resolveName(refName) : refName;
901
+ const resolvedName = node.ref ? this.options.resolver.name(refName) : refName;
739
902
  if (!this.options.nestedInObject) return `${resolvedName}(data)`;
740
903
  return `${resolvedName}()`;
741
904
  },
@@ -745,7 +908,7 @@ const printerFaker = ast.createPrinter((options) => {
745
908
  union(node) {
746
909
  const { discriminatorPropertyName } = node;
747
910
  const baseTypeName = this.options.typeName;
748
- const items = mapSchemaMembers(node, (member) => {
911
+ const items = ast.mapSchemaMembers(node, (member) => {
749
912
  const value = discriminatorPropertyName ? getDiscriminatorValue(member, discriminatorPropertyName) : void 0;
750
913
  if (baseTypeName && value !== void 0) return printNested(member, {
751
914
  typeName: `Extract<NonNullable<${baseTypeName}>, { ${JSON.stringify(discriminatorPropertyName)}: ${parseEnumValue(value)} }>`,
@@ -760,11 +923,11 @@ const printerFaker = ast.createPrinter((options) => {
760
923
  return fakerKeywordMapper.union(items);
761
924
  },
762
925
  intersection(node) {
763
- const items = mapSchemaMembers(node, (member) => printNested(member, { nestedInObject: true })).map(({ output }) => output).filter((item) => Boolean(item) && item !== "undefined");
926
+ const items = ast.mapSchemaMembers(node, (member) => printNested(member, { nestedInObject: true })).map(({ output }) => output).filter((item) => Boolean(item) && item !== "undefined");
764
927
  return fakerKeywordMapper.and(items);
765
928
  },
766
929
  array(node) {
767
- const items = mapSchemaItems(node, (member) => printNested(member, {
930
+ const items = ast.mapSchemaItems(node, (member) => printNested(member, {
768
931
  typeName: this.options.typeName ? `NonNullable<${this.options.typeName}>[number]` : void 0,
769
932
  nestedInObject: true
770
933
  })).map(({ output }) => output).filter((item) => Boolean(item));
@@ -784,7 +947,7 @@ const printerFaker = ast.createPrinter((options) => {
784
947
  typeName: this.options.typeName ? indexedTypeName(this.options.typeName, property.name, this.options.nestedInUnion) : void 0,
785
948
  nestedInObject: true
786
949
  }) ?? "undefined";
787
- if (cyclicSchemas && containsCircularRef(property.schema, {
950
+ if (cyclicSchemas && ast.containsCircularRef(property.schema, {
788
951
  circularSchemas: cyclicSchemas,
789
952
  excludeName: this.options.schemaName
790
953
  })) 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 }`;
@@ -819,22 +982,20 @@ const fakerGenerator = defineGenerator({
819
982
  const isEnumSchema = !!ast.narrowSchema(node, ast.schemaTypes.enum);
820
983
  const tsEnumType = pluginTs.options?.enum?.type;
821
984
  const tsEnumTypeSuffix = pluginTs.options?.enum?.typeSuffix ?? "Key";
822
- const schemaTypeName = isEnumSchema && tsEnumType === "asConst" ? tsResolver.resolveEnumKeyName({ name: schemaName }, tsEnumTypeSuffix) : tsResolver.resolveTypeName(schemaName);
985
+ const schemaTypeName = isEnumSchema && tsEnumType === "asConst" ? tsResolver.enum.keyName({ name: schemaName }, tsEnumTypeSuffix) : tsResolver.name(schemaName);
823
986
  const meta = {
824
- name: resolver.resolveName(schemaName),
825
- file: resolver.resolveFile({
987
+ name: resolver.name(schemaName),
988
+ file: resolver.file({
826
989
  name: schemaName,
827
- extname: ".ts"
828
- }, {
990
+ extname: ".ts",
829
991
  root,
830
992
  output,
831
993
  group: group ?? void 0
832
994
  }),
833
995
  typeName: schemaTypeName,
834
- typeFile: tsResolver.resolveFile({
996
+ typeFile: tsResolver.file({
835
997
  name: schemaName,
836
- extname: ".ts"
837
- }, {
998
+ extname: ".ts",
838
999
  root,
839
1000
  output: pluginTs.options?.output ?? output,
840
1001
  group: pluginTs.options?.group ?? void 0
@@ -862,11 +1023,10 @@ const fakerGenerator = defineGenerator({
862
1023
  typeFilePath: meta.typeFile.path
863
1024
  });
864
1025
  const usedImports = filterUsedImports(adapter.getImports(node, (schemaName) => ({
865
- name: resolver.resolveName(schemaName),
866
- path: resolver.resolveFile({
1026
+ name: resolver.name(schemaName),
1027
+ path: resolver.file({
867
1028
  name: schemaName,
868
- extname: ".ts"
869
- }, {
1029
+ extname: ".ts",
870
1030
  root,
871
1031
  output,
872
1032
  group: group ?? void 0
@@ -876,7 +1036,7 @@ const fakerGenerator = defineGenerator({
876
1036
  baseName: meta.file.baseName,
877
1037
  path: meta.file.path,
878
1038
  meta: meta.file.meta,
879
- banner: resolver.resolveBanner(ctx.meta, {
1039
+ banner: resolver.default.banner(ctx.meta, {
880
1040
  output,
881
1041
  config,
882
1042
  file: {
@@ -884,7 +1044,7 @@ const fakerGenerator = defineGenerator({
884
1044
  baseName: meta.file.baseName
885
1045
  }
886
1046
  }),
887
- footer: resolver.resolveFooter(ctx.meta, {
1047
+ footer: resolver.default.footer(ctx.meta, {
888
1048
  output,
889
1049
  config,
890
1050
  file: {
@@ -941,10 +1101,31 @@ const fakerGenerator = defineGenerator({
941
1101
  const pluginTs = ctx.driver.getPlugin(pluginTsName);
942
1102
  if (!pluginTs) return;
943
1103
  const tsResolver = ctx.driver.getResolver(pluginTsName);
944
- const paramEntries = caseParams(node.parameters, "camelcase").map((param) => ({
945
- param,
946
- name: resolveParamNameByLocation(resolver, node, param),
947
- typeName: resolveParamNameByLocation(tsResolver, node, param)
1104
+ const params = caseParams(node.parameters, "camelcase");
1105
+ const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters({
1106
+ ...node,
1107
+ parameters: params
1108
+ }, { paramsCasing: "original" });
1109
+ const paramGroups = [
1110
+ {
1111
+ params: pathParams,
1112
+ name: resolver.param.path,
1113
+ typeName: tsResolver.param.path
1114
+ },
1115
+ {
1116
+ params: queryParams,
1117
+ name: resolver.param.query,
1118
+ typeName: tsResolver.param.query
1119
+ },
1120
+ {
1121
+ params: headerParams,
1122
+ name: resolver.param.headers,
1123
+ typeName: tsResolver.param.headers
1124
+ }
1125
+ ].filter((group) => group.params.length > 0).map((group) => ({
1126
+ schema: buildParams({ params: group.params }),
1127
+ name: group.name(node, group.params[0]),
1128
+ typeName: group.typeName(node, group.params[0])
948
1129
  }));
949
1130
  function expandContentUnits(entries, baseName, tsBaseName, description, decorate) {
950
1131
  const withSchema = entries.filter((entry) => entry.schema);
@@ -981,36 +1162,34 @@ const fakerGenerator = defineGenerator({
981
1162
  skipImportNames: variants.map((variant) => variant.name)
982
1163
  }];
983
1164
  }
984
- const responseUnits = node.responses.flatMap((response) => expandContentUnits(response.content ?? [], resolver.resolveResponseStatusName(node, response.statusCode), tsResolver.resolveResponseStatusName(node, response.statusCode), response.description));
985
- const dataUnits = expandContentUnits(node.requestBody?.content ?? [], resolver.resolveDataName(node), tsResolver.resolveDataName(node), node.requestBody?.description, (schema) => ({
1165
+ const responseUnits = node.responses.flatMap((response) => expandContentUnits(response.content ?? [], resolver.response.status(node, response.statusCode), tsResolver.response.status(node, response.statusCode), response.description));
1166
+ const dataUnits = expandContentUnits(node.requestBody?.content ?? [], resolver.response.body(node), tsResolver.response.body(node), node.requestBody?.description, (schema) => ({
986
1167
  ...schema,
987
1168
  description: node.requestBody?.description ?? schema.description
988
1169
  }));
989
- const responseName = resolver.resolveResponseName(node);
1170
+ const responseName = resolver.response.response(node);
990
1171
  const localHelperNames = /* @__PURE__ */ new Set([
991
- ...paramEntries.map((entry) => entry.name),
1172
+ ...paramGroups.map((group) => group.name),
992
1173
  ...responseUnits.map((unit) => unit.name),
993
1174
  ...dataUnits.map((unit) => unit.name),
994
1175
  responseName
995
1176
  ]);
996
1177
  const cyclicSchemas = new Set(ctx.meta.circularNames);
997
1178
  const meta = {
998
- file: resolver.resolveFile({
1179
+ file: resolver.file({
999
1180
  name: node.operationId,
1000
1181
  extname: ".ts",
1001
1182
  tag: node.tags[0] ?? "default",
1002
- path: node.path
1003
- }, {
1183
+ path: node.path,
1004
1184
  root,
1005
1185
  output,
1006
1186
  group: group ?? void 0
1007
1187
  }),
1008
- typeFile: tsResolver.resolveFile({
1188
+ typeFile: tsResolver.file({
1009
1189
  name: node.operationId,
1010
1190
  extname: ".ts",
1011
1191
  tag: node.tags[0] ?? "default",
1012
- path: node.path
1013
- }, {
1192
+ path: node.path,
1014
1193
  root,
1015
1194
  output: pluginTs.options?.output ?? output,
1016
1195
  group: pluginTs.options?.group ?? void 0
@@ -1018,11 +1197,10 @@ const fakerGenerator = defineGenerator({
1018
1197
  };
1019
1198
  function resolveMockImports(schema) {
1020
1199
  return adapter.getImports(schema, (schemaName) => ({
1021
- name: resolver.resolveName(schemaName),
1022
- path: resolver.resolveFile({
1200
+ name: resolver.name(schemaName),
1201
+ path: resolver.file({
1023
1202
  name: schemaName,
1024
- extname: ".ts"
1025
- }, {
1203
+ extname: ".ts",
1026
1204
  root,
1027
1205
  output,
1028
1206
  group: group ?? void 0
@@ -1087,7 +1265,7 @@ const fakerGenerator = defineGenerator({
1087
1265
  baseName: meta.file.baseName,
1088
1266
  path: meta.file.path,
1089
1267
  meta: meta.file.meta,
1090
- banner: resolver.resolveBanner(ctx.meta, {
1268
+ banner: resolver.default.banner(ctx.meta, {
1091
1269
  output,
1092
1270
  config,
1093
1271
  file: {
@@ -1095,7 +1273,7 @@ const fakerGenerator = defineGenerator({
1095
1273
  baseName: meta.file.baseName
1096
1274
  }
1097
1275
  }),
1098
- footer: resolver.resolveFooter(ctx.meta, {
1276
+ footer: resolver.default.footer(ctx.meta, {
1099
1277
  output,
1100
1278
  config,
1101
1279
  file: {
@@ -1119,11 +1297,7 @@ const fakerGenerator = defineGenerator({
1119
1297
  path: dateParser,
1120
1298
  name: dateParser
1121
1299
  }),
1122
- paramEntries.map(({ param, name, typeName }) => renderEntry({
1123
- schema: param.schema,
1124
- name,
1125
- typeName
1126
- })),
1300
+ paramGroups.map((group) => renderEntry(group)),
1127
1301
  responseUnits.map((unit) => renderEntry({
1128
1302
  schema: unit.schema,
1129
1303
  name: unit.name,
@@ -1141,7 +1315,7 @@ const fakerGenerator = defineGenerator({
1141
1315
  renderEntry({
1142
1316
  schema: buildResponseUnionSchema(node, resolver),
1143
1317
  name: responseName,
1144
- typeName: tsResolver.resolveResponseName(node),
1318
+ typeName: tsResolver.response.response(node),
1145
1319
  skipImportNames: responseUnits.map((unit) => unit.name)
1146
1320
  })
1147
1321
  ]
@@ -1159,69 +1333,45 @@ const fakerGenerator = defineGenerator({
1159
1333
  * ```ts
1160
1334
  * import { resolverFaker } from '@kubb/plugin-faker'
1161
1335
  *
1162
- * resolverFaker.default('list pets', 'function') // 'createListPets'
1336
+ * resolverFaker.name('list pets') // 'createListPets'
1163
1337
  * ```
1164
1338
  */
1165
- const resolverFaker = defineResolver(() => {
1166
- return {
1167
- name: "default",
1168
- pluginName: "plugin-faker",
1169
- default(name, type) {
1170
- if (type === "file") return toFilePath(name, (part) => camelCase(part, { prefix: "create" }));
1171
- return ensureValidVarName(camelCase(name, { prefix: "create" }));
1172
- },
1173
- resolveName(name, type) {
1174
- return this.default(name, type);
1175
- },
1176
- resolvePathName(name, type) {
1177
- return this.default(name, type);
1178
- },
1179
- resolveFile({ name, extname, tag, path: groupPath }, context) {
1180
- const inputBaseName = `${context.output.mode === "file" ? "" : this.resolveName(name, "file")}${extname}`;
1181
- const filePath = this.resolvePath({
1182
- baseName: inputBaseName,
1183
- tag,
1184
- path: groupPath
1185
- }, context);
1186
- const baseName = path.basename(filePath);
1187
- return {
1188
- kind: "File",
1189
- id: createHash("sha256").update(filePath).digest("hex"),
1190
- name: path.basename(filePath, extname),
1191
- path: filePath,
1192
- baseName,
1193
- extname,
1194
- meta: { pluginName: this.pluginName },
1195
- sources: [],
1196
- imports: [],
1197
- exports: []
1198
- };
1199
- },
1200
- resolveParamName(node, param) {
1201
- return this.resolveName(`${node.operationId} ${param.in} ${param.name}`);
1202
- },
1203
- resolveDataName(node) {
1204
- return this.resolveName(`${node.operationId} Data`);
1339
+ const resolverFaker = createResolver({
1340
+ pluginName: "plugin-faker",
1341
+ name(name) {
1342
+ return ensureValidVarName(camelCase(name, { prefix: "create" }));
1343
+ },
1344
+ file: { baseName({ name, extname }) {
1345
+ return `${toFilePath(name, (part) => camelCase(part, { prefix: "create" }))}${extname}`;
1346
+ } },
1347
+ param: {
1348
+ name(node, param) {
1349
+ return this.name(`${node.operationId} ${param.in} ${param.name}`);
1205
1350
  },
1206
- resolveResponseStatusName(node, statusCode) {
1207
- return this.resolveName(`${node.operationId} Status ${statusCode}`);
1351
+ path(node) {
1352
+ return this.name(`${node.operationId} Path`);
1208
1353
  },
1209
- resolveResponseName(node) {
1210
- return this.resolveName(`${node.operationId} Response`);
1354
+ query(node) {
1355
+ return this.name(`${node.operationId} Query`);
1211
1356
  },
1212
- resolveResponsesName(node) {
1213
- return this.resolveName(`${node.operationId} Responses`);
1357
+ headers(node) {
1358
+ return this.name(`${node.operationId} Headers`);
1359
+ }
1360
+ },
1361
+ response: {
1362
+ status(node, statusCode) {
1363
+ return this.name(`${node.operationId} Status ${statusCode}`);
1214
1364
  },
1215
- resolvePathParamsName(node, param) {
1216
- return this.resolveParamName(node, param);
1365
+ body(node) {
1366
+ return this.name(`${node.operationId} Body`);
1217
1367
  },
1218
- resolveQueryParamsName(node, param) {
1219
- return this.resolveParamName(node, param);
1368
+ response(node) {
1369
+ return this.name(`${node.operationId} Response`);
1220
1370
  },
1221
- resolveHeaderParamsName(node, param) {
1222
- return this.resolveParamName(node, param);
1371
+ responses(node) {
1372
+ return this.name(`${node.operationId} Responses`);
1223
1373
  }
1224
- };
1374
+ }
1225
1375
  });
1226
1376
  //#endregion
1227
1377
  //#region src/plugin.ts
@@ -1237,7 +1387,7 @@ const pluginFakerName = "plugin-faker";
1237
1387
  *
1238
1388
  * @example
1239
1389
  * ```ts
1240
- * import { defineConfig } from 'kubb'
1390
+ * import { defineConfig } from 'kubb/config'
1241
1391
  * import { pluginTs } from '@kubb/plugin-ts'
1242
1392
  * import { pluginFaker } from '@kubb/plugin-faker'
1243
1393
  *
@@ -1277,10 +1427,7 @@ const pluginFaker = definePlugin((options) => {
1277
1427
  regexGenerator,
1278
1428
  printer
1279
1429
  });
1280
- ctx.setResolver(userResolver ? {
1281
- ...resolverFaker,
1282
- ...userResolver
1283
- } : resolverFaker);
1430
+ ctx.setResolver(userResolver ? Resolver.merge(resolverFaker, userResolver) : resolverFaker);
1284
1431
  if (userMacros?.length) ctx.setMacros(userMacros);
1285
1432
  ctx.addGenerator(fakerGenerator);
1286
1433
  } }