@workos/oagen 0.16.0 → 0.17.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
@@ -208,17 +208,64 @@ function extractInlineModelsFromProperties(schema, results, parentName) {
208
208
  }
209
209
  }
210
210
  if (fieldSchema.oneOf) {
211
- const objectVariant = fieldSchema.oneOf.find((v) => v.properties && (v.type === "object" || !v.type));
212
- if (objectVariant) {
211
+ const inlineObjectVariants = fieldSchema.oneOf.filter((v) => !v.$ref && v.properties && (v.type === "object" || !v.type));
212
+ if (inlineObjectVariants.length > 0) {
213
213
  const modelName = qualifyInlineModelName(toPascalCase(fieldName), parentName);
214
- if (!new Set(results.map((r) => r.name)).has(modelName)) {
215
- results.push(buildInlineModel(modelName, objectVariant));
216
- extractInlineModelsFromProperties(objectVariant, results, modelName);
214
+ const existingNames = new Set(results.map((r) => r.name));
215
+ const namingDiscProp = deriveOneOfNamingDiscriminator(inlineObjectVariants);
216
+ const emittedNames = [];
217
+ for (const variant of inlineObjectVariants) {
218
+ const variantName = nameOneOfVariant(variant, modelName, emittedNames, namingDiscProp);
219
+ emittedNames.push(variantName);
220
+ if (!existingNames.has(variantName)) {
221
+ existingNames.add(variantName);
222
+ results.push(buildInlineModel(variantName, variant));
223
+ extractInlineModelsFromProperties(variant, results, variantName);
224
+ }
217
225
  }
218
226
  }
219
227
  }
220
228
  }
221
229
  }
230
+ /** Find a single string-const-valued property shared by every variant whose
231
+ * values are all distinct — the implicit discriminator. Returns null when no
232
+ * such property exists. Mirrors `deriveConstNamingDiscriminator` in schemas.ts. */
233
+ function deriveOneOfNamingDiscriminator(variants) {
234
+ if (variants.length < 2) return null;
235
+ const candidates = Object.keys(variants[0]?.properties ?? {});
236
+ for (const propName of candidates) {
237
+ const values = variants.map((v) => readConstString(v.properties?.[propName]));
238
+ if (values.some((v) => v === null)) continue;
239
+ if (new Set(values).size !== values.length) continue;
240
+ return propName;
241
+ }
242
+ return null;
243
+ }
244
+ function readConstString(prop) {
245
+ if (!prop) return null;
246
+ if (typeof prop.const === "string") return prop.const;
247
+ if (Array.isArray(prop.enum) && prop.enum.length === 1 && typeof prop.enum[0] === "string") return prop.enum[0];
248
+ return null;
249
+ }
250
+ /** Produce a per-variant model name. Variant 0 keeps the bare parent name;
251
+ * later variants are prefixed by the const-derived label. Mirrors
252
+ * `nameVariantModel` in schemas.ts. Falls back to a numeric suffix when no
253
+ * discriminator is available, the const value PascalCases to nothing, or
254
+ * the derived candidate collides with the parent or an already-emitted name. */
255
+ function nameOneOfVariant(variant, parentName, alreadyEmitted, discriminatorProperty) {
256
+ if (alreadyEmitted.length === 0) return parentName;
257
+ if (discriminatorProperty) {
258
+ const constValue = readConstString(variant.properties?.[discriminatorProperty]);
259
+ if (constValue) {
260
+ const prefix = toPascalCase(constValue);
261
+ if (prefix) {
262
+ const candidate = parentName.startsWith(prefix) ? parentName : `${prefix}${parentName}`;
263
+ if (!(candidate === parentName || alreadyEmitted.includes(candidate))) return candidate;
264
+ }
265
+ }
266
+ }
267
+ return `${parentName}${alreadyEmitted.length + 1}`;
268
+ }
222
269
  function buildInlineModel(name, schema) {
223
270
  const requiredSet = new Set(schema.required ?? []);
224
271
  const fields = [];
@@ -585,6 +632,51 @@ function resolveVariantModelName(schema) {
585
632
  if (title) return resolveSchemaName(title);
586
633
  return null;
587
634
  }
635
+ /** Look up the discriminator's mapped variant name for an inline object
636
+ * variant by reading its const-valued discriminator property. Returns null
637
+ * when the variant doesn't pin the property to a string const, or when
638
+ * the value isn't in the mapping. */
639
+ function mapVariantToDiscriminatorEntry(variant, discriminator) {
640
+ const value = getConstPropertyValue(variant, discriminator.property);
641
+ if (value === null) return null;
642
+ return discriminator.mapping[value] ?? null;
643
+ }
644
+ /**
645
+ * Derive a discriminator value → variant model name mapping for an inline-
646
+ * variant `oneOf` whose explicit `discriminator:` has no `mapping:` clause.
647
+ *
648
+ * Each variant model name is reproduced via `nameVariantModel` so it matches
649
+ * what `extractNestedSchema` registered when it walked the same `oneOf` to
650
+ * pull variants out as named models. Variant 0 keeps the bare parent name
651
+ * (e.g. `ApiKeyOwner`); subsequent variants get a const-derived prefix
652
+ * (e.g. `UserApiKeyOwner` for the `type: const: user` variant).
653
+ *
654
+ * The "parent name" used here mirrors the one `collectNestedInlineModels`
655
+ * passes to `extractNestedSchema`: it's the qualified inline model name
656
+ * built from `contextName` (the field name) plus `parentModelName` (the
657
+ * containing model). Returns null when any non-object variant is present
658
+ * or when a variant lacks a const value on the discriminator property.
659
+ */
660
+ function deriveInlineVariantMapping(variants, discriminatorProperty, contextName, parentModelName) {
661
+ if (variants.length === 0 || !contextName) return null;
662
+ const inlineObjectVariants = variants.filter((v) => !v.$ref && v.properties && (v.type === "object" || !v.type));
663
+ if (inlineObjectVariants.length !== variants.length) return null;
664
+ const baseInlineName = qualifyInlineModelName(toPascalCase(contextName), parentModelName);
665
+ const namingDiscriminator = { property: discriminatorProperty };
666
+ const mapping = {};
667
+ const emittedSoFar = [];
668
+ for (const variant of inlineObjectVariants) {
669
+ const constValue = getConstPropertyValue(variant, discriminatorProperty);
670
+ if (constValue === null) return null;
671
+ const variantName = nameVariantModel(variant, baseInlineName, emittedSoFar, namingDiscriminator);
672
+ mapping[constValue] = variantName;
673
+ emittedSoFar.push({
674
+ name: variantName,
675
+ fields: []
676
+ });
677
+ }
678
+ return mapping;
679
+ }
588
680
  function extractEnum(name, schema) {
589
681
  return {
590
682
  name,
@@ -783,8 +875,20 @@ function extractInlineModelDeep(name, schema) {
783
875
  }));
784
876
  }
