@workos/oagen 0.15.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/smoke.mjs CHANGED
@@ -405,17 +405,64 @@ function extractInlineModelsFromProperties(schema, results, parentName) {
405
405
  }
406
406
  }
407
407
  if (fieldSchema.oneOf) {
408
- const objectVariant = fieldSchema.oneOf.find((v) => v.properties && (v.type === "object" || !v.type));
409
- if (objectVariant) {
408
+ const inlineObjectVariants = fieldSchema.oneOf.filter((v) => !v.$ref && v.properties && (v.type === "object" || !v.type));
409
+ if (inlineObjectVariants.length > 0) {
410
410
  const modelName = qualifyInlineModelName(toPascalCase(fieldName), parentName);
411
- if (!new Set(results.map((r) => r.name)).has(modelName)) {
412
- results.push(buildInlineModel(modelName, objectVariant));
413
- extractInlineModelsFromProperties(objectVariant, results, modelName);
411
+ const existingNames = new Set(results.map((r) => r.name));
412
+ const namingDiscProp = deriveOneOfNamingDiscriminator(inlineObjectVariants);
413
+ const emittedNames = [];
414
+ for (const variant of inlineObjectVariants) {
415
+ const variantName = nameOneOfVariant(variant, modelName, emittedNames, namingDiscProp);
416
+ emittedNames.push(variantName);
417
+ if (!existingNames.has(variantName)) {
418
+ existingNames.add(variantName);
419
+ results.push(buildInlineModel(variantName, variant));
420
+ extractInlineModelsFromProperties(variant, results, variantName);
421
+ }
414
422
  }
415
423
  }
416
424
  }
417
425
  }
418
426
  }
