@workos/oagen 0.24.3 → 0.24.5

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.
@@ -252,6 +252,28 @@ function walkTypeRef(ref, visitor) {
252
252
  default: assertNever(ref);
253
253
  }
254
254
  }
255
+ /**
256
+ * Validate model-placement hints against the FULL spec. Run once at the start of
257
+ * generation — where the complete model/service universe is known — so typos in
258
+ * `modelHints` fail loud even though {@link assignModelsToServices} is
259
+ * intentionally lenient about the partial slices it may be handed.
260
+ */
261
+ function validateModelHints(models, services, hints) {
262
+ const modelNames = new Set(models.map((m) => m.name));
263
+ const serviceNames = new Set(services.map((s) => s.name));
264
+ for (const [modelName, serviceName] of Object.entries(hints)) {
265
+ if (!modelNames.has(modelName)) {
266
+ const err = /* @__PURE__ */ new Error(`modelHints: unknown model "${modelName}". Hint keys must match an IR model name (post-cleanSchemaName/schemaNameTransform).`);
267
+ err.name = "ConfigError";
268
+ throw err;
269
+ }
270
+ if (!serviceNames.has(serviceName)) {
271
+ const err = /* @__PURE__ */ new Error(`modelHints: unknown service "${serviceName}" for model "${modelName}". Hint values must match an IR service name (PascalCase, derived from the operation's first tag).`);
272
+ err.name = "ConfigError";
273
+ throw err;
274
+ }
275
+ }
276
+ }
255
277
  //#endregion
256
278
  //#region src/utils/naming.ts
