@archora/forge-cli 2.0.0 → 2.1.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.
@@ -140,7 +140,9 @@ async function loadForgeConfig(filePath) {
140
140
  }
141
141
  function parseConfig(value) {
142
142
  if (!isForgeConfig(value)) {
143
- throw new Error("Invalid Archora Forge config: `input` must be a string or `inputs` must contain at least one schema entry.");
143
+ throw new Error(
144
+ "Invalid Archora Forge config: `input` must be a string or `inputs` must contain at least one schema entry."
145
+ );
144
146
  }
145
147
  return value;
146
148
  }
@@ -158,7 +160,10 @@ function loadConfigFromSource(source) {
158
160
  if (!match?.[1]) {
159
161
  return null;
160
162
  }
161
- const createConfig = new Function("defineForgeConfig", `return defineForgeConfig(${match[1]})`);
163
+ const createConfig = new Function(
164
+ "defineForgeConfig",
165
+ `return defineForgeConfig(${match[1]})`
166
+ );
162
167
  return parseConfig(createConfig(defineForgeConfig));
163
168
  }
164
169
 
@@ -872,7 +877,7 @@ var ForgeError = class extends Error {
872
877
  this.details = details;
873
878
  }
874
879
  };
875
- var forgeCoreVersion = "2.0.0";
880
+ var forgeCoreVersion = "2.1.0";
876
881
  var forgeGeneratedMarker = "// @archora-forge-generated";
877
882
  var forgeGeneratedMetadataMarker = "// @archora-forge-meta";