785
877
  if (fieldSchema.oneOf) {
786
- const objectVariant = fieldSchema.oneOf.find((v) => v.properties && (v.type === "object" || !v.type));
787
- if (objectVariant) nestedModels.push(...extractInlineModelDeep(qualifyNestedInlineName(name, fieldName), objectVariant));
878
+ const inlineObjectVariants = fieldSchema.oneOf.filter((v) => !v.$ref && v.properties && (v.type === "object" || !v.type));
879
+ if (inlineObjectVariants.length > 0) {
880
+ const baseQualified = qualifyNestedInlineName(name, fieldName);
881
+ const namingDisc = deriveConstNamingDiscriminator(inlineObjectVariants);
882
+ const emitted = [];
883
+ for (const variant of inlineObjectVariants) {
884
+ const variantName = nameVariantModel(variant, baseQualified, emitted, namingDisc);
885
+ emitted.push({
886
+ name: variantName,
887
+ fields: []
888
+ });
889
+ nestedModels.push(...extractInlineModelDeep(variantName, variant));
890
+ }
891
+ }
788
892
  }
789
893
  }
790
894
  return [{
@@ -881,17 +985,49 @@ function schemaToTypeRef(schema, contextName, parentModelName) {
881
985
  inner: enumRef
882
986
  } : enumRef;
883
987
  }
884
- const syntheticDiscriminator = !schema.discriminator && compositionKind === "oneOf" ? detectConstPropertyDiscriminator(nonNullVariants) : null;
885
- const variants = rawVariants.filter((v) => v.type !== "null").map((v) => schemaToTypeRef(v, contextName, parentModelName));
988
+ let resolvedDiscriminator;
989
+ if (schema.discriminator) {
990
+ const property = schema.discriminator.propertyName;
991
+ const explicitMapping = schema.discriminator.mapping ?? {};
992
+ let mapping = Object.fromEntries(Object.entries(explicitMapping).map(([k, v]) => [k, v.replace(/^#\/components\/schemas\//, "")]));
993
+ if (Object.keys(mapping).length === 0) {
994
+ const inlineMapping = deriveInlineVariantMapping(nonNullVariants, property, contextName, parentModelName);
995
+ if (inlineMapping) mapping = inlineMapping;
996
+ }
997
+ resolvedDiscriminator = {
998
+ property,
999
+ mapping
1000
+ };
1001
+ } else if (compositionKind === "oneOf") {
1002
+ const synthetic = detectConstPropertyDiscriminator(nonNullVariants);
1003
+ if (synthetic) resolvedDiscriminator = synthetic;
1004
+ else {
1005
+ const namingDisc = deriveConstNamingDiscriminator(nonNullVariants);
1006
+ if (namingDisc) {
1007
+ const inlineMapping = deriveInlineVariantMapping(nonNullVariants, namingDisc.property, contextName, parentModelName);
1008
+ if (inlineMapping) resolvedDiscriminator = {
1009
+ property: namingDisc.property,
1010
+ mapping: inlineMapping
1011
+ };
1012
+ }
1013
+ }
1014
+ }
1015
+ const variants = [];
1016
+ for (const v of rawVariants) {
1017
+ if (v === nullVariant) continue;
1018
+ const inlineMappingName = resolvedDiscriminator && !v.$ref && v.properties && (v.type === "object" || !v.type) ? mapVariantToDiscriminatorEntry(v, resolvedDiscriminator) : null;
1019
+ if (inlineMappingName) variants.push({
1020
+ kind: "model",
1021
+ name: inlineMappingName
1022
+ });
1023
+ else variants.push(schemaToTypeRef(v, contextName, parentModelName));
1024
+ }
886
1025
  const hasNull = !!nullVariant;
887
1026
  const union = {
888
1027
  kind: "union",
889
1028
  variants,
890
1029
  compositionKind,
891
- ...schema.discriminator ? { discriminator: {
892
- property: schema.discriminator.propertyName,
893
- mapping: Object.fromEntries(Object.entries(schema.discriminator.mapping ?? {}).map(([k, v]) => [k, v.replace(/^#\/components\/schemas\//, "")]))
894
- } } : syntheticDiscriminator ? { discriminator: syntheticDiscriminator } : {}
1030
+ ...resolvedDiscriminator ? { discriminator: resolvedDiscriminator } : {}
895
1031
  };
896
1032
  return hasNull ? {
897
1033
  kind: "nullable",