@kubb/plugin-faker 5.4.0-canary.20261111T122006 → 5.4.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.cjs +244 -283
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +244 -283
- package/dist/index.js.map +1 -1
- package/docs/options.md +1 -1
- package/kubb-extension.json +1 -1
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { Diagnostics, Resolver, addGenerator, ast, casing, createPrinter, createResolver, defineExtension, defineGenerator, setMacros, setResolver, text } from "@kubb/kit";
|
|
1
2
|
import { posix } from "node:path";
|
|
2
|
-
import { Diagnostics, Resolver, addGenerator, ast, camelCase, containsCircularRef, createResolver, defineExtension, defineGenerator, setMacros, setResolver, syncSchemaRef } from "@kubb/kit";
|
|
3
3
|
import { File, Function, jsxRenderer } from "@kubb/renderer-jsx";
|
|
4
4
|
import { Fragment, jsx, jsxs } from "@kubb/renderer-jsx/jsx-runtime";
|
|
5
5
|
import { buildParams, enumTypeName, pluginTsName } from "@kubb/plugin-ts";
|
|
@@ -142,67 +142,76 @@ function ensureValidVarName(name) {
|
|
|
142
142
|
function isIdentifier(name) {
|
|
143
143
|
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
144
144
|
}
|
|
145
|
-
// ../../internals/utils/src/
|
|
145
|
+
// ../../internals/utils/src/codegen.ts
|
|
146
146
|
/**
|
|
147
|
-
*
|
|
148
|
-
*
|
|
147
|
+
* Assembles a multi-line object literal from already-rendered `entries`, indenting each entry one
|
|
148
|
+
* level and closing the brace at column zero. Entries that are themselves multi-line objects indent
|
|
149
|
+
* cumulatively. Each entry ends with a trailing comma to match the formatter's multi-line style.
|
|
149
150
|
*
|
|
150
151
|
* @example
|
|
151
152
|
* ```ts
|
|
152
|
-
*
|
|
153
|
-
*
|
|
153
|
+
* buildObject(['id: z.number()', 'name: z.string()'])
|
|
154
|
+
* // '{\n id: z.number(),\n name: z.string(),\n}'
|
|
154
155
|
* ```
|
|
155
156
|
*/
|
|
156
|
-
function
|
|
157
|
-
if (
|
|
158
|
-
return `
|
|
157
|
+
function buildObject(entries) {
|
|
158
|
+
if (entries.length === 0) return "{}";
|
|
159
|
+
return `{\n${entries.map((entry) => `${text.indent({ text: entry })},`).join("\n")}\n}`;
|
|
159
160
|
}
|
|
160
161
|
/**
|
|
161
|
-
*
|
|
162
|
-
* Returns the string unchanged when no balanced quote pair is found.
|
|
162
|
+
* An object key, quoted only when it is not identifier-shaped. Reserved words such as `class` stay bare.
|
|
163
163
|
*
|
|
164
164
|
* @example
|
|
165
165
|
* ```ts
|
|
166
|
-
*
|
|
167
|
-
* trimQuotes('hello') // 'hello'
|
|
166
|
+
* objectKey('x-total') // "'x-total'"
|
|
168
167
|
* ```
|
|
169
168
|
*/
|
|
170
|
-
function
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
return
|
|
169
|
+
function objectKey(name) {
|
|
170
|
+
return isIdentifier(name) ? name : text.quote(name);
|
|
171
|
+
}
|
|
172
|
+
// ../../internals/utils/src/fs.ts
|
|
173
|
+
function toSlash(p) {
|
|
174
|
+
if (p.startsWith("\\\\?\\")) return p;
|
|
175
|
+
return p.replaceAll("\\", "/");
|
|
177
176
|
}
|
|
178
177
|
/**
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
* Escaping runs through `JSON.stringify`, then the result switches to single quotes so the generated
|
|
182
|
-
* code matches the repo style without a formatter.
|
|
178
|
+
* Returns the relative path from `rootDir` to `filePath`, always using forward slashes
|
|
179
|
+
* and prefixed with `./` when not already traversing upward.
|
|
183
180
|
*
|
|
184
181
|
* @example
|
|
185
182
|
* ```ts
|
|
186
|
-
*
|
|
187
|
-
*
|
|
183
|
+
* getRelativePath('/src/components', '/src/components/Button.tsx') // './Button.tsx'
|
|
184
|
+
* getRelativePath('/src/components', '/src/utils/helpers.ts') // '../utils/helpers.ts'
|
|
188
185
|
* ```
|
|
189
186
|
*/
|
|
190
|
-
function
|
|
191
|
-
|
|
192
|
-
return
|
|
187
|
+
function getRelativePath(rootDir, filePath) {
|
|
188
|
+
const relativePath = posix.relative(toSlash(rootDir), toSlash(filePath));
|
|
189
|
+
return relativePath.startsWith("../") ? relativePath : `./${relativePath}`;
|
|
193
190
|
}
|
|
194
191
|
/**
|
|
195
|
-
*
|
|
196
|
-
*
|
|
192
|
+
* Builds a nested file path from a dotted name. Splits on dots that precede a letter
|
|
193
|
+
* (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
|
|
194
|
+
* every earlier segment, applies `caseLast` to the final segment, and joins with `/`.
|
|
197
195
|
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
196
|
+
* Empty segments are dropped before joining. They arise when the name starts with a dot
|
|
197
|
+
* followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
|
|
198
|
+
* an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
|
|
199
|
+
* absolute path, letting generated files escape the configured output directory.
|
|
200
|
+
*
|
|
201
|
+
* @example Nested path from a dotted name
|
|
202
|
+
* `toFilePath('pet.petId') // 'pet/petId'`
|
|
203
|
+
*
|
|
204
|
+
* @example PascalCase the final segment
|
|
205
|
+
* `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
|
|
206
|
+
*
|
|
207
|
+
* @example Suffix applied to the final segment only
|
|
208
|
+
* `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
|
|
202
209
|
*/
|
|
203
|
-
function
|
|
204
|
-
|
|
210
|
+
function toFilePath(name, caseLast = casing.camel) {
|
|
211
|
+
const parts = name.split(/\.(?=[a-zA-Z])/);
|
|
212
|
+
return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : casing.camel(part)).filter(Boolean).join("/");
|
|
205
213
|
}
|
|
214
|
+
// ../../internals/utils/src/strings.ts
|
|
206
215
|
const NEEDS_UNICODE = /(?<!\\)(?:\\\\)*\\[pPu]\{|[\u{10000}-\u{10FFFF}]/u;
|
|
207
216
|
const KEPT_ESCAPE = /[A-Za-z0-9^$\\.*+?()[\]{}|/]/;
|
|
208
217
|
/**
|
|
@@ -242,8 +251,8 @@ function toRegExp(pattern, flags) {
|
|
|
242
251
|
* toRegExpString('^(?im)foo', null) // '/^foo/im'
|
|
243
252
|
* ```
|
|
244
253
|
*/
|
|
245
|
-
function toRegExpString(
|
|
246
|
-
const raw =
|
|
254
|
+
function toRegExpString(pattern, func = "RegExp") {
|
|
255
|
+
const raw = text.unquote(pattern);
|
|
247
256
|
const match = raw.match(/^\^(\(\?([igmsuy]+)\))/i);
|
|
248
257
|
const replacementTarget = match?.[1] ?? "";
|
|
249
258
|
const matchedFlags = match?.[2];
|
|
@@ -251,84 +260,17 @@ function toRegExpString(text, func = "RegExp") {
|
|
|
251
260
|
if (func === null) return `/${source}/${flags}`;
|
|
252
261
|
return `new ${func}(${JSON.stringify(source)}${flags ? `, ${JSON.stringify(flags)}` : ""})`;
|
|
253
262
|
}
|
|
254
|
-
// ../../internals/utils/src/codegen.ts
|
|
255
|
-
const INDENT = " ";
|
|
256
|
-
/**
|
|
257
|
-
* Indents every non-empty line of `text` by one indent level, leaving blank lines empty.
|
|
258
|
-
*/
|
|
259
|
-
function indentLines(text) {
|
|
260
|
-
if (!text) return "";
|
|
261
|
-
return text.split("\n").map((line) => line.trim() ? `${INDENT}${line}` : "").join("\n");
|
|
262
|
-
}
|
|
263
|
-
/**
|
|
264
|
-
* Renders an object key, quoting it with single quotes only when it is not a valid identifier.
|
|
265
|
-
* Reserved words and globals (`name`, `class`, …) are valid bare keys and stay unquoted.
|
|
266
|
-
*
|
|
267
|
-
* @example
|
|
268
|
-
* ```ts
|
|
269
|
-
* objectKey('name') // 'name'
|
|
270
|
-
* objectKey('x-total') // "'x-total'"
|
|
271
|
-
* ```
|
|
272
|
-
*/
|
|
273
|
-
function objectKey(name) {
|
|
274
|
-
return isIdentifier(name) ? name : singleQuote(name);
|
|
275
|
-
}
|
|
276
263
|
/**
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
* cumulatively. Each entry ends with a trailing comma to match the formatter's multi-line style.
|
|
280
|
-
*
|
|
281
|
-
* @example
|
|
282
|
-
* ```ts
|
|
283
|
-
* buildObject(['id: z.number()', 'name: z.string()'])
|
|
284
|
-
* // '{\n id: z.number(),\n name: z.string(),\n}'
|
|
285
|
-
* ```
|
|
286
|
-
*/
|
|
287
|
-
function buildObject(entries) {
|
|
288
|
-
if (entries.length === 0) return "{}";
|
|
289
|
-
return `{\n${entries.map((entry) => `${indentLines(entry)},`).join("\n")}\n}`;
|
|
290
|
-
}
|
|
291
|
-
// ../../internals/utils/src/fs.ts
|
|
292
|
-
function toSlash(p) {
|
|
293
|
-
if (p.startsWith("\\\\?\\")) return p;
|
|
294
|
-
return p.replaceAll("\\", "/");
|
|
295
|
-
}
|
|
296
|
-
/**
|
|
297
|
-
* Returns the relative path from `rootDir` to `filePath`, always using forward slashes
|
|
298
|
-
* and prefixed with `./` when not already traversing upward.
|
|
264
|
+
* Escapes an `@` at the start of a line, so a multi-line description can't start a new JSDoc tag.
|
|
265
|
+
* Editors render `\@` as `@` in hovers.
|
|
299
266
|
*
|
|
300
267
|
* @example
|
|
301
268
|
* ```ts
|
|
302
|
-
*
|
|
303
|
-
* getRelativePath('/src/components', '/src/utils/helpers.ts') // '../utils/helpers.ts'
|
|
269
|
+
* jsDocEscape('intro\n@example value') // 'intro\n\\@example value'
|
|
304
270
|
* ```
|
|
305
271
|
*/
|
|
306
|
-
function
|
|
307
|
-
|
|
308
|
-
return relativePath.startsWith("../") ? relativePath : `./${relativePath}`;
|
|
309
|
-
}
|
|
310
|
-
/**
|
|
311
|
-
* Builds a nested file path from a dotted name. Splits on dots that precede a letter
|
|
312
|
-
* (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
|
|
313
|
-
* every earlier segment, applies `caseLast` to the final segment, and joins with `/`.
|
|
314
|
-
*
|
|
315
|
-
* Empty segments are dropped before joining. They arise when the name starts with a dot
|
|
316
|
-
* followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
|
|
317
|
-
* an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
|
|
318
|
-
* absolute path, letting generated files escape the configured output directory.
|
|
319
|
-
*
|
|
320
|
-
* @example Nested path from a dotted name
|
|
321
|
-
* `toFilePath('pet.petId') // 'pet/petId'`
|
|
322
|
-
*
|
|
323
|
-
* @example PascalCase the final segment
|
|
324
|
-
* `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
|
|
325
|
-
*
|
|
326
|
-
* @example Suffix applied to the final segment only
|
|
327
|
-
* `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
|
|
328
|
-
*/
|
|
329
|
-
function toFilePath(name, caseLast = camelCase) {
|
|
330
|
-
const parts = name.split(/\.(?=[a-zA-Z])/);
|
|
331
|
-
return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
|
|
272
|
+
function jsDocEscape(input) {
|
|
273
|
+
return `${input}`.replace(/^(\s*)@/gm, "$1\\@");
|
|
332
274
|
}
|
|
333
275
|
// ../../internals/shared/src/operation.ts
|
|
334
276
|
/**
|
|
@@ -395,19 +337,12 @@ function resolveOperationFile(options) {
|
|
|
395
337
|
return file;
|
|
396
338
|
});
|
|
397
339
|
}
|
|
398
|
-
function baseContentType(contentType) {
|
|
399
|
-
return contentType.split(";")[0].trim().toLowerCase();
|
|
400
|
-
}
|
|
401
340
|
/**
|
|
402
|
-
*
|
|
403
|
-
* `application/x-ndjson`, `application/json-seq`, or any media type with an OpenAPI 3.2 `itemSchema`.
|
|
341
|
+
* How a content entry is framed as a stream: `'events'` for Server-Sent Events, `'lines'` for JSON Lines.
|
|
404
342
|
*/
|
|
405
|
-
function
|
|
406
|
-
|
|
407
|
-
return
|
|
408
|
-
}
|
|
409
|
-
function isJsonContentType(baseType) {
|
|
410
|
-
return baseType === "application/json" || baseType.endsWith("+json") || baseType === "text/json";
|
|
343
|
+
function getStreamFraming(entry) {
|
|
344
|
+
if (ast.mediaType.getBase(entry) === "text/event-stream") return "events";
|
|
345
|
+
return entry.stream && ast.mediaType.isStream(entry) ? "lines" : void 0;
|
|
411
346
|
}
|
|
412
347
|
/**
|
|
413
348
|
* Parser for one response content entry.
|
|
@@ -418,10 +353,10 @@ function isJsonContentType(baseType) {
|
|
|
418
353
|
* `image/*`, `audio/*`, and `video/*` are blobs.
|
|
419
354
|
*/
|
|
420
355
|
function responseTypeForContent(entry) {
|
|
421
|
-
const baseType =
|
|
356
|
+
const baseType = ast.mediaType.getBase(entry);
|
|
422
357
|
if (!baseType) return void 0;
|
|
423
|
-
if (
|
|
424
|
-
if (
|
|
358
|
+
if (getStreamFraming(entry)) return "stream";
|
|
359
|
+
if (ast.mediaType.isJson(baseType)) return void 0;
|
|
425
360
|
if (entry.schema?.type === "blob") return "blob";
|
|
426
361
|
if (baseType.startsWith("text/")) return "text";
|
|
427
362
|
if (isBinaryContentType(baseType)) return "blob";
|
|
@@ -459,14 +394,14 @@ function getPerContentTypeName(baseName, suffix) {
|
|
|
459
394
|
function toResponseContent(entry, entries = [entry]) {
|
|
460
395
|
const { contentType, schema } = entry;
|
|
461
396
|
if (!schema || entry.stream) return entry;
|
|
462
|
-
const baseType =
|
|
463
|
-
const resolved = ast.
|
|
397
|
+
const baseType = ast.mediaType.getBase(contentType);
|
|
398
|
+
const resolved = ast.ref.getSchema(schema);
|
|
464
399
|
if (isBinaryContentType(baseType)) return resolved.type === "blob" ? entry : {
|
|
465
400
|
...entry,
|
|
466
401
|
schema: ast.factory.createSchema({ type: "blob" }),
|
|
467
402
|
keysToOmit: null
|
|
468
403
|
};
|
|
469
|
-
const isPlainMediaType = Boolean(baseType) && !
|
|
404
|
+
const isPlainMediaType = Boolean(baseType) && !ast.mediaType.isJson(baseType) && !baseType.includes("*");
|
|
470
405
|
if (resolved.type === "blob") return isPlainMediaType && !entries.every((item) => item.schema?.type === "blob") ? {
|
|
471
406
|
...entry,
|
|
472
407
|
schema: ast.factory.createSchema({ type: "string" }),
|
|
@@ -509,50 +444,6 @@ function resolveContentTypeVariants(entries, baseName) {
|
|
|
509
444
|
};
|
|
510
445
|
});
|
|
511
446
|
}
|
|
512
|
-
const operationParameterGroupsByNode = /* @__PURE__ */ new WeakMap();
|
|
513
|
-
/**
|
|
514
|
-
* An OpenAPI 3.2 `querystring` parameter is the whole query string, so a form-encoded object becomes one query parameter
|
|
515
|
-
* per property, typed and serialized like any other.
|
|
516
|
-
* Another content type, such as JSON, stays one parameter named after it, and the runtime sends its value as the whole query string.
|
|
517
|
-
*/
|
|
518
|
-
function spreadQuerystring(param) {
|
|
519
|
-
const schema = param.schema.type === "ref" ? param.schema.schema : param.schema;
|
|
520
|
-
const formEncoded = !param.contentType || param.contentType.split(";")[0].trim().toLowerCase() === "application/x-www-form-urlencoded";
|
|
521
|
-
if (schema?.type !== "object" || !formEncoded) return [param];
|
|
522
|
-
return schema.properties.map((property) => ast.factory.createParameter({
|
|
523
|
-
name: property.name,
|
|
524
|
-
in: "query",
|
|
525
|
-
schema: property.schema,
|
|
526
|
-
required: param.required && property.required
|
|
527
|
-
}));
|
|
528
|
-
}
|
|
529
|
-
/**
|
|
530
|
-
* Groups an operation's parameters by location (`path`/`query`/`header`/`cookie`), deduping each
|
|
531
|
-
* group by name. Every plugin generator visiting the same `OperationNode` shares one AST instance
|
|
532
|
-
* (see `KubbDriver`), so the result is cached per node to avoid re-filtering and re-deduping the
|
|
533
|
-
* same parameters once per plugin.
|
|
534
|
-
*/
|
|
535
|
-
function getOperationParameters(node) {
|
|
536
|
-
const cached = operationParameterGroupsByNode.get(node);
|
|
537
|
-
if (cached) return cached;
|
|
538
|
-
const dedupe = (params) => {
|
|
539
|
-
const seen = /* @__PURE__ */ new Set();
|
|
540
|
-
return params.filter((param) => {
|
|
541
|
-
if (seen.has(param.name)) return false;
|
|
542
|
-
seen.add(param.name);
|
|
543
|
-
return true;
|
|
544
|
-
});
|
|
545
|
-
};
|
|
546
|
-
const group = (location) => dedupe(node.parameters.filter((param) => param.in === location));
|
|
547
|
-
const groups = {
|
|
548
|
-
path: group("path"),
|
|
549
|
-
query: dedupe([...group("query"), ...group("querystring").flatMap(spreadQuerystring)]),
|
|
550
|
-
header: group("header"),
|
|
551
|
-
cookie: group("cookie")
|
|
552
|
-
};
|
|
553
|
-
operationParameterGroupsByNode.set(node, groups);
|
|
554
|
-
return groups;
|
|
555
|
-
}
|
|
556
447
|
// ../../internals/shared/src/resolver.ts
|
|
557
448
|
/**
|
|
558
449
|
* Builds the allocation key for a declaration an operation owns. `scope` names which declaration
|
|
@@ -641,6 +532,13 @@ function createOperationResponseResolver() {
|
|
|
641
532
|
name
|
|
642
533
|
});
|
|
643
534
|
},
|
|
535
|
+
headers(node, statusCode) {
|
|
536
|
+
const name = this.name(`${node.operationId} Status ${statusCode} Headers`);
|
|
537
|
+
return this.resolveName({
|
|
538
|
+
key: operationKey(node, "response", "headers", statusCode),
|
|
539
|
+
name
|
|
540
|
+
});
|
|
541
|
+
},
|
|
644
542
|
body(node) {
|
|
645
543
|
const name = this.name(`${node.operationId} Body`);
|
|
646
544
|
return this.resolveName({
|
|
@@ -682,58 +580,7 @@ function createCasedFile(caseLast) {
|
|
|
682
580
|
* Names of the schemas in a circular reference chain, computed once per build.
|
|
683
581
|
*/
|
|
684
582
|
function getCircularNames(ctx) {
|
|
685
|
-
return ctx.memo("circularNames:set", () => ast.
|
|
686
|
-
}
|
|
687
|
-
// ../../internals/shared/src/group.ts
|
|
688
|
-
/**
|
|
689
|
-
* Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
|
|
690
|
-
* shared default naming so every plugin groups output consistently:
|
|
691
|
-
*
|
|
692
|
-
* - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
|
|
693
|
-
* - other groups use the camelCased group (`pet store` → `petStore`).
|
|
694
|
-
*
|
|
695
|
-
* A user-provided `group.name` always wins over the default namer, so callers stay in
|
|
696
|
-
* control of their output folders. Returns `null` when grouping is disabled, matching the
|
|
697
|
-
* per-plugin convention.
|
|
698
|
-
*
|
|
699
|
-
* @param group - The user-supplied group option, or `undefined` to disable grouping.
|
|
700
|
-
*
|
|
701
|
-
* @example
|
|
702
|
-
* ```ts
|
|
703
|
-
* createGroupConfig(group) // shared across every plugin
|
|
704
|
-
* ```
|
|
705
|
-
*/
|
|
706
|
-
function createGroupConfig(group) {
|
|
707
|
-
if (!group) return null;
|
|
708
|
-
const defaultName = (ctx) => {
|
|
709
|
-
if (group.type === "path") return `${ctx.group.split("/")[1]}`;
|
|
710
|
-
return camelCase(ctx.group);
|
|
711
|
-
};
|
|
712
|
-
return {
|
|
713
|
-
...group,
|
|
714
|
-
name: group.name ? group.name : defaultName
|
|
715
|
-
};
|
|
716
|
-
}
|
|
717
|
-
// ../../internals/shared/src/schemaTraversal.ts
|
|
718
|
-
/**
|
|
719
|
-
* Maps each member of a union or intersection schema to its transformed output, pairing every
|
|
720
|
-
* result with the original member.
|
|
721
|
-
*/
|
|
722
|
-
function mapSchemaMembers(node, transform) {
|
|
723
|
-
return (node.members ?? []).map((schema) => ({
|
|
724
|
-
schema,
|
|
725
|
-
output: transform(schema)
|
|
726
|
-
}));
|
|
727
|
-
}
|
|
728
|
-
/**
|
|
729
|
-
* Maps each item of an array or tuple schema to its transformed output, pairing every result with
|
|
730
|
-
* the original item.
|
|
731
|
-
*/
|
|
732
|
-
function mapSchemaItems(node, transform) {
|
|
733
|
-
return (node.items ?? []).map((schema) => ({
|
|
734
|
-
schema,
|
|
735
|
-
output: transform(schema)
|
|
736
|
-
}));
|
|
583
|
+
return ctx.memo("circularNames:set", () => ast.graph.getCircularNames(ctx.driver.inputNode?.schemas ?? []));
|
|
737
584
|
}
|
|
738
585
|
// src/utils.ts
|
|
739
586
|
/**
|
|
@@ -790,26 +637,29 @@ function canOverrideSchema(node) {
|
|
|
790
637
|
* Whether a schema can supply object properties when merging faker values.
|
|
791
638
|
*/
|
|
792
639
|
function isObjectSchema(node, seen = /* @__PURE__ */ new Set()) {
|
|
793
|
-
const resolved = ast.
|
|
640
|
+
const resolved = ast.ref.getSchema(node);
|
|
794
641
|
if (seen.has(resolved)) return false;
|
|
795
642
|
seen.add(resolved);
|
|
796
|
-
if (ast.
|
|
797
|
-
const composition = ast.
|
|
643
|
+
if (ast.schema.narrow(resolved, ast.schema.types.object) || ast.schema.narrow(resolved, ast.schema.types.ref)) return true;
|
|
644
|
+
const composition = ast.schema.narrow(resolved, ast.schema.types.intersection) ?? ast.schema.narrow(resolved, ast.schema.types.union);
|
|
798
645
|
if (!composition) return false;
|
|
799
|
-
return composition.type === ast.
|
|
646
|
+
return composition.type === ast.schema.types.intersection && !composition.members?.length || (composition.members ?? []).some((member) => isObjectSchema(member, seen));
|
|
800
647
|
}
|
|
801
648
|
function shouldInlineSingleResponseSchema(schema) {
|
|
802
649
|
return !NON_INLINABLE_RESPONSE_TYPES.has(schema.type);
|
|
803
650
|
}
|
|
804
651
|
/**
|
|
805
|
-
* Builds a response schema as a union of
|
|
652
|
+
* Builds a response schema as a union of the success response statuses, like the `<Operation>Response` type of
|
|
653
|
+
* `@kubb/plugin-ts`, so the mock never is an error body. Falls back to every response when no success response has a schema.
|
|
806
654
|
* Returns null if no responses are provided, or embeds single simple responses inline.
|
|
807
655
|
*/
|
|
808
656
|
function buildResponseUnionSchema(node, resolver) {
|
|
809
|
-
const
|
|
657
|
+
const withSchema = node.responses.map((response) => ({
|
|
810
658
|
...response,
|
|
811
659
|
content: response.content?.map((entry, _, content) => toResponseContent(entry, content))
|
|
812
660
|
})).filter((response) => response.content?.[0]?.schema);
|
|
661
|
+
const success = withSchema.filter((response) => ast.http.status.isSuccess(response));
|
|
662
|
+
const responses = success.length > 0 ? success : withSchema;
|
|
813
663
|
if (!responses.length) return null;
|
|
814
664
|
if (responses.length === 1) {
|
|
815
665
|
const schemas = responses[0].content?.flatMap((content) => content.schema ? [content.schema] : []) ?? [];
|
|
@@ -890,7 +740,7 @@ function getScalarType(node, typeName) {
|
|
|
890
740
|
* Determines the data type, return type, and whether it uses the type name.
|
|
891
741
|
*/
|
|
892
742
|
function resolveFakerTypeUsage(node, typeName, canOverride) {
|
|
893
|
-
const resolvedNode = ast.
|
|
743
|
+
const resolvedNode = ast.ref.getSchema(node);
|
|
894
744
|
const scalarType = SCALAR_TYPES.has(resolvedNode.type) ? `${getScalarType(resolvedNode, typeName)}${node.nullable || resolvedNode.nullable ? " | null" : ""}` : void 0;
|
|
895
745
|
const dataType = scalarType ?? `Partial<${typeName}>`;
|
|
896
746
|
const returnType = scalarType ?? (canOverride ? typeName : null);
|
|
@@ -909,8 +759,8 @@ function Faker({ node, description, name, typeName, printer, canOverride, typeMo
|
|
|
909
759
|
imp.name
|
|
910
760
|
].join("-")));
|
|
911
761
|
const isArray = node.type === "array";
|
|
912
|
-
const isObject = !!ast.
|
|
913
|
-
const isValueIntersection = node.type === ast.
|
|
762
|
+
const isObject = !!ast.schema.narrow(node, ast.schema.types.object) || node.type === ast.schema.types.intersection && isObjectSchema(node);
|
|
763
|
+
const isValueIntersection = node.type === ast.schema.types.intersection && !isObject;
|
|
914
764
|
const isTuple = node.type === "tuple";
|
|
915
765
|
const isScalar = SCALAR_TYPES.has(node.type);
|
|
916
766
|
const isUnion = node.type === "union";
|
|
@@ -919,13 +769,14 @@ function Faker({ node, description, name, typeName, printer, canOverride, typeMo
|
|
|
919
769
|
if (canOverride && node.type === "tuple") return `data && data.length === ${node.items?.length ?? 0} && !data.includes(undefined) ? data : ${fakerText}`;
|
|
920
770
|
if (canOverride && isArray) return `[\n ...${fakerText},\n ...(data || []).filter((item) => item !== undefined),\n]`;
|
|
921
771
|
if (canOverride && (isScalar || isUnion || isValueIntersection)) return node.nullable ? `data !== undefined ? data : ${fakerText}` : `data ?? ${fakerText}`;
|
|
772
|
+
if (canOverride && node.type === "ref" && node.nullable && SCALAR_TYPES.has(ast.ref.getSchema(node).type)) return `data === null ? data : ${fakerText}`;
|
|
922
773
|
return fakerText;
|
|
923
774
|
})();
|
|
924
775
|
const { dataType, returnType: resolvedReturnType } = resolveFakerTypeUsage(node, typeName, canOverride);
|
|
925
776
|
if (!useObjectOverride) {
|
|
926
777
|
const paramsSignature = `${/\bdata\b/.test(fakerTextWithOverride) ? "data" : "_data"}?: ${dataType}`;
|
|
927
778
|
const returnType = resolvedReturnType;
|
|
928
|
-
const returnExpression = canOverride && !!returnType && (node.type === "ref" && !SCALAR_TYPES.has(ast.
|
|
779
|
+
const returnExpression = canOverride && !!returnType && (node.type === "ref" && !SCALAR_TYPES.has(ast.ref.getSchema(node).type) || isArray || isTuple || isUnion) ? `${isTuple || isUnion ? `(${fakerTextWithOverride})` : fakerTextWithOverride} as ${returnType}` : fakerTextWithOverride;
|
|
929
780
|
return /* @__PURE__ */ jsxs(Fragment, { children: [imports, /* @__PURE__ */ jsx(File.Source, {
|
|
930
781
|
name,
|
|
931
782
|
isExportable: true,
|
|
@@ -943,10 +794,11 @@ function Faker({ node, description, name, typeName, printer, canOverride, typeMo
|
|
|
943
794
|
const { cyclicSchemas, schemaName } = printer.options;
|
|
944
795
|
const isCyclic = !!schemaName && !!cyclicSchemas?.has(schemaName);
|
|
945
796
|
const returnType = typeMode === "schema" ? typeName : `Omit<${isCyclic ? typeName : "typeof defaultFakeData"}, keyof NoInfer<TData>> & NoInfer<TData>`;
|
|
946
|
-
const hasGetters = node.type === "object" && !!cyclicSchemas && (node.properties ?? []).some((p) =>
|
|
797
|
+
const hasGetters = node.type === "object" && !!cyclicSchemas && (node.properties ?? []).some((p) => ast.graph.containsCircular(p.schema, {
|
|
947
798
|
circularSchemas: cyclicSchemas,
|
|
948
799
|
excludeName: schemaName
|
|
949
800
|
}));
|
|
801
|
+
const castThroughUnknown = (node.type !== "object" || node.min !== void 0) && typeMode !== "schema";
|
|
950
802
|
const declaration = `const ${keysToOmit?.length ? `{ ${keysToOmit.map((key, index) => `${objectKey(key)}: _omit${index}`).join(", ")}, ...defaultFakeData }` : "defaultFakeData"} = ${fakerText}`;
|
|
951
803
|
const functionBody = hasGetters ? `${declaration}
|
|
952
804
|
if (data) {
|
|
@@ -958,7 +810,7 @@ return defaultFakeData as ${returnType}` : `${declaration}
|
|
|
958
810
|
return {
|
|
959
811
|
...defaultFakeData,
|
|
960
812
|
...(data || {}),
|
|
961
|
-
} as ${returnType}`;
|
|
813
|
+
} as ${castThroughUnknown ? "unknown as " : ""}${returnType}`;
|
|
962
814
|
return /* @__PURE__ */ jsxs(Fragment, { children: [imports, /* @__PURE__ */ jsx(File.Source, {
|
|
963
815
|
name,
|
|
964
816
|
isExportable: true,
|
|
@@ -1035,11 +887,38 @@ const integerFormats = /* @__PURE__ */ new Set([
|
|
|
1035
887
|
"uint64"
|
|
1036
888
|
]);
|
|
1037
889
|
const maxInt32 = 2147483647;
|
|
890
|
+
const maxValueByFormat = {
|
|
891
|
+
int32: maxInt32,
|
|
892
|
+
uint32: 4294967295
|
|
893
|
+
};
|
|
894
|
+
function isCyclicRef(node, { schemaName, cyclicSchemas }) {
|
|
895
|
+
if (node.type !== "ref" || !schemaName) return false;
|
|
896
|
+
const refName = ast.ref.getName(node);
|
|
897
|
+
return refName === schemaName || Boolean(refName && cyclicSchemas?.has(schemaName) && cyclicSchemas.has(refName));
|
|
898
|
+
}
|
|
1038
899
|
/**
|
|
1039
|
-
* `faker.number.float()` or `faker.number.
|
|
900
|
+
* `faker.number.float()`, `faker.number.int()` or `faker.number.bigInt()`, with `min` and `max` passed only when set,
|
|
901
|
+
* moved inside an exclusive bound, or needed so a lone bound does not contradict faker's default for the other.
|
|
1040
902
|
*/
|
|
1041
|
-
function numberCall(kind, min, max) {
|
|
1042
|
-
const
|
|
903
|
+
function numberCall(kind, { min, max, exclusive, multipleOf }) {
|
|
904
|
+
const isWhole = kind !== "float";
|
|
905
|
+
const grid = isWhole && !Number.isInteger(multipleOf) ? void 0 : multipleOf;
|
|
906
|
+
const { min: lower, max: upper } = ast.schema.getNumberBounds({
|
|
907
|
+
min,
|
|
908
|
+
max,
|
|
909
|
+
exclusive,
|
|
910
|
+
integer: isWhole,
|
|
911
|
+
step: grid
|
|
912
|
+
});
|
|
913
|
+
const span = 100 * (grid ?? 1);
|
|
914
|
+
const maxValue = kind === "float" && lower !== void 0 && upper === void 0 && (lower >= 1 || grid !== void 0) ? lower + span : upper;
|
|
915
|
+
const minValue = maxValue !== void 0 && lower === void 0 && (isWhole ? maxValue < 0 : maxValue <= 0) ? maxValue - span : lower;
|
|
916
|
+
const suffix = kind === "bigInt" ? "n" : "";
|
|
917
|
+
const args = [
|
|
918
|
+
minValue !== void 0 && `min: ${minValue}${suffix}`,
|
|
919
|
+
maxValue !== void 0 && `max: ${maxValue}${suffix}`,
|
|
920
|
+
grid !== void 0 && `multipleOf: ${grid}${suffix}`
|
|
921
|
+
].filter(Boolean).join(", ");
|
|
1043
922
|
return `faker.number.${kind}(${args ? `{ ${args} }` : ""})`;
|
|
1044
923
|
}
|
|
1045
924
|
/**
|
|
@@ -1057,12 +936,58 @@ function dateTimeCall(representation, { format, substring }, parser = "faker", s
|
|
|
1057
936
|
});
|
|
1058
937
|
return "faker.date.anytime()";
|
|
1059
938
|
}
|
|
939
|
+
const encodingCalls = /* @__PURE__ */ new Map([["base64", "btoa(faker.string.alpha())"], ["base64url", "faker.string.alphanumeric(8)"]]);
|
|
940
|
+
const stringFormatCalls = /* @__PURE__ */ new Map([
|
|
941
|
+
["jwt", "faker.internet.jwt()"],
|
|
942
|
+
["ulid", "faker.string.ulid()"],
|
|
943
|
+
["iban", "faker.finance.iban()"],
|
|
944
|
+
["duration", "'P1D'"],
|
|
945
|
+
["hostname", "faker.internet.domainName()"]
|
|
946
|
+
]);
|
|
947
|
+
const classEscapes = new Map([
|
|
948
|
+
["d", "0-9"],
|
|
949
|
+
["w", "A-Za-z0-9_"],
|
|
950
|
+
...Array.from("-!@#$&()`.+,\"", (char) => [char, char])
|
|
951
|
+
]);
|
|
952
|
+
function toFakerPattern(pattern) {
|
|
953
|
+
let result = "";
|
|
954
|
+
let inClass = false;
|
|
955
|
+
let classStart = -1;
|
|
956
|
+
for (let index = 0; index < pattern.length; index++) {
|
|
957
|
+
const char = pattern[index];
|
|
958
|
+
const next = pattern[index + 1];
|
|
959
|
+
if (char === "\\" && next !== void 0) {
|
|
960
|
+
index++;
|
|
961
|
+
if (inClass && next === "-") {
|
|
962
|
+
if (result[classStart] !== "-") result = `${result.slice(0, classStart)}-${result.slice(classStart)}`;
|
|
963
|
+
continue;
|
|
964
|
+
}
|
|
965
|
+
const classMembers = classEscapes.get(next);
|
|
966
|
+
if (classMembers === void 0) {
|
|
967
|
+
result += `\\${next}`;
|
|
968
|
+
continue;
|
|
969
|
+
}
|
|
970
|
+
result += inClass ? classMembers : `[${classMembers}]`;
|
|
971
|
+
continue;
|
|
972
|
+
}
|
|
973
|
+
if (char === "[" && !inClass) {
|
|
974
|
+
inClass = true;
|
|
975
|
+
result += next === "^" ? "[^" : "[";
|
|
976
|
+
if (next === "^") index++;
|
|
977
|
+
classStart = result.length;
|
|
978
|
+
continue;
|
|
979
|
+
}
|
|
980
|
+
if (char === "]") inClass = false;
|
|
981
|
+
result += char;
|
|
982
|
+
}
|
|
983
|
+
return result.replace(/\{(\d+),\}/g, (_, min) => `{${min},${Number(min) + 10}}`);
|
|
984
|
+
}
|
|
1060
985
|
function matches(value, regexGenerator = "faker") {
|
|
1061
986
|
if (regexGenerator === "randexp") return `${toRegExpString(value, "RandExp")}.gen()`;
|
|
1062
|
-
return `faker.helpers.fromRegExp(${toRegExpString(value, null)})`;
|
|
987
|
+
return `faker.helpers.fromRegExp(${toRegExpString(toFakerPattern(value), null)})`;
|
|
1063
988
|
}
|
|
1064
989
|
function parseEnumValue(value) {
|
|
1065
|
-
if (typeof value === "string") return
|
|
990
|
+
if (typeof value === "string") return text.quote(value);
|
|
1066
991
|
return value;
|
|
1067
992
|
}
|
|
1068
993
|
/**
|
|
@@ -1073,11 +998,11 @@ function parseEnumValue(value) {
|
|
|
1073
998
|
* (a `ref` to the enum shared by every branch, say) identifies no branch and narrows nothing.
|
|
1074
999
|
*/
|
|
1075
1000
|
function getDiscriminator(member, discriminatorPropertyName) {
|
|
1076
|
-
for (const property of ast.
|
|
1001
|
+
for (const property of ast.schema.findProperties({
|
|
1077
1002
|
node: member,
|
|
1078
1003
|
propertyName: discriminatorPropertyName
|
|
1079
1004
|
})) {
|
|
1080
|
-
const values = ast.
|
|
1005
|
+
const values = ast.schema.getLiterals(property.schema);
|
|
1081
1006
|
if (values.length === 1) return {
|
|
1082
1007
|
value: values[0],
|
|
1083
1008
|
required: property.required
|
|
@@ -1094,11 +1019,14 @@ function indexedTypeName(typeName, propertyName, nestedInUnion) {
|
|
|
1094
1019
|
const key = JSON.stringify(propertyName);
|
|
1095
1020
|
return nestedInUnion ? `(NonNullable<${typeName}> & Record<${key}, unknown>)[${key}]` : `NonNullable<${typeName}>[${key}]`;
|
|
1096
1021
|
}
|
|
1022
|
+
function itemTypeName(typeName, nestedInUnion) {
|
|
1023
|
+
return nestedInUnion ? `Extract<NonNullable<${typeName}>, ReadonlyArray<unknown>>[number]` : `NonNullable<${typeName}>[number]`;
|
|
1024
|
+
}
|
|
1097
1025
|
/**
|
|
1098
1026
|
* Creates a Faker printer that generates mock data generation code from schema nodes.
|
|
1099
1027
|
* Handles circular references gracefully by emitting memoizing getters for cyclic properties.
|
|
1100
1028
|
*/
|
|
1101
|
-
const printerFaker =
|
|
1029
|
+
const printerFaker = createPrinter((options) => {
|
|
1102
1030
|
const printNested = (node, overrideOptions = {}) => {
|
|
1103
1031
|
return printerFaker({
|
|
1104
1032
|
...options,
|
|
@@ -1118,18 +1046,33 @@ const printerFaker = ast.createPrinter((options) => {
|
|
|
1118
1046
|
string(node) {
|
|
1119
1047
|
if (node.pattern) return matches(node.pattern, this.options.regexGenerator);
|
|
1120
1048
|
if (node.format && integerFormats.has(node.format)) return node.format === "int32" ? `faker.number.int({ max: ${maxInt32} }).toString()` : "faker.number.bigInt().toString()";
|
|
1049
|
+
const formatCall = stringFormatCalls.get(node.format);
|
|
1050
|
+
if (formatCall) return formatCall;
|
|
1051
|
+
const contentSchema = ast.schema.getEmbeddedJson(node);
|
|
1052
|
+
if (contentSchema) return `JSON.stringify(${printNested(contentSchema, {
|
|
1053
|
+
typeName: void 0,
|
|
1054
|
+
nestedInObject: true,
|
|
1055
|
+
nestedInUnion: false
|
|
1056
|
+
})})`;
|
|
1057
|
+
const encodingCall = encodingCalls.get(node.content?.encoding?.toLowerCase());
|
|
1058
|
+
if (encodingCall) return encodingCall;
|
|
1121
1059
|
const { min, max } = node;
|
|
1122
|
-
if (
|
|
1123
|
-
if (
|
|
1060
|
+
if (max !== void 0) return `faker.string.alpha({ length: { min: ${min ?? Math.min(1, max)}, max: ${Math.min(max, (min ?? 0) + 100)} } })`;
|
|
1061
|
+
if (min !== void 0) return `faker.string.alpha({ length: ${min} })`;
|
|
1124
1062
|
return "faker.string.alpha()";
|
|
1125
1063
|
},
|
|
1126
1064
|
email: () => "faker.internet.email()",
|
|
1127
1065
|
url: () => "faker.internet.url()",
|
|
1128
1066
|
uuid: () => "faker.string.uuid()",
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1067
|
+
ipv4: () => "faker.internet.ipv4()",
|
|
1068
|
+
ipv6: () => "faker.internet.ipv6()",
|
|
1069
|
+
number: (node) => numberCall("float", node),
|
|
1070
|
+
integer: (node) => numberCall("int", node.format && node.format in maxValueByFormat ? {
|
|
1071
|
+
...node,
|
|
1072
|
+
max: Math.min(node.max ?? Infinity, maxValueByFormat[node.format])
|
|
1073
|
+
} : node),
|
|
1074
|
+
bigint: (node) => numberCall("bigInt", node),
|
|
1075
|
+
blob: () => "new Blob([faker.string.alpha()])",
|
|
1133
1076
|
datetime(node) {
|
|
1134
1077
|
const parser = this.options.dateParser ?? "faker";
|
|
1135
1078
|
if (parser === "faker") return "faker.date.anytime().toISOString()";
|
|
@@ -1148,28 +1091,30 @@ const printerFaker = ast.createPrinter((options) => {
|
|
|
1148
1091
|
}, this.options.dateParser, this.options.schemaName);
|
|
1149
1092
|
},
|
|
1150
1093
|
ref(node) {
|
|
1151
|
-
const refName = ast.
|
|
1094
|
+
const refName = ast.ref.getName(node);
|
|
1152
1095
|
if (!refName) throw new Diagnostics.Error({
|
|
1153
1096
|
code: Diagnostics.code.pluginFailed,
|
|
1154
1097
|
severity: "error",
|
|
1155
1098
|
message: `Ref node "${node.ref ?? node.name ?? "unknown"}"${this.options.schemaName ? ` in schema "${this.options.schemaName}"` : ""} has no name.`,
|
|
1156
1099
|
plugin: "plugin-faker"
|
|
1157
1100
|
});
|
|
1158
|
-
if (
|
|
1101
|
+
if (isCyclicRef(node, this.options)) return this.options.typeName ? `undefined as unknown as ${this.options.typeName}` : "undefined as unknown";
|
|
1159
1102
|
const resolvedName = node.ref ? this.options.resolver.name(refName) : refName;
|
|
1160
|
-
if (!this.options.nestedInObject && canOverrideSchema(ast.
|
|
1103
|
+
if (!this.options.nestedInObject && canOverrideSchema(ast.ref.getSchema(node))) return `${resolvedName}(data)`;
|
|
1161
1104
|
return `${resolvedName}()`;
|
|
1162
1105
|
},
|
|
1163
1106
|
enum(node) {
|
|
1164
1107
|
const { typeName } = this.options;
|
|
1165
|
-
const values = ast.
|
|
1108
|
+
const values = ast.schema.getLiterals(node);
|
|
1166
1109
|
if (values.length === 1 && values[0] !== null && typeof values[0] === "object") return JSON.stringify(values[0]);
|
|
1167
|
-
return `faker.helpers.arrayElement
|
|
1110
|
+
return `faker.helpers.arrayElement([${values.map(parseEnumValue).join(", ")}]${typeName ? ` as Array<${typeName}>` : ""})`;
|
|
1168
1111
|
},
|
|
1169
1112
|
union(node) {
|
|
1170
1113
|
const { discriminatorPropertyName } = node;
|
|
1171
1114
|
const baseTypeName = this.options.typeName;
|
|
1172
|
-
|
|
1115
|
+
const isCyclicMember = (member) => isCyclicRef(member, this.options) || member.type === "intersection" && (member.members ?? []).some((part) => isCyclicRef(part, this.options));
|
|
1116
|
+
const acyclicMembers = (node.members ?? []).filter((member) => !isCyclicMember(member));
|
|
1117
|
+
return `faker.helpers.arrayElement([${((acyclicMembers.length ? acyclicMembers : node.members) ?? []).map((member) => {
|
|
1173
1118
|
const discriminator = discriminatorPropertyName ? getDiscriminator(member, discriminatorPropertyName) : void 0;
|
|
1174
1119
|
if (baseTypeName && discriminator) {
|
|
1175
1120
|
const key = `${JSON.stringify(discriminatorPropertyName)}${discriminator.required ? "" : "?"}`;
|
|
@@ -1185,29 +1130,31 @@ const printerFaker = ast.createPrinter((options) => {
|
|
|
1185
1130
|
nestedInObject: true,
|
|
1186
1131
|
nestedInUnion: true
|
|
1187
1132
|
});
|
|
1188
|
-
}).
|
|
1133
|
+
}).filter((item) => Boolean(item)).join(", ")}])`;
|
|
1189
1134
|
},
|
|
1190
1135
|
intersection(node) {
|
|
1191
1136
|
const objectMembers = (node.members ?? []).filter((member) => isObjectSchema(member));
|
|
1192
|
-
const
|
|
1193
|
-
const items =
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
}
|
|
1137
|
+
const memberRank = (member) => member.type === "ref" ? 0 : member.type === "union" ? 2 : 1;
|
|
1138
|
+
const items = ((objectMembers.length ? objectMembers.toSorted((a, b) => memberRank(a) - memberRank(b)) : node.members) ?? []).map((member) => printNested(member, {
|
|
1139
|
+
nestedInObject: true,
|
|
1140
|
+
...member.type === "ref" && ast.ref.getName(member) !== this.options.schemaName ? { cyclicSchemas: void 0 } : {}
|
|
1141
|
+
})).filter((item) => Boolean(item) && item !== "undefined");
|
|
1197
1142
|
if (items.length === 0) return "{}";
|
|
1198
1143
|
return !objectMembers.length || items.length === 1 ? items.at(-1) : `{...${items.join(", ...")}}`;
|
|
1199
1144
|
},
|
|
1200
1145
|
array(node) {
|
|
1201
|
-
|
|
1202
|
-
|
|
1146
|
+
if (node.items?.length === 1 && isCyclicRef(node.items[0], this.options)) return this.options.typeName ? `[] as ${this.options.typeName}` : "[]";
|
|
1147
|
+
const items = (node.items ?? []).map((member) => printNested(member, {
|
|
1148
|
+
typeName: this.options.typeName ? itemTypeName(this.options.typeName, this.options.nestedInUnion) : void 0,
|
|
1203
1149
|
nestedInObject: true
|
|
1204
|
-
})).
|
|
1150
|
+
})).filter((item) => Boolean(item));
|
|
1205
1151
|
if (items.length > 1) return `faker.helpers.arrayElements([${items.join(", ")}])`;
|
|
1206
1152
|
const { min, max } = node;
|
|
1207
1153
|
const item = items[0];
|
|
1208
|
-
|
|
1154
|
+
const countRange = max === void 0 ? void 0 : `{ min: ${min ?? 0}, max: ${Math.min(max, (min ?? 0) + 100)} }`;
|
|
1155
|
+
if (node.unique) return `faker.helpers.uniqueArray(() => (${item}), ${countRange ? `faker.number.int(${countRange})` : min ?? 3})`;
|
|
1156
|
+
if (countRange) return `faker.helpers.multiple(() => (${item}), { count: ${countRange}})`;
|
|
1209
1157
|
if (min !== void 0) return `faker.helpers.multiple(() => (${item}), { count: ${min} })`;
|
|
1210
|
-
if (max !== void 0) return `faker.helpers.multiple(() => (${item}), { count: { min: 0, max: ${max} }})`;
|
|
1211
1158
|
return `faker.helpers.multiple(() => (${item}))`;
|
|
1212
1159
|
},
|
|
1213
1160
|
tuple(node) {
|
|
@@ -1218,18 +1165,30 @@ const printerFaker = ast.createPrinter((options) => {
|
|
|
1218
1165
|
},
|
|
1219
1166
|
object(node) {
|
|
1220
1167
|
const cyclicSchemas = this.options.cyclicSchemas;
|
|
1221
|
-
const keySchema = node.propertyNames &&
|
|
1168
|
+
const keySchema = node.propertyNames && ast.ref.mergeSchema(node.propertyNames);
|
|
1222
1169
|
const valueSchema = node.additionalProperties;
|
|
1223
|
-
const recordProperties = keySchema?.type === "enum" && typeof valueSchema === "object" ? ast.
|
|
1170
|
+
const recordProperties = keySchema?.type === "enum" && typeof valueSchema === "object" ? ast.schema.getLiterals(keySchema).filter((key) => typeof key === "string" && !node.properties.some((property) => property.name === key)).map((name) => ast.factory.createProperty({
|
|
1224
1171
|
name,
|
|
1225
1172
|
schema: valueSchema
|
|
1226
1173
|
})) : [];
|
|
1227
|
-
|
|
1174
|
+
const properties = [...node.properties, ...recordProperties];
|
|
1175
|
+
const keptProperties = node.max === void 0 ? properties : properties.toSorted((a, b) => Number(b.required) - Number(a.required)).slice(0, node.max);
|
|
1176
|
+
const declaredNames = new Set(properties.map((property) => property.name));
|
|
1177
|
+
const extras = [];
|
|
1178
|
+
for (let index = 1; typeof valueSchema === "object" && keptProperties.length + extras.length < (node.min ?? 0); index++) {
|
|
1179
|
+
const name = `additionalProperty${index}`;
|
|
1180
|
+
if (!declaredNames.has(name)) extras.push(ast.factory.createProperty({
|
|
1181
|
+
name,
|
|
1182
|
+
schema: valueSchema
|
|
1183
|
+
}));
|
|
1184
|
+
}
|
|
1185
|
+
return buildObject([...keptProperties, ...extras].map((property) => {
|
|
1228
1186
|
const value = printNested(property.schema, {
|
|
1229
1187
|
typeName: this.options.typeName ? indexedTypeName(this.options.typeName, property.name, this.options.nestedInUnion) : void 0,
|
|
1230
|
-
nestedInObject: true
|
|
1188
|
+
nestedInObject: true,
|
|
1189
|
+
...property.required && property.schema.type === "ref" ? { cyclicSchemas: void 0 } : {}
|
|
1231
1190
|
}) ?? "undefined";
|
|
1232
|
-
if (cyclicSchemas &&
|
|
1191
|
+
if (cyclicSchemas && ast.graph.containsCircular(property.schema, {
|
|
1233
1192
|
circularSchemas: cyclicSchemas,
|
|
1234
1193
|
excludeName: this.options.schemaName
|
|
1235
1194
|
})) 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 }`;
|
|
@@ -1257,7 +1216,7 @@ const fakerGenerator = defineGenerator({
|
|
|
1257
1216
|
if (!node.name) return;
|
|
1258
1217
|
const tsResolver = ctx.driver.getResolver(pluginTsName);
|
|
1259
1218
|
const schemaName = node.name;
|
|
1260
|
-
const isEnumSchema = !!ast.
|
|
1219
|
+
const isEnumSchema = !!ast.schema.narrow(node, ast.schema.types.enum);
|
|
1261
1220
|
const schemaTypeName = enumTypeName({
|
|
1262
1221
|
resolver: tsResolver,
|
|
1263
1222
|
name: schemaName,
|
|
@@ -1306,7 +1265,8 @@ const fakerGenerator = defineGenerator({
|
|
|
1306
1265
|
node,
|
|
1307
1266
|
root,
|
|
1308
1267
|
output,
|
|
1309
|
-
group: group ?? void 0
|
|
1268
|
+
group: group ?? void 0,
|
|
1269
|
+
checks: true
|
|
1310
1270
|
}), fakerText);
|
|
1311
1271
|
return /* @__PURE__ */ jsxs(File, {
|
|
1312
1272
|
baseName: meta.file.baseName,
|
|
@@ -1380,7 +1340,7 @@ const fakerGenerator = defineGenerator({
|
|
|
1380
1340
|
const { output, group, dateParser, regexGenerator, seed, locale, printer, typeMode } = ctx.options;
|
|
1381
1341
|
const pluginTs = ctx.requirePlugin(pluginTsName);
|
|
1382
1342
|
const tsResolver = ctx.driver.getResolver(pluginTsName);
|
|
1383
|
-
const { path: pathParams, query: queryParams, header: headerParams, cookie: cookieParams } =
|
|
1343
|
+
const { path: pathParams, query: queryParams, header: headerParams, cookie: cookieParams } = ast.http.groupParameters(node);
|
|
1384
1344
|
const paramGroups = [
|
|
1385
1345
|
{
|
|
1386
1346
|
params: pathParams,
|
|
@@ -1449,7 +1409,7 @@ const fakerGenerator = defineGenerator({
|
|
|
1449
1409
|
description: node.requestBody?.description ?? schema.description
|
|
1450
1410
|
}));
|
|
1451
1411
|
const responseName = resolver.response.response(node);
|
|
1452
|
-
const hasResponseNameCollision = new Set(
|
|
1412
|
+
const hasResponseNameCollision = new Set(ast.http.responses.getImportedNames(node.responses).map((name) => resolver.name(name))).has(responseName);
|
|
1453
1413
|
const localHelperNames = /* @__PURE__ */ new Set([
|
|
1454
1414
|
...paramGroups.map((group) => group.name),
|
|
1455
1415
|
...responseUnits.map((unit) => unit.name),
|
|
@@ -1480,12 +1440,13 @@ const fakerGenerator = defineGenerator({
|
|
|
1480
1440
|
node: schema,
|
|
1481
1441
|
root,
|
|
1482
1442
|
output,
|
|
1483
|
-
group: group ?? void 0
|
|
1443
|
+
group: group ?? void 0,
|
|
1444
|
+
checks: true
|
|
1484
1445
|
}).filter((entry) => entry.path !== meta.file.path);
|
|
1485
1446
|
}
|
|
1486
1447
|
function renderEntry({ schema, name, typeName, description, keysToOmit, skipImportNames = [] }) {
|
|
1487
1448
|
if (!schema) return null;
|
|
1488
|
-
if (schema.type === ast.
|
|
1449
|
+
if (schema.type === ast.schema.types.object && keysToOmit?.length) {
|
|
1489
1450
|
schema = {
|
|
1490
1451
|
...schema,
|
|
1491
1452
|
properties: schema.properties.filter((property) => !keysToOmit?.includes(property.name))
|
|
@@ -1600,11 +1561,11 @@ const fakerGenerator = defineGenerator({
|
|
|
1600
1561
|
});
|
|
1601
1562
|
// kubb-extension.json
|
|
1602
1563
|
var compatibility = {
|
|
1603
|
-
"kubb": ">=5.
|
|
1564
|
+
"kubb": ">=5.18.4",
|
|
1604
1565
|
"node": ">=22.22"
|
|
1605
1566
|
};
|
|
1606
1567
|
// package.json
|
|
1607
|
-
var version = "5.4.0
|
|
1568
|
+
var version = "5.4.0";
|
|
1608
1569
|
// src/resolvers/resolverFaker.ts
|
|
1609
1570
|
/**
|
|
1610
1571
|
* Default resolver used by `@kubb/plugin-faker`. Decides the names and file
|
|
@@ -1621,9 +1582,9 @@ var version = "5.4.0-canary.20261111T122006";
|
|
|
1621
1582
|
const resolverFaker = createResolver({
|
|
1622
1583
|
pluginName: "plugin-faker",
|
|
1623
1584
|
name(name) {
|
|
1624
|
-
return this.memo(`name:${name}`, () => ensureValidVarName(
|
|
1585
|
+
return this.memo(`name:${name}`, () => ensureValidVarName(casing.camel(name, { prefix: "create" })));
|
|
1625
1586
|
},
|
|
1626
|
-
file: createCasedFile((part) =>
|
|
1587
|
+
file: createCasedFile((part) => casing.camel(part, { prefix: "create" })),
|
|
1627
1588
|
param: createOperationParamResolver(),
|
|
1628
1589
|
response: createOperationResponseResolver()
|
|
1629
1590
|
});
|
|
@@ -1681,7 +1642,7 @@ const pluginFaker = defineExtension({
|
|
|
1681
1642
|
include,
|
|
1682
1643
|
override,
|
|
1683
1644
|
typeMode,
|
|
1684
|
-
group:
|
|
1645
|
+
group: group ?? null,
|
|
1685
1646
|
dateParser,
|
|
1686
1647
|
regexGenerator,
|
|
1687
1648
|
printer
|