@orval/mock 8.16.0 → 8.18.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.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { DefaultTag, EnumGeneration, OutputMockType, OutputMode, PropertySortOrder, camel, compareVersions, escape, escapeRegExp, generalJSTypesWithArray, generateDependencyImports, getKey, getRefInfo, isBoolean, isFunction, isMswMock, isNumber, isObject, isReference, isSchema, isString, kebab, mergeDeep, pascal, resolveRef, sanitize, stringify } from "@orval/core";
1
+ import { DefaultTag, EnumGeneration, OutputMockType, OutputMode, PropertySortOrder, camel, compareVersions, escapeRegExp, generalJSTypesWithArray, generateDependencyImports, getKey, getRefInfo, isBoolean, isFunction, isMswMock, isNumber, isObject, isReference, isSchema, isString, jsStringLiteralEscape, kebab, mergeDeep, pascal, resolveRef, sanitize, stringify } from "@orval/core";
2
2
  import { prop } from "remeda";
3
3
  //#region src/mock-types.ts
4
4
  function isStrictMock(mockOptions) {
@@ -20,13 +20,65 @@ export type MockWithNullableOverrides<
20
20
  [K in Extract<KeysWithNull<O>, keyof T>]: M[K] | null;
21
21
  };`;
22
22
  }
23
- function getStrictMockTypeDeclaration(typeName) {
24
- return `export type ${getStrictMockTypeName(typeName)} = {\n [K in keyof Required<${typeName}>]: NonNullable<Required<${typeName}>[K]>;\n};`;
23
+ function classifyStrictMockSchemaType(schema, context) {
24
+ if (!schema) return "object";
25
+ if (schema.format === "binary" || schema.contentMediaType === "application/octet-stream" && !schema.contentEncoding) return "binary";
26
+ if (typeof schema.$ref === "string") {
27
+ if (context) {
28
+ const { schema: resolved } = resolveRef(schema, context);
29
+ return classifyStrictMockSchemaType(resolved, context);
30
+ }
31
+ return "object";
32
+ }
33
+ if (schema.type === "object" || schema.properties || isComposedObjectSchema(schema)) return "object";
34
+ return "alias";
35
+ }
36
+ function isComposedObjectSchema(schema) {
37
+ const branches = schema.oneOf ?? schema.anyOf ?? schema.allOf;
38
+ if (!branches?.length) return false;
39
+ return branches.some((branch) => {
40
+ const item = branch;
41
+ if (typeof item.$ref === "string" || item.type === "object" || item.properties) return true;
42
+ return isComposedObjectSchema(item);
43
+ });
44
+ }
45
+ function getStrictMockTypeDeclaration(typeName, kind = "object", options) {
46
+ const mockTypeName = getStrictMockTypeName(typeName);
47
+ if (kind === "alias") return `export type ${mockTypeName} = ${typeName};`;
48
+ if (kind === "binary") return `export type ${mockTypeName} = ArrayBuffer;`;
49
+ const mappedType = `{\n [K in keyof Required<NonNullable<${typeName}>>]: NonNullable<Required<NonNullable<${typeName}>>[K]>;\n}`;
50
+ return `export type ${mockTypeName} = ${options?.schemaNullableAtRoot ? `${mappedType} | null` : mappedType};`;
25
51
  }
26
- function getStrictMockTypeDeclarations(typeNames) {
52
+ function getStrictMockTypeDeclarations(typeNames, kinds, nullableAtRoot) {
27
53
  const unique = [...new Set(typeNames)];
28
54
  if (unique.length === 0) return "";
29
- return unique.map((typeName) => getStrictMockTypeDeclaration(typeName)).join("\n\n");
55
+ return unique.map((typeName) => getStrictMockTypeDeclaration(typeName, kinds?.[typeName] ?? "object", { schemaNullableAtRoot: nullableAtRoot?.[typeName] })).join("\n\n");
56
+ }
57
+ function strictMockResolvedImportMatches(typeName, resolvedImport, importBareName) {
58
+ const resolvedName = resolvedImport?.name;
59
+ if (!resolvedName) return false;
60
+ if (typeName === resolvedName) return true;
61
+ if (resolvedImport.alias && typeName === resolvedImport.alias) return true;
62
+ return importBareName !== void 0 && importBareName === resolvedName;
63
+ }
64
+ function resolveStrictMockSchemaForTypeName(typeName, originalSchema, context, importBareName) {
65
+ if (!originalSchema) return;
66
+ if (!context) return originalSchema;
67
+ const branches = originalSchema.oneOf ?? originalSchema.anyOf ?? originalSchema.allOf;
68
+ if (branches?.length) {
69
+ for (const branch of branches) {
70
+ if (typeof branch.$ref !== "string") continue;
71
+ const resolved = resolveRef(branch, context);
72
+ if (strictMockResolvedImportMatches(typeName, resolved.imports[0], importBareName)) return resolved.schema;
73
+ }
74
+ return;
75
+ }
76
+ if (typeof originalSchema.$ref === "string") {
77
+ const resolved = resolveRef(originalSchema, context);
78
+ if (strictMockResolvedImportMatches(typeName, resolved.imports[0], importBareName)) return resolved.schema;
79
+ return;
80
+ }
81
+ return originalSchema;
30
82
  }
31
83
  function getMockFactoryReturnType(typeName, mockOptions) {
32
84
  return isStrictMock(mockOptions) ? getStrictMockTypeName(typeName) : typeName;
@@ -73,8 +125,33 @@ function getSchemaTypeNamesFromResponses(responses) {
73
125
  }
74
126
  return [...names];
75
127
  }
76
- function buildStrictMockTypeFileHeader(schemaTypeNames) {
77
- const schemaBlock = getStrictMockTypeDeclarations([...new Set(schemaTypeNames)]);
128
+ function getStrictMockSchemaKindsFromResponses(responses, context) {
129
+ const kinds = {};
130
+ for (const response of responses) {
131
+ for (const imp of response.imports) {
132
+ if (imp.values || imp.schemaFactory) continue;
133
+ const importName = imp.alias ?? imp.name;
134
+ if (!/^[A-Z]\w*$/.test(importName)) continue;
135
+ const schemaForImport = resolveStrictMockSchemaForTypeName(importName, response.originalSchema, context, imp.name);
136
+ if (!schemaForImport) continue;
137
+ kinds[importName] = classifyStrictMockSchemaType(schemaForImport, context);
138
+ }
139
+ const { value } = response;
140
+ if (!value || !response.originalSchema) continue;
141
+ const baseType = value.endsWith("[]") ? value.slice(0, -2) : value;
142
+ if (!/^[A-Z]\w*$/.test(baseType)) continue;
143
+ const schema = response.originalSchema;
144
+ if (value.endsWith("[]") && schema.type === "array" && schema.items) {
145
+ const items = schema.items;
146
+ kinds[baseType] = classifyStrictMockSchemaType(items, context);
147
+ continue;
148
+ }
149
+ kinds[baseType] = classifyStrictMockSchemaType(resolveStrictMockSchemaForTypeName(baseType, response.originalSchema, context) ?? response.originalSchema, context);
150
+ }
151
+ return kinds;
152
+ }
153
+ function buildStrictMockTypeFileHeader(schemaTypeNames, kinds) {
154
+ const schemaBlock = getStrictMockTypeDeclarations([...new Set(schemaTypeNames)], kinds);
78
155
  return [getStrictMockHelperTypeDeclarations(), schemaBlock].filter(Boolean).join("\n\n");
79
156
  }
80
157
  /**
@@ -88,7 +165,7 @@ function dedupeStrictMockTypeDeclarations(implementation, options = {}) {
88
165
  if (!isStrictMock(options.mockOptions)) return implementation;
89
166
  const schemaTypeNames = options.strictSchemaTypeNames ? [...new Set(options.strictSchemaTypeNames)] : [];
90
167
  if (schemaTypeNames.length === 0) return implementation;
91
- return `${buildStrictMockTypeFileHeader(schemaTypeNames)}\n\n${implementation.trimStart()}`;
168
+ return `${buildStrictMockTypeFileHeader(schemaTypeNames, options.strictMockSchemaKinds)}\n\n${implementation.trimStart()}`;
92
169
  }
93
170
  function applyStrictMockReturnType(returnType, schemaTypeNames) {
94
171
  if (schemaTypeNames.length === 0) return returnType;
@@ -97,6 +174,85 @@ function applyStrictMockReturnType(returnType, schemaTypeNames) {
97
174
  for (const name of sorted) result = result.replaceAll(new RegExp(String.raw`\b${escapeRegExp(name)}\b`, "g"), getStrictMockTypeName(name));
98
175
  return result;
99
176
  }
177
+ const STRICT_MOCK_SCHEMA_TYPE_FROM_OVERRIDES = /MockWithNullableOverrides<([A-Z]\w*),/g;
178
+ const STRICT_MOCK_SCHEMA_TYPE_FROM_OVERRIDE_ALIAS = /MockWithNullableOverrides<[^,]+,\s*[^,]+,\s*([A-Z]\w*Mock)>/g;
179
+ const STRICT_MOCK_SCHEMA_TYPE_FROM_MOCK_ALIAS_RETURN = /\): ([A-Z]\w*Mock)(?:\[\]|;)/g;
180
+ /** Inverse of {@link getStrictMockTypeName}: `PetMock` → `Pet`, `WidgetMockMock` → `WidgetMock`. */
181
+ function getSchemaTypeNameFromStrictMockAlias(alias) {
182
+ return alias.endsWith("Mock") ? alias.slice(0, -4) : alias;
183
+ }
184
+ /**
185
+ * Collect schema type names referenced by strict mock factories in generated
186
+ * implementation text (nested split factories, array item helpers, etc.).
187
+ *
188
+ * This reverse-parses emitted factory syntax and is therefore coupled to the
189
+ * current `formatMockFactoryDeclaration` / `getMockFactorySignatureParts`
190
+ * shape. The structurally robust alternative is to record each nested item's
191
+ * schema name where split factories are generated (array-item / faker getters,
192
+ * where the `$ref` name is known) and thread it into `strictMockSchemaTypeNames`.
193
+ */
194
+ function collectStrictMockSchemaTypeNamesFromImplementation(implementation) {
195
+ const names = /* @__PURE__ */ new Set();
196
+ for (const match of implementation.matchAll(STRICT_MOCK_SCHEMA_TYPE_FROM_OVERRIDES)) names.add(match[1]);
197
+ for (const pattern of [STRICT_MOCK_SCHEMA_TYPE_FROM_OVERRIDE_ALIAS, STRICT_MOCK_SCHEMA_TYPE_FROM_MOCK_ALIAS_RETURN]) for (const match of implementation.matchAll(pattern)) names.add(getSchemaTypeNameFromStrictMockAlias(match[1]));
198
+ return [...names];
199
+ }
200
+ function mergeStrictMockSchemaTypeNames(...groups) {
201
+ const names = /* @__PURE__ */ new Set();
202
+ for (const group of groups) {
203
+ if (!group) continue;
204
+ for (const name of group) names.add(name);
205
+ }
206
+ return names.size > 0 ? [...names] : void 0;
207
+ }
208
+ function mergeStrictMockSchemaKinds(...groups) {
209
+ const merged = {};
210
+ for (const group of groups) {
211
+ if (!group) continue;
212
+ for (const [name, kind] of Object.entries(group)) merged[name] ??= kind;
213
+ }
214
+ return Object.keys(merged).length > 0 ? merged : void 0;
215
+ }
216
+ //#endregion
217
+ //#region src/faker/imports.ts
218
+ /**
219
+ * Appends entries added to `source` since `sinceIndex`. Uses indexed push
220
+ * instead of spread so large import batches (common with `schemas: true`
221
+ * delegation on wide objects) do not overflow the call stack.
222
+ */
223
+ function appendImportsDelta(target, source, sinceIndex) {
224
+ for (let i = sinceIndex; i < source.length; i++) target.push(source[i]);
225
+ }
226
+ /**
227
+ * Merge imports returned from mock resolution when the shared imports array
228
+ * was not mutated in place. Enum mocks and nested object factories return
229
+ * their imports separately; schema-factory delegation mutates `sharedImports`
230
+ * directly and must not be merged again from `resolvedImports`.
231
+ */
232
+ function mergeReturnedMockImports(sharedImports, sharedBefore, resolvedImports) {
233
+ if (sharedImports.length === sharedBefore) appendImportsDelta(sharedImports, resolvedImports, 0);
234
+ }
235
+ /** Recover type imports referenced by nested oneOf split mock helpers. */
236
+ function collectSplitMockTypeImports(implementations) {
237
+ const seen = /* @__PURE__ */ new Set();
238
+ const imports = [];
239
+ const addType = (name) => {
240
+ if (!name || seen.has(name)) return;
241
+ seen.add(name);
242
+ imports.push({
243
+ name,
244
+ values: false
245
+ });
246
+ };
247
+ for (const impl of implementations) {
248
+ for (const match of impl.matchAll(/export const get\w+Mock = \(\s*overrideResponse: Partial<(\w+)[^)]*\):\s*(\w+)\s*=>/g)) {
249
+ addType(match[1]);
250
+ addType(match[2]);
251
+ }
252
+ for (const match of impl.matchAll(/export const get\w+Mock[\s\S]*?MockWithNullableOverrides<(?:Extract<(\w+),[^>]+>|(\w+)),/g)) addType(match[1] ?? match[2]);
253
+ }
254
+ return imports;
255
+ }
100
256
  //#endregion
101
257
  //#region src/delay.ts
102
258
  const getDelay = (override, options) => {
@@ -149,6 +305,13 @@ const DEFAULT_OBJECT_KEY_MOCK = "faker.string.alphanumeric(5)";
149
305
  //#endregion
150
306
  //#region src/faker/getters/object.ts
151
307
  const overrideVarName = "overrideResponse";
308
+ function wrapRootNullableObjectValue(value, schemaItem, mockOptions, combine) {
309
+ const nullableAtRoot = !combine && isNullableSchema(schemaItem) && !mockOptions?.nonNullable;
310
+ return {
311
+ value: nullableAtRoot ? getNullable(value, true) : value,
312
+ nullWrapped: nullableAtRoot
313
+ };
314
+ }
152
315
  function getReferenceName$1(ref, context) {
153
316
  if (!ref) return "";
154
317
  return getRefInfo(ref, context).name;
@@ -191,10 +354,44 @@ function getMockObject({ item, mockOptions, operationId, tags, combine, context,
191
354
  splitMockImplementations
192
355
  });
193
356
  if (Array.isArray(itemType)) {
357
+ const nonNullTypes = mockOptions?.nonNullable ? itemType.filter((type) => type !== "null") : itemType;
358
+ if (nonNullTypes.length === 0) return {
359
+ value: "null",
360
+ imports: [],
361
+ name: schemaItem.name
362
+ };
363
+ if (nonNullTypes.length === 1) return getMockObject({
364
+ item: {
365
+ ...schemaItem,
366
+ type: nonNullTypes[0]
367
+ },
368
+ mockOptions,
369
+ operationId,
370
+ tags,
371
+ combine,
372
+ context,
373
+ imports,
374
+ existingReferencedProperties,
375
+ existingReferencedAllOfRefs,
376
+ splitMockImplementations,
377
+ allowOverride
378
+ });
379
+ if (!itemProperties && (!itemRequired || itemRequired.length === 0) && !itemAdditionalProperties && nonNullTypes.includes("object") && nonNullTypes.includes("null") && nonNullTypes.every((type) => type === "object" || type === "null")) {
380
+ if (mockOptions?.nonNullable) return {
381
+ value: "{}",
382
+ imports: [],
383
+ name: schemaItem.name
384
+ };
385
+ return {
386
+ value: "faker.helpers.arrayElement([{}, null])",
387
+ imports: [],
388
+ name: schemaItem.name
389
+ };
390
+ }
194
391
  const baseItem = schemaItem;
195
392
  return combineSchemasMock({
196
393
  item: {
197
- anyOf: itemType.map((type) => ({
394
+ anyOf: nonNullTypes.map((type) => ({
198
395
  ...baseItem,
199
396
  type
200
397
  })),
@@ -228,6 +425,7 @@ function getMockObject({ item, mockOptions, operationId, tags, combine, context,
228
425
  if (isRequired) return `${getKey(key)}: null`;
229
426
  return;
230
427
  }
428
+ const importsBefore = imports.length;
231
429
  const resolvedValue = resolveMockValue({
232
430
  schema: {
233
431
  ...prop,
@@ -244,7 +442,7 @@ function getMockObject({ item, mockOptions, operationId, tags, combine, context,
244
442
  existingReferencedAllOfRefs: [],
245
443
  splitMockImplementations
246
444
  });
247
- imports.push(...resolvedValue.imports);
445
+ mergeReturnedMockImports(imports, importsBefore, resolvedValue.imports);
248
446
  includedProperties.push(key);
249
447
  const keyDefinition = getKey(key);
250
448
  const hasDefault = "default" in prop && prop.default !== void 0;
@@ -258,25 +456,35 @@ function getMockObject({ item, mockOptions, operationId, tags, combine, context,
258
456
  if (allowOverride) propertyScalars.push(`...${overrideVarName}`);
259
457
  value += propertyScalars.join(", ");
260
458
  value += !combine || combine.separator === "oneOf" || combine.separator === "anyOf" ? "}" : "";
459
+ const { value: finalValue, nullWrapped } = wrapRootNullableObjectValue(value, schemaItem, mockOptions, combine);
261
460
  return {
262
- value,
461
+ value: finalValue,
462
+ nullWrapped,
263
463
  imports,
264
464
  name: schemaItem.name,
265
465
  includedProperties
266
466
  };
267
467
  }
268
468
  if (itemAdditionalProperties) {
269
- if (itemAdditionalProperties === true) return {
270
- value: `{}`,
271
- imports: [],
272
- name: schemaItem.name
273
- };
469
+ if (itemAdditionalProperties === true) {
470
+ const { value: finalValue, nullWrapped } = wrapRootNullableObjectValue(`{}`, schemaItem, mockOptions, combine);
471
+ return {
472
+ value: finalValue,
473
+ nullWrapped,
474
+ imports: [],
475
+ name: schemaItem.name
476
+ };
477
+ }
274
478
  const additionalProperties = itemAdditionalProperties;
275
- if (isReference(additionalProperties) && existingReferencedProperties.includes(getReferenceName$1(additionalProperties.$ref, context))) return {
276
- value: `{}`,
277
- imports: [],
278
- name: schemaItem.name
279
- };
479
+ if (isReference(additionalProperties) && existingReferencedProperties.includes(getReferenceName$1(additionalProperties.$ref, context))) {
480
+ const { value: finalValue, nullWrapped } = wrapRootNullableObjectValue(`{}`, schemaItem, mockOptions, combine);
481
+ return {
482
+ value: finalValue,
483
+ nullWrapped,
484
+ imports: [],
485
+ name: schemaItem.name
486
+ };
487
+ }
280
488
  const resolvedValue = resolveMockValue({
281
489
  schema: {
282
490
  ...additionalProperties,
@@ -292,15 +500,19 @@ function getMockObject({ item, mockOptions, operationId, tags, combine, context,
292
500
  existingReferencedAllOfRefs: [],
293
501
  splitMockImplementations
294
502
  });
503
+ const { value: finalValue, nullWrapped } = wrapRootNullableObjectValue(`{
504
+ [${DEFAULT_OBJECT_KEY_MOCK}]: ${resolvedValue.value}
505
+ }`, schemaItem, mockOptions, combine);
295
506
  return {
296
507
  ...resolvedValue,
297
- value: `{
298
- [${DEFAULT_OBJECT_KEY_MOCK}]: ${resolvedValue.value}
299
- }`
508
+ value: finalValue,
509
+ nullWrapped
300
510
  };
301
511
  }
512
+ const { value: finalValue, nullWrapped } = wrapRootNullableObjectValue("{}", schemaItem, mockOptions, combine);
302
513
  return {
303
- value: "{}",
514
+ value: finalValue,
515
+ nullWrapped,
304
516
  imports: [],
305
517
  name: schemaItem.name
306
518
  };
@@ -314,9 +526,12 @@ function getMockObject({ item, mockOptions, operationId, tags, combine, context,
314
526
  */
315
527
  function getArrayItemMockFileScope(context, tags) {
316
528
  const mode = context.output.mode;
317
- if (mode === OutputMode.TAGS || mode === OutputMode.TAGS_SPLIT) return `tag:${kebab(tags.length > 0 ? tags[0] : DefaultTag)}`;
318
- if (mode === OutputMode.SPLIT) return "split";
319
- return "single";
529
+ const mockType = context.activeMockOutputType ?? OutputMockType.MSW;
530
+ let base;
531
+ if (mode === OutputMode.TAGS || mode === OutputMode.TAGS_SPLIT) base = `tag:${kebab(tags.length > 0 ? tags[0] : DefaultTag)}`;
532
+ else if (mode === OutputMode.SPLIT) base = "split";
533
+ else base = "single";
534
+ return `${base}:${mockType}`;
320
535
  }
321
536
  function getFileLevelExtractedFactories(context, scope) {
322
537
  context.arrayItemMockFactories ??= /* @__PURE__ */ new Map();
@@ -327,11 +542,11 @@ function getFileLevelExtractedFactories(context, scope) {
327
542
  return factories;
328
543
  }
329
544
  /**
330
- * True when the active faker generator entry opts into reusable array-item
331
- * mock factories for object-like array item schemas in operation responses.
545
+ * True when any mock generator entry opts into reusable array-item mock
546
+ * factories for object-like array item schemas in operation responses.
332
547
  */
333
548
  function shouldExtractArrayItemFactories(context) {
334
- return !!context.output.mock.generators.find((g) => !isFunction(g) && g.type === OutputMockType.FAKER && g.arrayItems === true);
549
+ return context.output.mock.generators.some((g) => !isFunction(g) && g.arrayItems === true);
335
550
  }
336
551
  /**
337
552
  * True when `schemas: true` already emits a consolidated factory for this
@@ -429,8 +644,8 @@ function extractArrayItemMock({ items, propertyName, parentName, operationId, ta
429
644
  if (!names) return;
430
645
  const { factoryName, typeName } = names;
431
646
  const fileLevelFactories = getFileLevelExtractedFactories(context, getArrayItemMockFileScope(context, tags));
647
+ const mockOptions = context.output.override.mock;
432
648
  if (!(fileLevelFactories.has(factoryName) || splitMockImplementations.some((f) => f.includes(`export const ${factoryName}`)))) {
433
- const mockOptions = context.output.override.mock;
434
649
  const { param, returnType, returnCast } = getMockFactorySignatureParts(typeName, mockOptions, {
435
650
  isOverridable: true,
436
651
  overrideType: `Partial<${typeName}>`
@@ -440,7 +655,116 @@ function extractArrayItemMock({ items, propertyName, parentName, operationId, ta
440
655
  fileLevelFactories.add(factoryName);
441
656
  }
442
657
  imports.push({ name: typeName });
443
- return `{...${factoryName}()}`;
658
+ return `{...${factoryName}()${isStrictMock(mockOptions) ? ` as ${getStrictMockTypeName(typeName)}` : ""}}`;
659
+ }
660
+ //#endregion
661
+ //#region src/faker/format-example-value.ts
662
+ const DATE_FORMATS = new Set(["date", "date-time"]);
663
+ function isDateFormat(format) {
664
+ return format !== void 0 && DATE_FORMATS.has(format);
665
+ }
666
+ function isSchemaObject(schema) {
667
+ return typeof schema === "object" && schema !== null && !Array.isArray(schema);
668
+ }
669
+ function resolveSchema(schema, context) {
670
+ if (!schema) return;
671
+ if (isReference(schema)) return resolveRef(schema, context).schema;
672
+ return schema;
673
+ }
674
+ function mergePropertySchemas(...schemas) {
675
+ const merged = {};
676
+ for (const schema of schemas) {
677
+ if (!schema?.properties) continue;
678
+ for (const [key, prop] of Object.entries(schema.properties)) if (isSchemaObject(prop)) merged[key] = prop;
679
+ }
680
+ return merged;
681
+ }
682
+ function getEffectiveScalarFormat(resolved, context) {
683
+ if (!resolved) return;
684
+ if (isDateFormat(resolved.format)) return resolved.format;
685
+ const oneOf = resolved.oneOf;
686
+ const anyOf = resolved.anyOf;
687
+ for (const variant of [...oneOf ?? [], ...anyOf ?? []]) {
688
+ const resolvedVariant = resolveSchema(isReference(variant) || isSchemaObject(variant) ? variant : void 0, context);
689
+ if (isDateFormat(resolvedVariant?.format)) return resolvedVariant.format;
690
+ }
691
+ }
692
+ /**
693
+ * Resolves compositional schemas (allOf / oneOf / anyOf) so example formatting
694
+ * can see property formats on nested and referenced types.
695
+ */
696
+ function resolveExampleSchema(schema, context, seenRefs = /* @__PURE__ */ new Set()) {
697
+ if (!schema) return;
698
+ if (isReference(schema)) {
699
+ const ref = schema.$ref;
700
+ if (ref && seenRefs.has(ref)) return resolveRef(schema, context).schema;
701
+ if (ref) seenRefs = new Set(seenRefs).add(ref);
702
+ }
703
+ const resolved = resolveSchema(schema, context);
704
+ if (!resolved) return;
705
+ const allOf = resolved.allOf;
706
+ const oneOf = resolved.oneOf;
707
+ const anyOf = resolved.anyOf;
708
+ const compositors = [
709
+ ...allOf ?? [],
710
+ ...oneOf ?? [],
711
+ ...anyOf ?? []
712
+ ];
713
+ const properties = mergePropertySchemas(resolved, ...compositors.map((sub) => resolveExampleSchema(sub, context, seenRefs)));
714
+ const baseResolved = resolved;
715
+ if (resolved.type === "array" && isSchemaObject(resolved.items)) {
716
+ const items = resolveExampleSchema(resolved.items, context, seenRefs);
717
+ const itemProperties = items?.properties;
718
+ const normalizedItems = itemProperties && Object.keys(itemProperties).length > 0 ? {
719
+ type: "object",
720
+ properties: itemProperties
721
+ } : items;
722
+ return {
723
+ ...baseResolved,
724
+ ...Object.keys(properties).length > 0 ? { properties } : {},
725
+ items: normalizedItems ?? resolved.items
726
+ };
727
+ }
728
+ if (Object.keys(properties).length > 0) return {
729
+ ...baseResolved,
730
+ properties
731
+ };
732
+ if (compositors.length > 0 && (oneOf ?? anyOf)) {
733
+ const variantProperties = mergePropertySchemas(...compositors.map((sub) => resolveExampleSchema(sub, context, seenRefs)));
734
+ if (Object.keys(variantProperties).length > 0) return {
735
+ type: "object",
736
+ properties: variantProperties
737
+ };
738
+ }
739
+ const scalarFormat = getEffectiveScalarFormat(resolved, context);
740
+ if (scalarFormat) return {
741
+ ...baseResolved,
742
+ format: scalarFormat
743
+ };
744
+ return resolved;
745
+ }
746
+ function formatLiteralValue(example, schema, context) {
747
+ if (example === null) return "null";
748
+ if (example === void 0) return "undefined";
749
+ const resolved = resolveExampleSchema(schema, context);
750
+ if (Array.isArray(example)) {
751
+ const itemsSchema = resolved?.type === "array" && isSchemaObject(resolved.items) ? resolveExampleSchema(resolved.items, context) : resolved;
752
+ return `[${example.map((item) => formatLiteralValue(item, itemsSchema, context)).join(", ")}]`;
753
+ }
754
+ if (typeof example === "object") {
755
+ const properties = resolved?.properties ?? {};
756
+ return `{ ${Object.entries(example).map(([key, value]) => {
757
+ const propSchema = properties[key];
758
+ const resolvedProp = isSchemaObject(propSchema) ? resolveExampleSchema(propSchema, context) : void 0;
759
+ return `${/^[a-zA-Z_$][\w$]*$/.test(key) ? key : JSON.stringify(key)}: ${formatLiteralValue(value, resolvedProp, context)}`;
760
+ }).join(", ")} }`;
761
+ }
762
+ if (context.output.override.useDates && typeof example === "string" && isDateFormat(getEffectiveScalarFormat(resolved, context))) return `new Date(${JSON.stringify(example)})`;
763
+ return JSON.stringify(example);
764
+ }
765
+ function formatSchemaExampleValue(example, schema, context) {
766
+ if (!context.output.override.useDates || schema === void 0) return JSON.stringify(example);
767
+ return formatLiteralValue(example, schema, context);
444
768
  }
445
769
  //#endregion
446
770
  //#region src/faker/getters/scalar.ts
@@ -463,7 +787,7 @@ function getMockScalar({ item, imports, mockOptions, operationId, tags, combine,
463
787
  if (context.output.override.mock?.useExamples || safeMockOptions.useExamples) {
464
788
  const propertyExample = item.example === void 0 ? Array.isArray(item.examples) && item.examples.length > 0 ? item.examples[0] : void 0 : item.example;
465
789
  if (propertyExample !== void 0) return {
466
- value: JSON.stringify(propertyExample),
790
+ value: formatSchemaExampleValue(propertyExample, item, context),
467
791
  imports: [],
468
792
  name: item.name,
469
793
  overrided: true
@@ -700,7 +1024,7 @@ function getItemType(item) {
700
1024
  }
701
1025
  function getEnum(item, imports, context, existingReferencedProperties, type) {
702
1026
  if (!item.enum) return "";
703
- let enumValue = `[${item.enum.filter((e) => e !== null).map((e) => type === "string" || type === void 0 && isString(e) ? `'${escape(e)}'` : e).join(",")}]`;
1027
+ let enumValue = `[${item.enum.filter((e) => e !== null).map((e) => type === "string" || type === void 0 && isString(e) ? `'${jsStringLiteralEscape(e)}'` : e).join(",")}]`;
704
1028
  if (context.output.override.enumGenerationType === EnumGeneration.ENUM) if (item.isRef || existingReferencedProperties.length === 0) {
705
1029
  enumValue += ` as ${item.name}${item.name.endsWith("[]") ? "" : "[]"}`;
706
1030
  imports.push({ name: item.name });
@@ -844,19 +1168,28 @@ function resolveMockValue({ schema, mockOptions, operationId, tags, combine, con
844
1168
  const newSeparator = newSchema.allOf ? "allOf" : newSchema.oneOf ? "oneOf" : "anyOf";
845
1169
  if (shouldDelegateToSchemaFactories(context) && isComponentsSchemaRef(refPaths) && !hasOverrideTouchingSchema(schemaRef?.properties, mockOptions, operationId, tags, schemaReference.path)) {
846
1170
  const factoryName = `get${pascal(name)}Mock`;
847
- imports.push({
1171
+ const factoryImport = {
848
1172
  name: factoryName,
849
1173
  values: true,
850
1174
  schemaFactory: true
851
- });
1175
+ };
1176
+ const isObjectLike = newSchema.type === "object" || !!newSchema.allOf || resolvesToObjectLike(newSchema, context);
1177
+ const mockTypeName = getStrictMockTypeName(pascal(name));
1178
+ const strictMockTypeImport = isStrictMock(mockOptions) && isObjectLike ? {
1179
+ name: mockTypeName,
1180
+ values: false,
1181
+ schemaFactory: true
1182
+ } : void 0;
1183
+ const strictObjectCast = isStrictMock(mockOptions) && isObjectLike ? ` as ${mockTypeName}` : "";
852
1184
  return {
853
- value: getNullable(newSchema.type === "object" || !!newSchema.allOf || resolvesToObjectLike(newSchema, context) ? `{ ...${factoryName}() }` : `${factoryName}()`, Boolean(newSchema.nullable), mockOptions?.nonNullable),
854
- imports,
1185
+ value: getNullable(isObjectLike ? `{ ...${factoryName}()${strictObjectCast} }` : `${factoryName}()`, Boolean(newSchema.nullable), mockOptions?.nonNullable),
1186
+ imports: strictMockTypeImport ? [factoryImport, strictMockTypeImport] : [factoryImport],
855
1187
  name: newSchema.name,
856
1188
  type: getType(newSchema),
857
1189
  nullWrapped: Boolean(newSchema.nullable) && !mockOptions?.nonNullable
858
1190
  };
859
1191
  }
1192
+ const importsBefore = imports.length;
860
1193
  const scalar = getMockScalar({
861
1194
  item: newSchema,
862
1195
  mockOptions,
@@ -887,27 +1220,36 @@ function resolveMockValue({ schema, mockOptions, operationId, tags, combine, con
887
1220
  splitMockImplementations.push(func);
888
1221
  }
889
1222
  scalar.value = newSchema.nullable ? `${funcName}()` : `{...${funcName}()}`;
890
- scalar.imports.push({ name: newSchema.name });
1223
+ const typeImport = {
1224
+ name: newSchema.name,
1225
+ values: false
1226
+ };
1227
+ scalar.imports.push(typeImport);
1228
+ if (scalar.imports !== imports) imports.push(typeImport);
891
1229
  }
1230
+ mergeReturnedMockImports(imports, importsBefore, scalar.imports);
892
1231
  return {
893
1232
  ...scalar,
894
1233
  type: getType(newSchema)
895
1234
  };
896
1235
  }
1236
+ const importsBefore = imports.length;
1237
+ const scalar = getMockScalar({
1238
+ item: schema,
1239
+ mockOptions,
1240
+ operationId,
1241
+ tags,
1242
+ combine,
1243
+ context,
1244
+ imports,
1245
+ existingReferencedProperties,
1246
+ existingReferencedAllOfRefs,
1247
+ splitMockImplementations,
1248
+ allowOverride
1249
+ });
1250
+ mergeReturnedMockImports(imports, importsBefore, scalar.imports);
897
1251
  return {
898
- ...getMockScalar({
899
- item: schema,
900
- mockOptions,
901
- operationId,
902
- tags,
903
- combine,
904
- context,
905
- imports,
906
- existingReferencedProperties,
907
- existingReferencedAllOfRefs,
908
- splitMockImplementations,
909
- allowOverride
910
- }),
1252
+ ...scalar,
911
1253
  type: getType(schema)
912
1254
  };
913
1255
  }
@@ -1167,7 +1509,8 @@ function getResponsesMockDefinition({ operationId, tags, returnType, responses,
1167
1509
  if (context.output.override.mock?.useExamples || mockOptions?.useExamples) {
1168
1510
  const exampleValue = unwrapExampleValue(example ?? originalSchema?.example ?? getExampleEntries(examples)[0] ?? getExampleEntries(originalSchema?.examples)[0]);
1169
1511
  if (exampleValue !== void 0) {
1170
- result.definitions.push(transformer ? transformer(exampleValue, returnType) : JSON.stringify(exampleValue));
1512
+ const formatted = formatSchemaExampleValue(exampleValue, originalSchema, context);
1513
+ result.definitions.push(transformer ? transformer(formatted, returnType) : formatted);
1171
1514
  continue;
1172
1515
  }
1173
1516
  }
@@ -1182,13 +1525,15 @@ function getResponsesMockDefinition({ operationId, tags, returnType, responses,
1182
1525
  };
1183
1526
  else if (!originalSchema) continue;
1184
1527
  const resolvedSchema = resolveRef(originalSchema, context).schema;
1528
+ const responseImports = imports ?? [];
1529
+ const importsBefore = responseImports.length;
1185
1530
  const scalar = getMockScalar({
1186
1531
  item: {
1187
1532
  ...resolvedSchema,
1188
1533
  name: definition,
1189
1534
  ...context.output.override.enumGenerationType === "enum" && isRef ? { isRef: true } : {}
1190
1535
  },
1191
- imports,
1536
+ imports: responseImports,
1192
1537
  mockOptions: mockOptionsWithoutFunc,
1193
1538
  operationId,
1194
1539
  tags,
@@ -1197,9 +1542,11 @@ function getResponsesMockDefinition({ operationId, tags, returnType, responses,
1197
1542
  splitMockImplementations,
1198
1543
  allowOverride: true
1199
1544
  });
1200
- result.imports.push(...scalar.imports);
1545
+ appendImportsDelta(result.imports, responseImports, importsBefore);
1546
+ if (scalar.imports !== responseImports) appendImportsDelta(result.imports, scalar.imports, 0);
1201
1547
  result.definitions.push(transformer ? transformer(scalar.value, returnType) : scalar.value);
1202
1548
  }
1549
+ appendImportsDelta(result.imports, collectSplitMockTypeImports(splitMockImplementations), 0);
1203
1550
  return result;
1204
1551
  }
1205
1552
  function getMockDefinition({ operationId, tags, returnType, responses, override, transformer, context, mockOptions, splitMockImplementations }) {
@@ -1403,7 +1750,8 @@ export const ${handlerName} = (overrideResponse?: ${mockReturnType} | ((${infoPa
1403
1750
  handler: handlerImplementation
1404
1751
  },
1405
1752
  imports: includeResponseImports,
1406
- strictMockSchemaTypeNames: strictMock && schemaTypeNames.length > 0 ? schemaTypeNames : void 0
1753
+ strictMockSchemaTypeNames: strictMock ? mergeStrictMockSchemaTypeNames(schemaTypeNames, collectStrictMockSchemaTypeNamesFromImplementation(mockImplementation)) : void 0,
1754
+ strictMockSchemaKinds: strictMock ? mergeStrictMockSchemaKinds(getStrictMockSchemaKindsFromResponses(responses, context), Object.fromEntries((collectStrictMockSchemaTypeNamesFromImplementation(mockImplementation) ?? []).map((name) => [name, "object"]))) : void 0
1407
1755
  };
1408
1756
  }
1409
1757
  function generateMSW(generatorVerbOptions, generatorOptions) {
@@ -1420,12 +1768,14 @@ function generateMSW(generatorVerbOptions, generatorOptions) {
1420
1768
  const handlerImplementations = [baseDefinition.implementation.handler];
1421
1769
  const imports = [...baseDefinition.imports];
1422
1770
  const strictMockSchemaTypeNames = new Set(baseDefinition.strictMockSchemaTypeNames);
1771
+ const strictMockSchemaKinds = { ...baseDefinition.strictMockSchemaKinds };
1423
1772
  if (generatorOptions.mock && isObject(generatorOptions.mock) && generatorOptions.mock.generateEachHttpStatus) for (const statusResponse of [...response.types.success, ...response.types.errors]) {
1424
1773
  const definition = generateDefinition(statusResponse.key, route, getResponseMockFunctionName, handlerName, generatorVerbOptions, generatorOptions, statusResponse.value, statusResponse.key, response.imports, [statusResponse], [statusResponse.contentType], splitMockImplementations);
1425
1774
  mockImplementations.push(definition.implementation.function);
1426
1775
  handlerImplementations.push(definition.implementation.handler);
1427
1776
  imports.push(...definition.imports);
1428
1777
  for (const name of definition.strictMockSchemaTypeNames ?? []) strictMockSchemaTypeNames.add(name);
1778
+ if (definition.strictMockSchemaKinds) for (const [name, kind] of Object.entries(definition.strictMockSchemaKinds)) strictMockSchemaKinds[name] ??= kind;
1429
1779
  }
1430
1780
  const aggregatedStrictNames = [...strictMockSchemaTypeNames];
1431
1781
  return {
@@ -1435,7 +1785,8 @@ function generateMSW(generatorVerbOptions, generatorOptions) {
1435
1785
  handler: handlerImplementations.join("\n")
1436
1786
  },
1437
1787
  imports,
1438
- strictMockSchemaTypeNames: aggregatedStrictNames.length > 0 ? aggregatedStrictNames : void 0
1788
+ strictMockSchemaTypeNames: aggregatedStrictNames.length > 0 ? aggregatedStrictNames : void 0,
1789
+ strictMockSchemaKinds: mergeStrictMockSchemaKinds(strictMockSchemaKinds)
1439
1790
  };
1440
1791
  }
1441
1792
  //#endregion
@@ -1473,7 +1824,8 @@ function generateFaker(generatorVerbOptions, generatorOptions) {
1473
1824
  handlerName: ""
1474
1825
  },
1475
1826
  imports: result.imports,
1476
- strictMockSchemaTypeNames: result.strictMockSchemaTypeNames
1827
+ strictMockSchemaTypeNames: result.strictMockSchemaTypeNames,
1828
+ strictMockSchemaKinds: result.strictMockSchemaKinds
1477
1829
  };
1478
1830
  }
1479
1831
  /**
@@ -1488,38 +1840,47 @@ function generateFaker(generatorVerbOptions, generatorOptions) {
1488
1840
  function generateFakerForSchemas(schemas, context, options) {
1489
1841
  const factories = [];
1490
1842
  const strictMockTypeNames = /* @__PURE__ */ new Set();
1843
+ const strictMockSchemaKinds = {};
1491
1844
  const allImports = [];
1492
1845
  const splitMockImplementations = [];
1493
1846
  const localFactoryNames = new Set(schemas.filter((s) => !!s.schema).map((s) => `get${pascal(s.name)}Mock`));
1494
- const mockOptions = context.output.override.mock;
1847
+ const localMockTypeNames = new Set(schemas.filter((s) => !!s.schema).map((s) => getStrictMockTypeName(pascal(s.name))));
1848
+ const mockOptions = getMockWithoutFunc(context.spec, context.output.override);
1495
1849
  for (const generatorSchema of schemas) {
1496
1850
  const { name, schema } = generatorSchema;
1497
1851
  if (!schema) continue;
1498
1852
  const factoryName = `get${pascal(name)}Mock`;
1499
1853
  const factoryImports = [];
1854
+ const factoryImportsBefore = factoryImports.length;
1855
+ const schemaName = pascal(name);
1500
1856
  const result = getMockScalar({
1501
1857
  item: {
1502
1858
  ...schema,
1503
- name
1859
+ name: schemaName
1504
1860
  },
1505
1861
  imports: factoryImports,
1506
1862
  mockOptions,
1507
1863
  operationId: name,
1508
1864
  tags: [],
1509
1865
  context,
1510
- existingReferencedProperties: [],
1866
+ existingReferencedProperties: [schemaName],
1867
+ existingReferencedAllOfRefs: [schemaName],
1511
1868
  splitMockImplementations,
1512
1869
  allowOverride: true,
1513
1870
  isRef: false
1514
1871
  });
1515
- allImports.push(...result.imports, ...factoryImports);
1872
+ appendImportsDelta(allImports, factoryImports, factoryImportsBefore);
1873
+ if (result.imports !== factoryImports) appendImportsDelta(allImports, result.imports, 0);
1516
1874
  const typeName = pascal(name);
1517
1875
  const { param, returnType, returnCast } = getMockFactorySignatureParts(typeName, mockOptions, {
1518
1876
  isOverridable: result.value.includes("overrideResponse"),
1519
1877
  overrideType: `Partial<${typeName}>`
1520
1878
  });
1521
1879
  const factory = formatMockFactoryDeclaration(factoryName, param, returnType, result.value, returnCast);
1522
- if (isStrictMock(mockOptions)) strictMockTypeNames.add(typeName);
1880
+ if (isStrictMock(mockOptions)) {
1881
+ strictMockTypeNames.add(typeName);
1882
+ strictMockSchemaKinds[typeName] = classifyStrictMockSchemaType(schema);
1883
+ }
1523
1884
  factories.push(factory);
1524
1885
  allImports.push({
1525
1886
  name: pascal(name),
@@ -1529,6 +1890,7 @@ function generateFakerForSchemas(schemas, context, options) {
1529
1890
  const mergedImports = /* @__PURE__ */ new Map();
1530
1891
  for (const imp of allImports) {
1531
1892
  if (imp.schemaFactory && localFactoryNames.has(imp.name)) continue;
1893
+ if (imp.schemaFactory && !imp.values && localMockTypeNames.has(imp.name)) continue;
1532
1894
  const key = `${imp.name}::${imp.alias ?? ""}`;
1533
1895
  const existing = mergedImports.get(key);
1534
1896
  if (!existing) {
@@ -1539,11 +1901,16 @@ function generateFakerForSchemas(schemas, context, options) {
1539
1901
  }
1540
1902
  const uniqueImports = [...mergedImports.values()];
1541
1903
  const implementation = [...splitMockImplementations, ...factories].filter(Boolean).join("\n\n");
1904
+ for (const name of collectStrictMockSchemaTypeNamesFromImplementation(implementation)) {
1905
+ strictMockTypeNames.add(name);
1906
+ strictMockSchemaKinds[name] ??= "object";
1907
+ }
1542
1908
  const aggregatedStrictNames = [...strictMockTypeNames];
1543
1909
  return {
1544
1910
  implementation,
1545
1911
  imports: uniqueImports,
1546
- strictMockSchemaTypeNames: aggregatedStrictNames.length > 0 ? aggregatedStrictNames : void 0
1912
+ strictMockSchemaTypeNames: aggregatedStrictNames.length > 0 ? aggregatedStrictNames : void 0,
1913
+ strictMockSchemaKinds: mergeStrictMockSchemaKinds(strictMockSchemaKinds)
1547
1914
  };
1548
1915
  }
1549
1916
  //#endregion
@@ -1585,9 +1952,16 @@ const generateMockImports = (importOptions) => {
1585
1952
  * `output.mock.generators` is dispatched here individually.
1586
1953
  */
1587
1954
  function generateMock(generatorVerbOptions, generatorOptions) {
1588
- switch (generatorOptions.mock.type) {
1589
- case OutputMockType.FAKER: return generateFaker(generatorVerbOptions, generatorOptions);
1590
- default: return generateMSW(generatorVerbOptions, generatorOptions);
1955
+ const { context } = generatorOptions;
1956
+ const previousActiveMockOutputType = context.activeMockOutputType;
1957
+ context.activeMockOutputType = generatorOptions.mock.type;
1958
+ try {
1959
+ switch (generatorOptions.mock.type) {
1960
+ case OutputMockType.FAKER: return generateFaker(generatorVerbOptions, generatorOptions);
1961
+ default: return generateMSW(generatorVerbOptions, generatorOptions);
1962
+ }
1963
+ } finally {
1964
+ context.activeMockOutputType = previousActiveMockOutputType;
1591
1965
  }
1592
1966
  }
1593
1967
  //#endregion