878
883
  async function writeGeneratedFiles(files, options) {
@@ -1270,10 +1275,14 @@ function createOperationTypeNames(operation) {
1270
1275
  }
1271
1276
  function createTypeScriptTypes(normalized, resource) {
1272
1277
  const declarations = [];
1273
- if (!normalized.schemas.some((schema) => toSafeTypeName(schema.name) === toSafeTypeName(resource.entity))) {
1274
- declarations.push(`export interface ${toSafeTypeName(resource.entity)} {
1278
+ if (!normalized.schemas.some(
1279
+ (schema) => toSafeTypeName(schema.name) === toSafeTypeName(resource.entity)
1280
+ )) {
1281
+ declarations.push(
1282
+ `export interface ${toSafeTypeName(resource.entity)} {
1275
1283
  [key: string]: unknown
1276
- }`);
1284
+ }`
1285
+ );
1277
1286
  }
1278
1287
  declarations.push(createOperationAliases(normalized, resource));
1279
1288
  const body = declarations.filter(Boolean).join("\n\n");
@@ -1290,13 +1299,19 @@ function createSharedSchemaTypes(normalized) {
1290
1299
  const declarations = [];
1291
1300
  const enumAliases = /* @__PURE__ */ new Map();
1292
1301
  for (const schema of normalized.schemas) {
1293
- declarations.push(createSchemaDeclaration(normalized, toSafeTypeName(schema.name), schema.schema, enumAliases));
1302
+ declarations.push(
1303
+ createSchemaDeclaration(normalized, toSafeTypeName(schema.name), schema.schema, enumAliases)
1304
+ );
1294
1305
  }
1295
- declarations.unshift(...[...enumAliases.entries()].map(([name, value]) => `export type ${name} = ${value}`));
1306
+ declarations.unshift(
1307
+ ...[...enumAliases.entries()].map(([name, value]) => `export type ${name} = ${value}`)
1308
+ );
1296
1309
  return declarations.length > 0 ? `${declarations.filter(Boolean).join("\n\n")}
1297
1310
  ` : "export {}\n";
1298
1311
  }
1299
- function schemaToTypeScript(normalized, schema, options = { mode: "response" }) {
1312
+ function schemaToTypeScript(normalized, schema, options = {
1313
+ mode: "response"
1314
+ }) {
1300
1315
  if (!schema) return "unknown";
1301
1316
  if (schema.$ref) return toSafeTypeName(schema.$ref.split("/").at(-1) ?? "unknown");
1302
1317
  const unwrappedAllOf = unwrapAnnotationOnlyAllOfSchema(schema);
@@ -1314,27 +1329,42 @@ function schemaToTypeScript(normalized, schema, options = { mode: "response" })
1314
1329
  }
1315
1330
  if (hasConst(schema)) return enumValueToTypeScript(schema.const);
1316
1331
  if (schema.enum) return schema.enum.map(enumValueToTypeScript).join(" | ");
1317
- if (schema.type === "string" && schema.format === "binary") return options.mode === "request" || options.indent !== void 0 ? "Blob | File" : "Blob";
1332
+ if (schema.type === "string" && schema.format === "binary")
1333
+ return options.mode === "request" || options.indent !== void 0 ? "Blob | File" : "Blob";
1318
1334
  if (schema.type === "string") return "string";
1319
1335
  if (schema.type === "number" || schema.type === "integer") return "number";
1320
1336
  if (schema.type === "boolean") return "boolean";
1321
- if (schema.type === "array") return `${toArrayElementType(schemaToTypeScript(normalized, schema.items, options))}[]`;
1322
- if (isPureDictionarySchema(schema)) return createDictionaryType(normalized, schema.additionalProperties, options);
1337
+ if (schema.type === "array")
1338
+ return `${toArrayElementType(schemaToTypeScript(normalized, schema.items, options))}[]`;
1339
+ if (isPureDictionarySchema(schema))
1340
+ return createDictionaryType(normalized, schema.additionalProperties, options);
1323
1341
  if (schema.type === "object" && !schema.properties) return "Record<string, unknown>";
1324
1342
  if (schema.type === "object" || schema.properties) {
1325
1343
  const indent = options.indent ?? 0;
1326
1344
  const childIndent = " ".repeat(indent + 2);
1327
1345
  const closeIndent = " ".repeat(indent);
1328
1346
  const required = new Set(schema.required ?? []);
1329
- const lines = Object.entries(schema.properties ?? {}).filter(([, property]) => options.mode === "request" ? !property.readOnly : !property.writeOnly).map(([name, property]) => {
1347
+ const lines = Object.entries(schema.properties ?? {}).filter(
1348
+ ([, property]) => options.mode === "request" ? !property.readOnly : !property.writeOnly
1349
+ ).map(([name, property]) => {
1330
1350
  const optional = required.has(name) ? "" : "?";
1331
1351
  const nullable = property.nullable ? " | null" : "";
1332
- return `${childIndent}${quoteObjectKeyIfNeeded(name)}${optional}: ${schemaToTypeScript(normalized, property, {
1333
- ...options,
1334
- indent: indent + 2
1335
- })}${nullable}`;
1352
+ return `${childIndent}${quoteObjectKeyIfNeeded(name)}${optional}: ${schemaToTypeScript(
1353
+ normalized,
1354
+ property,
1355
+ {
1356
+ ...options,
1357
+ indent: indent + 2
1358
+ }
1359
+ )}${nullable}`;
1336
1360
  });
1337
- const indexSignature = createAdditionalPropertiesIndex(normalized, schema, options, childIndent, required);
1361
+ const indexSignature = createAdditionalPropertiesIndex(
1362
+ normalized,
1363
+ schema,
1364
+ options,
1365
+ childIndent,
1366
+ required
1367
+ );
1338
1368
  if (indexSignature) lines.push(indexSignature);
1339
1369
  return `{
1340
1370
  ${lines.join("\n")}
@@ -1359,18 +1389,50 @@ function getDiscriminatorInfo(schema) {
1359
1389
  function renderUnionBranch(normalized, branch, options, discriminator) {
1360
1390
  const type = schemaToTypeScript(normalized, branch, options);
1361
1391
  if (!discriminator || type === "unknown" || !isObjectSchemaBranch(normalized, branch)) return type;
1362
- const literal = discriminatorLiteral(discriminator, branch);
1392
+ const literal = discriminatorLiteral(normalized, discriminator, branch);
1363
1393
  if (literal === null) return type;
1364
1394
  return `(${type} & { ${quoteObjectKeyIfNeeded(discriminator.propertyName)}: ${JSON.stringify(literal)} })`;
1365
1395
  }
1366
- function discriminatorLiteral(discriminator, branch) {
1396
+ function discriminatorLiteral(normalized, discriminator, branch) {
1367
1397
  if (!branch.$ref) return null;
1368
1398
  const refName = branch.$ref.split("/").at(-1) ?? "";
1369
1399
  for (const [key, target] of Object.entries(discriminator.mapping)) {
1370
1400
  if (target === branch.$ref || target.split("/").at(-1) === refName) return key;
1371
1401
  }
1402
+ const candidates = discriminatorPropertyLiterals(normalized, branch, discriminator.propertyName);
1403
+ if (candidates.length > 0) {
1404
+ const exact = candidates.find((value) => value === refName);
1405
+ if (exact !== void 0) return exact;
1406
+ const caseInsensitive = candidates.find(
1407
+ (value) => value.toLowerCase() === refName.toLowerCase()
1408
+ );
1409
+ if (caseInsensitive !== void 0) return caseInsensitive;
1410
+ if (candidates.length === 1) return candidates[0] ?? null;
1411
+ }
1372
1412
  return refName.length > 0 ? refName : null;
1373
1413
  }
1414
+ function discriminatorPropertyLiterals(normalized, branch, propertyName) {
1415
+ const resolved = resolveSchema(normalized, branch);
1416
+ const property = resolved?.properties?.[propertyName];
1417
+ if (!property) return [];
1418
+ const propertySchema = resolveSchema(normalized, property) ?? property;
1419
+ const values = propertySchema.enum ?? (hasConst(propertySchema) ? [propertySchema.const] : []);
1420
+ return values.filter((value) => typeof value === "string");
1421
+ }
1422
+ function resolveDiscriminatedUnion(normalized, schema) {
1423
+ const branches = schema.oneOf ?? schema.anyOf;
1424
+ if (!branches || branches.length === 0) return null;
1425
+ const info = getDiscriminatorInfo(schema);
1426
+ if (!info) return null;
1427
+ if (!branches.every((branch) => isObjectSchemaBranch(normalized, branch))) return null;
1428
+ const resolved = [];
1429
+ for (const branch of branches) {
1430
+ const literal = discriminatorLiteral(normalized, info, branch);
1431
+ if (literal === null) return null;
1432
+ resolved.push({ schema: branch, literal });
1433
+ }
1434
+ return { propertyName: info.propertyName, branches: resolved };
1435
+ }
1374
1436
  function isObjectSchemaBranch(normalized, branch) {
1375
1437
  const resolved = branch.$ref ? resolveSchema(normalized, branch) : branch;
1376
1438
  if (!resolved) return false;
@@ -1396,7 +1458,9 @@ function getHeaderParams(operation) {
1396
1458
  return (operation?.parameters ?? []).filter((parameter) => parameter.in === "header");
1397
1459
  }
1398
1460
  function getOperationParams(operation) {
1399
- return (operation?.parameters ?? []).filter((parameter) => parameter.in === "path" || parameter.in === "query" || parameter.in === "header");
1461
+ return (operation?.parameters ?? []).filter(
1462
+ (parameter) => parameter.in === "path" || parameter.in === "query" || parameter.in === "header"
1463
+ );
1400
1464
  }
1401
1465
  function getCollectionParams(resource) {
1402
1466
  const params = [
@@ -1419,18 +1483,25 @@ function resolveSchema(normalized, schema) {
1419
1483
  if (!schema) return null;
1420
1484
  if (!schema.$ref) return schema;
1421
1485
  const name = schema.$ref.split("/").at(-1);
1422
- return normalized.schemas.find((candidate) => candidate.name === name || toSafeTypeName(candidate.name) === toSafeTypeName(name ?? ""))?.schema ?? schema;
1486
+ return normalized.schemas.find(
1487
+ (candidate) => candidate.name === name || toSafeTypeName(candidate.name) === toSafeTypeName(name ?? "")
1488
+ )?.schema ?? schema;
1423
1489
  }
1424
1490
  function resolveSchemaName(schema) {
1425
1491
  if (!schema?.$ref) return null;
1426
1492
  return toSafeTypeName(schema.$ref.split("/").at(-1) ?? "");
1427
1493
  }
1428
1494
  function createSchemaDeclaration(normalized, name, schema, enumAliases) {
1495
+ const nullableSuffix = schema.nullable ? " | null" : "";
1429
1496
  if (schema.oneOf?.length || schema.anyOf?.length) {
1430
- return `export type ${name} = ${schemaToTypeScript(normalized, schema, { mode: "response" })}`;
1497
+ return `export type ${name} = ${schemaToTypeScript(normalized, schema, { mode: "response" })}${nullableSuffix}`;
1431
1498
  }
1432
1499
  if (isPureDictionarySchema(schema)) {
1433
- return `export type ${name} = ${createDictionaryType(normalized, schema.additionalProperties, { mode: "response" })}`;
1500
+ return `export type ${name} = ${createDictionaryType(normalized, schema.additionalProperties, { mode: "response" })}${nullableSuffix}`;
1501
+ }
1502
+ const isScalarOrEnumDeclaration = schema.enum !== void 0 || hasConst(schema) || Array.isArray(schema.type) || typeof schema.type === "string" && schema.type !== "object";
1503
+ if (isScalarOrEnumDeclaration) {
1504
+ return `export type ${name} = ${schemaToTypeScript(normalized, schema, { mode: "response" })}${nullableSuffix}`;
1434
1505
  }
1435
1506
  const mode = name.endsWith("Dto") ? "request" : "response";
1436
1507
  const required = new Set(schema.required ?? []);
@@ -1444,7 +1515,13 @@ function createSchemaDeclaration(normalized, name, schema, enumAliases) {
1444
1515
  const nullable = property.nullable ? " | null" : "";
1445
1516
  return ` ${quoteObjectKeyIfNeeded(propertyName)}${optional}: ${baseType}${nullable}`;
1446
1517
  });
1447
- const indexSignature = createAdditionalPropertiesIndex(normalized, schema, { mode, indent: 0 }, " ", required);
1518
+ const indexSignature = createAdditionalPropertiesIndex(
1519
+ normalized,
1520
+ schema,
1521
+ { mode, indent: 0 },
1522
+ " ",
1523
+ required
1524
+ );
1448
1525
  if (indexSignature) lines.push(indexSignature);
1449
1526
  return `export interface ${name} {
1450
1527
  ${lines.join("\n")}
@@ -1457,11 +1534,13 @@ function isPureDictionarySchema(schema) {
1457
1534
  }
1458
1535
  function createDictionaryType(normalized, additionalProperties, options) {
1459
1536
  if (additionalProperties === false) return "Record<string, never>";
1460
- if (additionalProperties === true || additionalProperties === void 0) return "Record<string, unknown>";
1537
+ if (additionalProperties === true || additionalProperties === void 0)
1538
+ return "Record<string, unknown>";
1461
1539
  return `Record<string, ${schemaToTypeScript(normalized, additionalProperties, options)}>`;
1462
1540
  }
1463
1541
  function createAdditionalPropertiesIndex(normalized, schema, options, childIndent, required) {
1464
- if (schema.additionalProperties === void 0 || schema.additionalProperties === false || isPureDictionarySchema(schema)) return null;
1542
+ if (schema.additionalProperties === void 0 || schema.additionalProperties === false || isPureDictionarySchema(schema))
1543
+ return null;
1465
1544
  const valueTypes = /* @__PURE__ */ new Set();
1466
1545
  if (schema.additionalProperties === true) {
1467
1546
  valueTypes.add("unknown");
@@ -1492,14 +1571,36 @@ function createOperationAliases(normalized, resource) {
1492
1571
  const names = createResourceTypeNames(resource);
1493
1572
  const identityParams = getIdentityParams(resource);
1494
1573
  const idDeclaration = identityParams.length > 1 ? createParamsInterface(normalized, names.idType, identityParams) : `export type ${names.idType} = ${identityParams[0]?.schema ? schemaToTypeScript(normalized, identityParams[0].schema, { mode: "request" }) : "string"}`;
1495
- const listParams = createParamsInterface(normalized, names.listParamsType, getCollectionParams(resource));
1496
- const listResponse = createResponseDeclaration(normalized, names.listResponseType, resource.operations.list);
1497
- const detailResponse = createResponseType(normalized, resource.operations.detail, names.entityType);
1574
+ const listParams = createParamsInterface(
1575
+ normalized,
1576
+ names.listParamsType,
1577
+ getCollectionParams(resource)
1578
+ );
1579
+ const listResponse = createResponseDeclaration(
1580
+ normalized,
1581
+ names.listResponseType,
1582
+ resource.operations.list
1583
+ );
1584
+ const detailResponse = createResponseType(
1585
+ normalized,
1586
+ resource.operations.detail,
1587
+ names.entityType
1588
+ );
1498
1589
  const createRequest = createRequestBodyType(normalized, resource.operations.create) ?? `Partial<${names.entityType}>`;
1499
1590
  const updateRequest = createRequestBodyType(normalized, resource.operations.update) ?? `Partial<${names.entityType}>`;
1500
- const createResponse = createResponseType(normalized, resource.operations.create, names.entityType);
1501
- const updateResponse = createResponseType(normalized, resource.operations.update, names.entityType);
1502
- const operationAliases = resource.operationsList.filter((operation) => operation.operationKind !== "unsupported-operation" && !Object.values(resource.operations).includes(operation)).map((operation) => createOperationAlias(normalized, operation));
1591
+ const createResponse = createResponseType(
1592
+ normalized,
1593
+ resource.operations.create,
1594
+ names.entityType
1595
+ );
1596
+ const updateResponse = createResponseType(
1597
+ normalized,
1598
+ resource.operations.update,
1599
+ names.entityType
1600
+ );
1601
+ const operationAliases = resource.operationsList.filter(
1602
+ (operation) => operation.operationKind !== "unsupported-operation" && !Object.values(resource.operations).includes(operation)
1603
+ ).map((operation) => createOperationAlias(normalized, operation));
1503
1604
  return [
1504
1605
  idDeclaration,
1505
1606
  `export type ${names.detailResponseType} = ${detailResponse}`,
@@ -1517,13 +1618,18 @@ function createOperationAlias(normalized, operation) {
1517
1618
  const params = createParamsInterface(normalized, names.paramsType, getOperationParams(operation));
1518
1619
  const request = createRequestBodyType(normalized, operation) ?? "void";
1519
1620
  const response = createResponseType(normalized, operation, "unknown");
1520
- return [params, `export type ${names.requestType} = ${request}`, `export type ${names.responseType} = ${response}`].join("\n\n");
1621
+ return [
1622
+ params,
1623
+ `export type ${names.requestType} = ${request}`,
1624
+ `export type ${names.responseType} = ${response}`
1625
+ ].join("\n\n");
1521
1626
  }
1522
1627
  function createRequestBodyType(normalized, operation) {
1523
1628
  if (!operation?.requestBodySchema) return null;
1524
1629
  if (operation.requestContentTypes.some(isMultipartContentType)) return "FormData";
1525
1630
  if (operation.requestContentTypes.some(isFormUrlEncodedContentType)) return "URLSearchParams";
1526
- if (operation.requestContentTypes.some(isBinaryContentType)) return "Blob | ArrayBuffer | ReadableStream";
1631
+ if (operation.requestContentTypes.some(isBinaryContentType))
1632
+ return "Blob | ArrayBuffer | ReadableStream";
1527
1633
  return resolveSchemaName(operation.requestBodySchema) ?? schemaToTypeScript(normalized, operation.requestBodySchema, { mode: "request" });
1528
1634
  }
1529
1635
  function isMultipartContentType(contentType) {
@@ -1618,9 +1724,9 @@ function isForgePlugin(value) {
1618
1724
  function collectDiagnostics(normalized, options = {}) {
1619
1725
  const diagnostics = [];
1620
1726
  const groupedParameters = /* @__PURE__ */ new Map();
1621
- const unsupportedSecuritySchemes = Object.entries(normalized.document.components?.securitySchemes ?? {}).filter(
1622
- ([, scheme]) => !isRuntimeSupportedSecurityScheme(scheme)
1623
- );
1727
+ const unsupportedSecuritySchemes = Object.entries(
1728
+ normalized.document.components?.securitySchemes ?? {}
1729
+ ).filter(([, scheme]) => !isRuntimeSupportedSecurityScheme(scheme));
1624
1730
  if (unsupportedSecuritySchemes.length > 0) {
1625
1731
  diagnostics.push({
1626
1732
  severity: "warning",
@@ -1633,7 +1739,9 @@ function collectDiagnostics(normalized, options = {}) {
1633
1739
  for (const operation of normalized.operations) {
1634
1740
  const location = `${operation.method.toUpperCase()} ${operation.path}`;
1635
1741
  const requestContentTypes = getContentTypes(operation.operation.requestBody);
1636
- const responseContentTypes = Object.values(operation.operation.responses ?? {}).flatMap((response) => Object.keys(response.content ?? {}));
1742
+ const responseContentTypes = Object.values(operation.operation.responses ?? {}).flatMap(
1743
+ (response) => Object.keys(response.content ?? {})
1744
+ );
1637
1745
  if (operation.operationKind === "unsupported-operation") {
1638
1746
  diagnostics.push({
1639
1747
  severity: "warning",
@@ -1666,15 +1774,16 @@ function collectDiagnostics(normalized, options = {}) {
1666
1774
  }
1667
1775
  for (const parameter of operation.parameters) {
1668
1776
  if (parameter.in === "header" || parameter.in === "cookie") {
1669
- if (parameter.in === "header" && parameter.name.toLowerCase() === "authorization") {
1670
- continue;
1671
- }
1672
- if (parameter.in === "header" && operation.operationKind !== "crud-resource" && parameter.name.toLowerCase() !== "authorization") {
1777
+ if (parameter.in === "header") {
1673
1778
  continue;
1674
1779
  }
1675
1780
  if (parameter.in === "cookie" && parameter.name.toLowerCase() === "authorization") {
1676
1781
  const key = `${parameter.in}:${parameter.name.toLowerCase()}`;
1677
- const grouped = groupedParameters.get(key) ?? { in: parameter.in, name: parameter.name, locations: [] };
1782
+ const grouped = groupedParameters.get(key) ?? {
1783
+ in: parameter.in,
1784
+ name: parameter.name,
1785
+ locations: []
1786
+ };
1678
1787
  grouped.locations.push(location);
1679
1788
  groupedParameters.set(key, grouped);
1680
1789
  continue;
@@ -1713,7 +1822,12 @@ function collectDiagnostics(normalized, options = {}) {
1713
1822
  const schemaDiagnostics = [];
1714
1823
  for (const schema of normalized.schemas) {
1715
1824
  const originalSchema = normalized.document.components?.schemas?.[schema.name] ?? schema.schema;
1716
- collectSchemaDiagnostics(normalized, originalSchema, `#/components/schemas/${schema.name}`, schemaDiagnostics);
1825
+ collectSchemaDiagnostics(
1826
+ normalized,
1827
+ originalSchema,
1828
+ `#/components/schemas/${schema.name}`,
1829
+ schemaDiagnostics
1830
+ );
1717
1831
  }
1718
1832
  diagnostics.push(...schemaDiagnostics);
1719
1833
  diagnostics.push(...runPluginDiagnostics({ plugins: options.plugins, normalized }));
@@ -1780,7 +1894,8 @@ function isSupportedDiscriminatedUnion(normalized, schema) {
1780
1894
  function isRuntimeSupportedSecurityScheme(value) {
1781
1895
  if (!value || typeof value !== "object") return false;
1782
1896
  const scheme = value;
1783
- if (scheme.type === "http" && typeof scheme.scheme === "string" && scheme.scheme.toLowerCase() === "bearer") return true;
1897
+ if (scheme.type === "http" && typeof scheme.scheme === "string" && scheme.scheme.toLowerCase() === "bearer")
1898
+ return true;
1784
1899
  if (scheme.type === "oauth2") return true;
1785
1900
  return scheme.type === "apiKey" && scheme.in === "header";
1786
1901
  }
@@ -1796,10 +1911,19 @@ function isJsonCompatibleContentType(contentType) {
1796
1911
  function createSchemaCoverageMatrix(normalized, diagnostics) {
1797
1912
  const operations = normalized.operations;
1798
1913
  const unsupportedConstructs = countUnsupportedSchemaConstructs(normalized);
1799
- const operationDiagnosticOnly = operations.filter((operation) => operation.operationKind === "unsupported-operation").length;
1800
- const schemaDiagnosticOnly = Object.values(unsupportedConstructs).reduce((total, count) => total + count, 0);
1801
- const generated = operations.filter((operation) => operation.operationKind !== "unsupported-operation").length;
1802
- const fallback = operations.filter(hasFallbackShape).length + diagnostics.filter((diagnostic) => diagnostic.code === "missing-request-schema" || diagnostic.code === "missing-response-schema").length;
1914
+ const operationDiagnosticOnly = operations.filter(
1915
+ (operation) => operation.operationKind === "unsupported-operation"
1916
+ ).length;
1917
+ const schemaDiagnosticOnly = Object.values(unsupportedConstructs).reduce(
1918
+ (total, count) => total + count,
1919
+ 0
1920
+ );
1921
+ const generated = operations.filter(
1922
+ (operation) => operation.operationKind !== "unsupported-operation"
1923
+ ).length;
1924
+ const fallback = operations.filter(hasFallbackShape).length + diagnostics.filter(
1925
+ (diagnostic) => diagnostic.code === "missing-request-schema" || diagnostic.code === "missing-response-schema"
1926
+ ).length;
1803
1927
  return {
1804
1928
  operations: {
1805
1929
  total: operations.length,
@@ -1833,7 +1957,9 @@ function mergeSchemaCoverageMatrices(matrices) {
1833
1957
  },
1834
1958
  schemas: {
1835
1959
  total: sum(matrices, (matrix) => matrix.schemas.total),
1836
- unsupportedConstructs: mergeRecords(matrices.map((matrix) => matrix.schemas.unsupportedConstructs))
1960
+ unsupportedConstructs: mergeRecords(
1961
+ matrices.map((matrix) => matrix.schemas.unsupportedConstructs)
1962
+ )
1837
1963
  },
1838
1964
  cases: {
1839
1965
  generated: sum(matrices, (matrix) => matrix.cases.generated),
@@ -1873,18 +1999,28 @@ function countUnsupportedSchemaConstructs(normalized) {
1873
1999
  return counts;
1874
2000
  }
1875
2001
  function visitSchema(normalized, schema, counts) {
1876
- if (schema.allOf) counts.allOf = (counts.allOf ?? 0) + 1;
1877
- if (schema.oneOf && !isSupportedUnion(normalized, schema, schema.oneOf)) counts.oneOf = (counts.oneOf ?? 0) + 1;
1878
- if (schema.anyOf && !isSupportedUnion(normalized, schema, schema.anyOf)) counts.anyOf = (counts.anyOf ?? 0) + 1;
2002
+ if (schema.allOf && !unwrapAnnotationOnlyAllOfSchema(schema)) {
2003
+ if (analyzeAllOfSchema(normalized.document, schema).kind !== "mergeable")
2004
+ counts.allOf = (counts.allOf ?? 0) + 1;
2005
+ }
2006
+ if (schema.oneOf && !isSupportedUnion(normalized, schema, schema.oneOf))
2007
+ counts.oneOf = (counts.oneOf ?? 0) + 1;
2008
+ if (schema.anyOf && !isSupportedUnion(normalized, schema, schema.anyOf))
2009
+ counts.anyOf = (counts.anyOf ?? 0) + 1;
1879
2010
  const union = schema.oneOf ?? schema.anyOf;
1880
- if (schema.discriminator && !(union && isSupportedUnion(normalized, schema, union))) counts.discriminator = (counts.discriminator ?? 0) + 1;
2011
+ if (schema.discriminator && !(union && isSupportedUnion(normalized, schema, union)))
2012
+ counts.discriminator = (counts.discriminator ?? 0) + 1;
1881
2013
  for (const property of Object.values(schema.properties ?? {})) {
1882
2014
  visitSchema(normalized, property, counts);
1883
2015
  }
1884
2016
  if (schema.items) {
1885
2017
  visitSchema(normalized, schema.items, counts);
1886
2018
  }
1887
- for (const branch of [...schema.allOf ?? [], ...schema.oneOf ?? [], ...schema.anyOf ?? []]) {
2019
+ for (const branch of [
2020
+ ...schema.allOf ?? [],
2021
+ ...schema.oneOf ?? [],
2022
+ ...schema.anyOf ?? []
2023
+ ]) {
1888
2024
  visitSchema(normalized, branch, counts);
1889
2025
  }
1890
2026
  }
@@ -2130,7 +2266,10 @@ function createClientArtifact(normalized, resourceName2, resource) {
2130
2266
  const pathParams = getPathParams(resource.operations.list);
2131
2267
  const queryParams = getQueryParams(resource.operations.list);
2132
2268
  const requiresParams = pathParams.length > 0;
2133
- signatures.push(` ${resource.operations.list.id}: (params${requiresParams ? "" : "?"}: ${names.listParamsType}, options?: ${requestOptionsType}) => Promise<${response}>`);
2269
+ const listOptions = crudOptionsType(resource.operations.list, requestOptionsType);
2270
+ signatures.push(
2271
+ ` ${resource.operations.list.id}: (params${requiresParams ? "" : "?"}: ${names.listParamsType}, ${optionsParam(listOptions)}) => Promise<${response}>`
2272
+ );
2134
2273
  implementations.push(
2135
2274
  ` ${resource.operations.list.id}: (params, options) => apiClient.request<${response}>('GET', ${pathWithParamsObject(resource.operations.list.path)}${createListRequestOptions(queryParams, requiresParams)}),`
2136
2275
  );
@@ -2140,7 +2279,9 @@ function createClientArtifact(normalized, resourceName2, resource) {
2140
2279
  const pathParams = getPathParams(resource.operations.detail);
2141
2280
  const paramSignature = createPathParamSignature(pathParams, names.idType);
2142
2281
  const paramName = pathParams.length > 1 ? "params" : toSafeIdentifier(pathParams[0]?.name ?? "id");
2143
- signatures.push(` ${resource.operations.detail.id}: (${paramName}: ${paramSignature}, options?: ${requestOptionsType}) => Promise<${response}>`);
2282
+ signatures.push(
2283
+ ` ${resource.operations.detail.id}: (${paramName}: ${paramSignature}, ${optionsParam(crudOptionsType(resource.operations.detail, requestOptionsType))}) => Promise<${response}>`
2284
+ );
2144
2285
  implementations.push(
2145
2286
  ` ${resource.operations.detail.id}: (${paramName}, options) => apiClient.request<${response}>('GET', ${pathWithParams(resource.operations.detail.path, pathParams)}, options),`
2146
2287
  );
@@ -2149,12 +2290,16 @@ function createClientArtifact(normalized, resourceName2, resource) {
2149
2290
  const response = names.createResponseType;
2150
2291
  const pathParams = getPathParams(resource.operations.create);
2151
2292
  if (pathParams.length > 0) {
2152
- signatures.push(` ${resource.operations.create.id}: (params: ${names.listParamsType}, payload: ${names.createRequestType}, options?: ${requestOptionsType}) => Promise<${response}>`);
2293
+ signatures.push(
2294
+ ` ${resource.operations.create.id}: (params: ${names.listParamsType}, payload: ${names.createRequestType}, ${optionsParam(crudOptionsType(resource.operations.create, requestOptionsType))}) => Promise<${response}>`
2295
+ );
2153
2296
  implementations.push(
2154
2297
  ` ${resource.operations.create.id}: (params, payload, options) => apiClient.request<${response}>('POST', ${pathWithParamsObject(resource.operations.create.path)}, { ...options, body: payload }),`
2155
2298
  );
2156
2299
  } else {
2157
- signatures.push(` ${resource.operations.create.id}: (payload: ${names.createRequestType}, options?: ${requestOptionsType}) => Promise<${response}>`);
2300
+ signatures.push(
2301
+ ` ${resource.operations.create.id}: (payload: ${names.createRequestType}, ${optionsParam(crudOptionsType(resource.operations.create, requestOptionsType))}) => Promise<${response}>`
2302
+ );
2158
2303
  implementations.push(
2159
2304
  ` ${resource.operations.create.id}: (payload, options) => apiClient.request<${response}>('POST', '${resource.operations.create.path}', { ...options, body: payload }),`
2160
2305
  );
@@ -2165,7 +2310,9 @@ function createClientArtifact(normalized, resourceName2, resource) {
2165
2310
  const pathParams = getPathParams(resource.operations.update);
2166
2311
  const paramSignature = createPathParamSignature(pathParams, names.idType);
2167
2312
  const paramName = pathParams.length > 1 ? "params" : toSafeIdentifier(pathParams[0]?.name ?? "id");
2168
- signatures.push(` ${resource.operations.update.id}: (${paramName}: ${paramSignature}, payload: ${names.updateRequestType}, options?: ${requestOptionsType}) => Promise<${response}>`);
2313
+ signatures.push(
2314
+ ` ${resource.operations.update.id}: (${paramName}: ${paramSignature}, payload: ${names.updateRequestType}, ${optionsParam(crudOptionsType(resource.operations.update, requestOptionsType))}) => Promise<${response}>`
2315
+ );
2169
2316
  implementations.push(
2170
2317
  ` ${resource.operations.update.id}: (${paramName}, payload, options) => apiClient.request<${response}>('PATCH', ${pathWithParams(resource.operations.update.path, pathParams)}, { ...options, body: payload }),`
2171
2318
  );
@@ -2174,12 +2321,16 @@ function createClientArtifact(normalized, resourceName2, resource) {
2174
2321
  const pathParams = getPathParams(resource.operations.delete);
2175
2322
  const paramSignature = createPathParamSignature(pathParams, names.idType);
2176
2323
  const paramName = pathParams.length > 1 ? "params" : toSafeIdentifier(pathParams[0]?.name ?? "id");
2177
- signatures.push(` ${resource.operations.delete.id}: (${paramName}: ${paramSignature}, options?: ${requestOptionsType}) => Promise<void>`);
2324
+ signatures.push(
2325
+ ` ${resource.operations.delete.id}: (${paramName}: ${paramSignature}, ${optionsParam(crudOptionsType(resource.operations.delete, requestOptionsType))}) => Promise<void>`
2326
+ );
2178
2327
  implementations.push(
2179
2328
  ` ${resource.operations.delete.id}: (${paramName}, options) => apiClient.request<void>('DELETE', ${pathWithParams(resource.operations.delete.path, pathParams)}, options),`
2180
2329
  );
2181
2330
  }
2182
- for (const operation of resource.operationsList.filter((item) => isGeneratedOperation(resource, item))) {
2331
+ for (const operation of resource.operationsList.filter(
2332
+ (item) => isGeneratedOperation(resource, item)
2333
+ )) {
2183
2334
  const names2 = createOperationTypeNames(operation);
2184
2335
  const response = names2.responseType;
2185
2336
  const params = getOperationParams(operation);
@@ -2187,15 +2338,26 @@ function createClientArtifact(normalized, resourceName2, resource) {
2187
2338
  const headerParams = getHeaderParams(operation);
2188
2339
  const method = operation.method.toUpperCase();
2189
2340
  const pathExpression = pathWithOperationParams(operation.path);
2190
- const options = createOperationRequestOptions(operation.requestBodySchema ? "payload" : null, queryParams, headerParams);
2191
- signatures.push(` ${operation.id}: ${createOperationSignature(names2, Boolean(operation.requestBodySchema), params.length > 0, response, requestOptionsType)}`);
2192
- implementations.push(` ${operation.id}: ${createOperationImplementationArgs(Boolean(operation.requestBodySchema), params.length > 0)} => apiClient.request<${response}>('${method}', ${pathExpression}${options}),`);
2341
+ const options = createOperationRequestOptions(
2342
+ operation.requestBodySchema ? "payload" : null,
2343
+ queryParams,
2344
+ headerParams
2345
+ );
2346
+ signatures.push(
2347
+ ` ${operation.id}: ${createOperationSignature(names2, Boolean(operation.requestBodySchema), params.length > 0, response, requestOptionsType)}`
2348
+ );
2349
+ implementations.push(
2350
+ ` ${operation.id}: ${createOperationImplementationArgs(Boolean(operation.requestBodySchema), params.length > 0)} => apiClient.request<${response}>('${method}', ${pathExpression}${options}),`
2351
+ );
2193
2352
  }
2194
2353
  const imports = collectClientTypes(resource);
2195
2354
  const typeImport = imports.length > 0 ? `import type { ${imports.join(", ")} } from './${resourceName2}.types'
2196
2355
 
2197
2356
  ` : "";
2198
- const runtimeImports = ["createApiClient", ...usesQueryParamWrapper(resource) ? ["queryParam"] : []];
2357
+ const runtimeImports = [
2358
+ "createApiClient",
2359
+ ...usesQueryParamWrapper(resource) ? ["queryParam"] : []
2360
+ ];
2199
2361
  const entityCollectionName = pluralizeTypeName(resource.entity);
2200
2362
  const configureName = `configure${entityCollectionName}Client`;
2201
2363
  const setName = `set${entityCollectionName}Client`;
@@ -2233,14 +2395,16 @@ function collectClientTypes(resource) {
2233
2395
  const types = /* @__PURE__ */ new Set();
2234
2396
  if (resource.operations.list?.id) {
2235
2397
  types.add(names.listParamsType);
2236
- if (resource.operations.list.responseSchema || resource.operations.list.responseBodyEmpty) types.add(names.listResponseType);
2398
+ if (resource.operations.list.responseSchema || resource.operations.list.responseBodyEmpty)
2399
+ types.add(names.listResponseType);
2237
2400
  }
2238
2401
  if (resource.operations.detail?.id) {
2239
2402
  types.add(names.idType);
2240
2403
  types.add(names.detailResponseType);
2241
2404
  }
2242
2405
  if (resource.operations.create?.id) {
2243
- if (!resource.operations.list?.id && getPathParams(resource.operations.create).length > 0) types.add(names.listParamsType);
2406
+ if (!resource.operations.list?.id && getPathParams(resource.operations.create).length > 0)
2407
+ types.add(names.listParamsType);
2244
2408
  types.add(names.createRequestType);
2245
2409
  types.add(names.createResponseType);
2246
2410
  }
@@ -2252,7 +2416,9 @@ function collectClientTypes(resource) {
2252
2416
  if (resource.operations.delete?.id) {
2253
2417
  types.add(names.idType);
2254
2418
  }
2255
- for (const operation of resource.operationsList.filter((item) => isGeneratedOperation(resource, item))) {
2419
+ for (const operation of resource.operationsList.filter(
2420
+ (item) => isGeneratedOperation(resource, item)
2421
+ )) {
2256
2422
  const operationNames = createOperationTypeNames(operation);
2257
2423
  if (getOperationParams(operation).length > 0) types.add(operationNames.paramsType);
2258
2424
  if (operation.requestBodySchema) types.add(operationNames.requestType);
@@ -2276,10 +2442,29 @@ function pathWithParamsObject(path) {
2276
2442
  function isGeneratedOperation(resource, operation) {
2277
2443
  return operation.operationKind !== "unsupported-operation" && Boolean(operation.id) && !Object.values(resource.operations).includes(operation);
2278
2444
  }
2445
+ function typedHeaderParams(operation) {
2446
+ return getHeaderParams(operation).filter(
2447
+ (parameter) => parameter.name.toLowerCase() !== "authorization"
2448
+ );
2449
+ }
2450
+ function crudOptionsType(operation, baseType) {
2451
+ const headers = typedHeaderParams(operation);
2452
+ if (headers.length === 0) return baseType;
2453
+ const entries = headers.map(
2454
+ (parameter) => `${quoteObjectKeyIfNeeded(parameter.name)}${parameter.required ? "" : "?"}: string`
2455
+ ).join("; ");
2456
+ return `${baseType} & { headers: { ${entries} } & Record<string, string> }`;
2457
+ }
2458
+ function optionsParam(optionsType) {
2459
+ return `options?: ${optionsType}`;
2460
+ }
2279
2461
  function createOperationSignature(names, hasPayload, hasParams, response, requestOptionsType) {
2280
- if (hasPayload && hasParams) return `(payload: ${names.requestType}, params: ${names.paramsType}, options?: ${requestOptionsType}) => Promise<${response}>`;
2281
- if (hasPayload) return `(payload: ${names.requestType}, options?: ${requestOptionsType}) => Promise<${response}>`;
2282
- if (hasParams) return `(params: ${names.paramsType}, options?: ${requestOptionsType}) => Promise<${response}>`;
2462
+ if (hasPayload && hasParams)
2463
+ return `(payload: ${names.requestType}, params: ${names.paramsType}, options?: ${requestOptionsType}) => Promise<${response}>`;
2464
+ if (hasPayload)
2465
+ return `(payload: ${names.requestType}, options?: ${requestOptionsType}) => Promise<${response}>`;
2466
+ if (hasParams)
2467
+ return `(params: ${names.paramsType}, options?: ${requestOptionsType}) => Promise<${response}>`;
2283
2468
  return `(options?: ${requestOptionsType}) => Promise<${response}>`;
2284
2469
  }
2285
2470
  function createOperationImplementationArgs(hasPayload, hasParams) {
@@ -2296,13 +2481,16 @@ function createOperationRequestOptions(payloadName, queryParams, headerParams) {
2296
2481
  options.push(`params: ${createQueryParamsObject(queryParams, false)}`);
2297
2482
  }
2298
2483
  if (headerParams.length > 0) {
2299
- options.push(`headers: createOperationHeaders(options?.headers, { ${headerParams.map((param) => `${quoteObjectKeyIfNeeded(param.name)}: ${paramValueAccess("params", param.name)}`).join(", ")} })`);
2484
+ options.push(
2485
+ `headers: createOperationHeaders(options?.headers, { ${headerParams.map((param) => `${quoteObjectKeyIfNeeded(param.name)}: ${paramValueAccess("params", param.name)}`).join(", ")} })`
2486
+ );
2300
2487
  }
2301
2488
  return options.length > 1 ? `, { ${options.join(", ")} }` : ", options";
2302
2489
  }
2303
2490
  function createListRequestOptions(queryParams, requiresParams) {
2304
2491
  if (queryParams.length === 0) return ", options";
2305
- if (!requiresParams && !queryParams.some(requiresQueryParamWrapper)) return ", { ...options, params: params as Record<string, unknown> | undefined }";
2492
+ if (!requiresParams && !queryParams.some(requiresQueryParamWrapper))
2493
+ return ", { ...options, params: params as Record<string, unknown> | undefined }";
2306
2494
  return `, { ...options, params: ${createQueryParamsObject(queryParams, !requiresParams)} }`;
2307
2495
  }
2308
2496
  function pathWithOperationParams(path) {
@@ -2330,7 +2518,9 @@ function requiresQueryParamWrapper(param) {
2330
2518
  return param.in === "query" && param.schema?.type === "array" && (param.style ?? "form") === "form" && param.explode === false;
2331
2519
  }
2332
2520
  function usesQueryParamWrapper(resource) {
2333
- return resource.operationsList.some((operation) => getQueryParams(operation).some(requiresQueryParamWrapper));
2521
+ return resource.operationsList.some(
2522
+ (operation) => getQueryParams(operation).some(requiresQueryParamWrapper)
2523
+ );
2334
2524
  }
2335
2525
  function createFixturesArtifact(resourceName2, entity) {
2336
2526
  return `export const ${resourceName2}Fixtures: ${entity}Fixture[] = []
@@ -2460,17 +2650,35 @@ function schemaToZod(normalized, schema, resolvingRefs = []) {
2460
2650
  return appendNullable("z.unknown()", resolved);
2461
2651
  }
2462
2652
  if (resolved.oneOf?.length || resolved.anyOf?.length) {
2463
- return appendNullable(createZodUnion(normalized, resolved.oneOf ?? resolved.anyOf ?? [], resolvingRefs), resolved);
2653
+ const discriminated = resolveDiscriminatedUnion(normalized, resolved);
2654
+ if (discriminated) {
2655
+ return appendNullable(
2656
+ createZodDiscriminatedUnion(normalized, discriminated, resolvingRefs),
2657
+ resolved
2658
+ );
2659
+ }
2660
+ return appendNullable(
2661
+ createZodUnion(normalized, resolved.oneOf ?? resolved.anyOf ?? [], resolvingRefs),
2662
+ resolved
2663
+ );
2464
2664
  }
2465
- if (hasConst2(resolved)) return appendNullable(`z.literal(${JSON.stringify(resolved.const)})`, resolved);
2665
+ if (hasConst2(resolved))
2666
+ return appendNullable(`z.literal(${JSON.stringify(resolved.const)})`, resolved);
2466
2667
  if (resolved.enum?.length) return appendNullable(createZodEnum(resolved.enum), resolved);
2467
2668
  if (resolved.type === "string") return zodString(resolved);
2468
2669
  if (resolved.type === "integer") return appendNullable("z.number().int()", resolved);
2469
2670
  if (resolved.type === "number") return appendNullable(zodNumber(resolved), resolved);
2470
2671
  if (resolved.type === "boolean") return appendNullable("z.boolean()", resolved);
2471
- if (resolved.type === "array") return appendNullable(`z.array(${schemaToZod(normalized, resolved.items, resolvingRefs)})`, resolved);
2672
+ if (resolved.type === "array")
2673
+ return appendNullable(
2674
+ `z.array(${schemaToZod(normalized, resolved.items, resolvingRefs)})`,
2675
+ resolved
2676
+ );
2472
2677
  if (isPureDictionarySchema2(resolved)) {
2473
- return appendNullable(`z.record(z.string(), ${schemaToZodRecordValue(normalized, resolved.additionalProperties, resolvingRefs)})`, resolved);
2678
+ return appendNullable(
2679
+ `z.record(z.string(), ${schemaToZodRecordValue(normalized, resolved.additionalProperties, resolvingRefs)})`,
2680
+ resolved
2681
+ );
2474
2682
  }
2475
2683
  if (resolved.type === "object" || resolved.properties) {
2476
2684
  const required = new Set(resolved.required ?? []);
@@ -2493,7 +2701,11 @@ function schemaToValibot(normalized, schema, resolvingRefs = []) {
2493
2701
  }
2494
2702
  const resolved2 = resolveSchema(normalized, schema);
2495
2703
  if (!resolved2 || resolved2 === schema) return wrapValibotNullable("v.unknown()", schema);
2496
- return schemaToValibot(normalized, resolved2, refName ? [...resolvingRefs, refName] : resolvingRefs);
2704
+ return schemaToValibot(
2705
+ normalized,
2706
+ resolved2,
2707
+ refName ? [...resolvingRefs, refName] : resolvingRefs
2708
+ );
2497
2709
  }
2498
2710
  const resolved = resolveSchema(normalized, schema);
2499
2711
  if (!resolved) return "v.looseObject({})";
@@ -2503,19 +2715,41 @@ function schemaToValibot(normalized, schema, resolvingRefs = []) {
2503
2715
  return wrapValibotNullable("v.unknown()", resolved);
2504
2716
  }
2505
2717
  if (resolved.oneOf?.length || resolved.anyOf?.length) {
2506
- return wrapValibotNullable(createValibotUnion(normalized, resolved.oneOf ?? resolved.anyOf ?? [], resolvingRefs), resolved);
2718
+ const discriminated = resolveDiscriminatedUnion(normalized, resolved);
2719
+ if (discriminated) {
2720
+ return wrapValibotNullable(
2721
+ createValibotVariant(normalized, discriminated, resolvingRefs),
2722
+ resolved
2723
+ );
2724
+ }
2725
+ return wrapValibotNullable(
2726
+ createValibotUnion(normalized, resolved.oneOf ?? resolved.anyOf ?? [], resolvingRefs),
2727
+ resolved
2728
+ );
2507
2729
  }
2508
- if (hasConst2(resolved)) return wrapValibotNullable(`v.literal(${JSON.stringify(resolved.const)})`, resolved);
2730
+ if (hasConst2(resolved))
2731
+ return wrapValibotNullable(`v.literal(${JSON.stringify(resolved.const)})`, resolved);
2509
2732
  if (resolved.enum?.length) {
2510
- return wrapValibotNullable(`v.picklist([${resolved.enum.map((value) => JSON.stringify(value)).join(", ")}])`, resolved);
2733
+ return wrapValibotNullable(
2734
+ `v.picklist([${resolved.enum.map((value) => JSON.stringify(value)).join(", ")}])`,
2735
+ resolved
2736
+ );
2511
2737
  }
2512
2738
  if (resolved.type === "string") return valibotString(resolved);
2513
- if (resolved.type === "integer") return wrapValibotNullable("v.pipe(v.number(), v.integer())", resolved);
2739
+ if (resolved.type === "integer")
2740
+ return wrapValibotNullable("v.pipe(v.number(), v.integer())", resolved);
2514
2741
  if (resolved.type === "number") return wrapValibotNullable(valibotNumber(resolved), resolved);
2515
2742
  if (resolved.type === "boolean") return wrapValibotNullable("v.boolean()", resolved);
2516
- if (resolved.type === "array") return wrapValibotNullable(`v.array(${schemaToValibot(normalized, resolved.items, resolvingRefs)})`, resolved);
2743
+ if (resolved.type === "array")
2744
+ return wrapValibotNullable(
2745
+ `v.array(${schemaToValibot(normalized, resolved.items, resolvingRefs)})`,
2746
+ resolved
2747
+ );
2517
2748
  if (isPureDictionarySchema2(resolved)) {
2518
- return wrapValibotNullable(`v.record(v.string(), ${schemaToValibotRecordValue(normalized, resolved.additionalProperties, resolvingRefs)})`, resolved);
2749
+ return wrapValibotNullable(
2750
+ `v.record(v.string(), ${schemaToValibotRecordValue(normalized, resolved.additionalProperties, resolvingRefs)})`,
2751
+ resolved
2752
+ );
2519
2753
  }
2520
2754
  if (resolved.type === "object" || resolved.properties) {
2521
2755
  const required = new Set(resolved.required ?? []);
@@ -2530,6 +2764,47 @@ ${fields}
2530
2764
  }
2531
2765
  return wrapValibotNullable("v.unknown()", resolved);
2532
2766
  }
2767
+ function createZodDiscriminatedUnion(normalized, discriminated, resolvingRefs) {
2768
+ const branches = discriminated.branches.map(
2769
+ (branch) => discriminatedBranch(normalized, branch, discriminated.propertyName, resolvingRefs, {
2770
+ object: (fields) => `z.object({
2771
+ ${fields}
2772
+ })`,
2773
+ literal: (value) => `z.literal(${JSON.stringify(value)})`,
2774
+ optional: (expression) => `${expression}.optional()`,
2775
+ render: schemaToZod
2776
+ })
2777
+ );
2778
+ return `z.discriminatedUnion(${JSON.stringify(discriminated.propertyName)}, [${branches.join(", ")}])`;
2779
+ }
2780
+ function createValibotVariant(normalized, discriminated, resolvingRefs) {
2781
+ const branches = discriminated.branches.map(
2782
+ (branch) => discriminatedBranch(normalized, branch, discriminated.propertyName, resolvingRefs, {
2783
+ object: (fields) => `v.object({
2784
+ ${fields}
2785
+ })`,
2786
+ literal: (value) => `v.literal(${JSON.stringify(value)})`,
2787
+ optional: (expression) => `v.optional(${expression})`,
2788
+ render: schemaToValibot
2789
+ })
2790
+ );
2791
+ return `v.variant(${JSON.stringify(discriminated.propertyName)}, [${branches.join(", ")}])`;
2792
+ }
2793
+ function discriminatedBranch(normalized, branch, propertyName, resolvingRefs, emit) {
2794
+ const resolved = resolveSchema(normalized, branch.schema);
2795
+ if (!resolved || resolved.type !== "object" && !resolved.properties) {
2796
+ return emit.render(normalized, branch.schema, resolvingRefs);
2797
+ }
2798
+ const refName = resolveSchemaName(branch.schema);
2799
+ const branchRefs = refName ? [...resolvingRefs, refName] : resolvingRefs;
2800
+ const required = new Set(resolved.required ?? []);
2801
+ const fields = Object.entries(resolved.properties ?? {}).map(([name, property]) => {
2802
+ const rendered = name === propertyName ? emit.literal(branch.literal) : emit.render(normalized, property, branchRefs);
2803
+ const value = required.has(name) ? rendered : emit.optional(rendered);
2804
+ return ` ${toCodeKey(name)}: ${value},`;
2805
+ }).join("\n");
2806
+ return emit.object(fields);
2807
+ }
2533
2808
  function zodString(schema) {
2534
2809
  let expression = "z.string()";
2535
2810
  if (schema.format === "email") expression += ".email()";
@@ -3813,21 +4088,58 @@ function dedupeDiagnostics(diagnostics) {
3813
4088
  }
3814
4089
 
3815
4090
  // ../adapters/dist/index.js
3816
- var tanstackQueryComposables = createQueryComposables("@tanstack/react-query");
3817
- var vueQueryComposables = createQueryComposables("@tanstack/vue-query");
4091
+ var reactQueryHooks = {
4092
+ queryHook: "useQuery",
4093
+ mutationHook: "useMutation",
4094
+ queryOptions: "UseQueryOptions",
4095
+ mutationOptions: "UseMutationOptions",
4096
+ queryClientHook: "useQueryClient",
4097
+ optionsAsFunction: false
4098
+ };
4099
+ var QUERY_PROFILES = {
4100
+ "tanstack-query": { module: "@tanstack/react-query", ...reactQueryHooks },
4101
+ "vue-query": { module: "@tanstack/vue-query", ...reactQueryHooks },
4102
+ "svelte-query": {
4103
+ module: "@tanstack/svelte-query",
4104
+ queryHook: "createQuery",
4105
+ mutationHook: "createMutation",
4106
+ queryOptions: "CreateQueryOptions",
4107
+ mutationOptions: "CreateMutationOptions",
4108
+ queryClientHook: "useQueryClient",
4109
+ optionsAsFunction: false
4110
+ },
4111
+ "angular-query": {
4112
+ module: "@tanstack/angular-query-experimental",
4113
+ queryHook: "injectQuery",
4114
+ mutationHook: "injectMutation",
4115
+ queryOptions: "CreateQueryOptions",
4116
+ mutationOptions: "CreateMutationOptions",
4117
+ queryClientHook: "injectQueryClient",
4118
+ optionsAsFunction: true
4119
+ }
4120
+ };
4121
+ var tanstackQueryComposables = createQueryComposables("tanstack-query");
4122
+ var vueQueryComposables = createQueryComposables("vue-query");
4123
+ var svelteQueryComposables = createQueryComposables("svelte-query");
4124
+ var angularQueryComposables = createQueryComposables("angular-query");
4125
+ var COMPOSABLES_BY_TARGET = {
4126
+ "tanstack-query": tanstackQueryComposables,
4127
+ "vue-query": vueQueryComposables,
4128
+ "svelte-query": svelteQueryComposables,
4129
+ "angular-query": angularQueryComposables
4130
+ };
3818
4131
  function resolveQueryComposables(target) {
3819
- if (target === "tanstack-query") return tanstackQueryComposables;
3820
- if (target === "vue-query") return vueQueryComposables;
3821
- return void 0;
4132
+ return target && target in COMPOSABLES_BY_TARGET ? COMPOSABLES_BY_TARGET[target] : void 0;
3822
4133
  }
3823
- function createQueryComposables(queryModule) {
4134
+ function createQueryComposables(target) {
4135
+ const profile = typeof target === "string" ? QUERY_PROFILES[target] : target;
3824
4136
  return {
3825
- list: (resourceName2, resource) => listHook(queryModule, resourceName2, resource),
3826
- detail: (resourceName2, resource) => detailHook(queryModule, resourceName2, resource),
3827
- createMutation: (resourceName2, resource) => createMutationHook(queryModule, resourceName2, resource),
3828
- updateMutation: (resourceName2, resource) => updateMutationHook(queryModule, resourceName2, resource),
3829
- deleteMutation: (resourceName2, resource) => deleteMutationHook(queryModule, resourceName2, resource),
3830
- operation: (resourceName2, operation) => operationHook(queryModule, resourceName2, operation)
4137
+ list: (resourceName2, resource) => listHook(profile, resourceName2, resource),
4138
+ detail: (resourceName2, resource) => detailHook(profile, resourceName2, resource),
4139
+ createMutation: (resourceName2, resource) => createMutationHook(profile, resourceName2, resource),
4140
+ updateMutation: (resourceName2, resource) => updateMutationHook(profile, resourceName2, resource),
4141
+ deleteMutation: (resourceName2, resource) => deleteMutationHook(profile, resourceName2, resource),
4142
+ operation: (resourceName2, operation) => operationHook(profile, resourceName2, operation)
3831
4143
  };
3832
4144
  }
3833
4145
  function importPaths(resourceName2) {
@@ -3846,57 +4158,70 @@ function missingHook(name) {
3846
4158
  }
3847
4159
  `;
3848
4160
  }
4161
+ function callQuery(profile, hook, optionsLiteral) {
4162
+ return profile.optionsAsFunction ? `${hook}(() => (${optionsLiteral}))` : `${hook}(${optionsLiteral})`;
4163
+ }
3849
4164
  function invalidateOnSuccess(invalidation, usesVariables) {
3850
4165
  const signature = usesVariables ? "(_data, variables)" : "()";
3851
4166
  return `onSuccess: ${signature} => {
3852
4167
  queryClient.invalidateQueries({ queryKey: ${invalidation} })
3853
4168
  },`;
3854
4169
  }
3855
- function listHook(queryModule, resourceName2, resource) {
4170
+ function listHook(profile, resourceName2, resource) {
3856
4171
  const collection = pluralizeTypeName(resource.entity);
3857
4172
  if (!resource.operations.list?.id) return missingHook(`use${collection}Query`);
3858
4173
  const names = createResourceTypeNames(resource);
3859
4174
  const paths = importPaths(resourceName2);
3860
4175
  const requiresParams = getPathParams(resource.operations.list).length > 0;
3861
- return `import { useQuery, type UseQueryOptions } from '${queryModule}'
4176
+ const call = callQuery(
4177
+ profile,
4178
+ profile.queryHook,
4179
+ `{
4180
+ queryKey: ${keysName(resourceName2)}.list(params),
4181
+ queryFn: () => ${clientName(resourceName2)}.${resource.operations.list.id}(params),
4182
+ ...options,
4183
+ }`
4184
+ );
4185
+ return `import { ${profile.queryHook}, type ${profile.queryOptions} } from '${profile.module}'
3862
4186
  import { ${clientName(resourceName2)} } from '${paths.client}'
3863
4187
  import { ${keysName(resourceName2)} } from '${paths.keys}'
3864
4188
  import type { ${names.listParamsType}, ${names.listResponseType} } from '${paths.types}'
3865
4189
 
3866
4190
  export function use${collection}Query(
3867
4191
  params${requiresParams ? "" : "?"}: ${names.listParamsType},
3868
- options?: Omit<UseQueryOptions<${names.listResponseType}>, 'queryKey' | 'queryFn'>,
4192
+ options?: Omit<${profile.queryOptions}<${names.listResponseType}>, 'queryKey' | 'queryFn'>,
3869
4193
  ) {
3870
- return useQuery({
3871
- queryKey: ${keysName(resourceName2)}.list(params),
3872
- queryFn: () => ${clientName(resourceName2)}.${resource.operations.list.id}(params),
3873
- ...options,
3874
- })
4194
+ return ${call}
3875
4195
  }
3876
4196
  `;
3877
4197
  }
3878
- function detailHook(queryModule, resourceName2, resource) {
4198
+ function detailHook(profile, resourceName2, resource) {
3879
4199
  if (!resource.operations.detail?.id) return missingHook(`use${resource.entity}Query`);
3880
4200
  const names = createResourceTypeNames(resource);
3881
4201
  const paths = importPaths(resourceName2);
3882
- return `import { useQuery, type UseQueryOptions } from '${queryModule}'
4202
+ const call = callQuery(
4203
+ profile,
4204
+ profile.queryHook,
4205
+ `{
4206
+ queryKey: ${keysName(resourceName2)}.detail(id),
4207
+ queryFn: () => ${clientName(resourceName2)}.${resource.operations.detail.id}(id),
4208
+ ...options,
4209
+ }`
4210
+ );
4211
+ return `import { ${profile.queryHook}, type ${profile.queryOptions} } from '${profile.module}'
3883
4212
  import { ${clientName(resourceName2)} } from '${paths.client}'
3884
4213
  import { ${keysName(resourceName2)} } from '${paths.keys}'
3885
4214
  import type { ${names.detailResponseType}, ${names.idType} } from '${paths.types}'
3886
4215
 
3887
4216
  export function use${resource.entity}Query(
3888
4217
  id: ${names.idType},
3889
- options?: Omit<UseQueryOptions<${names.detailResponseType}>, 'queryKey' | 'queryFn'>,
4218
+ options?: Omit<${profile.queryOptions}<${names.detailResponseType}>, 'queryKey' | 'queryFn'>,
3890
4219
  ) {
3891
- return useQuery({
3892
- queryKey: ${keysName(resourceName2)}.detail(id),
3893
- queryFn: () => ${clientName(resourceName2)}.${resource.operations.detail.id}(id),
3894
- ...options,
3895
- })
4220
+ return ${call}
3896
4221
  }
3897
4222
  `;
3898
4223
  }
3899
- function createMutationHook(queryModule, resourceName2, resource) {
4224
+ function createMutationHook(profile, resourceName2, resource) {
3900
4225
  if (!resource.operations.create?.id) return missingHook(`useCreate${resource.entity}Mutation`);
3901
4226
  const names = createResourceTypeNames(resource);
3902
4227
  const paths = importPaths(resourceName2);
@@ -3905,70 +4230,84 @@ function createMutationHook(queryModule, resourceName2, resource) {
3905
4230
  const callArgs = hasPathParams ? "input.params, input.payload" : "input";
3906
4231
  const invalidation = hasPathParams ? `${keysName(resourceName2)}.list(variables.params)` : `${keysName(resourceName2)}.list()`;
3907
4232
  const typeImports = hasPathParams ? `${names.createRequestType}, ${names.createResponseType}, ${names.listParamsType}` : `${names.createRequestType}, ${names.createResponseType}`;
3908
- return `import { useMutation, useQueryClient, type UseMutationOptions } from '${queryModule}'
4233
+ const call = callQuery(
4234
+ profile,
4235
+ profile.mutationHook,
4236
+ `{
4237
+ mutationFn: (input: ${inputType}) => ${clientName(resourceName2)}.${resource.operations.create.id}(${callArgs}),
4238
+ ${invalidateOnSuccess(invalidation, hasPathParams)}
4239
+ ...options,
4240
+ }`
4241
+ );
4242
+ return `import { ${profile.mutationHook}, ${profile.queryClientHook}, type ${profile.mutationOptions} } from '${profile.module}'
3909
4243
  import { ${clientName(resourceName2)} } from '${paths.client}'
3910
4244
  import { ${keysName(resourceName2)} } from '${paths.keys}'
3911
4245
  import type { ${typeImports} } from '${paths.types}'
3912
4246
 
3913
4247
  export function useCreate${resource.entity}Mutation(
3914
- options?: Omit<UseMutationOptions<${names.createResponseType}, Error, ${inputType}>, 'mutationFn'>,
4248
+ options?: Omit<${profile.mutationOptions}<${names.createResponseType}, Error, ${inputType}>, 'mutationFn'>,
3915
4249
  ) {
3916
- const queryClient = useQueryClient()
3917
- return useMutation({
3918
- mutationFn: (input: ${inputType}) => ${clientName(resourceName2)}.${resource.operations.create.id}(${callArgs}),
3919
- ${invalidateOnSuccess(invalidation, hasPathParams)}
3920
- ...options,
3921
- })
4250
+ const queryClient = ${profile.queryClientHook}()
4251
+ return ${call}
3922
4252
  }
3923
4253
  `;
3924
4254
  }
3925
- function updateMutationHook(queryModule, resourceName2, resource) {
4255
+ function updateMutationHook(profile, resourceName2, resource) {
3926
4256
  if (!resource.operations.update?.id) return missingHook(`useUpdate${resource.entity}Mutation`);
3927
4257
  const names = createResourceTypeNames(resource);
3928
4258
  const paths = importPaths(resourceName2);
3929
4259
  const inputType = `{ id: ${names.idType}; payload: ${names.updateRequestType} }`;
3930
- return `import { useMutation, useQueryClient, type UseMutationOptions } from '${queryModule}'
4260
+ const call = callQuery(
4261
+ profile,
4262
+ profile.mutationHook,
4263
+ `{
4264
+ mutationFn: (input: ${inputType}) => ${clientName(resourceName2)}.${resource.operations.update.id}(input.id, input.payload),
4265
+ ${invalidateOnSuccess(`${keysName(resourceName2)}.detail(variables.id)`, true)}
4266
+ ...options,
4267
+ }`
4268
+ );
4269
+ return `import { ${profile.mutationHook}, ${profile.queryClientHook}, type ${profile.mutationOptions} } from '${profile.module}'
3931
4270
  import { ${clientName(resourceName2)} } from '${paths.client}'
3932
4271
  import { ${keysName(resourceName2)} } from '${paths.keys}'
3933
4272
  import type { ${names.idType}, ${names.updateRequestType}, ${names.updateResponseType} } from '${paths.types}'
3934
4273
 
3935
4274
  export function useUpdate${resource.entity}Mutation(
3936
- options?: Omit<UseMutationOptions<${names.updateResponseType}, Error, ${inputType}>, 'mutationFn'>,
4275
+ options?: Omit<${profile.mutationOptions}<${names.updateResponseType}, Error, ${inputType}>, 'mutationFn'>,
3937
4276
  ) {
3938
- const queryClient = useQueryClient()
3939
- return useMutation({
3940
- mutationFn: (input: ${inputType}) => ${clientName(resourceName2)}.${resource.operations.update.id}(input.id, input.payload),
3941
- ${invalidateOnSuccess(`${keysName(resourceName2)}.detail(variables.id)`, true)}
3942
- ...options,
3943
- })
4277
+ const queryClient = ${profile.queryClientHook}()
4278
+ return ${call}
3944
4279
  }
3945
4280
  `;
3946
4281
  }
3947
- function deleteMutationHook(queryModule, resourceName2, resource) {
4282
+ function deleteMutationHook(profile, resourceName2, resource) {
3948
4283
  if (!resource.operations.delete?.id) return missingHook(`useDelete${resource.entity}Mutation`);
3949
4284
  const names = createResourceTypeNames(resource);
3950
4285
  const paths = importPaths(resourceName2);
3951
4286
  const invalidatesDetail = getPathParams(resource.operations.delete).length > 1;
3952
4287
  const invalidation = invalidatesDetail ? `${keysName(resourceName2)}.detail(variables)` : `${keysName(resourceName2)}.list()`;
3953
- const onSuccess = invalidateOnSuccess(invalidation, invalidatesDetail);
3954
- return `import { useMutation, useQueryClient, type UseMutationOptions } from '${queryModule}'
4288
+ const call = callQuery(
4289
+ profile,
4290
+ profile.mutationHook,
4291
+ `{
4292
+ mutationFn: (id: ${names.idType}) => ${clientName(resourceName2)}.${resource.operations.delete.id}(id),
4293
+ ${invalidateOnSuccess(invalidation, invalidatesDetail)}
4294
+ ...options,
4295
+ }`
4296
+ );
4297
+ return `import { ${profile.mutationHook}, ${profile.queryClientHook}, type ${profile.mutationOptions} } from '${profile.module}'
3955
4298
  import { ${clientName(resourceName2)} } from '${paths.client}'
3956
4299
  import { ${keysName(resourceName2)} } from '${paths.keys}'
3957
4300
  import type { ${names.idType} } from '${paths.types}'
3958
4301
 
3959
4302
  export function useDelete${resource.entity}Mutation(
3960
- options?: Omit<UseMutationOptions<void, Error, ${names.idType}>, 'mutationFn'>,
4303
+ options?: Omit<${profile.mutationOptions}<void, Error, ${names.idType}>, 'mutationFn'>,
3961
4304
  ) {
3962
- const queryClient = useQueryClient()
3963
- return useMutation({
3964
- mutationFn: (id: ${names.idType}) => ${clientName(resourceName2)}.${resource.operations.delete.id}(id),
3965
- ${onSuccess}
3966
- ...options,
3967
- })
4305
+ const queryClient = ${profile.queryClientHook}()
4306
+ return ${call}
3968
4307
  }
3969
4308
  `;
3970
4309
  }
3971
- function operationHook(queryModule, resourceName2, operation) {
4310
+ function operationHook(profile, resourceName2, operation) {
3972
4311
  const names = createOperationTypeNames(operation);
3973
4312
  const hookName = operationComposableName(operation);
3974
4313
  const paths = importPaths(resourceName2);
@@ -3985,34 +4324,44 @@ function operationHook(queryModule, resourceName2, operation) {
3985
4324
  ].join(", ");
3986
4325
  if (isQuery) {
3987
4326
  const keyEntries = hasInput ? `[...${keysName(resourceName2)}.all, '${operation.id}', input]` : `[...${keysName(resourceName2)}.all, '${operation.id}']`;
3988
- return `import { useQuery, type UseQueryOptions } from '${queryModule}'
4327
+ const call2 = callQuery(
4328
+ profile,
4329
+ profile.queryHook,
4330
+ `{
4331
+ queryKey: ${keyEntries},
4332
+ queryFn: () => ${clientName(resourceName2)}.${operation.id}(${callArgs}),
4333
+ ...options,
4334
+ }`
4335
+ );
4336
+ return `import { ${profile.queryHook}, type ${profile.queryOptions} } from '${profile.module}'
3989
4337
  import { ${clientName(resourceName2)} } from '${paths.client}'
3990
4338
  import { ${keysName(resourceName2)} } from '${paths.keys}'
3991
4339
  import type { ${typeImports} } from '${paths.types}'
3992
4340
 
3993
4341
  export function ${hookName}(
3994
4342
  ${hasInput ? ` input: ${inputType},
3995
- ` : ""} options?: Omit<UseQueryOptions<${names.responseType}>, 'queryKey' | 'queryFn'>,
4343
+ ` : ""} options?: Omit<${profile.queryOptions}<${names.responseType}>, 'queryKey' | 'queryFn'>,
3996
4344
  ) {
3997
- return useQuery({
3998
- queryKey: ${keyEntries},
3999
- queryFn: () => ${clientName(resourceName2)}.${operation.id}(${callArgs}),
4000
- ...options,
4001
- })
4345
+ return ${call2}
4002
4346
  }
4003
4347
  `;
4004
4348
  }
4005
- return `import { useMutation, type UseMutationOptions } from '${queryModule}'
4349
+ const call = callQuery(
4350
+ profile,
4351
+ profile.mutationHook,
4352
+ `{
4353
+ mutationFn: (${hasInput ? `input: ${inputType}` : ""}) => ${clientName(resourceName2)}.${operation.id}(${callArgs}),
4354
+ ...options,
4355
+ }`
4356
+ );
4357
+ return `import { ${profile.mutationHook}, type ${profile.mutationOptions} } from '${profile.module}'
4006
4358
  import { ${clientName(resourceName2)} } from '${paths.client}'
4007
4359
  import type { ${typeImports} } from '${paths.types}'
4008
4360
 
4009
4361
  export function ${hookName}(
4010
- options?: Omit<UseMutationOptions<${names.responseType}, Error, ${inputType}>, 'mutationFn'>,
4362
+ options?: Omit<${profile.mutationOptions}<${names.responseType}, Error, ${inputType}>, 'mutationFn'>,
4011
4363
  ) {
4012
- return useMutation({
4013
- mutationFn: (${hasInput ? `input: ${inputType}` : ""}) => ${clientName(resourceName2)}.${operation.id}(${callArgs}),
4014
- ...options,
4015
- })
4364
+ return ${call}
4016
4365
  }
4017
4366
  `;
4018
4367
  }
package/dist/index.js CHANGED
@@ -29,7 +29,7 @@ import {
29
29
  summarizeFilePlan,
30
30
  writeGeneratedFiles,
31
31
  writeReportFile
32
- } from "./chunk-RL23SDLM.js";
32
+ } from "./chunk-FSB2T27T.js";
33
33
 
34
34
  // src/index.ts
35
35
  import { cac } from "cac";
package/dist/internal.js CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  scanSourceUsages,
20
20
  validateLicenseKey,
21
21
  writeReportFile
22
- } from "./chunk-RL23SDLM.js";
22
+ } from "./chunk-FSB2T27T.js";
23
23
 
24
24
  // src/git-base-schema.ts
25
25
  import { execFile } from "child_process";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archora/forge-cli",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "CLI for generating typed frontend resource contracts from OpenAPI contracts.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -44,9 +44,9 @@
44
44
  "@types/node": "^22.15.17",
45
45
  "tsup": "^8.4.0",
46
46
  "typescript": "^5.8.3",
47
- "@archora/forge-adapters": "2.0.0",
48
- "@archora/forge-config": "2.0.0",
49
- "@archora/forge-core": "2.0.0"
47
+ "@archora/forge-adapters": "2.1.0",
48
+ "@archora/forge-config": "2.1.0",
49
+ "@archora/forge-core": "2.1.0"
50
50
  },
51
51
  "scripts": {
52
52
  "build": "tsup",