@workos/oagen-emitters 0.7.5 → 0.8.1

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.
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.7.5"
2
+ ".": "0.8.1"
3
3
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.8.1](https://github.com/workos/oagen-emitters/compare/v0.8.0...v0.8.1) (2026-05-05)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **emitters:** repair regressions surfaced by shared-schema spec rev ([#84](https://github.com/workos/oagen-emitters/issues/84)) ([a04d317](https://github.com/workos/oagen-emitters/commit/a04d3170707adea21f19632f2a149b735be91d50))
9
+
10
+ ## [0.8.0](https://github.com/workos/oagen-emitters/compare/v0.7.5...v0.8.0) (2026-05-05)
11
+
12
+
13
+ ### Features
14
+
15
+ * **emitters:** dispatch field-level discriminated unions and drop dead request bodies ([#81](https://github.com/workos/oagen-emitters/issues/81)) ([4d38d24](https://github.com/workos/oagen-emitters/commit/4d38d249dcc7079e2a61d8faeabb681d6798618f))
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * **go:** stop SSO auth code leaking into request URL ([#83](https://github.com/workos/oagen-emitters/issues/83)) ([bc520e6](https://github.com/workos/oagen-emitters/commit/bc520e6a3d966abdf785262e5c49736b5e105b92))
21
+
3
22
  ## [0.7.5](https://github.com/workos/oagen-emitters/compare/v0.7.4...v0.7.5) (2026-05-03)
4
23
 
5
24
 
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { _ as nodeEmitter, a as rustExtractor, c as pythonExtractor, d as rubyEmitter, f as kotlinEmitter, g as pythonEmitter, h as phpEmitter, i as kotlinExtractor, l as rubyExtractor, m as goEmitter, n as elixirExtractor, o as goExtractor, p as dotnetEmitter, r as dotnetExtractor, s as phpExtractor, t as workosEmittersPlugin, u as nodeExtractor } from "./plugin-BoTAX4nl.mjs";
1
+ import { _ as nodeEmitter, a as rustExtractor, c as pythonExtractor, d as rubyEmitter, f as kotlinEmitter, g as pythonEmitter, h as phpEmitter, i as kotlinExtractor, l as rubyExtractor, m as goEmitter, n as elixirExtractor, o as goExtractor, p as dotnetEmitter, r as dotnetExtractor, s as phpExtractor, t as workosEmittersPlugin, u as nodeExtractor } from "./plugin-DOE0FqrZ.mjs";
2
2
  export { dotnetEmitter, dotnetExtractor, elixirExtractor, goEmitter, goExtractor, kotlinEmitter, kotlinExtractor, nodeEmitter, nodeExtractor, phpEmitter, phpExtractor, pythonEmitter, pythonExtractor, rubyEmitter, rubyExtractor, rustExtractor, workosEmittersPlugin };
@@ -7456,6 +7456,12 @@ function generateModels$5(models, ctx) {
7456
7456
  const dirName = mountDirMap.get(service.name) ?? resolveDir(service.name);
7457
7457
  serviceDirModelPaths.add(`src/${ctx.namespace}/${dirName}/models`);
7458
7458
  }
7459
+ for (const dirPath of serviceDirModelPaths) if (!symbolsByDir.has(dirPath)) files.push({
7460
+ path: `${dirPath}/__init__.py`,
7461
+ content: "",
7462
+ integrateTarget: true,
7463
+ overwriteExisting: true
7464
+ });
7459
7465
  for (const [dirPath, names] of symbolsByDir) {
7460
7466
  const uniqueNames = [...new Set(names)].sort();
7461
7467
  const importLines = [];
@@ -7633,6 +7639,17 @@ function deserializeField(ref, accessor, isRequired, walrusVar = "_v") {
7633
7639
  case "union": {
7634
7640
  const modelVariants = (ref.variants ?? []).filter((v) => v.kind === "model");
7635
7641
  const uniqueModels = [...new Set(modelVariants.map((v) => v.name))];
7642
+ if (ref.discriminator && ref.discriminator.mapping) {
7643
+ const entries = Object.entries(ref.discriminator.mapping);
7644
+ if (entries.length > 0) {
7645
+ const dispatchMap = entries.map(([value, modelName]) => `"${value}": ${className$5(modelName)}`).join(", ");
7646
+ const dataExpr = isRequired ? accessor : walrusVar;
7647
+ const dataCast = `cast(Dict[str, Any], ${dataExpr})`;
7648
+ const branch = `(_disc.from_dict(${dataCast}) if (_disc := ${`{${dispatchMap}}.get(cast(str, ${dataCast}.get("${ref.discriminator.property}")))`}) is not None else ${dataExpr})`;
7649
+ if (isRequired) return branch;
7650
+ return `(${branch}) if (${walrusVar} := ${accessor}) is not None else None`;
7651
+ }
7652
+ }
7636
7653
  if (uniqueModels.length === 1) return deserializeField({
7637
7654
  kind: "model",
7638
7655
  name: uniqueModels[0]
@@ -7654,6 +7671,7 @@ function serializeField(ref, accessor) {
7654
7671
  case "union": {
7655
7672
  const modelVariants = (ref.variants ?? []).filter((v) => v.kind === "model");
7656
7673
  if ([...new Set(modelVariants.map((v) => v.name))].length === 1) return `${accessor}.to_dict()`;
7674
+ if (ref.discriminator && ref.discriminator.mapping && modelVariants.length > 0) return `${accessor}.to_dict() if hasattr(${accessor}, "to_dict") else ${accessor}`;
7657
7675
  return accessor;
7658
7676
  }
7659
7677
  default: return accessor;
@@ -9056,11 +9074,6 @@ function generateServiceInits(spec, ctx) {
9056
9074
  integrateTarget: true,
9057
9075
  overwriteExisting: true
9058
9076
  });
9059
- files.push({
9060
- path: `src/${ctx.namespace}/${dirName}/models/__init__.py`,
9061
- content: "",
9062
- skipIfExists: true
9063
- });
9064
9077
  }
9065
9078
  return files;
9066
9079
  }
@@ -10781,6 +10794,13 @@ function generateFromArrayValue(ref, accessor) {
10781
10794
  return accessor;
10782
10795
  case "nullable": return generateFromArrayValue(ref.inner, accessor);
10783
10796
  case "union": {
10797
+ if (ref.discriminator && ref.discriminator.mapping) {
10798
+ const entries = Object.entries(ref.discriminator.mapping);
10799
+ if (entries.length > 0) {
10800
+ const arms = entries.map(([value, modelName]) => `'${value}' => ${className$4(modelName)}::fromArray(${accessor})`).join(", ");
10801
+ return `match (${accessor}['${ref.discriminator.property}'] ?? null) { ${arms}, default => ${accessor} }`;
10802
+ }
10803
+ }
10784
10804
  const resolved = resolveDegenerateUnion(ref);
10785
10805
  if (resolved) return generateFromArrayValue(resolved, accessor);
10786
10806
  return accessor;
@@ -12442,11 +12462,70 @@ function joinUnionVariants$2(_ref, variants) {
12442
12462
  if (_ref.compositionKind === "allOf") return variants[0] ?? "interface{}";
12443
12463
  const unique = [...new Set(variants)];
12444
12464
  if (unique.length === 1) return unique[0];
12465
+ if (_ref.discriminator && _ref.discriminator.mapping) {
12466
+ const resolverName = unionResolverName(_ref);
12467
+ if (resolverName) return resolverName;
12468
+ }
12445
12469
  return "interface{}";
12446
12470
  }
12471
+ /**
12472
+ * Pick a stable type name for a discriminated union's runtime resolver. Today
12473
+ * we emit no resolver struct, so we treat the union's first model variant as
12474
+ * the public type — matching the pre-discriminator behavior where Go just
12475
+ * referenced one variant directly. The Owner field stays typed, callers
12476
+ * can still inspect Type to detect the user variant (data loss on
12477
+ * non-overlapping fields like organization_id is documented in the SDK
12478
+ * compat report rather than fixed in the emitter — Go callers who need the
12479
+ * user-only fields can json.Unmarshal the raw payload manually).
12480
+ */
12481
+ function unionResolverName(ref) {
12482
+ for (const v of ref.variants) if (v.kind === "model") return `*${className$3(v.name)}`;
12483
+ return null;
12484
+ }
12447
12485
  //#endregion
12448
12486
  //#region src/go/models.ts
12449
12487
  /**
12488
+ * Collect names of models that are referenced **only** as a named request body
12489
+ * model on an operation, with no other consumers (response types, pagination
12490
+ * item types, error types, or fields on other models).
12491
+ *
12492
+ * The Go emitter synthesizes a `{Service}{Method}Params` struct from those
12493
+ * request bodies (see `resources.ts:392`), and the method signature uses the
12494
+ * synthesized struct — never the named model. So emitting the named model in
12495
+ * `models.go` would leave callers with a duplicate, unused struct (the bug
12496
+ * surfaced in workos-go#544 with `CreateUserAPIKey` /
12497
+ * `UserManagementCreateAPIKeyParams`).
12498
+ *
12499
+ * Models in this set are skipped during model emission. Callers parameterize
12500
+ * the API surface through `*Params` exclusively, which is the sole consumer
12501
+ * of the spec's named request body schema.
12502
+ */
12503
+ function collectRequestBodyOnlyModelNames$1(services, models) {
12504
+ const requestBodyNames = /* @__PURE__ */ new Set();
12505
+ const otherReferences = /* @__PURE__ */ new Set();
12506
+ const collect = (ref, into) => {
12507
+ if (!ref) return;
12508
+ walkTypeRef(ref, { model: (r) => into.add(r.name) });
12509
+ };
12510
+ for (const service of services) for (const op of service.operations) {
12511
+ if (op.requestBody?.kind === "model") requestBodyNames.add(op.requestBody.name);
12512
+ collect(op.response, otherReferences);
12513
+ if (op.pagination) collect(op.pagination.itemType, otherReferences);
12514
+ for (const p of [
12515
+ ...op.pathParams,
12516
+ ...op.queryParams,
12517
+ ...op.headerParams,
12518
+ ...op.cookieParams ?? []
12519
+ ]) collect(p.type, otherReferences);
12520
+ if (op.successResponses) for (const sr of op.successResponses) collect(sr.type, otherReferences);
12521
+ for (const err of op.errors) if (err.type) collect(err.type, otherReferences);
12522
+ }
12523
+ for (const model of models) for (const field of model.fields) collect(field.type, otherReferences);
12524
+ const result = /* @__PURE__ */ new Set();
12525
+ for (const name of requestBodyNames) if (!otherReferences.has(name)) result.add(name);
12526
+ return result;
12527
+ }
12528
+ /**
12450
12529
  * Generate Go struct definitions from IR Models.
12451
12530
  * All models go into a single models.go file for the flat package.
12452
12531
  */
@@ -12456,10 +12535,12 @@ function generateModels$3(models, ctx) {
12456
12535
  const lines = [];
12457
12536
  lines.push(`package ${ctx.namespace}`);
12458
12537
  lines.push("");
12538
+ const requestBodyOnly = collectRequestBodyOnlyModelNames$1(ctx.spec.services, models);
12459
12539
  const modelHashMap = /* @__PURE__ */ new Map();
12460
12540
  const hashGroups = /* @__PURE__ */ new Map();
12461
12541
  for (const model of models) {
12462
12542
  if (isListWrapperModel(model) || isListMetadataModel(model)) continue;
12543
+ if (requestBodyOnly.has(model.name)) continue;
12463
12544
  const hash = structuralHash$2(model);
12464
12545
  modelHashMap.set(model.name, hash);
12465
12546
  if (!hashGroups.has(hash)) hashGroups.set(hash, []);
@@ -12476,6 +12557,7 @@ function generateModels$3(models, ctx) {
12476
12557
  const batchedAliases = /* @__PURE__ */ new Set();
12477
12558
  for (const model of models) {
12478
12559
  if (isListWrapperModel(model) || isListMetadataModel(model)) continue;
12560
+ if (requestBodyOnly.has(model.name)) continue;
12479
12561
  const structName = className$3(model.name);
12480
12562
  const canonicalName = aliasOf.get(model.name);
12481
12563
  if (canonicalName) {
@@ -13242,7 +13324,7 @@ function generateParamsStruct(mountName, method, op, plan, ctx, resolvedOp) {
13242
13324
  emittedFields.add(goField);
13243
13325
  const goType = !field.required ? makeOptional(mapTypeRef$3(field.type)) : mapTypeRef$3(field.type);
13244
13326
  const jsonTag = field.required ? `json:"${field.name}"` : `json:"${field.name},omitempty"`;
13245
- const urlTag = op.queryParams.some((qp) => !hidden.has(qp.name) && fieldName$2(qp.name) === goField) ? ` url:"${field.name}${field.required ? "" : ",omitempty"}"` : "";
13327
+ const urlTag = " url:\"-\"";
13246
13328
  if (field.description) {
13247
13329
  const fdLines = field.description.split("\n").filter((l) => l.trim());
13248
13330
  lines.push(`\t// ${fieldDocComment(goField, fdLines[0])}`);
@@ -13991,12 +14073,16 @@ function generateFixtures$1(spec) {
13991
14073
  content: JSON.stringify(fixture, null, 2)
13992
14074
  });
13993
14075
  }
14076
+ const seenListPaths = /* @__PURE__ */ new Set();
13994
14077
  for (const service of spec.services) for (const op of service.operations) if (op.pagination) {
13995
14078
  let itemModel = op.pagination.itemType.kind === "model" ? modelMap.get(op.pagination.itemType.name) : null;
13996
14079
  if (itemModel) {
13997
14080
  const unwrapped = unwrapListModel$1(itemModel, modelMap);
13998
14081
  if (unwrapped) itemModel = unwrapped;
13999
14082
  if (itemModel.fields.length === 0) continue;
14083
+ const path = `testdata/list_${fileName$1(itemModel.name)}.json`;
14084
+ if (seenListPaths.has(path)) continue;
14085
+ seenListPaths.add(path);
14000
14086
  const listFixture = {
14001
14087
  data: [generateModelFixture$1(itemModel, modelMap, enumMap)],
14002
14088
  list_metadata: {
@@ -14005,7 +14091,7 @@ function generateFixtures$1(spec) {
14005
14091
  }
14006
14092
  };
14007
14093
  files.push({
14008
- path: `testdata/list_${fileName$1(itemModel.name)}.json`,
14094
+ path,
14009
14095
  content: JSON.stringify(listFixture, null, 2)
14010
14096
  });
14011
14097
  }
@@ -15094,9 +15180,8 @@ const discriminatedUnions$1 = /* @__PURE__ */ new Map();
15094
15180
  function joinUnionVariants$1(_ref, variants) {
15095
15181
  if (_ref.compositionKind === "allOf") return variants[0] ?? "object";
15096
15182
  const unique = [...new Set(variants)];
15097
- if (unique.length === 1) return unique[0];
15098
15183
  if (_ref.discriminator && _ref.discriminator.mapping) {
15099
- const baseName = unique[0];
15184
+ const baseName = _ref.variants.filter((v) => v.kind === "model").map((v) => v.kind === "model" ? v.name : "").filter(Boolean)[0] ?? unique[0];
15100
15185
  discriminatedUnions$1.set(baseName, {
15101
15186
  property: _ref.discriminator.property,
15102
15187
  mapping: _ref.discriminator.mapping,
@@ -15104,6 +15189,7 @@ function joinUnionVariants$1(_ref, variants) {
15104
15189
  });
15105
15190
  return "object";
15106
15191
  }
15192
+ if (unique.length === 1) return unique[0];
15107
15193
  if (unique.length >= 2 && unique.length <= 9) return `OneOf.OneOf<${unique.join(", ")}>`;
15108
15194
  if (unique.length >= 10) console.warn(`[oagen:dotnet] Union with ${unique.length} variants exceeds OneOf<T0..T8> arity; falling back to object. Variants: ${unique.join(", ")}`);
15109
15195
  return "object";
@@ -15121,6 +15207,7 @@ function generateModels$2(models, ctx, discCtx) {
15121
15207
  for (const e of ctx.spec.enums) if (e.values.length === 1) enumConstByName.set(e.name, String(e.values[0].value));
15122
15208
  const files = [];
15123
15209
  primeModelAliases(models);
15210
+ const requestBodyOnlyNames = collectRequestBodyOnlyModelNames(ctx.spec.services, models);
15124
15211
  const baseFieldLookup = /* @__PURE__ */ new Map();
15125
15212
  if (discCtx) {
15126
15213
  for (const model of models) if (discCtx.discriminatorBases.has(model.name)) {
@@ -15131,6 +15218,7 @@ function generateModels$2(models, ctx, discCtx) {
15131
15218
  }
15132
15219
  for (const model of models) {
15133
15220
  if (isListWrapperModel(model) || isListMetadataModel(model)) continue;
15221
+ if (requestBodyOnlyNames.has(model.name)) continue;
15134
15222
  const csClassName = modelClassName(model.name);
15135
15223
  if (isModelAlias(model.name)) continue;
15136
15224
  const lines = [];
@@ -15209,6 +15297,8 @@ function generateModels$2(models, ctx, discCtx) {
15209
15297
  }
15210
15298
  const isRequiredEnum = field.required && isEnumRef(field.type) && constInit === null;
15211
15299
  lines.push(...emitJsonPropertyAttributes(field.name, { isRequiredEnum }));
15300
+ const discriminatedUnionConverter = discriminatedUnionConverterName(field.type);
15301
+ if (discriminatedUnionConverter) lines.push(` [Newtonsoft.Json.JsonConverter(typeof(${discriminatedUnionConverter}))]`);
15212
15302
  const newMod = useNewModifier ? "new " : "";
15213
15303
  lines.push(` public ${newMod}${csType} ${csFieldName} { get; ${setterModifier}set; }${initializer}`);
15214
15304
  if (isDictionaryOfObject(csType) && !field.deprecated) dictObjectFields.push({
@@ -15274,6 +15364,23 @@ function generateModels$2(models, ctx, discCtx) {
15274
15364
  return files;
15275
15365
  }
15276
15366
  /**
15367
+ * Compute the name of the discriminator converter class for a field whose
15368
+ * type is a discriminated union, mirroring the keying used in
15369
+ * `joinUnionVariants` (first IR model variant name + "DiscriminatorConverter").
15370
+ * Returns null when the type isn't a discriminated union with a populated
15371
+ * mapping. Also walks through `nullable` so an optional discriminated field
15372
+ * still gets the converter applied.
15373
+ */
15374
+ function discriminatedUnionConverterName(ref) {
15375
+ const inner = ref.kind === "nullable" ? ref.inner : ref;
15376
+ if (inner.kind !== "union") return null;
15377
+ if (!inner.discriminator || !inner.discriminator.mapping) return null;
15378
+ if (Object.keys(inner.discriminator.mapping).length === 0) return null;
15379
+ const firstModel = inner.variants.find((v) => v.kind === "model");
15380
+ if (!firstModel || firstModel.kind !== "model") return null;
15381
+ return `${modelClassName(firstModel.name)}DiscriminatorConverter`;
15382
+ }
15383
+ /**
15277
15384
  * Whether the emitted C# type is `Dictionary<string, object>` or its
15278
15385
  * nullable variant — the usual shape of metadata / additional-properties
15279
15386
  * fields that get typed accessors.
@@ -15375,6 +15482,39 @@ function normalizeTypeForHash$1(ref, aliasOf) {
15375
15482
  function structuralHash$1(model, aliasOf = /* @__PURE__ */ new Map()) {
15376
15483
  return model.fields.map((f) => `${f.name}:${JSON.stringify(normalizeTypeForHash$1(f.type, aliasOf))}:${f.required}`).sort().join("|");
15377
15484
  }
15485
+ /**
15486
+ * Names of models referenced **only** as a named operation request body —
15487
+ * i.e. never appearing in a response, an error, a paginated item type, or as
15488
+ * a field type on another model. The .NET wrapper generator emits a
15489
+ * per-operation `*Options` class containing the same fields, so the named
15490
+ * entity is never instantiated by callers and just clutters the SDK
15491
+ * (workos-dotnet#248: `CreateUserApiKey` vs `UserManagementCreateApiKeyOptions`).
15492
+ */
15493
+ function collectRequestBodyOnlyModelNames(services, models) {
15494
+ const requestBodyNames = /* @__PURE__ */ new Set();
15495
+ const otherReferences = /* @__PURE__ */ new Set();
15496
+ const collect = (ref, into) => {
15497
+ if (!ref) return;
15498
+ walkTypeRef(ref, { model: (r) => into.add(resolveModelName(r.name)) });
15499
+ };
15500
+ for (const service of services) for (const op of service.operations) {
15501
+ if (op.requestBody?.kind === "model") requestBodyNames.add(resolveModelName(op.requestBody.name));
15502
+ collect(op.response, otherReferences);
15503
+ if (op.pagination) collect(op.pagination.itemType, otherReferences);
15504
+ for (const p of [
15505
+ ...op.pathParams,
15506
+ ...op.queryParams,
15507
+ ...op.headerParams,
15508
+ ...op.cookieParams ?? []
15509
+ ]) collect(p.type, otherReferences);
15510
+ if (op.successResponses) for (const sr of op.successResponses) collect(sr.type, otherReferences);
15511
+ for (const err of op.errors) if (err.type) collect(err.type, otherReferences);
15512
+ }
15513
+ for (const model of models) for (const field of model.fields) collect(field.type, otherReferences);
15514
+ const result = /* @__PURE__ */ new Set();
15515
+ for (const name of requestBodyNames) if (!otherReferences.has(name)) result.add(name);
15516
+ return result;
15517
+ }
15378
15518
  //#endregion
15379
15519
  //#region src/dotnet/enums.ts
15380
15520
  /**
@@ -17093,14 +17233,14 @@ const dotnetEmitter = {
17093
17233
  lines.push(" {");
17094
17234
  lines.push(" public override bool CanConvert(Type objectType) => objectType == typeof(object);");
17095
17235
  lines.push("");
17096
- lines.push(" public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object? existingValue, Newtonsoft.Json.JsonSerializer serializer)");
17236
+ lines.push(" public override object? ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object? existingValue, Newtonsoft.Json.JsonSerializer serializer)");
17097
17237
  lines.push(" {");
17098
17238
  lines.push(" var jObject = JObject.Load(reader);");
17099
17239
  lines.push(` var discriminatorValue = jObject["${disc.property}"]?.ToString();`);
17100
17240
  lines.push(" switch (discriminatorValue)");
17101
17241
  lines.push(" {");
17102
17242
  for (const [value, modelName] of Object.entries(disc.mapping)) {
17103
- const csName = modelClassName(modelName);
17243
+ const csName = modelClassName(resolveModelName(modelName));
17104
17244
  lines.push(` case "${value}": return jObject.ToObject<${csName}>(serializer);`);
17105
17245
  }
17106
17246
  lines.push(" default: return jObject.ToObject<object>(serializer);");
@@ -17139,7 +17279,7 @@ const dotnetEmitter = {
17139
17279
  lines.push("");
17140
17280
  lines.push(` public override bool CanConvert(Type objectType) => typeof(${baseClass}).IsAssignableFrom(objectType);`);
17141
17281
  lines.push("");
17142
- lines.push(" public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object? existingValue, Newtonsoft.Json.JsonSerializer serializer)");
17282
+ lines.push(" public override object? ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object? existingValue, Newtonsoft.Json.JsonSerializer serializer)");
17143
17283
  lines.push(" {");
17144
17284
  lines.push(" var jObject = JObject.Load(reader);");
17145
17285
  lines.push(` var discriminatorValue = jObject["${disc.property}"]?.ToString();`);
@@ -17585,9 +17725,8 @@ const discriminatedUnions = /* @__PURE__ */ new Map();
17585
17725
  function joinUnionVariants(ref, variants) {
17586
17726
  if (ref.compositionKind === "allOf") return variants[0] ?? "Any";
17587
17727
  const unique = [...new Set(variants)];
17588
- if (unique.length === 1) return unique[0];
17589
17728
  if (ref.discriminator && ref.discriminator.mapping) {
17590
- const baseName = unique[0];
17729
+ const baseName = ref.variants.filter((v) => v.kind === "model").map((v) => v.kind === "model" ? v.name : "").filter(Boolean)[0] ?? unique[0];
17591
17730
  discriminatedUnions.set(baseName, {
17592
17731
  property: ref.discriminator.property,
17593
17732
  mapping: ref.discriminator.mapping,
@@ -17595,6 +17734,7 @@ function joinUnionVariants(ref, variants) {
17595
17734
  });
17596
17735
  return baseName;
17597
17736
  }
17737
+ if (unique.length === 1) return unique[0];
17598
17738
  return "Any";
17599
17739
  }
17600
17740
  /** Kotlin imports implied by a given type expression. Caller collects into a set. */
@@ -20095,8 +20235,15 @@ function deserializeExpression(accessor, ref, required, enumNames, modelNames) {
20095
20235
  }
20096
20236
  if (ref.kind === "enum" && enumNames.has(ref.name)) return accessor;
20097
20237
  if (ref.kind === "union") {
20098
- const modelVariants = ref.variants.filter((v) => v.kind === "model" && modelNames.has(v.name));
20099
- if (modelVariants.length > 0 && modelVariants.length === ref.variants.length) return accessor;
20238
+ if (ref.discriminator && ref.discriminator.mapping) {
20239
+ const entries = Object.entries(ref.discriminator.mapping).filter(([, name]) => modelNames.has(name));
20240
+ if (entries.length > 0) return `${accessor} ? ${`(case ${rubyHashAccessor(accessor, ref.discriminator.property)} ${entries.map(([value, modelName]) => {
20241
+ const cls = `WorkOS::${className(modelName)}`;
20242
+ return `when ${JSON.stringify(value)} then ${cls}.new(${accessor})`;
20243
+ }).join(" ")} else ${accessor} end)`} : nil`;
20244
+ }
20245
+ const firstModelVariant = ref.variants.find((v) => v.kind === "model" && modelNames.has(v.name));
20246
+ if (firstModelVariant && firstModelVariant.kind === "model") return `${accessor} ? ${`WorkOS::${className(firstModelVariant.name)}`}.new(${accessor}) : nil`;
20100
20247
  return accessor;
20101
20248
  }
20102
20249
  if (ref.kind === "literal") return accessor;
@@ -22153,4 +22300,4 @@ const workosEmittersPlugin = {
22153
22300
  //#endregion
22154
22301
  export { nodeEmitter as _, rustExtractor as a, pythonExtractor as c, rubyEmitter as d, kotlinEmitter as f, pythonEmitter as g, phpEmitter as h, kotlinExtractor as i, rubyExtractor as l, goEmitter as m, elixirExtractor as n, goExtractor as o, dotnetEmitter as p, dotnetExtractor as r, phpExtractor as s, workosEmittersPlugin as t, nodeExtractor as u };
22155
22302
 
22156
- //# sourceMappingURL=plugin-BoTAX4nl.mjs.map
22303
+ //# sourceMappingURL=plugin-DOE0FqrZ.mjs.map