@kubb/plugin-faker 5.4.0-canary.20261111T122006 → 5.4.1
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 +255 -289
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -9
- package/dist/index.d.ts +8 -9
- package/dist/index.js +255 -289
- package/dist/index.js.map +1 -1
- package/docs/options.md +14 -11
- package/docs/overview.md +2 -2
- 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,60 +580,13 @@ 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
|
|
586
|
+
/** The identifier a `dateParser` module is imported and called under: `my-date-lib` becomes `myDateLib`. */
|
|
587
|
+
function dateParserName(dateParser) {
|
|
588
|
+
return ensureValidVarName(casing.camel(dateParser));
|
|
589
|
+
}
|
|
739
590
|
/**
|
|
740
591
|
* Returns the `@faker-js/faker` named export for a locale code.
|
|
741
592
|
*
|
|
@@ -790,26 +641,29 @@ function canOverrideSchema(node) {
|
|
|
790
641
|
* Whether a schema can supply object properties when merging faker values.
|
|
791
642
|
*/
|
|
792
643
|
function isObjectSchema(node, seen = /* @__PURE__ */ new Set()) {
|
|
793
|
-
const resolved = ast.
|
|
644
|
+
const resolved = ast.ref.getSchema(node);
|
|
794
645
|
if (seen.has(resolved)) return false;
|
|
795
646
|
seen.add(resolved);
|
|
796
|
-
if (ast.
|
|
797
|
-
const composition = ast.
|
|
647
|
+
if (ast.schema.narrow(resolved, ast.schema.types.object) || ast.schema.narrow(resolved, ast.schema.types.ref)) return true;
|
|
648
|
+
const composition = ast.schema.narrow(resolved, ast.schema.types.intersection) ?? ast.schema.narrow(resolved, ast.schema.types.union);
|
|
798
649
|
if (!composition) return false;
|
|
799
|
-
return composition.type === ast.
|
|
650
|
+
return composition.type === ast.schema.types.intersection && !composition.members?.length || (composition.members ?? []).some((member) => isObjectSchema(member, seen));
|
|
800
651
|
}
|
|
801
652
|
function shouldInlineSingleResponseSchema(schema) {
|
|
802
653
|
return !NON_INLINABLE_RESPONSE_TYPES.has(schema.type);
|
|
803
654
|
}
|
|
804
655
|
/**
|
|
805
|
-
* Builds a response schema as a union of
|
|
656
|
+
* Builds a response schema as a union of the success response statuses, like the `<Operation>Response` type of
|
|
657
|
+
* `@kubb/plugin-ts`, so the mock never is an error body. Falls back to every response when no success response has a schema.
|
|
806
658
|
* Returns null if no responses are provided, or embeds single simple responses inline.
|
|
807
659
|
*/
|
|
808
660
|
function buildResponseUnionSchema(node, resolver) {
|
|
809
|
-
const
|
|
661
|
+
const withSchema = node.responses.map((response) => ({
|
|
810
662
|
...response,
|
|
811
663
|
content: response.content?.map((entry, _, content) => toResponseContent(entry, content))
|
|
812
664
|
})).filter((response) => response.content?.[0]?.schema);
|
|
665
|
+
const success = withSchema.filter((response) => ast.http.status.isSuccess(response));
|
|
666
|
+
const responses = success.length > 0 ? success : withSchema;
|
|
813
667
|
if (!responses.length) return null;
|
|
814
668
|
if (responses.length === 1) {
|
|
815
669
|
const schemas = responses[0].content?.flatMap((content) => content.schema ? [content.schema] : []) ?? [];
|
|
@@ -890,7 +744,7 @@ function getScalarType(node, typeName) {
|
|
|
890
744
|
* Determines the data type, return type, and whether it uses the type name.
|
|
891
745
|
*/
|
|
892
746
|
function resolveFakerTypeUsage(node, typeName, canOverride) {
|
|
893
|
-
const resolvedNode = ast.
|
|
747
|
+
const resolvedNode = ast.ref.getSchema(node);
|
|
894
748
|
const scalarType = SCALAR_TYPES.has(resolvedNode.type) ? `${getScalarType(resolvedNode, typeName)}${node.nullable || resolvedNode.nullable ? " | null" : ""}` : void 0;
|
|
895
749
|
const dataType = scalarType ?? `Partial<${typeName}>`;
|
|
896
750
|
const returnType = scalarType ?? (canOverride ? typeName : null);
|
|
@@ -909,8 +763,8 @@ function Faker({ node, description, name, typeName, printer, canOverride, typeMo
|
|
|
909
763
|
imp.name
|
|
910
764
|
].join("-")));
|
|
911
765
|
const isArray = node.type === "array";
|
|
912
|
-
const isObject = !!ast.
|
|
913
|
-
const isValueIntersection = node.type === ast.
|
|
766
|
+
const isObject = !!ast.schema.narrow(node, ast.schema.types.object) || node.type === ast.schema.types.intersection && isObjectSchema(node);
|
|
767
|
+
const isValueIntersection = node.type === ast.schema.types.intersection && !isObject;
|
|
914
768
|
const isTuple = node.type === "tuple";
|
|
915
769
|
const isScalar = SCALAR_TYPES.has(node.type);
|
|
916
770
|
const isUnion = node.type === "union";
|
|
@@ -919,13 +773,14 @@ function Faker({ node, description, name, typeName, printer, canOverride, typeMo
|
|
|
919
773
|
if (canOverride && node.type === "tuple") return `data && data.length === ${node.items?.length ?? 0} && !data.includes(undefined) ? data : ${fakerText}`;
|
|
920
774
|
if (canOverride && isArray) return `[\n ...${fakerText},\n ...(data || []).filter((item) => item !== undefined),\n]`;
|
|
921
775
|
if (canOverride && (isScalar || isUnion || isValueIntersection)) return node.nullable ? `data !== undefined ? data : ${fakerText}` : `data ?? ${fakerText}`;
|
|
776
|
+
if (canOverride && node.type === "ref" && node.nullable && SCALAR_TYPES.has(ast.ref.getSchema(node).type)) return `data === null ? data : ${fakerText}`;
|
|
922
777
|
return fakerText;
|
|
923
778
|
})();
|
|
924
779
|
const { dataType, returnType: resolvedReturnType } = resolveFakerTypeUsage(node, typeName, canOverride);
|
|
925
780
|
if (!useObjectOverride) {
|
|
926
781
|
const paramsSignature = `${/\bdata\b/.test(fakerTextWithOverride) ? "data" : "_data"}?: ${dataType}`;
|
|
927
782
|
const returnType = resolvedReturnType;
|
|
928
|
-
const returnExpression = canOverride && !!returnType && (node.type === "ref" && !SCALAR_TYPES.has(ast.
|
|
783
|
+
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
784
|
return /* @__PURE__ */ jsxs(Fragment, { children: [imports, /* @__PURE__ */ jsx(File.Source, {
|
|
930
785
|
name,
|
|
931
786
|
isExportable: true,
|
|
@@ -943,10 +798,11 @@ function Faker({ node, description, name, typeName, printer, canOverride, typeMo
|
|
|
943
798
|
const { cyclicSchemas, schemaName } = printer.options;
|
|
944
799
|
const isCyclic = !!schemaName && !!cyclicSchemas?.has(schemaName);
|
|
945
800
|
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) =>
|
|
801
|
+
const hasGetters = node.type === "object" && !!cyclicSchemas && (node.properties ?? []).some((p) => ast.graph.containsCircular(p.schema, {
|
|
947
802
|
circularSchemas: cyclicSchemas,
|
|
948
803
|
excludeName: schemaName
|
|
949
804
|
}));
|
|
805
|
+
const castThroughUnknown = (node.type !== "object" || node.min !== void 0) && typeMode !== "schema";
|
|
950
806
|
const declaration = `const ${keysToOmit?.length ? `{ ${keysToOmit.map((key, index) => `${objectKey(key)}: _omit${index}`).join(", ")}, ...defaultFakeData }` : "defaultFakeData"} = ${fakerText}`;
|
|
951
807
|
const functionBody = hasGetters ? `${declaration}
|
|
952
808
|
if (data) {
|
|
@@ -958,7 +814,7 @@ return defaultFakeData as ${returnType}` : `${declaration}
|
|
|
958
814
|
return {
|
|
959
815
|
...defaultFakeData,
|
|
960
816
|
...(data || {}),
|
|
961
|
-
} as ${returnType}`;
|
|
817
|
+
} as ${castThroughUnknown ? "unknown as " : ""}${returnType}`;
|
|
962
818
|
return /* @__PURE__ */ jsxs(Fragment, { children: [imports, /* @__PURE__ */ jsx(File.Source, {
|
|
963
819
|
name,
|
|
964
820
|
isExportable: true,
|
|
@@ -1035,18 +891,45 @@ const integerFormats = /* @__PURE__ */ new Set([
|
|
|
1035
891
|
"uint64"
|
|
1036
892
|
]);
|
|
1037
893
|
const maxInt32 = 2147483647;
|
|
894
|
+
const maxValueByFormat = {
|
|
895
|
+
int32: maxInt32,
|
|
896
|
+
uint32: 4294967295
|
|
897
|
+
};
|
|
898
|
+
function isCyclicRef(node, { schemaName, cyclicSchemas }) {
|
|
899
|
+
if (node.type !== "ref" || !schemaName) return false;
|
|
900
|
+
const refName = ast.ref.getName(node);
|
|
901
|
+
return refName === schemaName || Boolean(refName && cyclicSchemas?.has(schemaName) && cyclicSchemas.has(refName));
|
|
902
|
+
}
|
|
1038
903
|
/**
|
|
1039
|
-
* `faker.number.float()` or `faker.number.
|
|
904
|
+
* `faker.number.float()`, `faker.number.int()` or `faker.number.bigInt()`, with `min` and `max` passed only when set,
|
|
905
|
+
* moved inside an exclusive bound, or needed so a lone bound does not contradict faker's default for the other.
|
|
1040
906
|
*/
|
|
1041
|
-
function numberCall(kind, min, max) {
|
|
1042
|
-
const
|
|
907
|
+
function numberCall(kind, { min, max, exclusive, multipleOf }) {
|
|
908
|
+
const isWhole = kind !== "float";
|
|
909
|
+
const grid = isWhole && !Number.isInteger(multipleOf) ? void 0 : multipleOf;
|
|
910
|
+
const { min: lower, max: upper } = ast.schema.getNumberBounds({
|
|
911
|
+
min,
|
|
912
|
+
max,
|
|
913
|
+
exclusive,
|
|
914
|
+
integer: isWhole,
|
|
915
|
+
step: grid
|
|
916
|
+
});
|
|
917
|
+
const span = 100 * (grid ?? 1);
|
|
918
|
+
const maxValue = kind === "float" && lower !== void 0 && upper === void 0 && (lower >= 1 || grid !== void 0) ? lower + span : upper;
|
|
919
|
+
const minValue = maxValue !== void 0 && lower === void 0 && (isWhole ? maxValue < 0 : maxValue <= 0) ? maxValue - span : lower;
|
|
920
|
+
const suffix = kind === "bigInt" ? "n" : "";
|
|
921
|
+
const args = [
|
|
922
|
+
minValue !== void 0 && `min: ${minValue}${suffix}`,
|
|
923
|
+
maxValue !== void 0 && `max: ${maxValue}${suffix}`,
|
|
924
|
+
grid !== void 0 && `multipleOf: ${grid}${suffix}`
|
|
925
|
+
].filter(Boolean).join(", ");
|
|
1043
926
|
return `faker.number.${kind}(${args ? `{ ${args} }` : ""})`;
|
|
1044
927
|
}
|
|
1045
928
|
/**
|
|
1046
929
|
* A `date` or `time` value: an ISO substring, a `dateParser` call, or a `Date` when `representation` is `'date'`.
|
|
1047
930
|
*/
|
|
1048
931
|
function dateTimeCall(representation, { format, substring }, parser = "faker", schemaName) {
|
|
1049
|
-
if (representation === "string") return parser !== "faker" ? `${parser}(faker.date.anytime()).format("${format}")` : `faker.date.anytime().toISOString().substring(${substring})`;
|
|
932
|
+
if (representation === "string") return parser !== "faker" ? `${dateParserName(parser)}(faker.date.anytime()).format("${format}")` : `faker.date.anytime().toISOString().substring(${substring})`;
|
|
1050
933
|
if (parser !== "faker") throw new Diagnostics.Error({
|
|
1051
934
|
code: Diagnostics.code.invalidPluginOptions,
|
|
1052
935
|
severity: "error",
|
|
@@ -1057,12 +940,58 @@ function dateTimeCall(representation, { format, substring }, parser = "faker", s
|
|
|
1057
940
|
});
|
|
1058
941
|
return "faker.date.anytime()";
|
|
1059
942
|
}
|
|
943
|
+
const encodingCalls = /* @__PURE__ */ new Map([["base64", "btoa(faker.string.alpha())"], ["base64url", "faker.string.alphanumeric(8)"]]);
|
|
944
|
+
const stringFormatCalls = /* @__PURE__ */ new Map([
|
|
945
|
+
["jwt", "faker.internet.jwt()"],
|
|
946
|
+
["ulid", "faker.string.ulid()"],
|
|
947
|
+
["iban", "faker.finance.iban()"],
|
|
948
|
+
["duration", "'P1D'"],
|
|
949
|
+
["hostname", "faker.internet.domainName()"]
|
|
950
|
+
]);
|
|
951
|
+
const classEscapes = new Map([
|
|
952
|
+
["d", "0-9"],
|
|
953
|
+
["w", "A-Za-z0-9_"],
|
|
954
|
+
...Array.from("-!@#$&()`.+,\"", (char) => [char, char])
|
|
955
|
+
]);
|
|
956
|
+
function toFakerPattern(pattern) {
|
|
957
|
+
let result = "";
|
|
958
|
+
let inClass = false;
|
|
959
|
+
let classStart = -1;
|
|
960
|
+
for (let index = 0; index < pattern.length; index++) {
|
|
961
|
+
const char = pattern[index];
|
|
962
|
+
const next = pattern[index + 1];
|
|
963
|
+
if (char === "\\" && next !== void 0) {
|
|
964
|
+
index++;
|
|
965
|
+
if (inClass && next === "-") {
|
|
966
|
+
if (result[classStart] !== "-") result = `${result.slice(0, classStart)}-${result.slice(classStart)}`;
|
|
967
|
+
continue;
|
|
968
|
+
}
|
|
969
|
+
const classMembers = classEscapes.get(next);
|
|
970
|
+
if (classMembers === void 0) {
|
|
971
|
+
result += `\\${next}`;
|
|
972
|
+
continue;
|
|
973
|
+
}
|
|
974
|
+
result += inClass ? classMembers : `[${classMembers}]`;
|
|
975
|
+
continue;
|
|
976
|
+
}
|
|
977
|
+
if (char === "[" && !inClass) {
|
|
978
|
+
inClass = true;
|
|
979
|
+
result += next === "^" ? "[^" : "[";
|
|
980
|
+
if (next === "^") index++;
|
|
981
|
+
classStart = result.length;
|
|
982
|
+
continue;
|
|
983
|
+
}
|
|
984
|
+
if (char === "]") inClass = false;
|
|
985
|
+
result += char;
|
|
986
|
+
}
|
|
987
|
+
return result.replace(/\{(\d+),\}/g, (_, min) => `{${min},${Number(min) + 10}}`);
|
|
988
|
+
}
|
|
1060
989
|
function matches(value, regexGenerator = "faker") {
|
|
1061
990
|
if (regexGenerator === "randexp") return `${toRegExpString(value, "RandExp")}.gen()`;
|
|
1062
|
-
return `faker.helpers.fromRegExp(${toRegExpString(value, null)})`;
|
|
991
|
+
return `faker.helpers.fromRegExp(${toRegExpString(toFakerPattern(value), null)})`;
|
|
1063
992
|
}
|
|
1064
993
|
function parseEnumValue(value) {
|
|
1065
|
-
if (typeof value === "string") return
|
|
994
|
+
if (typeof value === "string") return text.quote(value);
|
|
1066
995
|
return value;
|
|
1067
996
|
}
|
|
1068
997
|
/**
|
|
@@ -1073,11 +1002,11 @@ function parseEnumValue(value) {
|
|
|
1073
1002
|
* (a `ref` to the enum shared by every branch, say) identifies no branch and narrows nothing.
|
|
1074
1003
|
*/
|
|
1075
1004
|
function getDiscriminator(member, discriminatorPropertyName) {
|
|
1076
|
-
for (const property of ast.
|
|
1005
|
+
for (const property of ast.schema.findProperties({
|
|
1077
1006
|
node: member,
|
|
1078
1007
|
propertyName: discriminatorPropertyName
|
|
1079
1008
|
})) {
|
|
1080
|
-
const values = ast.
|
|
1009
|
+
const values = ast.schema.getLiterals(property.schema);
|
|
1081
1010
|
if (values.length === 1) return {
|
|
1082
1011
|
value: values[0],
|
|
1083
1012
|
required: property.required
|
|
@@ -1094,11 +1023,14 @@ function indexedTypeName(typeName, propertyName, nestedInUnion) {
|
|
|
1094
1023
|
const key = JSON.stringify(propertyName);
|
|
1095
1024
|
return nestedInUnion ? `(NonNullable<${typeName}> & Record<${key}, unknown>)[${key}]` : `NonNullable<${typeName}>[${key}]`;
|
|
1096
1025
|
}
|
|
1026
|
+
function itemTypeName(typeName, nestedInUnion) {
|
|
1027
|
+
return nestedInUnion ? `Extract<NonNullable<${typeName}>, ReadonlyArray<unknown>>[number]` : `NonNullable<${typeName}>[number]`;
|
|
1028
|
+
}
|
|
1097
1029
|
/**
|
|
1098
1030
|
* Creates a Faker printer that generates mock data generation code from schema nodes.
|
|
1099
1031
|
* Handles circular references gracefully by emitting memoizing getters for cyclic properties.
|
|
1100
1032
|
*/
|
|
1101
|
-
const printerFaker =
|
|
1033
|
+
const printerFaker = createPrinter((options) => {
|
|
1102
1034
|
const printNested = (node, overrideOptions = {}) => {
|
|
1103
1035
|
return printerFaker({
|
|
1104
1036
|
...options,
|
|
@@ -1118,22 +1050,38 @@ const printerFaker = ast.createPrinter((options) => {
|
|
|
1118
1050
|
string(node) {
|
|
1119
1051
|
if (node.pattern) return matches(node.pattern, this.options.regexGenerator);
|
|
1120
1052
|
if (node.format && integerFormats.has(node.format)) return node.format === "int32" ? `faker.number.int({ max: ${maxInt32} }).toString()` : "faker.number.bigInt().toString()";
|
|
1053
|
+
const formatCall = stringFormatCalls.get(node.format);
|
|
1054
|
+
if (formatCall) return formatCall;
|
|
1055
|
+
const contentSchema = ast.schema.getEmbeddedJson(node);
|
|
1056
|
+
if (contentSchema) return `JSON.stringify(${printNested(contentSchema, {
|
|
1057
|
+
typeName: void 0,
|
|
1058
|
+
nestedInObject: true,
|
|
1059
|
+
nestedInUnion: false
|
|
1060
|
+
})})`;
|
|
1061
|
+
const encodingCall = encodingCalls.get(node.content?.encoding?.toLowerCase());
|
|
1062
|
+
if (encodingCall) return encodingCall;
|
|
1121
1063
|
const { min, max } = node;
|
|
1122
|
-
if (
|
|
1123
|
-
if (
|
|
1064
|
+
if (max !== void 0) return `faker.string.alpha({ length: { min: ${min ?? Math.min(1, max)}, max: ${Math.min(max, (min ?? 0) + 100)} } })`;
|
|
1065
|
+
if (min !== void 0) return `faker.string.alpha({ length: ${min} })`;
|
|
1124
1066
|
return "faker.string.alpha()";
|
|
1125
1067
|
},
|
|
1126
1068
|
email: () => "faker.internet.email()",
|
|
1127
1069
|
url: () => "faker.internet.url()",
|
|
1128
1070
|
uuid: () => "faker.string.uuid()",
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1071
|
+
ipv4: () => "faker.internet.ipv4()",
|
|
1072
|
+
ipv6: () => "faker.internet.ipv6()",
|
|
1073
|
+
number: (node) => numberCall("float", node),
|
|
1074
|
+
integer: (node) => numberCall("int", node.format && node.format in maxValueByFormat ? {
|
|
1075
|
+
...node,
|
|
1076
|
+
max: Math.min(node.max ?? Infinity, maxValueByFormat[node.format])
|
|
1077
|
+
} : node),
|
|
1078
|
+
bigint: (node) => numberCall("bigInt", node),
|
|
1079
|
+
blob: () => "new Blob([faker.string.alpha()])",
|
|
1133
1080
|
datetime(node) {
|
|
1134
1081
|
const parser = this.options.dateParser ?? "faker";
|
|
1135
1082
|
if (parser === "faker") return "faker.date.anytime().toISOString()";
|
|
1136
|
-
|
|
1083
|
+
const format = node.local ? "YYYY-MM-DDTHH:mm:ss" : node.offset ? "YYYY-MM-DDTHH:mm:ssZ" : "YYYY-MM-DDTHH:mm:ss[Z]";
|
|
1084
|
+
return `${dateParserName(parser)}(faker.date.anytime()).format("${format}")`;
|
|
1137
1085
|
},
|
|
1138
1086
|
date(node) {
|
|
1139
1087
|
return dateTimeCall(node.representation ?? "string", {
|
|
@@ -1148,28 +1096,30 @@ const printerFaker = ast.createPrinter((options) => {
|
|
|
1148
1096
|
}, this.options.dateParser, this.options.schemaName);
|
|
1149
1097
|
},
|
|
1150
1098
|
ref(node) {
|
|
1151
|
-
const refName = ast.
|
|
1099
|
+
const refName = ast.ref.getName(node);
|
|
1152
1100
|
if (!refName) throw new Diagnostics.Error({
|
|
1153
1101
|
code: Diagnostics.code.pluginFailed,
|
|
1154
1102
|
severity: "error",
|
|
1155
1103
|
message: `Ref node "${node.ref ?? node.name ?? "unknown"}"${this.options.schemaName ? ` in schema "${this.options.schemaName}"` : ""} has no name.`,
|
|
1156
1104
|
plugin: "plugin-faker"
|
|
1157
1105
|
});
|
|
1158
|
-
if (
|
|
1106
|
+
if (isCyclicRef(node, this.options)) return this.options.typeName ? `undefined as unknown as ${this.options.typeName}` : "undefined as unknown";
|
|
1159
1107
|
const resolvedName = node.ref ? this.options.resolver.name(refName) : refName;
|
|
1160
|
-
if (!this.options.nestedInObject && canOverrideSchema(ast.
|
|
1108
|
+
if (!this.options.nestedInObject && canOverrideSchema(ast.ref.getSchema(node))) return `${resolvedName}(data)`;
|
|
1161
1109
|
return `${resolvedName}()`;
|
|
1162
1110
|
},
|
|
1163
1111
|
enum(node) {
|
|
1164
1112
|
const { typeName } = this.options;
|
|
1165
|
-
const values = ast.
|
|
1113
|
+
const values = ast.schema.getLiterals(node);
|
|
1166
1114
|
if (values.length === 1 && values[0] !== null && typeof values[0] === "object") return JSON.stringify(values[0]);
|
|
1167
|
-
return `faker.helpers.arrayElement
|
|
1115
|
+
return `faker.helpers.arrayElement([${values.map(parseEnumValue).join(", ")}]${typeName ? ` as Array<${typeName}>` : ""})`;
|
|
1168
1116
|
},
|
|
1169
1117
|
union(node) {
|
|
1170
1118
|
const { discriminatorPropertyName } = node;
|
|
1171
1119
|
const baseTypeName = this.options.typeName;
|
|
1172
|
-
|
|
1120
|
+
const isCyclicMember = (member) => isCyclicRef(member, this.options) || member.type === "intersection" && (member.members ?? []).some((part) => isCyclicRef(part, this.options));
|
|
1121
|
+
const acyclicMembers = (node.members ?? []).filter((member) => !isCyclicMember(member));
|
|
1122
|
+
return `faker.helpers.arrayElement([${((acyclicMembers.length ? acyclicMembers : node.members) ?? []).map((member) => {
|
|
1173
1123
|
const discriminator = discriminatorPropertyName ? getDiscriminator(member, discriminatorPropertyName) : void 0;
|
|
1174
1124
|
if (baseTypeName && discriminator) {
|
|
1175
1125
|
const key = `${JSON.stringify(discriminatorPropertyName)}${discriminator.required ? "" : "?"}`;
|
|
@@ -1185,29 +1135,31 @@ const printerFaker = ast.createPrinter((options) => {
|
|
|
1185
1135
|
nestedInObject: true,
|
|
1186
1136
|
nestedInUnion: true
|
|
1187
1137
|
});
|
|
1188
|
-
}).
|
|
1138
|
+
}).filter((item) => Boolean(item)).join(", ")}])`;
|
|
1189
1139
|
},
|
|
1190
1140
|
intersection(node) {
|
|
1191
1141
|
const objectMembers = (node.members ?? []).filter((member) => isObjectSchema(member));
|
|
1192
|
-
const
|
|
1193
|
-
const items =
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
}
|
|
1142
|
+
const memberRank = (member) => member.type === "ref" ? 0 : member.type === "union" ? 2 : 1;
|
|
1143
|
+
const items = ((objectMembers.length ? objectMembers.toSorted((a, b) => memberRank(a) - memberRank(b)) : node.members) ?? []).map((member) => printNested(member, {
|
|
1144
|
+
nestedInObject: true,
|
|
1145
|
+
...member.type === "ref" && ast.ref.getName(member) !== this.options.schemaName ? { cyclicSchemas: void 0 } : {}
|
|
1146
|
+
})).filter((item) => Boolean(item) && item !== "undefined");
|
|
1197
1147
|
if (items.length === 0) return "{}";
|
|
1198
1148
|
return !objectMembers.length || items.length === 1 ? items.at(-1) : `{...${items.join(", ...")}}`;
|
|
1199
1149
|
},
|
|
1200
1150
|
array(node) {
|
|
1201
|
-
|
|
1202
|
-
|
|
1151
|
+
if (node.items?.length === 1 && isCyclicRef(node.items[0], this.options)) return this.options.typeName ? `[] as ${this.options.typeName}` : "[]";
|
|
1152
|
+
const items = (node.items ?? []).map((member) => printNested(member, {
|
|
1153
|
+
typeName: this.options.typeName ? itemTypeName(this.options.typeName, this.options.nestedInUnion) : void 0,
|
|
1203
1154
|
nestedInObject: true
|
|
1204
|
-
})).
|
|
1155
|
+
})).filter((item) => Boolean(item));
|
|
1205
1156
|
if (items.length > 1) return `faker.helpers.arrayElements([${items.join(", ")}])`;
|
|
1206
1157
|
const { min, max } = node;
|
|
1207
1158
|
const item = items[0];
|
|
1208
|
-
|
|
1159
|
+
const countRange = max === void 0 ? void 0 : `{ min: ${min ?? 0}, max: ${Math.min(max, (min ?? 0) + 100)} }`;
|
|
1160
|
+
if (node.unique) return `faker.helpers.uniqueArray(() => (${item}), ${countRange ? `faker.number.int(${countRange})` : min ?? 3})`;
|
|
1161
|
+
if (countRange) return `faker.helpers.multiple(() => (${item}), { count: ${countRange}})`;
|
|
1209
1162
|
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
1163
|
return `faker.helpers.multiple(() => (${item}))`;
|
|
1212
1164
|
},
|
|
1213
1165
|
tuple(node) {
|
|
@@ -1218,18 +1170,30 @@ const printerFaker = ast.createPrinter((options) => {
|
|
|
1218
1170
|
},
|
|
1219
1171
|
object(node) {
|
|
1220
1172
|
const cyclicSchemas = this.options.cyclicSchemas;
|
|
1221
|
-
const keySchema = node.propertyNames &&
|
|
1173
|
+
const keySchema = node.propertyNames && ast.ref.mergeSchema(node.propertyNames);
|
|
1222
1174
|
const valueSchema = node.additionalProperties;
|
|
1223
|
-
const recordProperties = keySchema?.type === "enum" && typeof valueSchema === "object" ? ast.
|
|
1175
|
+
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
1176
|
name,
|
|
1225
1177
|
schema: valueSchema
|
|
1226
1178
|
})) : [];
|
|
1227
|
-
|
|
1179
|
+
const properties = [...node.properties, ...recordProperties];
|
|
1180
|
+
const keptProperties = node.max === void 0 ? properties : properties.toSorted((a, b) => Number(b.required) - Number(a.required)).slice(0, node.max);
|
|
1181
|
+
const declaredNames = new Set(properties.map((property) => property.name));
|
|
1182
|
+
const extras = [];
|
|
1183
|
+
for (let index = 1; typeof valueSchema === "object" && keptProperties.length + extras.length < (node.min ?? 0); index++) {
|
|
1184
|
+
const name = `additionalProperty${index}`;
|
|
1185
|
+
if (!declaredNames.has(name)) extras.push(ast.factory.createProperty({
|
|
1186
|
+
name,
|
|
1187
|
+
schema: valueSchema
|
|
1188
|
+
}));
|
|
1189
|
+
}
|
|
1190
|
+
return buildObject([...keptProperties, ...extras].map((property) => {
|
|
1228
1191
|
const value = printNested(property.schema, {
|
|
1229
1192
|
typeName: this.options.typeName ? indexedTypeName(this.options.typeName, property.name, this.options.nestedInUnion) : void 0,
|
|
1230
|
-
nestedInObject: true
|
|
1193
|
+
nestedInObject: true,
|
|
1194
|
+
...property.required && property.schema.type === "ref" ? { cyclicSchemas: void 0 } : {}
|
|
1231
1195
|
}) ?? "undefined";
|
|
1232
|
-
if (cyclicSchemas &&
|
|
1196
|
+
if (cyclicSchemas && ast.graph.containsCircular(property.schema, {
|
|
1233
1197
|
circularSchemas: cyclicSchemas,
|
|
1234
1198
|
excludeName: this.options.schemaName
|
|
1235
1199
|
})) 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 +1221,7 @@ const fakerGenerator = defineGenerator({
|
|
|
1257
1221
|
if (!node.name) return;
|
|
1258
1222
|
const tsResolver = ctx.driver.getResolver(pluginTsName);
|
|
1259
1223
|
const schemaName = node.name;
|
|
1260
|
-
const isEnumSchema = !!ast.
|
|
1224
|
+
const isEnumSchema = !!ast.schema.narrow(node, ast.schema.types.enum);
|
|
1261
1225
|
const schemaTypeName = enumTypeName({
|
|
1262
1226
|
resolver: tsResolver,
|
|
1263
1227
|
name: schemaName,
|
|
@@ -1306,7 +1270,8 @@ const fakerGenerator = defineGenerator({
|
|
|
1306
1270
|
node,
|
|
1307
1271
|
root,
|
|
1308
1272
|
output,
|
|
1309
|
-
group: group ?? void 0
|
|
1273
|
+
group: group ?? void 0,
|
|
1274
|
+
checks: true
|
|
1310
1275
|
}), fakerText);
|
|
1311
1276
|
return /* @__PURE__ */ jsxs(File, {
|
|
1312
1277
|
baseName: meta.file.baseName,
|
|
@@ -1342,7 +1307,7 @@ const fakerGenerator = defineGenerator({
|
|
|
1342
1307
|
}),
|
|
1343
1308
|
dateParser !== "faker" && /* @__PURE__ */ jsx(File.Import, {
|
|
1344
1309
|
path: dateParser,
|
|
1345
|
-
name: dateParser
|
|
1310
|
+
name: dateParserName(dateParser)
|
|
1346
1311
|
}),
|
|
1347
1312
|
typeReference.importPath && /* @__PURE__ */ jsx(File.Import, {
|
|
1348
1313
|
isTypeOnly: true,
|
|
@@ -1359,7 +1324,7 @@ const fakerGenerator = defineGenerator({
|
|
|
1359
1324
|
imp.path,
|
|
1360
1325
|
imp.name
|
|
1361
1326
|
].join("-"))),
|
|
1362
|
-
seed ? /* @__PURE__ */ jsx(File.Source, {
|
|
1327
|
+
seed !== void 0 ? /* @__PURE__ */ jsx(File.Source, {
|
|
1363
1328
|
name: "faker-seed",
|
|
1364
1329
|
children: `faker.seed(${JSON.stringify(seed)})`
|
|
1365
1330
|
}) : void 0,
|
|
@@ -1380,7 +1345,7 @@ const fakerGenerator = defineGenerator({
|
|
|
1380
1345
|
const { output, group, dateParser, regexGenerator, seed, locale, printer, typeMode } = ctx.options;
|
|
1381
1346
|
const pluginTs = ctx.requirePlugin(pluginTsName);
|
|
1382
1347
|
const tsResolver = ctx.driver.getResolver(pluginTsName);
|
|
1383
|
-
const { path: pathParams, query: queryParams, header: headerParams, cookie: cookieParams } =
|
|
1348
|
+
const { path: pathParams, query: queryParams, header: headerParams, cookie: cookieParams } = ast.http.groupParameters(node);
|
|
1384
1349
|
const paramGroups = [
|
|
1385
1350
|
{
|
|
1386
1351
|
params: pathParams,
|
|
@@ -1449,7 +1414,7 @@ const fakerGenerator = defineGenerator({
|
|
|
1449
1414
|
description: node.requestBody?.description ?? schema.description
|
|
1450
1415
|
}));
|
|
1451
1416
|
const responseName = resolver.response.response(node);
|
|
1452
|
-
const hasResponseNameCollision = new Set(
|
|
1417
|
+
const hasResponseNameCollision = new Set(ast.http.responses.getImportedNames(node.responses).map((name) => resolver.name(name))).has(responseName);
|
|
1453
1418
|
const localHelperNames = /* @__PURE__ */ new Set([
|
|
1454
1419
|
...paramGroups.map((group) => group.name),
|
|
1455
1420
|
...responseUnits.map((unit) => unit.name),
|
|
@@ -1480,12 +1445,13 @@ const fakerGenerator = defineGenerator({
|
|
|
1480
1445
|
node: schema,
|
|
1481
1446
|
root,
|
|
1482
1447
|
output,
|
|
1483
|
-
group: group ?? void 0
|
|
1448
|
+
group: group ?? void 0,
|
|
1449
|
+
checks: true
|
|
1484
1450
|
}).filter((entry) => entry.path !== meta.file.path);
|
|
1485
1451
|
}
|
|
1486
1452
|
function renderEntry({ schema, name, typeName, description, keysToOmit, skipImportNames = [] }) {
|
|
1487
1453
|
if (!schema) return null;
|
|
1488
|
-
if (schema.type === ast.
|
|
1454
|
+
if (schema.type === ast.schema.types.object && keysToOmit?.length) {
|
|
1489
1455
|
schema = {
|
|
1490
1456
|
...schema,
|
|
1491
1457
|
properties: schema.properties.filter((property) => !keysToOmit?.includes(property.name))
|
|
@@ -1579,9 +1545,9 @@ const fakerGenerator = defineGenerator({
|
|
|
1579
1545
|
}),
|
|
1580
1546
|
dateParser !== "faker" && /* @__PURE__ */ jsx(File.Import, {
|
|
1581
1547
|
path: dateParser,
|
|
1582
|
-
name: dateParser
|
|
1548
|
+
name: dateParserName(dateParser)
|
|
1583
1549
|
}),
|
|
1584
|
-
seed ? /* @__PURE__ */ jsx(File.Source, {
|
|
1550
|
+
seed !== void 0 ? /* @__PURE__ */ jsx(File.Source, {
|
|
1585
1551
|
name: "faker-seed",
|
|
1586
1552
|
children: `faker.seed(${JSON.stringify(seed)})`
|
|
1587
1553
|
}) : void 0,
|
|
@@ -1600,11 +1566,11 @@ const fakerGenerator = defineGenerator({
|
|
|
1600
1566
|
});
|
|
1601
1567
|
// kubb-extension.json
|
|
1602
1568
|
var compatibility = {
|
|
1603
|
-
"kubb": ">=5.
|
|
1569
|
+
"kubb": ">=5.18.4",
|
|
1604
1570
|
"node": ">=22.22"
|
|
1605
1571
|
};
|
|
1606
1572
|
// package.json
|
|
1607
|
-
var version = "5.4.
|
|
1573
|
+
var version = "5.4.1";
|
|
1608
1574
|
// src/resolvers/resolverFaker.ts
|
|
1609
1575
|
/**
|
|
1610
1576
|
* Default resolver used by `@kubb/plugin-faker`. Decides the names and file
|
|
@@ -1621,9 +1587,9 @@ var version = "5.4.0-canary.20261111T122006";
|
|
|
1621
1587
|
const resolverFaker = createResolver({
|
|
1622
1588
|
pluginName: "plugin-faker",
|
|
1623
1589
|
name(name) {
|
|
1624
|
-
return this.memo(`name:${name}`, () => ensureValidVarName(
|
|
1590
|
+
return this.memo(`name:${name}`, () => ensureValidVarName(casing.camel(name, { prefix: "create" })));
|
|
1625
1591
|
},
|
|
1626
|
-
file: createCasedFile((part) =>
|
|
1592
|
+
file: createCasedFile((part) => casing.camel(part, { prefix: "create" })),
|
|
1627
1593
|
param: createOperationParamResolver(),
|
|
1628
1594
|
response: createOperationResponseResolver()
|
|
1629
1595
|
});
|
|
@@ -1681,7 +1647,7 @@ const pluginFaker = defineExtension({
|
|
|
1681
1647
|
include,
|
|
1682
1648
|
override,
|
|
1683
1649
|
typeMode,
|
|
1684
|
-
group:
|
|
1650
|
+
group: group ?? null,
|
|
1685
1651
|
dateParser,
|
|
1686
1652
|
regexGenerator,
|
|
1687
1653
|
printer
|