427
+ /** Find a single string-const-valued property shared by every variant whose
428
+ * values are all distinct — the implicit discriminator. Returns null when no
429
+ * such property exists. Mirrors `deriveConstNamingDiscriminator` in schemas.ts. */
430
+ function deriveOneOfNamingDiscriminator(variants) {
431
+ if (variants.length < 2) return null;
432
+ const candidates = Object.keys(variants[0]?.properties ?? {});
433
+ for (const propName of candidates) {
434
+ const values = variants.map((v) => readConstString(v.properties?.[propName]));
435
+ if (values.some((v) => v === null)) continue;
436
+ if (new Set(values).size !== values.length) continue;
437
+ return propName;
438
+ }
439
+ return null;
440
+ }
441
+ function readConstString(prop) {
442
+ if (!prop) return null;
443
+ if (typeof prop.const === "string") return prop.const;
444
+ if (Array.isArray(prop.enum) && prop.enum.length === 1 && typeof prop.enum[0] === "string") return prop.enum[0];
445
+ return null;
446
+ }
447
+ /** Produce a per-variant model name. Variant 0 keeps the bare parent name;
448
+ * later variants are prefixed by the const-derived label. Mirrors
449
+ * `nameVariantModel` in schemas.ts. Falls back to a numeric suffix when no
450
+ * discriminator is available, the const value PascalCases to nothing, or
451
+ * the derived candidate collides with the parent or an already-emitted name. */
452
+ function nameOneOfVariant(variant, parentName, alreadyEmitted, discriminatorProperty) {
453
+ if (alreadyEmitted.length === 0) return parentName;
454
+ if (discriminatorProperty) {
455
+ const constValue = readConstString(variant.properties?.[discriminatorProperty]);
456
+ if (constValue) {
457
+ const prefix = toPascalCase(constValue);
458
+ if (prefix) {
459
+ const candidate = parentName.startsWith(prefix) ? parentName : `${prefix}${parentName}`;
460
+ if (!(candidate === parentName || alreadyEmitted.includes(candidate))) return candidate;
461
+ }
462
+ }
463
+ }
464
+ return `${parentName}${alreadyEmitted.length + 1}`;
465
+ }
419
466
  function buildInlineModel(name, schema) {
420
467
  const requiredSet = new Set(schema.required ?? []);
421
468
  const fields = [];
@@ -577,6 +624,7 @@ function findNestedFieldSchema(fieldName, parentSchema, schemas) {
577
624
  function extractNestedSchema(name, schema) {
578
625
  if (schema.oneOf) {
579
626
  const models = [];
627
+ const constNamingDiscriminator = deriveConstNamingDiscriminator(schema.oneOf.filter((v) => !v.$ref && v.properties && (v.type === "object" || !v.type)));
580
628
  for (const variant of schema.oneOf) {
581
629
  if (variant.$ref) continue;
582
630
  if (variant.properties && (variant.type === "object" || !variant.type)) {
@@ -586,7 +634,7 @@ function extractNestedSchema(name, schema) {
586
634
  if (!fs) continue;
587
635
  fields.push(buildFieldFromSchema(fn, fs, name, requiredSet));
588
636
  }
589
- const variantName = models.length === 0 ? name : `${name}${models.length + 1}`;
637
+ const variantName = nameVariantModel(variant, name, models, constNamingDiscriminator);
590
638
  models.push({
591
639
  name: variantName,
592
640
  description: variant.description,
@@ -624,6 +672,69 @@ function extractNestedSchema(name, schema) {
624
672
  }
625
673
  return [];
626
674
  }
675
+ /**
676
+ * Derive a meaningful name for a oneOf object variant past the first,
677
+ * falling back to the numeric-suffix scheme (`Foo2`, `Foo3`) when no
678
+ * better signal is available. The "better signal" is a const-valued
679
+ * discriminator property that distinguishes one variant from the others.
680
+ *
681
+ * For a schema like `ApiKey.owner` whose oneOf has two variants — one
682
+ * with `type: { const: "organization" }` and one with `type: { const:
683
+ * "user" }` — this returns `ApiKeyOwner` for the first variant (the
684
+ * baseline name that the union TypeRef will reference, so consumer-
685
+ * visible typed fields stay backward-compatible) and `UserApiKeyOwner`
686
+ * for the second instead of `ApiKeyOwner2`. SDK consumers reading the
687
+ * generated types can tell the variants apart at a glance.
688
+ *
689
+ * The first variant always keeps the bare parent name because
690
+ * `schemaToTypeRef` builds union variants whose model refs collapse to
691
+ * `qualifyInlineModelName(field, parent)` — so the first model name
692
+ * must match for the union's degenerate-collapse to land on a valid
693
+ * type. Renaming variant 0 would orphan it.
694
+ *
695
+ * Falls back to numeric suffix when:
696
+ * - The variant has no discriminator property (no shared const
697
+ * property across all object variants), OR
698
+ * - The const value PascalCases to an empty/whitespace name, OR
699
+ * - The derived name collides with the parent name or an already-
700
+ * emitted variant name (numeric keeps uniqueness).
701
+ */
702
+ function nameVariantModel(variant, parentName, alreadyEmitted, discriminator) {
703
+ if (alreadyEmitted.length === 0) return parentName;
704
+ if (discriminator) {
705
+ const constValue = getConstPropertyValue(variant, discriminator.property);
706
+ if (constValue) {
707
+ const prefix = toPascalCase(constValue);
708
+ if (prefix) {
709
+ const candidate = parentName.startsWith(prefix) ? parentName : `${prefix}${parentName}`;
710
+ if (!(candidate === parentName || alreadyEmitted.some((m) => m.name === candidate))) return candidate;
711
+ }
712
+ }
713
+ }
714
+ return `${parentName}${alreadyEmitted.length + 1}`;
715
+ }
716
+ /**
717
+ * Returns a discriminator descriptor when every inline object variant in a
718
+ * oneOf has a string-const value on the same property — the signature of a
719
+ * discriminated union without an explicit `discriminator:` keyword. Used by
720
+ * `nameVariantModel` to derive variant names from the const values.
721
+ *
722
+ * Distinct from `detectConstPropertyDiscriminator` (which requires every
723
+ * variant to carry a `$ref` or `title` so a name mapping can be built):
724
+ * this pass operates on inline anonymous variants, where naming is exactly
725
+ * what we're trying to derive.
726
+ */
727
+ function deriveConstNamingDiscriminator(variants) {
728
+ if (variants.length < 2) return null;
729
+ const candidates = Object.keys(variants[0]?.properties ?? {});
730
+ for (const propName of candidates) {
731
+ const values = variants.map((v) => getConstPropertyValue(v, propName));
732
+ if (values.some((v) => v === null)) continue;
733
+ if (new Set(values).size !== values.length) continue;
734
+ return { property: propName };
735
+ }
736
+ return null;
737
+ }
627
738
  /** Build a single Field from a schema property entry. Shared across all extraction sites. */
628
739
  function buildFieldFromSchema(fieldName, fieldSchema, contextName, requiredSet) {
629
740
  return {
@@ -718,6 +829,51 @@ function resolveVariantModelName(schema) {
718
829
  if (title) return resolveSchemaName(title);
719
830
  return null;
720
831
  }
832
+ /** Look up the discriminator's mapped variant name for an inline object
833
+ * variant by reading its const-valued discriminator property. Returns null
834
+ * when the variant doesn't pin the property to a string const, or when
835
+ * the value isn't in the mapping. */
836
+ function mapVariantToDiscriminatorEntry(variant, discriminator) {
837
+ const value = getConstPropertyValue(variant, discriminator.property);
838
+ if (value === null) return null;
839
+ return discriminator.mapping[value] ?? null;
840
+ }
841
+ /**
842
+ * Derive a discriminator value → variant model name mapping for an inline-
843
+ * variant `oneOf` whose explicit `discriminator:` has no `mapping:` clause.
844
+ *
845
+ * Each variant model name is reproduced via `nameVariantModel` so it matches
846
+ * what `extractNestedSchema` registered when it walked the same `oneOf` to
847
+ * pull variants out as named models. Variant 0 keeps the bare parent name
848
+ * (e.g. `ApiKeyOwner`); subsequent variants get a const-derived prefix
849
+ * (e.g. `UserApiKeyOwner` for the `type: const: user` variant).
850
+ *
851
+ * The "parent name" used here mirrors the one `collectNestedInlineModels`
852
+ * passes to `extractNestedSchema`: it's the qualified inline model name
853
+ * built from `contextName` (the field name) plus `parentModelName` (the
854
+ * containing model). Returns null when any non-object variant is present
855
+ * or when a variant lacks a const value on the discriminator property.
856
+ */
857
+ function deriveInlineVariantMapping(variants, discriminatorProperty, contextName, parentModelName) {
858
+ if (variants.length === 0 || !contextName) return null;
859
+ const inlineObjectVariants = variants.filter((v) => !v.$ref && v.properties && (v.type === "object" || !v.type));
860
+ if (inlineObjectVariants.length !== variants.length) return null;
861
+ const baseInlineName = qualifyInlineModelName(toPascalCase(contextName), parentModelName);
862
+ const namingDiscriminator = { property: discriminatorProperty };
863
+ const mapping = {};
864
+ const emittedSoFar = [];
865
+ for (const variant of inlineObjectVariants) {
866
+ const constValue = getConstPropertyValue(variant, discriminatorProperty);
867
+ if (constValue === null) return null;
868
+ const variantName = nameVariantModel(variant, baseInlineName, emittedSoFar, namingDiscriminator);
869
+ mapping[constValue] = variantName;
870
+ emittedSoFar.push({
871
+ name: variantName,
872
+ fields: []
873
+ });
874
+ }
875
+ return mapping;
876
+ }
721
877
  function extractEnum(name, schema) {
722
878
  return {
723
879
  name,
@@ -916,8 +1072,20 @@ function extractInlineModelDeep(name, schema) {
916
1072
  }));
917
1073
  }
918
1074
  if (fieldSchema.oneOf) {
919
- const objectVariant = fieldSchema.oneOf.find((v) => v.properties && (v.type === "object" || !v.type));
920
- if (objectVariant) nestedModels.push(...extractInlineModelDeep(qualifyNestedInlineName(name, fieldName), objectVariant));
1075
+ const inlineObjectVariants = fieldSchema.oneOf.filter((v) => !v.$ref && v.properties && (v.type === "object" || !v.type));
1076
+ if (inlineObjectVariants.length > 0) {
1077
+ const baseQualified = qualifyNestedInlineName(name, fieldName);
1078
+ const namingDisc = deriveConstNamingDiscriminator(inlineObjectVariants);
1079
+ const emitted = [];
1080
+ for (const variant of inlineObjectVariants) {
1081
+ const variantName = nameVariantModel(variant, baseQualified, emitted, namingDisc);
1082
+ emitted.push({
1083
+ name: variantName,
1084
+ fields: []
1085
+ });
1086
+ nestedModels.push(...extractInlineModelDeep(variantName, variant));
1087
+ }
1088
+ }
921
1089
  }
922
1090
  }
923
1091
  return [{
@@ -1014,17 +1182,49 @@ function schemaToTypeRef(schema, contextName, parentModelName) {
1014
1182
  inner: enumRef
1015
1183
  } : enumRef;
1016
1184
  }
1017
- const syntheticDiscriminator = !schema.discriminator && compositionKind === "oneOf" ? detectConstPropertyDiscriminator(nonNullVariants) : null;
1018
- const variants = rawVariants.filter((v) => v.type !== "null").map((v) => schemaToTypeRef(v, contextName, parentModelName));
1185
+ let resolvedDiscriminator;
1186
+ if (schema.discriminator) {
1187
+ const property = schema.discriminator.propertyName;
1188
+ const explicitMapping = schema.discriminator.mapping ?? {};
1189
+ let mapping = Object.fromEntries(Object.entries(explicitMapping).map(([k, v]) => [k, v.replace(/^#\/components\/schemas\//, "")]));
1190
+ if (Object.keys(mapping).length === 0) {
1191
+ const inlineMapping = deriveInlineVariantMapping(nonNullVariants, property, contextName, parentModelName);
1192
+ if (inlineMapping) mapping = inlineMapping;
1193
+ }
1194
+ resolvedDiscriminator = {
1195
+ property,
1196
+ mapping
1197
+ };
1198
+ } else if (compositionKind === "oneOf") {
1199
+ const synthetic = detectConstPropertyDiscriminator(nonNullVariants);
1200
+ if (synthetic) resolvedDiscriminator = synthetic;
1201
+ else {
1202
+ const namingDisc = deriveConstNamingDiscriminator(nonNullVariants);
1203
+ if (namingDisc) {
1204
+ const inlineMapping = deriveInlineVariantMapping(nonNullVariants, namingDisc.property, contextName, parentModelName);
1205
+ if (inlineMapping) resolvedDiscriminator = {
1206
+ property: namingDisc.property,
1207
+ mapping: inlineMapping
1208
+ };
1209
+ }
1210
+ }
1211
+ }
1212
+ const variants = [];
1213
+ for (const v of rawVariants) {
1214
+ if (v === nullVariant) continue;
1215
+ const inlineMappingName = resolvedDiscriminator && !v.$ref && v.properties && (v.type === "object" || !v.type) ? mapVariantToDiscriminatorEntry(v, resolvedDiscriminator) : null;
1216
+ if (inlineMappingName) variants.push({
1217
+ kind: "model",
1218
+ name: inlineMappingName
1219
+ });
1220
+ else variants.push(schemaToTypeRef(v, contextName, parentModelName));
1221
+ }
1019
1222
  const hasNull = !!nullVariant;
1020
1223
  const union = {
1021
1224
  kind: "union",
1022
1225
  variants,
1023
1226
  compositionKind,
1024
- ...schema.discriminator ? { discriminator: {
1025
- property: schema.discriminator.propertyName,
1026
- mapping: Object.fromEntries(Object.entries(schema.discriminator.mapping ?? {}).map(([k, v]) => [k, v.replace(/^#\/components\/schemas\//, "")]))
1027
- } } : syntheticDiscriminator ? { discriminator: syntheticDiscriminator } : {}
1227
+ ...resolvedDiscriminator ? { discriminator: resolvedDiscriminator } : {}
1028
1228
  };
1029
1229
  return hasNull ? {
1030
1230
  kind: "nullable",