@orval/core 8.12.3 → 8.13.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
@@ -143,6 +143,16 @@ const TEMPLATE_TAG_REGEX = /\${(.+?)}/g;
143
143
  function isReference(obj) {
144
144
  return !isNullish$1(obj) && Object.hasOwn(obj, "$ref");
145
145
  }
146
+ /**
147
+ * Discriminator helper for {@link OpenApiDynamicReferenceObject}.
148
+ *
149
+ * Returns `true` when `obj` has a `$dynamicRef` string property,
150
+ * indicating it is an OpenAPI 3.1 dynamic reference rather than a
151
+ * static `$ref`.
152
+ */
153
+ function isDynamicReference(obj) {
154
+ return !isNullish$1(obj) && Object.hasOwn(obj, "$dynamicRef") && typeof obj.$dynamicRef === "string";
155
+ }
146
156
  function isDirectory(pathValue) {
147
157
  return !nodePath.extname(pathValue);
148
158
  }
@@ -741,13 +751,21 @@ function count(str = "", key) {
741
751
  var path_exports = /* @__PURE__ */ __exportAll({
742
752
  getRelativeImportPath: () => getRelativeImportPath,
743
753
  getSchemaFileName: () => getSchemaFileName,
754
+ isAbsolute: () => isAbsolute,
744
755
  join: () => join,
745
756
  joinSafe: () => joinSafe,
746
757
  normalizeSafe: () => normalizeSafe,
747
758
  relativeSafe: () => relativeSafe,
759
+ resolve: () => resolve,
748
760
  separator: () => "/",
749
761
  toUnix: () => toUnix
750
762
  });
763
+ function isAbsolute(value) {
764
+ return nodePath.isAbsolute(value);
765
+ }
766
+ function resolve(...args) {
767
+ return toUnix(nodePath.resolve(...args));
768
+ }
751
769
  function toUnix(value) {
752
770
  value = value.replaceAll("\\", "/");
753
771
  value = value.replaceAll(/(?<!^)\/+/g, "/");
@@ -1357,6 +1375,16 @@ function getRefInfo($ref, context) {
1357
1375
  refPaths
1358
1376
  };
1359
1377
  }
1378
+ /**
1379
+ * Extracts the anchor name from a fragment-only `$dynamicRef` (e.g. `#category` → `category`).
1380
+ *
1381
+ * Returns `undefined` for external-document `$dynamicRef` values (e.g. `other.json#anchor`)
1382
+ * which are not supported.
1383
+ */
1384
+ function getDynamicAnchorName(dynamicRef) {
1385
+ if (!dynamicRef.startsWith("#") || dynamicRef.length <= 1) return;
1386
+ return dynamicRef.slice(1);
1387
+ }
1360
1388
  //#endregion
1361
1389
  //#region src/getters/imports.ts
1362
1390
  function getAliasedImports({ name, resolvedValue, context }) {
@@ -1514,7 +1542,9 @@ function combineSchemas({ name, schema, separator, context, nullable, formDataCo
1514
1542
  const originalProps = resolvedValue.originalSchema.properties;
1515
1543
  if (resolvedValue.type === "object" && originalProps) resolvedData.allProperties.push(...Object.keys(originalProps));
1516
1544
  }
1517
- if (resolvedData.isEnum.every(Boolean) && name && items.length > 1 && context.output.override.enumGenerationType !== EnumGeneration.UNION) {
1545
+ const isAllEnums = resolvedData.isEnum.every(Boolean);
1546
+ const isNullableEnumComposition = (separator === "anyOf" || separator === "oneOf") && !isAllEnums && resolvedData.isEnum.some(Boolean) && resolvedData.isEnum.every((isEnum, index) => isEnum && !resolvedData.isRef[index] || resolvedData.types[index] === "null");
1547
+ if (isAllEnums && name && items.length > 1 && context.output.override.enumGenerationType !== EnumGeneration.UNION) {
1518
1548
  const { value: combinedEnumValue, valueImports, hasNull } = getCombinedEnumValue(resolvedData.values.map((value, index) => ({
1519
1549
  value,
1520
1550
  isRef: resolvedData.isRef[index],
@@ -1575,7 +1605,7 @@ function combineSchemas({ name, schema, separator, context, nullable, formDataCo
1575
1605
  imports: resolvedValue ? [...resolvedData.imports, ...resolvedValue.imports] : resolvedData.imports,
1576
1606
  schemas: resolvedValue ? [...resolvedData.schemas, ...resolvedValue.schemas] : resolvedData.schemas,
1577
1607
  dependencies: resolvedValue ? [...resolvedData.dependencies, ...resolvedValue.dependencies] : resolvedData.dependencies,
1578
- isEnum: false,
1608
+ isEnum: isNullableEnumComposition,
1579
1609
  type: "object",
1580
1610
  isRef: false,
1581
1611
  hasReadonlyProps: resolvedData.hasReadonlyProps || (resolvedValue?.hasReadonlyProps ?? false),
@@ -2041,16 +2071,33 @@ function getScalar({ item, name, context, formDataContext }) {
2041
2071
  examples: resolveExampleRefs(schemaExamples, context)
2042
2072
  };
2043
2073
  }
2044
- case "null": return {
2045
- value: "null",
2046
- isEnum: false,
2047
- type: "null",
2048
- imports: [],
2049
- schemas: [],
2050
- isRef: false,
2051
- hasReadonlyProps: schemaReadOnly ?? false,
2052
- dependencies: []
2053
- };
2074
+ case "null": {
2075
+ const itemAllOf = item.allOf;
2076
+ const itemOneOf = item.oneOf;
2077
+ const itemAnyOf = item.anyOf;
2078
+ let separator;
2079
+ if (itemAllOf?.length) separator = "allOf";
2080
+ else if (itemOneOf?.length) separator = "oneOf";
2081
+ else if (itemAnyOf?.length) separator = "anyOf";
2082
+ if (separator) return combineSchemas({
2083
+ schema: Object.fromEntries(Object.entries(item).filter(([key]) => key !== "type")),
2084
+ name,
2085
+ separator,
2086
+ context,
2087
+ nullable: nullable || " | null",
2088
+ formDataContext
2089
+ });
2090
+ return {
2091
+ value: "null",
2092
+ isEnum: false,
2093
+ type: "null",
2094
+ imports: [],
2095
+ schemas: [],
2096
+ isRef: false,
2097
+ hasReadonlyProps: schemaReadOnly ?? false,
2098
+ dependencies: []
2099
+ };
2100
+ }
2054
2101
  default: {
2055
2102
  if (isArray(itemType)) return combineSchemas({
2056
2103
  schema: { anyOf: itemType.map((type) => Object.assign({}, item, { type })) },
@@ -2088,6 +2135,28 @@ function getScalar({ item, name, context, formDataContext }) {
2088
2135
  }
2089
2136
  //#endregion
2090
2137
  //#region src/resolvers/ref.ts
2138
+ /** Convert a `$dynamicAnchor` name to a valid TypeScript generic parameter identifier. */
2139
+ function dynamicAnchorToParamName(anchor) {
2140
+ return sanitize(anchor, {
2141
+ underscore: "_",
2142
+ whitespace: "_",
2143
+ dash: "_",
2144
+ es5keyword: true,
2145
+ es5IdentifierName: true
2146
+ });
2147
+ }
2148
+ function dynamicAnchorsToUniqueParamNames(anchors) {
2149
+ const result = /* @__PURE__ */ new Map();
2150
+ const usedNames = /* @__PURE__ */ new Map();
2151
+ for (const anchor of anchors) {
2152
+ const base = dynamicAnchorToParamName(anchor);
2153
+ const count = usedNames.get(base) ?? 0;
2154
+ usedNames.set(base, count + 1);
2155
+ const paramName = count === 0 ? base : `${base}${count + 1}`;
2156
+ result.set(anchor, paramName);
2157
+ }
2158
+ return result;
2159
+ }
2091
2160
  const REF_NOT_FOUND_PREFIX = "Oops... 🍻. Ref not found";
2092
2161
  /**
2093
2162
  * Recursively resolves a `$ref` in an OpenAPI document, following
@@ -2137,15 +2206,100 @@ function resolveRef(schema, context, imports = []) {
2137
2206
  schemaName: originalName
2138
2207
  }]);
2139
2208
  }
2209
+ /** Check whether a schema reference has at least one `$defs` entry with both `$dynamicAnchor` and `$ref`. */
2210
+ function isBoundAlias(schema) {
2211
+ const defs = schema.$defs;
2212
+ if (!defs || typeof defs !== "object") return false;
2213
+ for (const defSchema of Object.values(defs)) {
2214
+ if (!defSchema || typeof defSchema !== "object") continue;
2215
+ const rec = defSchema;
2216
+ if (typeof rec.$dynamicAnchor === "string" && typeof rec.$ref === "string") return true;
2217
+ }
2218
+ return false;
2219
+ }
2140
2220
  /**
2141
- * Looks up a schema by its `$ref` path in the spec, applying suffix resolution.
2142
- *
2143
- * Preserves OpenAPI 3.0 `nullable` and 3.1 type-array (`["object", "null"]`)
2144
- * hints from the referencing schema onto the resolved target.
2145
- *
2146
- * @see https://spec.openapis.org/oas/v3.0.3#fixed-fields-18 (nullable)
2147
- * @see https://spec.openapis.org/oas/v3.1.0#schema-object (type as array)
2221
+ * Extract bound-alias information from a schema that references a generic template
2222
+ * and binds `$dynamicAnchor` entries to concrete types via `$defs`.
2148
2223
  */
2224
+ function extractBoundAliasInfo(schema, context) {
2225
+ let bindingElement;
2226
+ let extraSchemas;
2227
+ if (isReference(schema) && isBoundAlias(schema)) bindingElement = schema;
2228
+ else {
2229
+ const allOf = schema.allOf;
2230
+ if (Array.isArray(allOf)) for (let i = 0; i < allOf.length; i++) {
2231
+ const element = allOf[i];
2232
+ if (isReference(element) && isBoundAlias(element)) {
2233
+ bindingElement = element;
2234
+ extraSchemas = allOf.filter((_, j) => j !== i);
2235
+ break;
2236
+ }
2237
+ }
2238
+ }
2239
+ if (!bindingElement) return void 0;
2240
+ const defs = bindingElement.$defs;
2241
+ if (!defs || typeof defs !== "object") return void 0;
2242
+ const bindingByAnchor = /* @__PURE__ */ new Map();
2243
+ for (const defSchema of Object.values(defs)) {
2244
+ if (!defSchema || typeof defSchema !== "object") continue;
2245
+ const rec = defSchema;
2246
+ if (rec.$dynamicAnchor === void 0) continue;
2247
+ const ref = rec.$ref;
2248
+ if (!ref || !isComponentRef(ref)) continue;
2249
+ const anchor = rec.$dynamicAnchor;
2250
+ const { name, originalName } = getRefInfo(ref, context);
2251
+ bindingByAnchor.set(anchor, {
2252
+ typeName: name,
2253
+ originalName
2254
+ });
2255
+ }
2256
+ if (bindingByAnchor.size === 0) return void 0;
2257
+ const refPath = bindingElement.$ref;
2258
+ if (typeof refPath !== "string") return void 0;
2259
+ const { name: genericName, refPaths: templateRefPaths } = getRefInfo(refPath, context);
2260
+ const templateDefs = (templateRefPaths ? prop(context.spec, ...templateRefPaths) : void 0)?.$defs;
2261
+ const typeArgs = [];
2262
+ const genericParams = [];
2263
+ const imports = [];
2264
+ if (templateDefs && typeof templateDefs === "object") {
2265
+ const templateAnchors = [];
2266
+ for (const defSchema of Object.values(templateDefs)) {
2267
+ if (!defSchema || typeof defSchema !== "object") continue;
2268
+ const rec = defSchema;
2269
+ if (rec.$dynamicAnchor === void 0 || rec.$ref !== void 0) continue;
2270
+ templateAnchors.push(rec.$dynamicAnchor);
2271
+ }
2272
+ const uniqueNames = dynamicAnchorsToUniqueParamNames(templateAnchors);
2273
+ for (const anchor of templateAnchors) {
2274
+ const binding = bindingByAnchor.get(anchor);
2275
+ if (binding) {
2276
+ typeArgs.push(binding.typeName);
2277
+ imports.push({
2278
+ name: binding.typeName,
2279
+ schemaName: binding.originalName
2280
+ });
2281
+ } else {
2282
+ const paramName = uniqueNames.get(anchor) ?? dynamicAnchorToParamName(anchor);
2283
+ typeArgs.push(paramName);
2284
+ genericParams.push(paramName);
2285
+ }
2286
+ }
2287
+ }
2288
+ if (typeArgs.length === 0) for (const { typeName, originalName } of bindingByAnchor.values()) {
2289
+ typeArgs.push(typeName);
2290
+ imports.push({
2291
+ name: typeName,
2292
+ schemaName: originalName
2293
+ });
2294
+ }
2295
+ return {
2296
+ genericName,
2297
+ genericParams,
2298
+ typeArgs,
2299
+ imports,
2300
+ extraSchemas
2301
+ };
2302
+ }
2149
2303
  function getSchema$1(schema, context) {
2150
2304
  if (!schema.$ref) throw new Error(`${REF_NOT_FOUND_PREFIX}: missing $ref`);
2151
2305
  const refInfo = getRefInfo(schema.$ref, context);
@@ -2172,6 +2326,104 @@ function getSchema$1(schema, context) {
2172
2326
  refInfo
2173
2327
  };
2174
2328
  }
2329
+ function encodeJsonPointerSegment(segment) {
2330
+ return segment.replaceAll("~", "~0").replaceAll("/", "~1");
2331
+ }
2332
+ /**
2333
+ * Build the dynamic scope for a schema: maps `$dynamicAnchor` names to concrete
2334
+ * type entries for self-referential resolution, `$defs` bindings, and sibling anchors.
2335
+ */
2336
+ function buildDynamicScope(schemaName, schema, context) {
2337
+ const scope = {};
2338
+ const getSchemaScopeEntry = (name) => {
2339
+ const refInfo = getRefInfo(`#/components/schemas/${encodeJsonPointerSegment(name)}`, context);
2340
+ return {
2341
+ name: refInfo.name,
2342
+ schemaName: refInfo.originalName
2343
+ };
2344
+ };
2345
+ const schemaRecord = schema;
2346
+ if (typeof schemaRecord.$dynamicAnchor === "string") scope[schemaRecord.$dynamicAnchor] = getSchemaScopeEntry(schemaName);
2347
+ const defs = schemaRecord.$defs;
2348
+ if (defs && typeof defs === "object") {
2349
+ const unboundAnchors = [];
2350
+ for (const [, defSchema] of Object.entries(defs)) {
2351
+ if (!defSchema || typeof defSchema !== "object") continue;
2352
+ const defRecord = defSchema;
2353
+ if (typeof defRecord.$dynamicAnchor === "string") {
2354
+ const anchorName = defRecord.$dynamicAnchor;
2355
+ const refInDef = defSchema.$ref;
2356
+ if (refInDef?.startsWith("#/components/schemas/")) {
2357
+ const { name, originalName } = getRefInfo(refInDef, context);
2358
+ scope[anchorName] = {
2359
+ name,
2360
+ schemaName: originalName
2361
+ };
2362
+ } else if (!refInDef) unboundAnchors.push(anchorName);
2363
+ }
2364
+ }
2365
+ if (unboundAnchors.length > 0) {
2366
+ const uniqueNames = dynamicAnchorsToUniqueParamNames(unboundAnchors);
2367
+ for (const anchor of unboundAnchors) {
2368
+ const paramName = uniqueNames.get(anchor);
2369
+ if (paramName === void 0) continue;
2370
+ scope[anchor] = {
2371
+ name: paramName,
2372
+ schemaName: paramName,
2373
+ isParameter: true
2374
+ };
2375
+ }
2376
+ }
2377
+ }
2378
+ return scope;
2379
+ }
2380
+ /**
2381
+ * Resolve a `$dynamicRef` anchor to its concrete type using the current dynamic scope.
2382
+ * Returns `{ schema: {}, resolvedTypeName: 'unknown' }` when no scope override exists.
2383
+ */
2384
+ function resolveDynamicRef(anchorName, context, imports = []) {
2385
+ let scopeEntry = (context.dynamicScope ?? {})[anchorName];
2386
+ if (!scopeEntry) {
2387
+ const schemas = context.spec.components?.schemas;
2388
+ if (schemas && typeof schemas === "object") for (const [schemaName, schemaObj] of Object.entries(schemas)) {
2389
+ if (!schemaObj || typeof schemaObj !== "object") continue;
2390
+ if (schemaObj.$dynamicAnchor === anchorName) {
2391
+ const refInfo = getRefInfo(`#/components/schemas/${encodeJsonPointerSegment(schemaName)}`, context);
2392
+ scopeEntry = {
2393
+ name: refInfo.name,
2394
+ schemaName: refInfo.originalName
2395
+ };
2396
+ break;
2397
+ }
2398
+ }
2399
+ }
2400
+ if (!scopeEntry) return {
2401
+ schema: {},
2402
+ imports,
2403
+ resolvedTypeName: "unknown"
2404
+ };
2405
+ if (scopeEntry.isParameter) return {
2406
+ schema: {},
2407
+ imports,
2408
+ resolvedTypeName: scopeEntry.name
2409
+ };
2410
+ const resolvedTypeName = scopeEntry.name;
2411
+ const schemaRef = `#/components/schemas/${encodeJsonPointerSegment(scopeEntry.schemaName)}`;
2412
+ try {
2413
+ const { schema: resolvedSchema, imports: resolvedImports } = resolveRef({ $ref: schemaRef }, context, imports);
2414
+ return {
2415
+ schema: resolvedSchema,
2416
+ imports: resolvedImports,
2417
+ resolvedTypeName
2418
+ };
2419
+ } catch {
2420
+ return {
2421
+ schema: {},
2422
+ imports,
2423
+ resolvedTypeName: "unknown"
2424
+ };
2425
+ }
2426
+ }
2175
2427
  /** Recursively resolves `$ref` entries in an examples array or record. */
2176
2428
  function resolveExampleRefs(examples, context) {
2177
2429
  if (!examples) return;
@@ -2189,8 +2441,119 @@ function resolveExampleRefs(examples, context) {
2189
2441
  }
2190
2442
  //#endregion
2191
2443
  //#region src/resolvers/value.ts
2444
+ const schemaArrayKeys = [
2445
+ "allOf",
2446
+ "anyOf",
2447
+ "oneOf",
2448
+ "prefixItems"
2449
+ ];
2450
+ const schemaObjectKeys = [
2451
+ "additionalProperties",
2452
+ "contains",
2453
+ "else",
2454
+ "if",
2455
+ "items",
2456
+ "not",
2457
+ "propertyNames",
2458
+ "then",
2459
+ "unevaluatedItems",
2460
+ "unevaluatedProperties"
2461
+ ];
2462
+ const schemaMapKeys = [
2463
+ "$defs",
2464
+ "dependentSchemas",
2465
+ "patternProperties",
2466
+ "properties"
2467
+ ];
2468
+ /**
2469
+ * Recursively walks a schema value and returns `true` if any nested
2470
+ * `$dynamicRef` resolves — via the current `context.dynamicScope` — to a
2471
+ * schema *other* than `refName`.
2472
+ *
2473
+ * Used by `resolveValue` to decide whether a `$ref`'d schema must be
2474
+ * instantiated with its bound type arguments rather than referenced by name.
2475
+ *
2476
+ * @param value - The schema node (or sub-node) to inspect.
2477
+ * @param context - Current resolution context, including the dynamic scope.
2478
+ * @param refName - The resolved name of the enclosing `$ref` schema; dynamic
2479
+ * refs that resolve to this same name are considered
2480
+ * self-references and do not count as "scope-affected".
2481
+ * @param seen - Cycle-guard; tracks already-visited objects.
2482
+ */
2483
+ function hasScopeAffectedDynamicRef(value, context, refName, seen = /* @__PURE__ */ new WeakSet()) {
2484
+ if (!value || typeof value !== "object") return false;
2485
+ if (!context.dynamicScope || Object.keys(context.dynamicScope).length === 0) return false;
2486
+ if (seen.has(value)) return false;
2487
+ seen.add(value);
2488
+ if (isDynamicReference(value) && value.$dynamicRef.startsWith("#")) {
2489
+ const anchorName = getDynamicAnchorName(value.$dynamicRef);
2490
+ if (anchorName) {
2491
+ const scopeEntry = context.dynamicScope[anchorName];
2492
+ if (scopeEntry && scopeEntry.name !== refName) return true;
2493
+ }
2494
+ }
2495
+ const schema = value;
2496
+ for (const key of schemaArrayKeys) {
2497
+ const items = schema[key];
2498
+ if (Array.isArray(items) && items.some((item) => hasScopeAffectedDynamicRef(item, context, refName, seen))) return true;
2499
+ }
2500
+ for (const key of schemaObjectKeys) if (hasScopeAffectedDynamicRef(schema[key], context, refName, seen)) return true;
2501
+ for (const key of schemaMapKeys) {
2502
+ const schemaMap = schema[key];
2503
+ if (schemaMap && typeof schemaMap === "object" && Object.values(schemaMap).some((item) => hasScopeAffectedDynamicRef(item, context, refName, seen))) return true;
2504
+ }
2505
+ return false;
2506
+ }
2507
+ function makeUnknownValue(originalSchema) {
2508
+ return {
2509
+ value: "unknown",
2510
+ imports: [],
2511
+ type: "unknown",
2512
+ isEnum: false,
2513
+ schemas: [],
2514
+ isRef: false,
2515
+ hasReadonlyProps: false,
2516
+ originalSchema,
2517
+ dependencies: []
2518
+ };
2519
+ }
2520
+ /**
2521
+ * Resolves an OpenAPI schema or reference object to a {@link ResolverValue}
2522
+ * that carries the TypeScript type string, required imports, and metadata.
2523
+ *
2524
+ * Handles all schema forms in priority order:
2525
+ * 1. **Bound generic alias** — a `$ref` with `$defs` overrides; emits an
2526
+ * instantiated generic expression such as `Paginated<User>`.
2527
+ * 2. **Component `$ref`** — a named `$ref` pointing to `#/components/…`;
2528
+ * emits the schema name as a reference import.
2529
+ * 3. **Non-component `$ref`** — an anonymous or path-level ref; inlines the
2530
+ * resolved schema via {@link getScalar} (cycle-safe).
2531
+ * 4. **`$dynamicRef`** — resolved via the active dynamic scope; falls back to
2532
+ * `unknown` when the anchor is absent or the ref is a bare `#`.
2533
+ * 5. **Plain schema** — delegates to {@link getScalar} for all other cases
2534
+ * (primitives, objects, arrays, enums, …).
2535
+ */
2192
2536
  function resolveValue({ schema, name, context, formDataContext }) {
2193
2537
  if (isReference(schema)) {
2538
+ const alias = extractBoundAliasInfo(schema, context);
2539
+ if (alias) {
2540
+ const value = `${alias.genericName}<${alias.typeArgs.join(", ")}>`;
2541
+ const allImports = [{
2542
+ name: alias.genericName,
2543
+ schemaName: alias.genericName
2544
+ }, ...alias.imports];
2545
+ return {
2546
+ value,
2547
+ imports: allImports,
2548
+ type: "object",
2549
+ schemas: [],
2550
+ isEnum: false,
2551
+ originalSchema: schema,
2552
+ hasReadonlyProps: false,
2553
+ isRef: true,
2554
+ dependencies: allImports.map((i) => i.name)
2555
+ };
2556
+ }
2194
2557
  const refValue = schema.$ref;
2195
2558
  const { schema: schemaObject, imports } = resolveRef(schema, context);
2196
2559
  if (refValue && !isComponentRef(refValue)) {
@@ -2232,6 +2595,19 @@ function resolveValue({ schema, name, context, formDataContext }) {
2232
2595
  const resolvedImport = imports[0];
2233
2596
  let hasReadonlyProps = false;
2234
2597
  const refName = resolvedImport.name;
2598
+ if (!context.parents?.includes(refName) && hasScopeAffectedDynamicRef(schemaObject, context, refName)) return {
2599
+ ...getScalar({
2600
+ item: schemaObject,
2601
+ name: name ?? refName,
2602
+ context: {
2603
+ ...context,
2604
+ parents: [...context.parents ?? [], refName]
2605
+ },
2606
+ formDataContext
2607
+ }),
2608
+ originalSchema: schemaObject,
2609
+ isRef: false
2610
+ };
2235
2611
  if (!context.parents?.includes(refName)) hasReadonlyProps = getScalar({
2236
2612
  item: schemaObject,
2237
2613
  name: refName,
@@ -2258,6 +2634,25 @@ function resolveValue({ schema, name, context, formDataContext }) {
2258
2634
  dependencies: [resolvedImport.name]
2259
2635
  };
2260
2636
  }
2637
+ if (isDynamicReference(schema)) {
2638
+ const dynamicRef = schema.$dynamicRef;
2639
+ if (!dynamicRef.startsWith("#")) return makeUnknownValue(schema);
2640
+ const anchorName = getDynamicAnchorName(dynamicRef);
2641
+ if (!anchorName) return makeUnknownValue(schema);
2642
+ const { imports: resolvedImports, resolvedTypeName } = resolveDynamicRef(anchorName, context);
2643
+ if (resolvedTypeName === "unknown") return makeUnknownValue(schema);
2644
+ return {
2645
+ value: resolvedTypeName,
2646
+ imports: resolvedImports,
2647
+ type: "object",
2648
+ isEnum: false,
2649
+ schemas: [],
2650
+ isRef: true,
2651
+ hasReadonlyProps: false,
2652
+ originalSchema: schema,
2653
+ dependencies: [resolvedTypeName]
2654
+ };
2655
+ }
2261
2656
  return {
2262
2657
  ...getScalar({
2263
2658
  item: schema,
@@ -2349,7 +2744,8 @@ function resolveObject({ schema, propName, combined = false, context, formDataCo
2349
2744
  propName,
2350
2745
  combined,
2351
2746
  projectName: context.projectName ?? context.output.target,
2352
- formDataContext
2747
+ formDataContext,
2748
+ dynamicScope: context.dynamicScope
2353
2749
  });
2354
2750
  if (resolveObjectCacheMap.has(hashKey)) return resolveObjectCacheMap.get(hashKey);
2355
2751
  const result = resolveObjectOriginal({
@@ -3018,6 +3414,61 @@ function resolveDiscriminators(schemas, context) {
3018
3414
  }
3019
3415
  }
3020
3416
  }
3417
+ for (const [parentName, parentSchema] of Object.entries(transformedSchemas)) {
3418
+ if (isBoolean$1(parentSchema)) continue;
3419
+ if (!parentSchema.oneOf || !parentSchema.discriminator?.mapping) continue;
3420
+ const { mapping, propertyName } = parentSchema.discriminator;
3421
+ if (!propertyName) continue;
3422
+ const parentProperties = parentSchema.properties;
3423
+ const parentRequired = parentSchema.required;
3424
+ const inheritableProps = {};
3425
+ if (parentProperties) {
3426
+ for (const [key, value] of Object.entries(parentProperties)) if (key !== propertyName) inheritableProps[key] = value;
3427
+ }
3428
+ const inheritableRequired = parentRequired?.filter((key) => key !== propertyName);
3429
+ const hasInheritableProps = Object.keys(inheritableProps).length > 0;
3430
+ for (const mappingValue of Object.values(mapping)) {
3431
+ let variantSchema;
3432
+ try {
3433
+ const { originalName } = getRefInfo(mappingValue, context);
3434
+ variantSchema = transformedSchemas[pascal(originalName)] ?? transformedSchemas[originalName];
3435
+ } catch {
3436
+ variantSchema = transformedSchemas[mappingValue];
3437
+ }
3438
+ if (!variantSchema || isBoolean$1(variantSchema)) continue;
3439
+ const variantAllOf = variantSchema.allOf;
3440
+ if (!variantAllOf) continue;
3441
+ const rewritten = [];
3442
+ for (const item of variantAllOf) {
3443
+ if (!isReference(item) || !item.$ref) {
3444
+ rewritten.push(item);
3445
+ continue;
3446
+ }
3447
+ let refOriginalName;
3448
+ try {
3449
+ refOriginalName = getRefInfo(item.$ref, context).originalName;
3450
+ } catch {
3451
+ refOriginalName = void 0;
3452
+ }
3453
+ if (!(refOriginalName === parentName || refOriginalName !== void 0 && pascal(refOriginalName) === pascal(parentName))) {
3454
+ rewritten.push(item);
3455
+ continue;
3456
+ }
3457
+ const inlinedParent = { ...parentSchema };
3458
+ delete inlinedParent.oneOf;
3459
+ delete inlinedParent.discriminator;
3460
+ delete inlinedParent.allOf;
3461
+ delete inlinedParent.anyOf;
3462
+ if (hasInheritableProps) inlinedParent.properties = { ...inheritableProps };
3463
+ else delete inlinedParent.properties;
3464
+ if (inheritableRequired && inheritableRequired.length > 0) inlinedParent.required = [...inheritableRequired];
3465
+ else delete inlinedParent.required;
3466
+ if (Object.keys(inlinedParent).filter((key) => key !== "type").length > 0) rewritten.push(inlinedParent);
3467
+ }
3468
+ if (rewritten.length === 0) delete variantSchema.allOf;
3469
+ else variantSchema.allOf = rewritten;
3470
+ }
3471
+ }
3021
3472
  return transformedSchemas;
3022
3473
  }
3023
3474
  //#endregion
@@ -3270,7 +3721,8 @@ function getQueryParamsTypes(queryParams, operationName, context) {
3270
3721
  };
3271
3722
  if (resolvedValue.isEnum && !resolvedValue.isRef) {
3272
3723
  const enumName = queryName;
3273
- const enumValue = getEnum(resolvedValue.value, enumName, getEnumNames(resolvedValue.originalSchema), context.output.override.enumGenerationType, getEnumDescriptions(resolvedValue.originalSchema), context.output.override.namingConvention.enum);
3724
+ const parameterAsSchema = parameter;
3725
+ const enumValue = getEnum(resolvedValue.value, enumName, getEnumNames(resolvedValue.originalSchema) ?? getEnumNames(parameterAsSchema), context.output.override.enumGenerationType, getEnumDescriptions(resolvedValue.originalSchema) ?? getEnumDescriptions(parameterAsSchema), context.output.override.namingConvention.enum);
3274
3726
  return {
3275
3727
  name,
3276
3728
  required,
@@ -3312,6 +3764,7 @@ function getQueryParams({ queryParams, operationName, context, suffix = "params"
3312
3764
  },
3313
3765
  deps: schemas,
3314
3766
  isOptional: allOptional,
3767
+ paramNames: types.map(({ name }) => name),
3315
3768
  requiredNullableKeys,
3316
3769
  ...nonPrimitiveKeys.length > 0 ? { nonPrimitiveKeys } : {}
3317
3770
  };
@@ -3470,6 +3923,231 @@ function generateComponentDefinition(responses = {}, context, suffix) {
3470
3923
  return generatorSchemas;
3471
3924
  }
3472
3925
  //#endregion
3926
+ //#region src/generators/factory.ts
3927
+ const circularRefCache = /* @__PURE__ */ new WeakMap();
3928
+ function getSchemasPath(context) {
3929
+ const { schemas, target } = context.output;
3930
+ if (schemas) return normalizeSafe(isString(schemas) ? schemas : schemas.path);
3931
+ const { dirname, filename } = getFileInfo(target);
3932
+ return joinSafe(dirname, filename + ".schemas");
3933
+ }
3934
+ function getSchemaImportPath(refName, context) {
3935
+ if (context.output.factoryMethods?.mode === "single") return;
3936
+ let outputDir = context.output.factoryMethods?.outputDirectory;
3937
+ let schemasPath = getSchemasPath(context);
3938
+ if (context.output.workspace) {
3939
+ if (outputDir && !isAbsolute(outputDir)) outputDir = resolve(context.output.workspace, outputDir);
3940
+ if (schemasPath && !isAbsolute(schemasPath)) schemasPath = resolve(context.output.workspace, schemasPath);
3941
+ }
3942
+ return joinSafe(outputDir ? relativeSafe(outputDir, schemasPath) : "./", conventionName(refName, context.output.namingConvention));
3943
+ }
3944
+ function isReference$1(schema) {
3945
+ return "$ref" in schema;
3946
+ }
3947
+ function getResolvedRef(schema, context) {
3948
+ return resolveRef(schema, context);
3949
+ }
3950
+ function getProperties(schema) {
3951
+ return schema.properties ?? {};
3952
+ }
3953
+ function getItems(schema) {
3954
+ return schema.items;
3955
+ }
3956
+ function getAdditionalProperties(schema) {
3957
+ return schema.additionalProperties;
3958
+ }
3959
+ function getSchemas(schemas) {
3960
+ return schemas;
3961
+ }
3962
+ function getExtendedProps(schema) {
3963
+ const extended = schema;
3964
+ return {
3965
+ constValue: extended.const,
3966
+ prefixItems: extended.prefixItems,
3967
+ minItems: extended.minItems
3968
+ };
3969
+ }
3970
+ function generateFactory(schema, name, context) {
3971
+ if (!canGenerateSchema(schema) || !context.output.factoryMethods) return void 0;
3972
+ const { functionNamePrefix, mode } = context.output.factoryMethods;
3973
+ const factoryName = `${functionNamePrefix}${pascal(name)}`;
3974
+ const imports = [];
3975
+ const payload = buildPayload(schema, context, [name], imports);
3976
+ if (mode !== "single") {
3977
+ const schemaImportPath = getSchemaImportPath(name, context);
3978
+ imports.push({
3979
+ name,
3980
+ importPath: schemaImportPath
3981
+ });
3982
+ }
3983
+ return {
3984
+ model: `export function ${factoryName}(): ${name} {\n return ${payload};\n}\n`,
3985
+ imports
3986
+ };
3987
+ }
3988
+ function canGenerateSchema(schema) {
3989
+ return schema.type === "object" || schema.type === "array" || !!schema.properties || !!schema.allOf || !!schema.oneOf || !!schema.anyOf || !!schema.items || !!schema.enum;
3990
+ }
3991
+ function hasCircularReference(target, sourceName, context, visited = /* @__PURE__ */ new Set()) {
3992
+ if (isReference$1(target)) {
3993
+ const { imports, schema } = getResolvedRef(target, context);
3994
+ const refName = imports[0]?.name;
3995
+ if (refName === sourceName) return true;
3996
+ if (refName && visited.has(refName)) return false;
3997
+ if (refName) visited.add(refName);
3998
+ let cache = circularRefCache.get(context);
3999
+ if (!cache) {
4000
+ cache = /* @__PURE__ */ new Map();
4001
+ circularRefCache.set(context, cache);
4002
+ }
4003
+ const cacheKey = refName ? `${sourceName}::${refName}` : void 0;
4004
+ if (cacheKey) {
4005
+ const cached = cache.get(cacheKey);
4006
+ if (cached !== void 0) return cached;
4007
+ }
4008
+ const result = hasCircularReference(schema, sourceName, context, visited);
4009
+ if (cacheKey) cache.set(cacheKey, result);
4010
+ return result;
4011
+ }
4012
+ const check = (schemas) => schemas?.some((s) => hasCircularReference(s, sourceName, context, visited)) ?? false;
4013
+ const items = getItems(target);
4014
+ const additionalProperties = getAdditionalProperties(target);
4015
+ return check(getSchemas(target.allOf)) || check(getSchemas(target.oneOf)) || check(getSchemas(target.anyOf)) || Object.values(getProperties(target)).some((s) => hasCircularReference(s, sourceName, context, visited)) || !!items && hasCircularReference(items, sourceName, context, visited) || typeof additionalProperties === "object" && hasCircularReference(additionalProperties, sourceName, context, visited);
4016
+ }
4017
+ function buildPayload(target, context, parents, imports) {
4018
+ if (isReference$1(target)) return buildRefPayload(target, context, parents, imports);
4019
+ const schema = target;
4020
+ if (schema.allOf) return buildAllOfPayload(getSchemas(schema.allOf) ?? [], context, parents, imports);
4021
+ if (schema.oneOf) return buildFirstOfPayload(getSchemas(schema.oneOf) ?? [], context, parents, imports);
4022
+ if (schema.anyOf) return buildFirstOfPayload(getSchemas(schema.anyOf) ?? [], context, parents, imports);
4023
+ const { constValue } = getExtendedProps(schema);
4024
+ if (constValue !== void 0) return formatValue(constValue);
4025
+ if (schema.default !== void 0) return buildDefaultPayload(schema, context);
4026
+ const schemaType = inferSchemaType(schema);
4027
+ if (schemaType === "object" || schema.properties) return buildObjectPayload(schema, context, parents, imports);
4028
+ if (schemaType === "array") return buildArrayPayload(schema, context, parents, imports);
4029
+ return buildPrimitivePayload(schema, schemaType, context);
4030
+ }
4031
+ function buildRefPayload(schema, context, parents, imports) {
4032
+ const { schema: resolved, imports: refImports } = getResolvedRef(schema, context);
4033
+ const refName = refImports[0]?.name;
4034
+ if (!refName) return "{}";
4035
+ if (parents.includes(refName) || hasCircularReference(resolved, parents[0], context)) {
4036
+ imports.push({
4037
+ name: refName,
4038
+ importPath: getSchemaImportPath(refName, context)
4039
+ });
4040
+ return `{} as ${refName}`;
4041
+ }
4042
+ const { functionNamePrefix = "create", mode = "single" } = context.output.factoryMethods ?? {};
4043
+ const refFactoryName = `${functionNamePrefix}${pascal(refName)}`;
4044
+ if (mode !== "single-split") {
4045
+ const importPath = resolveImportPath(mode, refName, context);
4046
+ imports.push({
4047
+ name: refFactoryName,
4048
+ importPath,
4049
+ isConstant: true
4050
+ });
4051
+ }
4052
+ imports.push({
4053
+ name: refName,
4054
+ importPath: getSchemaImportPath(refName, context)
4055
+ });
4056
+ return `${refFactoryName}()`;
4057
+ }
4058
+ function resolveImportPath(mode, refName, context) {
4059
+ const baseName = conventionName(refName, context.output.namingConvention);
4060
+ switch (mode) {
4061
+ case "split": return `./${baseName}.factory`;
4062
+ case "single-split": return `./${conventionName("factoryMethods", context.output.namingConvention)}`;
4063
+ case "single": return `./${baseName}`;
4064
+ }
4065
+ }
4066
+ function buildAllOfPayload(allOf, context, parents, imports) {
4067
+ const payloads = allOf.map((s) => buildPayload(s, context, parents, imports));
4068
+ return payloads.length > 0 ? `Object.assign({}, ${payloads.join(", ")})` : "{}";
4069
+ }
4070
+ function buildFirstOfPayload(schemas, context, parents, imports) {
4071
+ const first = schemas[0];
4072
+ return first ? buildPayload(first, context, parents, imports) : "{}";
4073
+ }
4074
+ function buildObjectPayload(schema, context, parents, imports) {
4075
+ const { includeOptionalProperty = false } = context.output.factoryMethods ?? {};
4076
+ const props = getProperties(schema);
4077
+ const requiredProps = schema.required ?? [];
4078
+ const entries = Object.entries(props);
4079
+ if (context.output.propertySortOrder === PropertySortOrder.ALPHABETICAL) entries.sort(([a], [b]) => a.localeCompare(b));
4080
+ const includeOptional = includeOptionalProperty;
4081
+ const lines = [];
4082
+ for (const [key, prop] of entries) {
4083
+ const isRequired = requiredProps.includes(key);
4084
+ const resolved = isReference$1(prop) ? getResolvedRef(prop, context).schema : prop;
4085
+ const isReadOnly = !!prop.readOnly || !!resolved.readOnly;
4086
+ const isWriteOnly = !!prop.writeOnly || !!resolved.writeOnly;
4087
+ if (!isRequired) {
4088
+ if (isReadOnly) continue;
4089
+ if (!isWriteOnly && !includeOptional) continue;
4090
+ }
4091
+ const payload = buildPayload(prop, context, parents, imports);
4092
+ const safeKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
4093
+ lines.push(`${safeKey}: ${payload}`);
4094
+ }
4095
+ return `{\n ${lines.join(",\n ")}\n }`;
4096
+ }
4097
+ function buildArrayPayload(schema, context, parents, imports) {
4098
+ const { prefixItems, minItems } = getExtendedProps(schema);
4099
+ const items = getItems(schema);
4100
+ if (prefixItems && prefixItems.length > 0) return `[${prefixItems.map((item) => buildPayload(item, context, parents, imports)).join(", ")}]`;
4101
+ if (minItems && items) {
4102
+ const MAX_MIN_ITEMS = 50;
4103
+ if (minItems > MAX_MIN_ITEMS) logWarning(`Warning: minItems is ${minItems}, capping at ${MAX_MIN_ITEMS} to prevent massive payload.`);
4104
+ const count = Math.min(minItems, MAX_MIN_ITEMS);
4105
+ const itemPayload = buildPayload(items, context, parents, imports);
4106
+ return `[${Array.from({ length: count }).fill(itemPayload).join(", ")}]`;
4107
+ }
4108
+ return "[]";
4109
+ }
4110
+ function inferSchemaType(schema) {
4111
+ let type = schema.type;
4112
+ if (Array.isArray(type)) {
4113
+ const nonNull = type.filter((t) => t !== "null");
4114
+ type = nonNull.length > 0 ? nonNull[0] : "null";
4115
+ }
4116
+ if (!type && schema.items) return "array";
4117
+ if (!type && schema.enum) {
4118
+ const first = schema.enum[0];
4119
+ if (typeof first === "number") return "number";
4120
+ if (typeof first === "boolean") return "boolean";
4121
+ return "string";
4122
+ }
4123
+ return type;
4124
+ }
4125
+ function buildDefaultPayload(schema, context) {
4126
+ if (context.output.override.useDates && typeof schema.default === "string" && (schema.format === "date" || schema.format === "date-time")) return `new Date('${schema.default}')`;
4127
+ return formatValue(schema.default);
4128
+ }
4129
+ function buildPrimitivePayload(schema, schemaType, context) {
4130
+ if (schemaType === "null") return "null";
4131
+ const enumValues = schema.enum;
4132
+ if (schemaType === "boolean") return enumValues && enumValues.length > 0 ? String(enumValues[0]) : "false";
4133
+ if (schemaType === "number" || schemaType === "integer") return enumValues && enumValues.length > 0 ? String(enumValues[0]) : "0";
4134
+ if (schemaType === "string") {
4135
+ if (enumValues && enumValues.length > 0) {
4136
+ const first = enumValues[0];
4137
+ return typeof first === "string" ? JSON.stringify(first) : String(first);
4138
+ }
4139
+ if (schema.format === "date" || schema.format === "date-time") return context.output.override.useDates ? "new Date(0)" : `'${(/* @__PURE__ */ new Date(0)).toISOString()}'`;
4140
+ return "''";
4141
+ }
4142
+ return "undefined as unknown";
4143
+ }
4144
+ function formatValue(val) {
4145
+ if (val === null) return "null";
4146
+ if (typeof val === "string") return JSON.stringify(val);
4147
+ if (typeof val === "object") return JSON.stringify(val);
4148
+ return String(val);
4149
+ }
4150
+ //#endregion
3473
4151
  //#region src/generators/imports.ts
3474
4152
  function generateImports({ imports, namingConvention = NamingConvention.CAMEL_CASE, importExtension = "" }) {
3475
4153
  if (imports.length === 0) return "";
@@ -4160,7 +4838,7 @@ function generateParameterDefinition(parameters = {}, context, suffix) {
4160
4838
  * @param name interface name
4161
4839
  * @param schema
4162
4840
  */
4163
- function generateInterface({ name, schema, context }) {
4841
+ function generateInterface({ name, schema, context, genericParams }) {
4164
4842
  const scalar = getScalar({
4165
4843
  item: schema,
4166
4844
  name,
@@ -4168,6 +4846,7 @@ function generateInterface({ name, schema, context }) {
4168
4846
  });
4169
4847
  const isEmptyObject = scalar.value === "{}";
4170
4848
  const shouldUseTypeAlias = context.output.override.useTypeOverInterfaces ?? scalar.useTypeAlias;
4849
+ const genericSuffix = genericParams && genericParams.length > 0 ? `<${genericParams.join(", ")}>` : "";
4171
4850
  let model = "";
4172
4851
  model += jsDoc(schema);
4173
4852
  if (isEmptyObject) model += "// eslint-disable-next-line @typescript-eslint/no-empty-interface\n";
@@ -4175,12 +4854,12 @@ function generateInterface({ name, schema, context }) {
4175
4854
  const properties = schema.properties;
4176
4855
  if (properties && Object.values(properties).length > 0 && Object.values(properties).every((item) => "const" in item)) {
4177
4856
  const mappedScalarValue = scalar.value.replaceAll(";", ",").replaceAll("?:", ":");
4178
- model += `export const ${name}Value = ${mappedScalarValue} as const;\nexport type ${name} = typeof ${name}Value;\n`;
4857
+ model += `export const ${name}Value = ${mappedScalarValue} as const;\nexport type ${name}${genericSuffix} = typeof ${name}Value;\n`;
4179
4858
  } else {
4180
4859
  const blankInterfaceValue = scalar.value === "unknown" ? "{}" : scalar.value;
4181
- model += `export interface ${name} ${blankInterfaceValue}\n`;
4860
+ model += `export interface ${name}${genericSuffix} ${blankInterfaceValue}\n`;
4182
4861
  }
4183
- } else model += `export type ${name} = ${scalar.value};\n`;
4862
+ } else model += `export type ${name}${genericSuffix} = ${scalar.value};\n`;
4184
4863
  const externalModulesImportsOnly = scalar.imports.filter((importName) => importName.alias ? importName.alias !== name : importName.name !== name);
4185
4864
  return [...scalar.schemas, {
4186
4865
  name,
@@ -4214,6 +4893,17 @@ function generateSchemasDefinition(schemas = {}, context, suffix, filters) {
4214
4893
  const normalizedName = conventionName(schema.name, context.output.namingConvention);
4215
4894
  if (!seenNames.has(normalizedName)) {
4216
4895
  seenNames.add(normalizedName);
4896
+ if (context.output.factoryMethods && schema.schema) {
4897
+ const factoryData = generateFactory(schema.schema, schema.name, context);
4898
+ if (factoryData) if (context.output.factoryMethods.mode === "single") {
4899
+ schema.model += `\n${factoryData.model}`;
4900
+ for (const imp of factoryData.imports) if (!schema.imports.some((existing) => existing.name === imp.name)) schema.imports.push(imp);
4901
+ } else {
4902
+ schema.factory = factoryData.model;
4903
+ schema.factoryImports = factoryData.imports;
4904
+ schema.factoryMode = context.output.factoryMethods.mode;
4905
+ }
4906
+ }
4217
4907
  deduplicatedModels.push(schema);
4218
4908
  }
4219
4909
  }
@@ -4258,6 +4948,21 @@ function shouldCreateInterface(schema) {
4258
4948
  const isNullable = isArray(schema.type) && schema.type.includes("null");
4259
4949
  return (!schema.type || schema.type === "object") && !schema.allOf && !schema.oneOf && !schema.anyOf && isDereferenced(schema) && !schema.enum && !isNullable;
4260
4950
  }
4951
+ function collectGenericParams(schema) {
4952
+ const defs = schema.$defs;
4953
+ if (!defs || typeof defs !== "object") return [];
4954
+ const anchors = [];
4955
+ for (const defSchema of Object.values(defs)) {
4956
+ if (!defSchema || typeof defSchema !== "object") continue;
4957
+ const rec = defSchema;
4958
+ if (rec.$dynamicAnchor !== void 0 && rec.$ref === void 0) anchors.push(rec.$dynamicAnchor);
4959
+ }
4960
+ const uniqueNames = dynamicAnchorsToUniqueParamNames(anchors);
4961
+ return anchors.map((anchor) => ({
4962
+ anchorName: anchor,
4963
+ paramName: uniqueNames.get(anchor) ?? dynamicAnchorToParamName(anchor)
4964
+ }));
4965
+ }
4261
4966
  function generateSchemaDefinitions(schemaName, schema, context, suffix) {
4262
4967
  const sanitizedSchemaName = sanitize(`${pascal(schemaName)}${suffix}`, {
4263
4968
  underscore: "_",
@@ -4272,22 +4977,82 @@ function generateSchemaDefinitions(schemaName, schema, context, suffix) {
4272
4977
  imports: [],
4273
4978
  schema
4274
4979
  }];
4980
+ const alias = extractBoundAliasInfo(schema, context);
4981
+ if (alias) {
4982
+ const genericParams = alias.genericParams.map((paramName) => ({
4983
+ anchorName: paramName,
4984
+ paramName
4985
+ }));
4986
+ const genericSuffix = genericParams.length > 0 ? `<${genericParams.map((p) => p.paramName).join(", ")}>` : "";
4987
+ const typeArgsStr = alias.typeArgs.join(", ");
4988
+ const genericPart = `${alias.genericName}<${typeArgsStr}>`;
4989
+ const schemaType = schema.type;
4990
+ const nullable = Array.isArray(schemaType) && schemaType.includes("null") || schema.nullable === true ? " | null" : "";
4991
+ let model;
4992
+ const allImports = [{
4993
+ name: alias.genericName,
4994
+ schemaName: alias.genericName
4995
+ }, ...alias.imports];
4996
+ if (alias.extraSchemas && alias.extraSchemas.length > 0) {
4997
+ const aliasScopedContext = {
4998
+ ...context,
4999
+ dynamicScope: buildDynamicScope(schemaName, schema, context)
5000
+ };
5001
+ const subSchemas = [];
5002
+ model = `export type ${sanitizedSchemaName}${genericSuffix} = (${[genericPart, ...alias.extraSchemas.map((extraSchema) => {
5003
+ const resolved = resolveValue({
5004
+ schema: extraSchema,
5005
+ name: sanitizedSchemaName,
5006
+ context: aliasScopedContext
5007
+ });
5008
+ for (const imp of resolved.imports) {
5009
+ const impSchemaName = imp.schemaName ?? imp.name;
5010
+ if (!allImports.some((a) => a.name === imp.name && a.schemaName === impSchemaName)) allImports.push({
5011
+ name: imp.name,
5012
+ schemaName: impSchemaName
5013
+ });
5014
+ }
5015
+ for (const sub of resolved.schemas) if (sub.name !== sanitizedSchemaName) subSchemas.push(sub);
5016
+ return resolved.value;
5017
+ })].join(" & ")})${nullable};\n`;
5018
+ return [...subSchemas, {
5019
+ name: sanitizedSchemaName,
5020
+ model,
5021
+ imports: allImports,
5022
+ dependencies: allImports.map((i) => i.name),
5023
+ schema
5024
+ }];
5025
+ } else model = `export type ${sanitizedSchemaName}${genericSuffix} = ${genericPart}${nullable};\n`;
5026
+ return [{
5027
+ name: sanitizedSchemaName,
5028
+ model,
5029
+ imports: allImports,
5030
+ dependencies: allImports.map((i) => i.name),
5031
+ schema
5032
+ }];
5033
+ }
5034
+ const scopedContext = isBoolean(schema) ? context : {
5035
+ ...context,
5036
+ dynamicScope: buildDynamicScope(schemaName, schema, context)
5037
+ };
5038
+ const genericParams = collectGenericParams(schema);
4275
5039
  if (shouldCreateInterface(schema)) return generateInterface({
4276
5040
  name: sanitizedSchemaName,
4277
5041
  schema,
4278
- context
5042
+ context: scopedContext,
5043
+ genericParams: genericParams.length > 0 ? genericParams.map((p) => p.paramName) : void 0
4279
5044
  });
4280
5045
  const resolvedValue = resolveValue({
4281
5046
  schema,
4282
5047
  name: sanitizedSchemaName,
4283
- context
5048
+ context: scopedContext
4284
5049
  });
4285
5050
  let output = "";
4286
5051
  let imports = resolvedValue.imports;
4287
5052
  output += jsDoc(schema);
4288
5053
  if (resolvedValue.isEnum && !resolvedValue.isRef) output += getEnum(resolvedValue.value, sanitizedSchemaName, getEnumNames(resolvedValue.originalSchema), context.output.override.enumGenerationType, getEnumDescriptions(resolvedValue.originalSchema), context.output.override.namingConvention.enum);
4289
5054
  else if (sanitizedSchemaName === resolvedValue.value && resolvedValue.isRef) {
4290
- const { schema: referredSchema } = resolveRef(schema, context);
5055
+ const { schema: referredSchema } = resolveRef(schema, scopedContext);
4291
5056
  if (!shouldCreateInterface(referredSchema)) {
4292
5057
  const imp = resolvedValue.imports.find((imp) => imp.name === sanitizedSchemaName);
4293
5058
  if (imp) {
@@ -4308,7 +5073,8 @@ function generateSchemaDefinitions(schemaName, schema, context, suffix) {
4308
5073
  resolvedValue.dependencies.push(...schema.dependencies ?? []);
4309
5074
  return false;
4310
5075
  });
4311
- output += `export type ${sanitizedSchemaName} = ${resolvedValue.value};\n`;
5076
+ const genericSuffix = genericParams.length > 0 ? `<${genericParams.map((p) => p.paramName).join(", ")}>` : "";
5077
+ output += `export type ${sanitizedSchemaName}${genericSuffix} = ${resolvedValue.value};\n`;
4312
5078
  }
4313
5079
  return [...resolvedValue.schemas, {
4314
5080
  name: sanitizedSchemaName,
@@ -4620,33 +5386,54 @@ function getCanonicalMap(schemaGroups, schemaPath, namingConvention, fileExtensi
4620
5386
  canonicalNameMap
4621
5387
  };
4622
5388
  }
4623
- function normalizeCanonicalImportPaths(schemas, canonicalPathMap, canonicalNameMap, schemaPath, namingConvention, fileExtension, tsconfig) {
5389
+ function normalizeCanonicalImportPaths(schemas, canonicalPathMap, canonicalNameMap, schemaPath, namingConvention, fileExtension, tsconfig, factoryOutputDirectory) {
4624
5390
  const importExtension = getImportExtension(fileExtension, tsconfig);
4625
- for (const schema of schemas) schema.imports = schema.imports.map((imp) => {
4626
- const canonicalByName = canonicalNameMap.get(imp.name);
4627
- const resolvedImportKey = resolveImportKey(schemaPath, imp.importPath ?? `./${conventionName(imp.name, namingConvention)}`, fileExtension);
4628
- const canonicalByPath = canonicalPathMap.get(resolvedImportKey);
4629
- const canonical = canonicalByName ?? canonicalByPath;
4630
- if (!canonical?.importPath) return imp;
4631
- const relative = relativeSafe(schemaPath, canonical.importPath.replaceAll("\\", "/"));
4632
- const importPath = `${relative.endsWith(fileExtension) ? relative.slice(0, -fileExtension.length) : relative.replace(/\.ts$/, "")}${importExtension}`;
4633
- return {
4634
- ...imp,
4635
- importPath
4636
- };
4637
- });
5391
+ const factoryDir = factoryOutputDirectory ?? schemaPath;
5392
+ for (const schema of schemas) {
5393
+ schema.imports = schema.imports.map((imp) => {
5394
+ const canonicalByName = canonicalNameMap.get(imp.name);
5395
+ const resolvedImportKey = resolveImportKey(schemaPath, imp.importPath ?? `./${conventionName(imp.name, namingConvention)}`, fileExtension);
5396
+ const canonicalByPath = canonicalPathMap.get(resolvedImportKey);
5397
+ const canonical = canonicalByName ?? canonicalByPath;
5398
+ if (!canonical?.importPath) return imp;
5399
+ const relative = relativeSafe(schemaPath, canonical.importPath.replaceAll("\\", "/"));
5400
+ const importPath = `${relative.endsWith(fileExtension) ? relative.slice(0, -fileExtension.length) : relative.replace(/\.ts$/, "")}${importExtension}`;
5401
+ return {
5402
+ ...imp,
5403
+ importPath
5404
+ };
5405
+ });
5406
+ if (schema.factoryImports) schema.factoryImports = schema.factoryImports.map((imp) => {
5407
+ const canonicalByName = canonicalNameMap.get(imp.name);
5408
+ const resolvedImportKey = resolveImportKey(factoryDir, imp.importPath ?? `./${conventionName(imp.name, namingConvention)}`, fileExtension);
5409
+ const canonicalByPath = canonicalPathMap.get(resolvedImportKey);
5410
+ const canonical = canonicalByName ?? canonicalByPath;
5411
+ if (!canonical?.importPath) return imp;
5412
+ const relative = relativeSafe(factoryDir, canonical.importPath.replaceAll("\\", "/"));
5413
+ const importPath = `${relative.endsWith(fileExtension) ? relative.slice(0, -fileExtension.length) : relative.replace(/\.ts$/, "")}${importExtension}`;
5414
+ return {
5415
+ ...imp,
5416
+ importPath
5417
+ };
5418
+ });
5419
+ }
4638
5420
  }
4639
5421
  function mergeSchemaGroup(schemas) {
4640
5422
  const baseSchemaName = schemas[0].name;
4641
5423
  const baseSchema = schemas[0].schema;
4642
5424
  const mergedImports = [...new Map(schemas.flatMap((schema) => schema.imports).map((imp) => [JSON.stringify(imp), imp])).values()];
4643
5425
  const mergedDependencies = [...new Set(schemas.flatMap((schema) => schema.dependencies ?? []))];
5426
+ const mergedFactory = schemas.map((s) => s.factory).filter(Boolean).join("\n");
5427
+ const mergedFactoryImports = [...new Map(schemas.flatMap((schema) => schema.factoryImports ?? []).map((imp) => [JSON.stringify(imp), imp])).values()];
4644
5428
  return {
4645
5429
  name: baseSchemaName,
4646
5430
  schema: baseSchema,
4647
5431
  model: schemas.map((schema) => schema.model).join("\n"),
4648
5432
  imports: mergedImports,
4649
- dependencies: mergedDependencies
5433
+ dependencies: mergedDependencies,
5434
+ factory: mergedFactory || void 0,
5435
+ factoryImports: mergedFactoryImports,
5436
+ factoryMode: schemas[0].factoryMode
4650
5437
  };
4651
5438
  }
4652
5439
  function resolveImportKey(schemaPath, importPath, fileExtension) {
@@ -4688,10 +5475,39 @@ async function writeSchema({ path, schema, target, namingConvention, fileExtensi
4688
5475
  throw new Error(`Oups... 🍻. An Error occurred while writing schema ${name} => ${String(error)}`, { cause: error });
4689
5476
  }
4690
5477
  }
4691
- async function writeSchemas({ schemaPath, schemas, target, namingConvention, fileExtension, header, indexFiles, tsconfig }) {
5478
+ async function emitFactoryForSchema(schema, namingConvention, header, factoryDir, fileExtension, helpers) {
5479
+ if (schema.factory && schema.factoryMode) {
5480
+ const mode = schema.factoryMode;
5481
+ if (mode === "split") {
5482
+ const factoryName = `${conventionName(schema.name, namingConvention)}.factory`;
5483
+ helpers.separateFactoryNames.push(factoryName);
5484
+ const factoryFile = `${header}\n${generateImports({
5485
+ imports: schema.factoryImports ?? [],
5486
+ namingConvention
5487
+ })}\n\n${schema.factory}`;
5488
+ await writeGeneratedFile(getPath(factoryDir, factoryName, fileExtension), factoryFile);
5489
+ } else if (mode === "single-split") {
5490
+ helpers.isCombined.value = true;
5491
+ helpers.combinedFactoryContent.value += `${schema.factory}\n`;
5492
+ helpers.combinedFactoryImports.push(...schema.factoryImports ?? []);
5493
+ }
5494
+ }
5495
+ }
5496
+ async function writeSchemas({ schemaPath, schemas, target, namingConvention, fileExtension, header, indexFiles, tsconfig, factoryOutputDirectory }) {
4692
5497
  const schemaGroups = getSchemaGroups(schemaPath, schemas, namingConvention, fileExtension);
4693
5498
  const { canonicalPathMap, canonicalNameMap } = getCanonicalMap(schemaGroups, schemaPath, namingConvention, fileExtension);
4694
- normalizeCanonicalImportPaths(schemas, canonicalPathMap, canonicalNameMap, schemaPath, namingConvention, fileExtension, tsconfig);
5499
+ normalizeCanonicalImportPaths(schemas, canonicalPathMap, canonicalNameMap, schemaPath, namingConvention, fileExtension, tsconfig, factoryOutputDirectory);
5500
+ const factoryDir = factoryOutputDirectory ?? schemaPath;
5501
+ const combinedFactoryContent = { value: "" };
5502
+ const combinedFactoryImports = [];
5503
+ const isCombined = { value: false };
5504
+ const separateFactoryNames = [];
5505
+ const factoryHelpers = {
5506
+ separateFactoryNames,
5507
+ combinedFactoryContent,
5508
+ combinedFactoryImports,
5509
+ isCombined
5510
+ };
4695
5511
  for (const groupSchemas of Object.values(schemaGroups)) {
4696
5512
  if (groupSchemas.length === 1) {
4697
5513
  await writeSchema({
@@ -4703,17 +5519,29 @@ async function writeSchemas({ schemaPath, schemas, target, namingConvention, fil
4703
5519
  header,
4704
5520
  tsconfig
4705
5521
  });
5522
+ const singleSchema = groupSchemas[0];
5523
+ await emitFactoryForSchema(singleSchema, namingConvention, header, factoryDir, fileExtension, factoryHelpers);
4706
5524
  continue;
4707
5525
  }
5526
+ const mergedSchema = mergeSchemaGroup(groupSchemas);
4708
5527
  await writeSchema({
4709
5528
  path: schemaPath,
4710
- schema: mergeSchemaGroup(groupSchemas),
5529
+ schema: mergedSchema,
4711
5530
  target,
4712
5531
  namingConvention,
4713
5532
  fileExtension,
4714
5533
  header,
4715
5534
  tsconfig
4716
5535
  });
5536
+ await emitFactoryForSchema(mergedSchema, namingConvention, header, factoryDir, fileExtension, factoryHelpers);
5537
+ }
5538
+ if (isCombined.value) {
5539
+ const factoryFileName = conventionName("factoryMethods", namingConvention);
5540
+ const factoryFile = `${header}\n${generateImports({
5541
+ imports: combinedFactoryImports,
5542
+ namingConvention
5543
+ })}\n\n${combinedFactoryContent.value}`;
5544
+ await writeGeneratedFile(getPath(factoryDir, factoryFileName, fileExtension), factoryFile);
4717
5545
  }
4718
5546
  if (indexFiles) {
4719
5547
  const schemaFilePath = nodePath.join(schemaPath, `index.ts`);
@@ -4721,7 +5549,25 @@ async function writeSchemas({ schemaPath, schemas, target, namingConvention, fil
4721
5549
  const ext = getImportExtension(fileExtension, tsconfig);
4722
5550
  const conventionNamesSet = new Set(Object.values(schemaGroups).map((group) => conventionName(group[0].name, namingConvention)));
4723
5551
  try {
4724
- await writeGeneratedFile(schemaFilePath, `${header}\n${[...conventionNamesSet].map((schemaName) => `export * from './${schemaName}${ext}';`).toSorted((a, b) => a.localeCompare(b, "en", { numeric: true })).join("\n")}\n`);
5552
+ const currentExports = [...conventionNamesSet].map((schemaName) => `export * from './${schemaName}${ext}';`);
5553
+ if (factoryOutputDirectory && normalizeSafe(factoryOutputDirectory) !== normalizeSafe(schemaPath) && (isCombined.value || separateFactoryNames.length > 0)) {
5554
+ const factoryIndexFilePath = nodePath.join(factoryOutputDirectory, `index.ts`);
5555
+ await fs$1.ensureFile(factoryIndexFilePath);
5556
+ const factoryExports = [];
5557
+ if (isCombined.value) {
5558
+ const factoryFileName = conventionName("factoryMethods", namingConvention);
5559
+ factoryExports.push(`export * from './${factoryFileName}${ext}';`);
5560
+ }
5561
+ for (const fName of separateFactoryNames) factoryExports.push(`export * from './${fName}${ext}';`);
5562
+ await writeGeneratedFile(factoryIndexFilePath, `${header}\n${factoryExports.join("\n")}\n`);
5563
+ } else {
5564
+ if (isCombined.value) {
5565
+ const factoryFileName = conventionName("factoryMethods", namingConvention);
5566
+ currentExports.push(`export * from './${factoryFileName}${ext}';`);
5567
+ }
5568
+ for (const fName of separateFactoryNames) currentExports.push(`export * from './${fName}${ext}';`);
5569
+ }
5570
+ await writeGeneratedFile(schemaFilePath, `${header}\n${[...new Set(currentExports)].toSorted((a, b) => a.localeCompare(b, "en", { numeric: true })).join("\n")}\n`);
4725
5571
  } catch (error) {
4726
5572
  throw new Error(`Oups... 🍻. An Error occurred while writing schema index file ${schemaFilePath} => ${String(error)}`, { cause: error });
4727
5573
  }
@@ -4731,6 +5577,13 @@ async function writeSchemas({ schemaPath, schemas, target, namingConvention, fil
4731
5577
  //#region src/writers/generate-imports-for-builder.ts
4732
5578
  function generateImportsForBuilder(output, imports, relativeSchemasPath) {
4733
5579
  const isZodSchemaOutput = isObject(output.schemas) && output.schemas.type === "zod";
5580
+ const schemaFactoryImports = imports.filter((i) => i.schemaFactory);
5581
+ const schemaFactoryImportExtension = getImportExtension(output.fileExtension, output.tsconfig);
5582
+ const schemaFactoryDeps = schemaFactoryImports.length > 0 ? [{
5583
+ exports: uniqueBy(schemaFactoryImports, (entry) => `${entry.name}|${entry.alias ?? ""}`),
5584
+ dependency: joinSafe(relativeSchemasPath, `index.faker${schemaFactoryImportExtension}`)
5585
+ }] : [];
5586
+ imports = imports.filter((i) => !i.schemaFactory);
4734
5587
  let schemaImports;
4735
5588
  if (output.indexFiles) schemaImports = isZodSchemaOutput ? [{
4736
5589
  exports: imports.filter((i) => !i.importPath),
@@ -4757,7 +5610,11 @@ function generateImportsForBuilder(output, imports, relativeSchemasPath) {
4757
5610
  dependency: i.importPath
4758
5611
  };
4759
5612
  });
4760
- return [...schemaImports, ...otherImports];
5613
+ return [
5614
+ ...schemaImports,
5615
+ ...schemaFactoryDeps,
5616
+ ...otherImports
5617
+ ];
4761
5618
  }
4762
5619
  //#endregion
4763
5620
  //#region src/writers/mock-outputs.ts
@@ -5475,6 +6332,6 @@ async function writeTagsMode({ builder, output, projectName, header, needSchema,
5475
6332
  }))).flat();
5476
6333
  }
5477
6334
  //#endregion
5478
- export { BODY_TYPE_NAME, DefaultTag, EnumGeneration, ErrorWithTag, FormDataArrayHandling, GetterPropType, LogLevels, NAMED_COMPONENT_SECTIONS, NamingConvention, OutputClient, OutputHttpClient, OutputMockType, OutputMode, PropertySortOrder, RefComponentSuffix, SchemaType, SupportedFormatter, TEMPLATE_TAG_REGEX, URL_REGEX, VERBS_WITH_BODY, Verbs, addDependency, asyncReduce, buildAngularParamsFilterExpression, camel, collectReferencedComponents, combineSchemas, compareVersions, conventionName, count, createDebugger, createLogger, createSuccessMessage, createTypeAliasIfNeeded, dedupeUnionType, dynamicImport, escape, escapeRegExp, filterByContentType, filteredVerbs, fixCrossDirectoryImports, fixRegularSchemaImports, generalJSTypes, generalJSTypesWithArray, generateAxiosOptions, generateBodyMutatorConfig, generateBodyOptions, generateComponentDefinition, generateDependencyImports, generateFormDataAndUrlEncodedFunction, generateImports, generateModelInline, generateModelsInline, generateMutator, generateMutatorConfig, generateMutatorImports, generateMutatorRequestOptions, generateOptions, generateParameterDefinition, generateQueryParamsAxiosConfig, generateSchemasDefinition, generateTarget, generateTargetForTags, generateVerbImports, generateVerbOptions, generateVerbsOptions, getAngularFilteredParamsCallExpression, getAngularFilteredParamsExpression, getAngularFilteredParamsHelperBody, getArray, getBaseUrlRuntimeImports, getBodiesByContentType, getBody, getCombinedEnumValue, getDefaultContentType, getEnum, getEnumDescriptions, getEnumImplementation, getEnumNames, getEnumUnionFromSchema, getExtension, getFileInfo, getFormDataFieldFileType, getFullRoute, getImportExtension, getIsBodyVerb, getKey, getMockFileExtensionByTypeName, getNumberWord, getObject, getOperationId, getOrvalGeneratedTypes, getParameters, getParams, getParamsInPath, getPropertySafe, getProps, getQueryParams, getRefInfo, getResReqTypes, getResponse, getResponseTypeCategory, getRoute, getRouteAsArray, getScalar, getSuccessResponseType, getTypedResponse, getWarningCount, isBinaryContentType, isBinaryScalarSchema, isBoolean, isComponentRef, isDirectory, isFakerMock, isFunction, isModule, isMswMock, isNullish, isNumber, isNumeric, isObject, isReference, isSchema, isString, isStringLike, isSyntheticDefaultImportsAllow, isUrl, isVerb, isVerbose, jsDoc, jsStringEscape, kebab, keyValuePairsToJsDoc, log, logError, logVerbose, logWarning, makeRouteSafe, mergeDeep, mismatchArgsMessage, pascal, removeFilesAndEmptyFolders, resetWarnings, resolveDiscriminators, resolveExampleRefs, resolveInstalledVersion, resolveInstalledVersions, resolveObject, resolveRef, resolveValue, sanitize, setVerbose, snake, sortByPriority, splitSchemasByType, startMessage, stringify, toObjectString, path_exports as upath, upper, wrapRouteParameters, writeModelInline, writeModelsInline, writeSchema, writeSchemas, writeSingleMode, writeSplitMode, writeSplitTagsMode, writeTagsMode };
6335
+ export { BODY_TYPE_NAME, DefaultTag, EnumGeneration, ErrorWithTag, FormDataArrayHandling, GetterPropType, LogLevels, NAMED_COMPONENT_SECTIONS, NamingConvention, OutputClient, OutputHttpClient, OutputMockType, OutputMode, PropertySortOrder, RefComponentSuffix, SchemaType, SupportedFormatter, TEMPLATE_TAG_REGEX, URL_REGEX, VERBS_WITH_BODY, Verbs, addDependency, asyncReduce, buildAngularParamsFilterExpression, buildDynamicScope, camel, collectReferencedComponents, combineSchemas, compareVersions, conventionName, count, createDebugger, createLogger, createSuccessMessage, createTypeAliasIfNeeded, dedupeUnionType, dynamicAnchorToParamName, dynamicAnchorsToUniqueParamNames, dynamicImport, escape, escapeRegExp, extractBoundAliasInfo, filterByContentType, filteredVerbs, fixCrossDirectoryImports, fixRegularSchemaImports, generalJSTypes, generalJSTypesWithArray, generateAxiosOptions, generateBodyMutatorConfig, generateBodyOptions, generateComponentDefinition, generateDependencyImports, generateFactory, generateFormDataAndUrlEncodedFunction, generateImports, generateModelInline, generateModelsInline, generateMutator, generateMutatorConfig, generateMutatorImports, generateMutatorRequestOptions, generateOptions, generateParameterDefinition, generateQueryParamsAxiosConfig, generateSchemasDefinition, generateTarget, generateTargetForTags, generateVerbImports, generateVerbOptions, generateVerbsOptions, getAngularFilteredParamsCallExpression, getAngularFilteredParamsExpression, getAngularFilteredParamsHelperBody, getArray, getBaseUrlRuntimeImports, getBodiesByContentType, getBody, getCombinedEnumValue, getDefaultContentType, getDynamicAnchorName, getEnum, getEnumDescriptions, getEnumImplementation, getEnumNames, getEnumUnionFromSchema, getExtension, getFileInfo, getFormDataFieldFileType, getFullRoute, getImportExtension, getIsBodyVerb, getKey, getMockFileExtensionByTypeName, getNumberWord, getObject, getOperationId, getOrvalGeneratedTypes, getParameters, getParams, getParamsInPath, getPropertySafe, getProps, getQueryParams, getRefInfo, getResReqTypes, getResponse, getResponseTypeCategory, getRoute, getRouteAsArray, getScalar, getSuccessResponseType, getTypedResponse, getWarningCount, isBinaryContentType, isBinaryScalarSchema, isBoolean, isComponentRef, isDirectory, isDynamicReference, isFakerMock, isFunction, isModule, isMswMock, isNullish, isNumber, isNumeric, isObject, isReference, isSchema, isString, isStringLike, isSyntheticDefaultImportsAllow, isUrl, isVerb, isVerbose, jsDoc, jsStringEscape, kebab, keyValuePairsToJsDoc, log, logError, logVerbose, logWarning, makeRouteSafe, mergeDeep, mismatchArgsMessage, pascal, removeFilesAndEmptyFolders, resetWarnings, resolveDiscriminators, resolveDynamicRef, resolveExampleRefs, resolveInstalledVersion, resolveInstalledVersions, resolveObject, resolveRef, resolveValue, sanitize, setVerbose, snake, sortByPriority, splitSchemasByType, startMessage, stringify, toObjectString, path_exports as upath, upper, wrapRouteParameters, writeGeneratedFile, writeModelInline, writeModelsInline, writeSchema, writeSchemas, writeSingleMode, writeSplitMode, writeSplitTagsMode, writeTagsMode };
5479
6336
 
5480
6337
  //# sourceMappingURL=index.mjs.map