257
279
  /**
@@ -1592,7 +1614,7 @@ function detectPagination(response, queryParams, dataPath) {
1592
1614
  }
1593
1615
  //#endregion
1594
1616
  //#region src/parser/responses.ts
1595
- function classifyAndExtractResponse(schema, contextName) {
1617
+ function classifyAndExtractResponse(schema, contextName, componentSchemas) {
1596
1618
  if (schema.$ref) return {
1597
1619
  response: schemaToTypeRef(schema, contextName),
1598
1620
  inlineModels: [],
@@ -1601,7 +1623,7 @@ function classifyAndExtractResponse(schema, contextName) {
1601
1623
  const envelope = detectListEnvelope(schema);
1602
1624
  if (envelope.isEnvelope) return extractListResponse(schema, contextName, envelope.dataPath);
1603
1625
  if (isSingleResourceWrapper(schema)) return extractWrappedResource(schema, contextName);
1604
- return extractDirectResource(schema, contextName);
1626
+ return extractDirectResource(schema, contextName, componentSchemas);
1605
1627
  }
1606
1628
  const KNOWN_DATA_PATHS = new Set([
1607
1629
  "data",
@@ -1778,7 +1800,7 @@ function extractWrappedResource(schema, contextName) {
1778
1800
  isPaginated: false
1779
1801
  };
1780
1802
  }
1781
- function extractDirectResource(schema, contextName) {
1803
+ function extractDirectResource(schema, contextName, componentSchemas) {
1782
1804
  const inlineModels = [];
1783
1805
  if (schema.properties && (schema.type === "object" || !schema.type)) {
1784
1806
  const modelName = deriveModelName(schema, contextName);
@@ -1795,17 +1817,43 @@ function extractDirectResource(schema, contextName) {
1795
1817
  if (schema.allOf) {
1796
1818
  const mergedProperties = {};
1797
1819
  const mergedRequired = [];
1798
- for (const sub of schema.allOf) {
1799
- if (sub.properties) Object.assign(mergedProperties, sub.properties);
1800
- if (sub.required) mergedRequired.push(...sub.required);
1801
- }
1820
+ const unresolvedRefs = [];
1821
+ const seenRefs = /* @__PURE__ */ new Set();
1822
+ let mergedRefFields = false;
1823
+ const mergeSubSchemas = (subs) => {
1824
+ for (const sub of subs) {
1825
+ let resolved = sub;
1826
+ if (sub.$ref) {
1827
+ if (seenRefs.has(sub.$ref)) continue;
1828
+ seenRefs.add(sub.$ref);
1829
+ const segments = sub.$ref.split("/");
1830
+ const refName = segments[segments.length - 1];
1831
+ const refSchema = refName ? componentSchemas?.[refName] : void 0;
1832
+ if (!refSchema) {
1833
+ unresolvedRefs.push(sub.$ref);
1834
+ continue;
1835
+ }
1836
+ resolved = refSchema;
1837
+ mergedRefFields = true;
1838
+ }
1839
+ if (resolved.allOf) mergeSubSchemas(resolved.allOf);
1840
+ if (resolved.properties) Object.assign(mergedProperties, resolved.properties);
1841
+ if (resolved.required) mergedRequired.push(...resolved.required);
1842
+ }
1843
+ };
1844
+ mergeSubSchemas(schema.allOf);
1845
+ if (unresolvedRefs.length === 1 && !mergedRefFields) return {
1846
+ response: schemaToTypeRef({ $ref: unresolvedRefs[0] }, contextName),
1847
+ inlineModels,
1848
+ isPaginated: false
1849
+ };
1802
1850
  if (Object.keys(mergedProperties).length > 0) {
1803
1851
  const merged = {
1804
1852
  type: "object",
1805
1853
  properties: mergedProperties,
1806
1854
  required: mergedRequired
1807
1855
  };
1808
- const modelName = deriveModelName(merged, contextName);
1856
+ const modelName = mergedRefFields ? toPascalCase(contextName) : deriveModelName(merged, contextName);
1809
1857
  inlineModels.push(...extractInlineModel(modelName, merged));
1810
1858
  return {
1811
1859
  response: {
@@ -2208,7 +2256,7 @@ function buildOperation(method, path, op, pathLevelParams, operationIdTransform,
2208
2256
  const cookieParams = extractParams(allParams, "cookie", opContext, componentSchemas);
2209
2257
  const reqBodyModels = [];
2210
2258
  const { body: requestBody, encoding: requestBodyEncoding } = extractRequestBody(op.requestBody, op, reqBodyModels);
2211
- const { response, successResponses, errors, inlineModels, isPaginated, dataPath: responseDataPath, itemType: responseItemType } = extractResponses(op.responses, op, path, method);
2259
+ const { response, successResponses, errors, inlineModels, isPaginated, dataPath: responseDataPath, itemType: responseItemType } = extractResponses(op.responses, op, path, method, componentSchemas);
2212
2260
  inlineModels.push(...reqBodyModels);
2213
2261
  const paginationFromParams = detectPagination(response, queryParams, responseDataPath);
2214
2262
  let pagination;
@@ -2501,7 +2549,7 @@ function deriveResponseName(op, path, method) {
2501
2549
  if (op?.operationId) return cleanSchemaName(toPascalCase(normalizeOperationIdForNaming(op.operationId)) + "Response");
2502
2550
  return toPascalCase(path.split("/").filter(Boolean)[0] ?? "Unknown") + toPascalCase(method) + "Response";
2503
2551
  }
2504
- function extractResponses(responses, op, path, method) {
2552
+ function extractResponses(responses, op, path, method, componentSchemas) {
2505
2553
  const errors = [];
2506
2554
  let response = {
2507
2555
  kind: "primitive",
@@ -2528,7 +2576,7 @@ function extractResponses(responses, op, path, method) {
2528
2576
  const jsonContent = resp.content?.["application/json"];
2529
2577
  if (jsonContent?.schema) {
2530
2578
  const contextName = deriveResponseName(op, path ?? "/", method ?? "get");
2531
- const result = classifyAndExtractResponse(jsonContent.schema, contextName);
2579
+ const result = classifyAndExtractResponse(jsonContent.schema, contextName, componentSchemas);
2532
2580
  extractedType = result.response;
2533
2581
  inlineModels = result.inlineModels;
2534
2582
  isPaginated = result.isPaginated;
@@ -6425,6 +6473,7 @@ async function removeEmptyParents(startDir, stopDir) {
6425
6473
  //#endregion
6426
6474
  //#region src/engine/orchestrator.ts
6427
6475
  async function generate(spec, emitter, options) {
6476
+ if (options.modelHints) validateModelHints(spec.models, spec.services, options.modelHints);
6428
6477
  const scopedServices = options.services && options.services.length > 0 ? resolveScopedServices(spec, options.services, options.mountRules) : void 0;
6429
6478
  const scoped = scopedServices !== void 0;
6430
6479
  const noPrune = options.noPrune === true || scoped;