@workos/oagen-emitters 0.6.6 → 0.6.8

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.
@@ -7782,7 +7782,7 @@ const buildHiddenParams = buildHiddenParams$1;
7782
7782
  * PascalCase variant class name for a parameter group variant.
7783
7783
  * E.g., group "parent_resource", variant "by_id" -> "ParentResourceById".
7784
7784
  */
7785
- function groupVariantClassName$2(groupName, variantName) {
7785
+ function groupVariantClassName$3(groupName, variantName) {
7786
7786
  return className$5(`${groupName}_${variantName}`);
7787
7787
  }
7788
7788
  /**
@@ -7796,7 +7796,7 @@ function generateParameterGroupDataclasses(operations, specEnumNames, models) {
7796
7796
  for (const op of operations) {
7797
7797
  const bodyFieldTypes = collectBodyFieldTypes(op, models);
7798
7798
  for (const group of op.parameterGroups ?? []) for (const variant of group.variants) {
7799
- const variantClass = groupVariantClassName$2(group.name, variant.name);
7799
+ const variantClass = groupVariantClassName$3(group.name, variant.name);
7800
7800
  if (emitted.has(variantClass)) continue;
7801
7801
  emitted.add(variantClass);
7802
7802
  lines.push("");
@@ -7829,7 +7829,7 @@ function collectParameterGroupClassNames(operations) {
7829
7829
  const names = [];
7830
7830
  const seen = /* @__PURE__ */ new Set();
7831
7831
  for (const op of operations) for (const group of op.parameterGroups ?? []) for (const variant of group.variants) {
7832
- const cls = groupVariantClassName$2(group.name, variant.name);
7832
+ const cls = groupVariantClassName$3(group.name, variant.name);
7833
7833
  if (!seen.has(cls)) {
7834
7834
  seen.add(cls);
7835
7835
  names.push(cls);
@@ -7924,7 +7924,7 @@ function emitMethodSignature(lines, op, plan, method, isAsync, specEnumNames, mo
7924
7924
  }
7925
7925
  }
7926
7926
  for (const group of op.parameterGroups ?? []) {
7927
- const unionType = `Union[${group.variants.map((v) => groupVariantClassName$2(group.name, v.name)).join(", ")}]`;
7927
+ const unionType = `Union[${group.variants.map((v) => groupVariantClassName$3(group.name, v.name)).join(", ")}]`;
7928
7928
  const paramName = fieldName$4(group.name);
7929
7929
  if (group.optional) lines.push(` ${paramName}: Optional[${unionType}] = None,`);
7930
7930
  else lines.push(` ${paramName}: ${unionType},`);
@@ -8050,7 +8050,7 @@ function emitMethodDocstring(lines, op, plan, method, meta, specEnumNames, ctx,
8050
8050
  }
8051
8051
  }
8052
8052
  for (const group of op.parameterGroups ?? []) {
8053
- const variantClasses = group.variants.map((v) => groupVariantClassName$2(group.name, v.name));
8053
+ const variantClasses = group.variants.map((v) => groupVariantClassName$3(group.name, v.name));
8054
8054
  const desc = `Identifies the ${group.name.replace(/_/g, " ")}. One of: ${variantClasses.join(", ")}.`;
8055
8055
  allParams.push({
8056
8056
  name: fieldName$4(group.name),
@@ -8357,7 +8357,7 @@ function emitGroupDispatch(lines, op, target = "params", bodyFieldTypes) {
8357
8357
  const indent = group.optional ? " " : "";
8358
8358
  let first = true;
8359
8359
  for (const variant of group.variants) {
8360
- const variantClass = groupVariantClassName$2(group.name, variant.name);
8360
+ const variantClass = groupVariantClassName$3(group.name, variant.name);
8361
8361
  const keyword = first ? "if" : "elif";
8362
8362
  first = false;
8363
8363
  lines.push(` ${indent}${keyword} isinstance(${groupParam}, ${variantClass}):`);
@@ -10928,7 +10928,7 @@ function isRedirectEndpoint(op, resolvedOp) {
10928
10928
  return false;
10929
10929
  }
10930
10930
  /** PHP class name for a parameter group variant (e.g. ParentResourceById). */
10931
- function groupVariantClassName$1(groupName, variantName) {
10931
+ function groupVariantClassName$2(groupName, variantName) {
10932
10932
  return `${className$4(groupName)}${className$4(variantName)}`;
10933
10933
  }
10934
10934
  /**
@@ -10948,7 +10948,7 @@ function generateParameterGroupFiles(op, ctx, modelMap) {
10948
10948
  const files = [];
10949
10949
  const bodyFieldTypes = collectBodyFieldTypes(op, [...modelMap.values()]);
10950
10950
  for (const group of op.parameterGroups ?? []) for (const variant of group.variants) {
10951
- const variantClass = groupVariantClassName$1(group.name, variant.name);
10951
+ const variantClass = groupVariantClassName$2(group.name, variant.name);
10952
10952
  const lines = [];
10953
10953
  lines.push(`namespace ${ctx.namespacePascal}\\Service;`);
10954
10954
  lines.push("");
@@ -10982,7 +10982,7 @@ function generateGroupDispatch(op, indent, target = "$query") {
10982
10982
  const phpParamName = fieldName$3(group.name);
10983
10983
  for (let vi = 0; vi < group.variants.length; vi++) {
10984
10984
  const variant = group.variants[vi];
10985
- const variantClass = groupVariantClassName$1(group.name, variant.name);
10985
+ const variantClass = groupVariantClassName$2(group.name, variant.name);
10986
10986
  const keyword = vi === 0 ? "if" : "elseif";
10987
10987
  lines.push(`${indent}${keyword} ($${phpParamName} instanceof ${variantClass}) {`);
10988
10988
  for (const param of variant.parameters) {
@@ -11036,7 +11036,7 @@ function generateMethod$2(lines, op, service, ctx, modelMap, resolvedOp) {
11036
11036
  const phpName = fieldName$3(group.name);
11037
11037
  if (seenDocParams.has(phpName)) continue;
11038
11038
  seenDocParams.add(phpName);
11039
- const unionDocType = group.variants.map((v) => groupVariantClassName$1(group.name, v.name)).join("|");
11039
+ const unionDocType = group.variants.map((v) => groupVariantClassName$2(group.name, v.name)).join("|");
11040
11040
  const nullPrefix = group.optional ? "null|" : "";
11041
11041
  docParts.push(`@param ${nullPrefix}${unionDocType} $${phpName}`);
11042
11042
  }
@@ -11258,7 +11258,7 @@ function buildMethodParams(op, plan, modelMap, ctx, hiddenParams) {
11258
11258
  const phpName = fieldName$3(group.name);
11259
11259
  if (usedNames.has(phpName)) continue;
11260
11260
  usedNames.add(phpName);
11261
- const unionType = group.variants.map((v) => groupVariantClassName$1(group.name, v.name)).join("|");
11261
+ const unionType = group.variants.map((v) => groupVariantClassName$2(group.name, v.name)).join("|");
11262
11262
  if (group.optional) optional.push(`null|${unionType} $${phpName} = null`);
11263
11263
  else required.push(`${unionType} $${phpName}`);
11264
11264
  }
@@ -12395,7 +12395,21 @@ function makeOptional$1(goType) {
12395
12395
  return `*${goType}`;
12396
12396
  }
12397
12397
  function structuralHash$2(model) {
12398
- return model.fields.map((f) => `${f.name}:${JSON.stringify(f.type)}:${f.required}`).sort().join("|");
12398
+ const fieldHash = model.fields.map((f) => `${f.name}:${JSON.stringify(f.type)}:${f.required}`).sort().join("|");
12399
+ const domain = crudEntityDomain(model.name);
12400
+ return domain ? `${domain}::${fieldHash}` : fieldHash;
12401
+ }
12402
+ const CRUD_PREFIXES = [
12403
+ "Create",
12404
+ "Update",
12405
+ "Delete",
12406
+ "Get",
12407
+ "List"
12408
+ ];
12409
+ /** Strip CRUD verb prefix to get the entity name, or null if no prefix matches. */
12410
+ function crudEntityDomain(name) {
12411
+ for (const prefix of CRUD_PREFIXES) if (name.startsWith(prefix) && name.length > prefix.length) return name.slice(prefix.length);
12412
+ return null;
12399
12413
  }
12400
12414
  /** Known acronyms to preserve as single tokens during humanization. */
12401
12415
  const HUMANIZE_ACRONYMS$1 = [
@@ -14150,7 +14164,8 @@ function generateServiceTest$1(service, spec, ctx, _accessPaths, fixtureRewrites
14150
14164
  } else if (plan.responseModelName) {
14151
14165
  const respModel = plan.responseModelName;
14152
14166
  const isArrayResponse = !isPaginated && op.response?.kind === "array";
14153
- const fixturePath = `testdata/${fileName$1(respModel)}.json`;
14167
+ let fixturePath = `testdata/${fileName$1(respModel)}.json`;
14168
+ if (fixtureRewrites.has(fixturePath)) fixturePath = fixtureRewrites.get(fixturePath);
14154
14169
  const expectedPath = buildExpectedPath$1(op);
14155
14170
  const httpMethodUpper = op.httpMethod.toUpperCase();
14156
14171
  const isBodyMethod = httpMethodUpper === "POST" || httpMethodUpper === "PUT" || httpMethodUpper === "PATCH";
@@ -14231,7 +14246,8 @@ function generateServiceTest$1(service, spec, ctx, _accessPaths, fixtureRewrites
14231
14246
  const wrapperParamsStruct = paramsStructName(resolvedName, wrapperMethod);
14232
14247
  const responseType = wrapper.responseModelName;
14233
14248
  const testName = `Test${accessorName}_${wrapperMethod}`;
14234
- const fixturePath = responseType ? `testdata/${fileName$1(responseType)}.json` : null;
14249
+ let fixturePath = responseType ? `testdata/${fileName$1(responseType)}.json` : null;
14250
+ if (fixturePath && fixtureRewrites.has(fixturePath)) fixturePath = fixtureRewrites.get(fixturePath);
14235
14251
  const wrapperCallArgs = ["context.Background()"];
14236
14252
  for (const p of sortPathParamsByTemplateOrder$2(op)) wrapperCallArgs.push(`"test_${p.name}"`);
14237
14253
  wrapperCallArgs.push(`&${ctx.namespace}.${wrapperParamsStruct}{}`);
@@ -14975,7 +14991,8 @@ function generateModels$2(models, ctx, discCtx) {
14975
14991
  const human = humanize$1(model.name);
14976
14992
  lines.push(` /// <summary>Represents ${articleFor(human)} ${human}.</summary>`);
14977
14993
  }
14978
- if (discCtx?.discriminatorBases.has(model.name) ?? false) lines.push(` [Newtonsoft.Json.JsonConverter(typeof(${csClassName}DiscriminatorConverter))]`);
14994
+ const isDiscBase = discCtx?.discriminatorBases.has(model.name) ?? false;
14995
+ if (isDiscBase) lines.push(` [Newtonsoft.Json.JsonConverter(typeof(${csClassName}DiscriminatorConverter))]`);
14979
14996
  const baseName = discCtx?.variantToBase.get(model.name);
14980
14997
  const baseClassName = baseName ? modelClassName(baseName) : null;
14981
14998
  const baseFields = baseName ? baseFieldLookup.get(baseName) : void 0;
@@ -15003,10 +15020,16 @@ function generateModels$2(models, ctx, discCtx) {
15003
15020
  let csType;
15004
15021
  let initializer = "";
15005
15022
  let setterModifier = "";
15023
+ const discProp = isDiscBase ? discCtx?.discriminatorProperties?.get(model.name) : void 0;
15024
+ const isDiscriminatorField = discProp !== void 0 && field.name === discProp;
15006
15025
  if (constInit !== null && !isOptional) {
15007
15026
  csType = baseType;
15008
15027
  initializer = ` = ${constInit};`;
15009
15028
  setterModifier = "internal ";
15029
+ } else if (isDiscriminatorField) {
15030
+ csType = baseType;
15031
+ if (!isAlreadyNullable && !isValueTypeRef(field.type)) initializer = " = default!;";
15032
+ setterModifier = "internal ";
15010
15033
  } else if (isOptional) if (isAlreadyNullable) csType = baseType;
15011
15034
  else if (isValueTypeRef(field.type)) csType = `${baseType}?`;
15012
15035
  else csType = `${baseType}?`;
@@ -15039,6 +15062,14 @@ function generateModels$2(models, ctx, discCtx) {
15039
15062
  lines.push(` /// <paramref name="key"/> coerced to <typeparamref name="T"/>, or the default`);
15040
15063
  lines.push(` /// value when the key is missing or the value is not convertible.`);
15041
15064
  lines.push(` /// </summary>`);
15065
+ if (isDiscBase) {
15066
+ lines.push(` /// <remarks>`);
15067
+ lines.push(` /// Variant subclasses provide strongly-typed <c>${dict.csName}</c> properties that`);
15068
+ lines.push(` /// shadow this dictionary. This accessor is intended for forward-compatible handling`);
15069
+ lines.push(` /// of types not yet known to this SDK version. For recognized types, cast to the`);
15070
+ lines.push(` /// specific subclass and access its typed <c>${dict.csName}</c> property directly.`);
15071
+ lines.push(` /// </remarks>`);
15072
+ }
15042
15073
  lines.push(` /// <typeparam name="T">Expected value type.</typeparam>`);
15043
15074
  lines.push(` /// <param name="key">The key to look up.</param>`);
15044
15075
  lines.push(` public T? Get${dict.csName}Attribute<T>(string key)`);
@@ -15534,7 +15565,7 @@ function groupBaseClassName(mountName, groupName) {
15534
15565
  return `${className$2(mountName)}${className$2(groupName)}`;
15535
15566
  }
15536
15567
  /** Concrete variant class name (e.g. UserManagementRoleSingle). */
15537
- function groupVariantClassName(mountName, groupName, variantName) {
15568
+ function groupVariantClassName$1(mountName, groupName, variantName) {
15538
15569
  return `${className$2(mountName)}${className$2(groupName)}${className$2(variantName)}`;
15539
15570
  }
15540
15571
  /**
@@ -15552,7 +15583,7 @@ function generateParameterGroupTypes(mountName, op, models, emitted) {
15552
15583
  lines.push("");
15553
15584
  lines.push(` public abstract class ${baseName} { }`);
15554
15585
  for (const variant of group.variants) {
15555
- const variantName = groupVariantClassName(mountName, group.name, variant.name);
15586
+ const variantName = groupVariantClassName$1(mountName, group.name, variant.name);
15556
15587
  lines.push("");
15557
15588
  lines.push(` public class ${variantName} : ${baseName}`);
15558
15589
  lines.push(" {");
@@ -15580,7 +15611,7 @@ function emitGroupSerialization(mountName, op, indent, models, target) {
15580
15611
  const groupField = fieldName$1(group.name);
15581
15612
  let first = true;
15582
15613
  for (const variant of group.variants) {
15583
- const variantName = groupVariantClassName(mountName, group.name, variant.name);
15614
+ const variantName = groupVariantClassName$1(mountName, group.name, variant.name);
15584
15615
  const localVar = localName(variant.name);
15585
15616
  const keyword = first ? "if" : "else if";
15586
15617
  first = false;
@@ -16861,7 +16892,7 @@ const dotnetEmitter = {
16861
16892
  const discriminatorBases = /* @__PURE__ */ new Set();
16862
16893
  const variantToBase = /* @__PURE__ */ new Map();
16863
16894
  const modelDiscriminators = /* @__PURE__ */ new Map();
16864
- const files = generateModels$2(enriched.map((m) => {
16895
+ const dotnetModels = enriched.map((m) => {
16865
16896
  const disc = m.discriminator;
16866
16897
  if (disc && m.fields.length === 0) {
16867
16898
  const original = originalByName.get(m.name);
@@ -16876,9 +16907,13 @@ const dotnetEmitter = {
16876
16907
  }
16877
16908
  }
16878
16909
  return m;
16879
- }), c, {
16910
+ });
16911
+ const discriminatorProperties = /* @__PURE__ */ new Map();
16912
+ for (const [baseName, disc] of modelDiscriminators) discriminatorProperties.set(baseName, disc.property);
16913
+ const files = generateModels$2(dotnetModels, c, {
16880
16914
  discriminatorBases,
16881
- variantToBase
16915
+ variantToBase,
16916
+ discriminatorProperties
16882
16917
  });
16883
16918
  if (discriminatedUnions$1.size > 0) for (const [baseName, disc] of discriminatedUnions$1) {
16884
16919
  const converterName = `${baseName}DiscriminatorConverter`;
@@ -16939,6 +16974,8 @@ const dotnetEmitter = {
16939
16974
  lines.push(` /// </summary>`);
16940
16975
  lines.push(` public class ${converterName} : Newtonsoft.Json.JsonConverter`);
16941
16976
  lines.push(" {");
16977
+ lines.push(" public override bool CanWrite => false;");
16978
+ lines.push("");
16942
16979
  lines.push(` public override bool CanConvert(Type objectType) => typeof(${baseClass}).IsAssignableFrom(objectType);`);
16943
16980
  lines.push("");
16944
16981
  lines.push(" public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object? existingValue, Newtonsoft.Json.JsonSerializer serializer)");
@@ -16960,8 +16997,6 @@ const dotnetEmitter = {
16960
16997
  lines.push(" return target;");
16961
16998
  lines.push(" }");
16962
16999
  lines.push("");
16963
- lines.push(" public override bool CanWrite => false;");
16964
- lines.push("");
16965
17000
  lines.push(" public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object? value, Newtonsoft.Json.JsonSerializer serializer)");
16966
17001
  lines.push(" {");
16967
17002
  lines.push(" throw new NotImplementedException(\"Serialization is handled by the default serializer.\");");
@@ -19642,6 +19677,22 @@ function safeParamName(name) {
19642
19677
  function moduleName(name) {
19643
19678
  return toSnakeCase(name);
19644
19679
  }
19680
+ /**
19681
+ * PascalCase class name for a parameter-group variant. Mirrors the Python
19682
+ * convention: group "password" + variant "plaintext" → `PasswordPlaintext`.
19683
+ * Used as the Ruby constant under the WorkOS module.
19684
+ */
19685
+ function groupVariantClassName(groupName, variantName) {
19686
+ return className(`${groupName}_${variantName}`);
19687
+ }
19688
+ /**
19689
+ * Fully-qualified Ruby constant for a parameter-group variant scoped under
19690
+ * its owning resource module — e.g. "WorkOS::UserManagement::PasswordPlaintext".
19691
+ * Mirrors Python's `workos.user_management.PasswordPlaintext` namespacing.
19692
+ */
19693
+ function scopedGroupVariantClassName(mountTarget, groupName, variantName) {
19694
+ return `WorkOS::${className(mountTarget)}::${groupVariantClassName(groupName, variantName)}`;
19695
+ }
19645
19696
  /** snake_case property name for service accessors on the client. */
19646
19697
  function servicePropertyName(name) {
19647
19698
  return toSnakeCase(name);
@@ -20198,6 +20249,185 @@ function rubyStringLit$1(s) {
20198
20249
  return `'${s.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
20199
20250
  }
20200
20251
  //#endregion
20252
+ //#region src/ruby/parameter-groups.ts
20253
+ /**
20254
+ * Sorbet type string for a TypeRef. Mirrors `mapSorbetType` in rbi.ts but
20255
+ * lives here so the parameter-groups module is self-contained.
20256
+ */
20257
+ function mapSorbetType$1(ref) {
20258
+ switch (ref.kind) {
20259
+ case "primitive":
20260
+ switch (ref.type) {
20261
+ case "string": return "String";
20262
+ case "integer": return "Integer";
20263
+ case "number": return "Float";
20264
+ case "boolean": return "T::Boolean";
20265
+ case "unknown": return "T.untyped";
20266
+ }
20267
+ break;
20268
+ case "array": return `T::Array[${mapSorbetType$1(ref.items)}]`;
20269
+ case "model": return `WorkOS::${className(ref.name)}`;
20270
+ case "enum": return "String";
20271
+ case "nullable": return `T.nilable(${mapSorbetType$1(ref.inner)})`;
20272
+ case "literal":
20273
+ if (typeof ref.value === "string") return "String";
20274
+ if (ref.value === null) return "NilClass";
20275
+ if (typeof ref.value === "number") return Number.isInteger(ref.value) ? "Integer" : "Float";
20276
+ return "T::Boolean";
20277
+ case "union": {
20278
+ const variants = ref.variants.map((v) => mapSorbetType$1(v));
20279
+ const unique = [...new Set(variants)];
20280
+ if (unique.length === 1) return unique[0];
20281
+ return `T.any(${unique.join(", ")})`;
20282
+ }
20283
+ case "map": return `T::Hash[String, ${mapSorbetType$1(ref.valueType)}]`;
20284
+ }
20285
+ return "T.untyped";
20286
+ }
20287
+ /**
20288
+ * Build a stable groupName -> mountTarget map. Each parameter group is owned
20289
+ * by exactly one resource module — Ruby variant classes are inlined into
20290
+ * `WorkOS::<MountTarget>::<Variant>` (matching Python's per-resource layout),
20291
+ * so a dispatcher in another resource that references the same group still
20292
+ * resolves to a single canonical class.
20293
+ *
20294
+ * Mount targets are visited in alphabetical order so first-wins is
20295
+ * deterministic across runs. In the current spec no group is shared across
20296
+ * mount targets; if one ever is, the alphabetically-first owner gets the
20297
+ * class and other dispatchers reference it by full path.
20298
+ */
20299
+ function buildGroupOwnerMap(ctx) {
20300
+ const owner = /* @__PURE__ */ new Map();
20301
+ const groups = groupByMount(ctx);
20302
+ const sortedTargets = [...groups.keys()].sort();
20303
+ for (const target of sortedTargets) {
20304
+ const g = groups.get(target);
20305
+ if (!g) continue;
20306
+ for (const op of g.operations) for (const grp of op.parameterGroups ?? []) if (!owner.has(grp.name)) owner.set(grp.name, target);
20307
+ }
20308
+ return owner;
20309
+ }
20310
+ /**
20311
+ * Collect all variant classes a given mount target owns. Variants are
20312
+ * inlined into the resource file (and its RBI counterpart) — Zeitwerk's
20313
+ * collapse convention means subdirectories under `lib/workos/<service>/`
20314
+ * don't add a namespace level, so files there can't define
20315
+ * `WorkOS::<Service>::<Variant>`. Inline definitions sidestep that.
20316
+ *
20317
+ * Variant parameter types are taken from the IR's leaf type. When the IR's
20318
+ * leaf is a bare primitive but the request body model has a richer type
20319
+ * (array/enum/model/map), we fall back to the body type to recover fidelity
20320
+ * the IR drops. Body nullability is stripped — when a parameter group is
20321
+ * optional, the body field for the group becomes nullable, but within a
20322
+ * variant the leaf is always required (selecting the variant means passing it).
20323
+ */
20324
+ function collectVariantsForMountTarget(ctx, models, mountTarget) {
20325
+ const owner = buildGroupOwnerMap(ctx);
20326
+ const seen = /* @__PURE__ */ new Set();
20327
+ const out = [];
20328
+ const g = groupByMount(ctx).get(mountTarget);
20329
+ if (!g) return out;
20330
+ for (const op of g.operations) {
20331
+ const bodyFieldTypes = collectBodyFieldTypes(op, models);
20332
+ for (const group of op.parameterGroups ?? []) {
20333
+ if (owner.get(group.name) !== mountTarget) continue;
20334
+ for (const variant of group.variants) {
20335
+ const cls = groupVariantClassName(group.name, variant.name);
20336
+ if (seen.has(cls)) continue;
20337
+ seen.add(cls);
20338
+ out.push({
20339
+ className: cls,
20340
+ groupName: group.name,
20341
+ variantName: variant.name,
20342
+ mountTarget,
20343
+ parameters: variant.parameters.map((p) => ({
20344
+ name: p.name,
20345
+ type: pickVariantParamType(p.type, bodyFieldTypes.get(p.name))
20346
+ }))
20347
+ });
20348
+ }
20349
+ }
20350
+ }
20351
+ return out;
20352
+ }
20353
+ /**
20354
+ * Pick the type for a variant leaf parameter.
20355
+ *
20356
+ * Prefer the IR's leaf type. Use the body model's type only when the IR is a
20357
+ * bare primitive but the body has a structured type — that's the original
20358
+ * fidelity-recovery case the body fallback was added for. Strip any outer
20359
+ * nullable from the body type, since body nullability reflects the parent
20360
+ * group's optionality, not the leaf's required-ness within the variant.
20361
+ *
20362
+ * Exported so the test emitter can recover the same type the variant class
20363
+ * declares — IR primitives for fields like `role_slugs` would otherwise stub
20364
+ * as `"stub"` strings instead of the `["stub"]` arrays the class accepts.
20365
+ */
20366
+ function pickVariantParamType(irType, bodyType) {
20367
+ if (!bodyType) return irType;
20368
+ const unwrappedBody = bodyType.kind === "nullable" ? bodyType.inner : bodyType;
20369
+ const bodyIsStructured = unwrappedBody.kind === "array" || unwrappedBody.kind === "enum" || unwrappedBody.kind === "model" || unwrappedBody.kind === "map";
20370
+ if (irType.kind === "primitive" && bodyIsStructured) return unwrappedBody;
20371
+ return irType;
20372
+ }
20373
+ function readableName(name) {
20374
+ return name.replace(/_/g, " ");
20375
+ }
20376
+ /**
20377
+ * Render the inline `Data.define` block for a single variant, indented for
20378
+ * inclusion inside a `class <Service>` body. Returns an array of lines with
20379
+ * 4-space indent (the resource file's class members are 4-space indented).
20380
+ */
20381
+ function emitInlineVariantClass(v) {
20382
+ const lines = [];
20383
+ lines.push(` # Identifies the ${readableName(v.groupName)} (${readableName(v.variantName)} variant).`);
20384
+ lines.push(" #");
20385
+ for (const p of v.parameters) {
20386
+ const yardType = mapTypeRefForYard(p.type);
20387
+ lines.push(` # @!attribute [r] ${fieldName(p.name)}`);
20388
+ lines.push(` # @return [${yardType}]`);
20389
+ }
20390
+ if (v.parameters.length === 0) lines.push(` ${v.className} = Data.define`);
20391
+ else {
20392
+ const fields = v.parameters.map((p) => `:${fieldName(p.name)}`).join(", ");
20393
+ lines.push(` ${v.className} = Data.define(${fields})`);
20394
+ }
20395
+ return lines;
20396
+ }
20397
+ /**
20398
+ * Render the inline RBI `class` block for a single variant, indented for
20399
+ * inclusion inside a `class <Service>` body in a service .rbi file. Returns
20400
+ * lines with 4-space indent.
20401
+ */
20402
+ function emitInlineVariantRbi(v) {
20403
+ const lines = [];
20404
+ const fqcn = `WorkOS::${v.mountTarget}::${v.className}`;
20405
+ lines.push(` class ${v.className}`);
20406
+ for (const p of v.parameters) {
20407
+ lines.push(` sig { returns(${mapSorbetType$1(p.type)}) }`);
20408
+ lines.push(` def ${fieldName(p.name)}; end`);
20409
+ lines.push("");
20410
+ }
20411
+ if (v.parameters.length === 0) {
20412
+ lines.push(` sig { returns(${fqcn}) }`);
20413
+ lines.push(` def self.new; end`);
20414
+ } else {
20415
+ lines.push(" sig do");
20416
+ lines.push(" params(");
20417
+ for (let i = 0; i < v.parameters.length; i++) {
20418
+ const p = v.parameters[i];
20419
+ const sep = i === v.parameters.length - 1 ? "" : ",";
20420
+ lines.push(` ${fieldName(p.name)}: ${mapSorbetType$1(p.type)}${sep}`);
20421
+ }
20422
+ lines.push(` ).returns(${fqcn})`);
20423
+ lines.push(" end");
20424
+ const kwargs = v.parameters.map((p) => `${fieldName(p.name)}:`).join(", ");
20425
+ lines.push(` def self.new(${kwargs}); end`);
20426
+ }
20427
+ lines.push(" end");
20428
+ return lines;
20429
+ }
20430
+ //#endregion
20201
20431
  //#region src/ruby/resources.ts
20202
20432
  /**
20203
20433
  * Generate Ruby resource (service) classes from IR services.
@@ -20214,6 +20444,7 @@ function generateResources(services, ctx) {
20214
20444
  for (const m of ctx.spec.models) modelByName.set(m.name, m);
20215
20445
  const listWrapperModels = /* @__PURE__ */ new Map();
20216
20446
  for (const m of ctx.spec.models) if (isListWrapperModel(m)) listWrapperModels.set(m.name, m);
20447
+ const groupOwners = buildGroupOwnerMap(ctx);
20217
20448
  for (const [mountTarget, group] of groups) {
20218
20449
  const cls = className(mountTarget);
20219
20450
  const file = fileName(mountTarget);
@@ -20243,7 +20474,8 @@ function generateResources(services, ctx) {
20243
20474
  modelNames,
20244
20475
  modelByName,
20245
20476
  listWrapperModels,
20246
- requires
20477
+ requires,
20478
+ groupOwners
20247
20479
  });
20248
20480
  methodBodies.push(body);
20249
20481
  if (resolved?.wrappers && resolved.wrappers.length > 0) {
@@ -20264,6 +20496,11 @@ function generateResources(services, ctx) {
20264
20496
  }
20265
20497
  lines.push("module WorkOS");
20266
20498
  lines.push(` class ${cls}`);
20499
+ const variants = collectVariantsForMountTarget(ctx, ctx.spec.models, mountTarget);
20500
+ for (const v of variants) {
20501
+ for (const line of emitInlineVariantClass(v)) lines.push(line);
20502
+ lines.push("");
20503
+ }
20267
20504
  lines.push(" def initialize(client)");
20268
20505
  lines.push(" @client = client");
20269
20506
  lines.push(" end");
@@ -20284,13 +20521,19 @@ function generateResources(services, ctx) {
20284
20521
  }
20285
20522
  /** Build a single Ruby method from an Operation. */
20286
20523
  function emitMethod(args) {
20287
- const { op, method, defaults, inferFromClient, hiddenParams, enumNames, modelNames, modelByName, listWrapperModels, requires } = args;
20524
+ const { op, method, defaults, inferFromClient, hiddenParams, enumNames, modelNames, modelByName, listWrapperModels, requires, groupOwners } = args;
20525
+ /** Fully-qualified Ruby constant for a variant (e.g. WorkOS::UserManagement::PasswordPlaintext). */
20526
+ const variantClassRef = (group, variantName) => {
20527
+ const owner = groupOwners.get(group.name);
20528
+ if (!owner) throw new Error(`No owner mount target found for parameter group '${group.name}'`);
20529
+ return scopedGroupVariantClassName(owner, group.name, variantName);
20530
+ };
20288
20531
  planOperation(op);
20289
20532
  const lines = [];
20290
20533
  const pathParams = op.pathParams ?? [];
20291
20534
  const groupedParamNames = collectGroupedParamNames(op);
20292
20535
  const queryParams = (op.queryParams ?? []).filter((q) => !groupedParamNames.has(q.name));
20293
- const bodyFields = getRequestBodyFields(op, hiddenParams, modelByName);
20536
+ const bodyFields = getRequestBodyFields(op, hiddenParams, modelByName).filter((f) => !groupedParamNames.has(f.name));
20294
20537
  const pathParamNames = new Set(pathParams.map((p) => safeParamName(p.name)));
20295
20538
  const bodyFieldRenames = /* @__PURE__ */ new Map();
20296
20539
  for (const f of bodyFields) {
@@ -20358,7 +20601,7 @@ function emitMethod(args) {
20358
20601
  sigParts.push(`${n}: nil`);
20359
20602
  }
20360
20603
  sigParts.push("request_options: {}");
20361
- const doc = buildYardDoc(op, pathParams, queryParams, bodyFields, hiddenParams, bodyFieldRenames, listWrapperModels);
20604
+ const doc = buildYardDoc(op, pathParams, queryParams, bodyFields, hiddenParams, bodyFieldRenames, listWrapperModels, variantClassRef);
20362
20605
  for (const line of doc) lines.push(` ${line}`);
20363
20606
  if (sigParts.length === 0) lines.push(` def ${method}`);
20364
20607
  else if (sigParts.length === 1 && sigParts[0].length < 60) lines.push(` def ${method}(${sigParts[0]})`);
@@ -20384,28 +20627,32 @@ function emitMethod(args) {
20384
20627
  const groupsGoToQuery = hasGroups && !hasBodyMethod;
20385
20628
  const hasQuery = qEntries.length > 0 || groupsGoToQuery;
20386
20629
  if (hasQuery) {
20630
+ const queryCompact = qEntries.some((q) => !q.required) ? ".compact" : "";
20387
20631
  lines.push(" params = {");
20388
20632
  for (let i = 0; i < qEntries.length; i++) {
20389
20633
  const q = qEntries[i];
20390
20634
  const sep = i === qEntries.length - 1 && !groupsGoToQuery ? "" : ",";
20391
20635
  lines.push(` ${rubyStringLit(q.name)} => ${safeParamName(q.name)}${sep}`);
20392
20636
  }
20393
- lines.push(" }.compact");
20637
+ lines.push(` }${queryCompact}`);
20394
20638
  if (groupsGoToQuery) for (const group of op.parameterGroups ?? []) {
20395
20639
  const prop = fieldName(group.name);
20396
20640
  if (group.optional) {
20397
20641
  lines.push(` if ${prop}`);
20398
- lines.push(` case ${prop}[:type]`);
20399
- } else lines.push(` case ${prop}[:type]`);
20642
+ lines.push(` case ${prop}`);
20643
+ } else lines.push(` case ${prop}`);
20400
20644
  for (const variant of group.variants) {
20401
- lines.push(` when ${rubyStringLit(variant.name)}`);
20402
- for (const p of variant.parameters) lines.push(` params[${rubyStringLit(p.name)}] = ${prop}[:${fieldName(p.name)}]`);
20645
+ const variantClass = variantClassRef(group, variant.name);
20646
+ lines.push(` when ${variantClass}`);
20647
+ for (const p of variant.parameters) lines.push(` params[${rubyStringLit(p.name)}] = ${prop}.${fieldName(p.name)}`);
20403
20648
  }
20649
+ lines.push(` else`);
20650
+ lines.push(` raise ArgumentError, ${dispatchErrorLiteral(group, prop, variantClassRef)}`);
20404
20651
  lines.push(" end");
20405
20652
  if (group.optional) lines.push(" end");
20406
20653
  }
20407
20654
  }
20408
- const hasBody = bodyFields.length > 0 && !["get", "head"].includes(method_http);
20655
+ const hasBody = bodyFields.length > 0 && !["get", "head"].includes(method_http) || hasGroups && hasBodyMethod;
20409
20656
  if (hasBody) {
20410
20657
  const bodyEntries = [];
20411
20658
  for (const [k, v] of Object.entries(defaults)) {
@@ -20417,26 +20664,32 @@ function emitMethod(args) {
20417
20664
  const optKey = fc === "client_secret" ? "api_key" : fc;
20418
20665
  bodyEntries.push(`${rubyStringLit(fc)} => (request_options[:${optKey}] || @client.${clientProp})`);
20419
20666
  }
20667
+ let bodyHasNilable = false;
20420
20668
  for (const f of bodyFields) {
20421
20669
  if (hiddenParams.has(f.name)) continue;
20422
20670
  bodyEntries.push(`${rubyStringLit(f.name)} => ${bodyKwargName(f.name)}`);
20671
+ if (!f.required) bodyHasNilable = true;
20423
20672
  }
20673
+ const bodyCompact = bodyHasNilable ? ".compact" : "";
20424
20674
  lines.push(" body = {");
20425
20675
  for (let i = 0; i < bodyEntries.length; i++) {
20426
20676
  const sep = i === bodyEntries.length - 1 ? "" : ",";
20427
20677
  lines.push(` ${bodyEntries[i]}${sep}`);
20428
20678
  }
20429
- lines.push(" }.compact");
20679
+ lines.push(` }${bodyCompact}`);
20430
20680
  if (hasGroups && hasBodyMethod) for (const group of op.parameterGroups ?? []) {
20431
20681
  const prop = fieldName(group.name);
20432
20682
  if (group.optional) {
20433
20683
  lines.push(` if ${prop}`);
20434
- lines.push(` case ${prop}[:type]`);
20435
- } else lines.push(` case ${prop}[:type]`);
20684
+ lines.push(` case ${prop}`);
20685
+ } else lines.push(` case ${prop}`);
20436
20686
  for (const variant of group.variants) {
20437
- lines.push(` when ${rubyStringLit(variant.name)}`);
20438
- for (const p of variant.parameters) lines.push(` body[${rubyStringLit(p.name)}] = ${prop}[:${fieldName(p.name)}]`);
20687
+ const variantClass = variantClassRef(group, variant.name);
20688
+ lines.push(` when ${variantClass}`);
20689
+ for (const p of variant.parameters) lines.push(` body[${rubyStringLit(p.name)}] = ${prop}.${fieldName(p.name)}`);
20439
20690
  }
20691
+ lines.push(` else`);
20692
+ lines.push(` raise ArgumentError, ${dispatchErrorLiteral(group, prop, variantClassRef)}`);
20440
20693
  lines.push(" end");
20441
20694
  if (group.optional) lines.push(" end");
20442
20695
  }
@@ -20651,7 +20904,7 @@ function oneLine(desc) {
20651
20904
  if (!desc) return "";
20652
20905
  return desc.replace(/\r/g, " ").replace(/\n+/g, " ").replace(/\s+/g, " ").trim();
20653
20906
  }
20654
- function buildYardDoc(op, pathParams, queryParams, bodyFields, hiddenParams, bodyFieldRenames, listWrapperModels) {
20907
+ function buildYardDoc(op, pathParams, queryParams, bodyFields, hiddenParams, bodyFieldRenames, listWrapperModels, variantClassRef) {
20655
20908
  const lines = [];
20656
20909
  const firstLine = (op.description ?? `${op.httpMethod.toUpperCase()} ${op.path}`).split("\n")[0] ?? "";
20657
20910
  lines.push(`# ${firstLine}`);
@@ -20687,6 +20940,14 @@ function buildYardDoc(op, pathParams, queryParams, bodyFields, hiddenParams, bod
20687
20940
  const deprecatedPrefix = q.deprecated ? "(deprecated) " : "";
20688
20941
  lines.push(`# @param ${n} [${type}${suffix}] ${deprecatedPrefix}${oneLine(q.description)}`.trim());
20689
20942
  }
20943
+ for (const group of op.parameterGroups ?? []) {
20944
+ const n = fieldName(group.name);
20945
+ if (emittedParamNames.has(n)) continue;
20946
+ emittedParamNames.add(n);
20947
+ const variantTypes = group.variants.map((v) => variantClassRef(group, v.name)).join(", ");
20948
+ const suffix = group.optional ? ", nil" : "";
20949
+ lines.push(`# @param ${n} [${variantTypes}${suffix}] Identifies the ${group.name.replace(/_/g, " ")}.`);
20950
+ }
20690
20951
  lines.push(`# @param request_options [Hash] (see WorkOS::Types::RequestOptions)`);
20691
20952
  const ref = op.response;
20692
20953
  if (ref.kind === "primitive" && ref.type === "unknown") lines.push(`# @return [void]`);
@@ -20709,6 +20970,14 @@ function buildYardDoc(op, pathParams, queryParams, bodyFields, hiddenParams, bod
20709
20970
  function rubyStringLit(s) {
20710
20971
  return `'${s.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
20711
20972
  }
20973
+ /**
20974
+ * Build a Ruby double-quoted string expression for the `else raise ArgumentError`
20975
+ * arm of a parameter-group dispatcher. Lists the expected variant classes and
20976
+ * interpolates the actual class of the value the caller passed.
20977
+ */
20978
+ function dispatchErrorLiteral(group, prop, variantClassRef) {
20979
+ return `"expected ${prop} to be one of: ${group.variants.map((v) => variantClassRef(group, v.name)).join(", ")}, got #{${prop}.class}"`;
20980
+ }
20712
20981
  //#endregion
20713
20982
  //#region src/ruby/client.ts
20714
20983
  /**
@@ -20938,9 +21207,11 @@ function generateClientClass(spec, ctx) {
20938
21207
  function generateTests(spec, ctx) {
20939
21208
  const files = [];
20940
21209
  const groups = groupByMount(ctx);
21210
+ const models = spec.models;
20941
21211
  const modelByName = /* @__PURE__ */ new Map();
20942
- for (const m of spec.models) modelByName.set(m.name, m);
21212
+ for (const m of models) modelByName.set(m.name, m);
20943
21213
  const lookup = buildResolvedLookup(ctx);
21214
+ const groupOwners = buildGroupOwnerMap(ctx);
20944
21215
  for (const [mountTarget, group] of groups) {
20945
21216
  const cls = className(mountTarget);
20946
21217
  const prop = servicePropertyName(mountTarget);
@@ -20969,7 +21240,9 @@ function generateTests(spec, ctx) {
20969
21240
  op.httpMethod.toLowerCase();
20970
21241
  const httpMethodSym = `:${op.httpMethod.toLowerCase()}`;
20971
21242
  const resolved = lookupResolved(op, lookup);
20972
- const callArgs = buildCallArgsStub(op, modelByName, buildHiddenParams$1(resolved));
21243
+ const hiddenParams = buildHiddenParams$1(resolved);
21244
+ const callArgs = buildCallArgsStub(op, modelByName, hiddenParams, groupOwners, models);
21245
+ const bodyMatcher = buildBodyMatcher(op, modelByName, hiddenParams, models);
20973
21246
  authMethodManifest.push({
20974
21247
  method,
20975
21248
  httpMethodSym,
@@ -20978,23 +21251,47 @@ function generateTests(spec, ctx) {
20978
21251
  });
20979
21252
  const stubRegex = stubUrlRegex(stubUrl);
20980
21253
  lines.push(` def test_${method}_returns_expected_result`);
21254
+ lines.push(` stub_request(${httpMethodSym}, ${stubRegex})`);
21255
+ if (bodyMatcher) lines.push(` .with(body: ${bodyMatcher})`);
20981
21256
  if (isList) {
20982
- lines.push(` stub_request(${httpMethodSym}, ${stubRegex})`);
20983
21257
  lines.push(` .to_return(body: '{"data": [], "list_metadata": {}}', status: 200)`);
20984
21258
  lines.push(` result = @client.${prop}.${method}(${callArgs})`);
20985
21259
  lines.push(" assert_kind_of WorkOS::Types::ListStruct, result");
20986
21260
  } else if (op.response.kind === "primitive") {
20987
- lines.push(` stub_request(${httpMethodSym}, ${stubRegex})`);
20988
21261
  lines.push(` .to_return(body: "{}", status: 200)`);
20989
21262
  lines.push(` result = @client.${prop}.${method}(${callArgs})`);
20990
21263
  lines.push(" assert_nil result");
20991
21264
  } else {
20992
- lines.push(` stub_request(${httpMethodSym}, ${stubRegex})`);
20993
21265
  lines.push(` .to_return(body: "{}", status: 200)`);
20994
21266
  lines.push(` result = @client.${prop}.${method}(${callArgs})`);
20995
21267
  lines.push(" refute_nil result");
20996
21268
  }
20997
21269
  lines.push(" end");
21270
+ for (const group of op.parameterGroups ?? []) for (let vi = 1; vi < group.variants.length; vi++) {
21271
+ const variant = group.variants[vi];
21272
+ const overrides = new Map([[group.name, vi]]);
21273
+ const variantCallArgs = buildCallArgsStub(op, modelByName, hiddenParams, groupOwners, models, overrides);
21274
+ const variantBodyMatcher = buildBodyMatcher(op, modelByName, hiddenParams, models, overrides);
21275
+ const suffix = `with_${fieldName(group.name)}_${fieldName(variant.name)}`;
21276
+ lines.push("");
21277
+ lines.push(` def test_${method}_${suffix}_returns_expected_result`);
21278
+ lines.push(` stub_request(${httpMethodSym}, ${stubRegex})`);
21279
+ if (variantBodyMatcher) lines.push(` .with(body: ${variantBodyMatcher})`);
21280
+ if (isList) {
21281
+ lines.push(` .to_return(body: '{"data": [], "list_metadata": {}}', status: 200)`);
21282
+ lines.push(` result = @client.${prop}.${method}(${variantCallArgs})`);
21283
+ lines.push(" assert_kind_of WorkOS::Types::ListStruct, result");
21284
+ } else if (op.response.kind === "primitive") {
21285
+ lines.push(` .to_return(body: "{}", status: 200)`);
21286
+ lines.push(` result = @client.${prop}.${method}(${variantCallArgs})`);
21287
+ lines.push(" assert_nil result");
21288
+ } else {
21289
+ lines.push(` .to_return(body: "{}", status: 200)`);
21290
+ lines.push(` result = @client.${prop}.${method}(${variantCallArgs})`);
21291
+ lines.push(" refute_nil result");
21292
+ }
21293
+ lines.push(" end");
21294
+ }
20998
21295
  if (resolved?.wrappers && resolved.wrappers.length > 0) for (const wrapper of resolved.wrappers) emitWrapperTests({
20999
21296
  lines,
21000
21297
  wrapper,
@@ -21125,8 +21422,12 @@ function roundTripStub(ref, enumNames) {
21125
21422
  default: return `nil`;
21126
21423
  }
21127
21424
  }
21128
- /** Build minimal placeholder arguments for calling the SDK method from a test. */
21129
- function buildCallArgsStub(op, modelByName, hiddenParams) {
21425
+ /** Build minimal placeholder arguments for calling the SDK method from a test.
21426
+ * `variantOverrides` selects a non-zero variant index per group; absent groups
21427
+ * default to variant 0. Used to emit per-variant test cases that exercise the
21428
+ * second/third arm of each parameter-group dispatcher.
21429
+ */
21430
+ function buildCallArgsStub(op, modelByName, hiddenParams, groupOwners, models, variantOverrides = /* @__PURE__ */ new Map()) {
21130
21431
  const parts = [];
21131
21432
  const seen = /* @__PURE__ */ new Set();
21132
21433
  const pathParamNames = /* @__PURE__ */ new Set();
@@ -21158,17 +21459,68 @@ function buildCallArgsStub(op, modelByName, hiddenParams) {
21158
21459
  seen.add(name);
21159
21460
  parts.push(`${name}: ${stubValueFor(q.type)}`);
21160
21461
  }
21462
+ const bodyFieldTypes = collectBodyFieldTypes(op, models);
21161
21463
  for (const group of op.parameterGroups ?? []) {
21162
- if (group.optional) continue;
21163
21464
  const name = fieldName(group.name);
21164
21465
  if (seen.has(name)) continue;
21165
21466
  seen.add(name);
21166
- const firstVariant = group.variants[0];
21167
- if (firstVariant) parts.push(`${name}: { type: "${firstVariant.name}" }`);
21168
- else parts.push(`${name}: {}`);
21467
+ const idx = variantOverrides.get(group.name) ?? 0;
21468
+ const variant = group.variants[idx];
21469
+ if (variant) {
21470
+ const owner = groupOwners.get(group.name);
21471
+ if (!owner) throw new Error(`No owner mount target found for parameter group '${group.name}'`);
21472
+ const variantClass = scopedGroupVariantClassName(owner, group.name, variant.name);
21473
+ const fieldStubs = variant.parameters.map((p) => `${fieldName(p.name)}: ${stubValueFor(pickVariantParamType(p.type, bodyFieldTypes.get(p.name)))}`).join(", ");
21474
+ parts.push(`${name}: ${variantClass}.new(${fieldStubs})`);
21475
+ }
21169
21476
  }
21170
21477
  return parts.join(", ");
21171
21478
  }
21479
+ /**
21480
+ * Build a Ruby `hash_including(...)` matcher describing the wire body the
21481
+ * SDK should send for an operation whose body is constructed (in part) by a
21482
+ * parameter-group dispatcher. Returns `null` for operations without body
21483
+ * groups — those are still stubbed without a body matcher.
21484
+ *
21485
+ * The matcher includes every required non-group body field plus the first
21486
+ * variant's wire-name leaves for each group dispatched into the body. This
21487
+ * catches regressions where the dispatcher silently drops a passed group
21488
+ * (the original `update_organization_membership` regression).
21489
+ */
21490
+ function buildBodyMatcher(op, modelByName, hiddenParams, models, variantOverrides = /* @__PURE__ */ new Map()) {
21491
+ const httpMethod = op.httpMethod.toLowerCase();
21492
+ const hasBodyMethod = ![
21493
+ "get",
21494
+ "head",
21495
+ "delete"
21496
+ ].includes(httpMethod);
21497
+ const hasGroups = (op.parameterGroups?.length ?? 0) > 0;
21498
+ if (!hasBodyMethod || !hasGroups) return null;
21499
+ const groupedParamNames = /* @__PURE__ */ new Set();
21500
+ for (const group of op.parameterGroups ?? []) for (const variant of group.variants) for (const p of variant.parameters) groupedParamNames.add(p.name);
21501
+ const entries = [];
21502
+ if (op.requestBody) {
21503
+ const bodyModel = resolveBodyModel(op.requestBody, modelByName);
21504
+ if (bodyModel) for (const f of bodyModel.fields) {
21505
+ if (!f.required) continue;
21506
+ if (hiddenParams.has(f.name)) continue;
21507
+ if (groupedParamNames.has(f.name)) continue;
21508
+ entries.push(`"${f.name}" => ${stubValueFor(f.type)}`);
21509
+ }
21510
+ }
21511
+ const bodyFieldTypes = collectBodyFieldTypes(op, models);
21512
+ for (const group of op.parameterGroups ?? []) {
21513
+ const idx = variantOverrides.get(group.name) ?? 0;
21514
+ const variant = group.variants[idx];
21515
+ if (!variant) continue;
21516
+ for (const p of variant.parameters) {
21517
+ const recovered = pickVariantParamType(p.type, bodyFieldTypes.get(p.name));
21518
+ entries.push(`"${p.name}" => ${stubValueFor(recovered)}`);
21519
+ }
21520
+ }
21521
+ if (entries.length === 0) return null;
21522
+ return `hash_including(${entries.join(", ")})`;
21523
+ }
21172
21524
  function resolveBodyModel(ref, modelByName) {
21173
21525
  if (ref.kind === "model") return modelByName.get(ref.name) ?? null;
21174
21526
  if (ref.kind === "nullable") return resolveBodyModel(ref.inner, modelByName);
@@ -21222,7 +21574,7 @@ function stubValueFor(ref) {
21222
21574
  case "boolean": return `true`;
21223
21575
  default: return `nil`;
21224
21576
  }
21225
- case "array": return `[]`;
21577
+ case "array": return `[${stubValueFor(ref.items)}]`;
21226
21578
  case "map": return `{}`;
21227
21579
  case "enum": return `"stub"`;
21228
21580
  case "literal":
@@ -21357,6 +21709,7 @@ function generateRbiFiles(spec, ctx) {
21357
21709
  for (const m of spec.models) modelByName.set(m.name, m);
21358
21710
  const listWrapperModels = /* @__PURE__ */ new Map();
21359
21711
  for (const m of spec.models) if (isListWrapperModel(m)) listWrapperModels.set(m.name, m);
21712
+ const groupOwners = buildGroupOwnerMap(ctx);
21360
21713
  for (const [mountTarget, group] of groups) {
21361
21714
  const cls = className(mountTarget);
21362
21715
  const lines = [];
@@ -21364,6 +21717,11 @@ function generateRbiFiles(spec, ctx) {
21364
21717
  lines.push("");
21365
21718
  lines.push("module WorkOS");
21366
21719
  lines.push(` class ${cls}`);
21720
+ const variants = collectVariantsForMountTarget(ctx, spec.models, mountTarget);
21721
+ for (const v of variants) {
21722
+ for (const line of emitInlineVariantRbi(v)) lines.push(line);
21723
+ lines.push("");
21724
+ }
21367
21725
  lines.push(" sig { params(client: WorkOS::BaseClient).void }");
21368
21726
  lines.push(" def initialize(client); end");
21369
21727
  lines.push("");
@@ -21380,7 +21738,15 @@ function generateRbiFiles(spec, ctx) {
21380
21738
  const hiddenParams = buildHiddenParams$1(resolved);
21381
21739
  const groupedParamNames = collectGroupedParamNames(op);
21382
21740
  const queryParams = (op.queryParams ?? []).filter((q) => !groupedParamNames.has(q.name));
21383
- const bodyFields = getRequestBodyFieldsFlat(op, hiddenParams, modelByName);
21741
+ const bodyFields = getRequestBodyFieldsFlat(op, hiddenParams, modelByName).filter((f) => !groupedParamNames.has(f.name));
21742
+ const parameterGroups = op.parameterGroups ?? [];
21743
+ const groupSorbetType = (group) => {
21744
+ const owner = groupOwners.get(group.name);
21745
+ if (!owner) throw new Error(`No owner mount target found for parameter group '${group.name}'`);
21746
+ const variants = group.variants.map((v) => scopedGroupVariantClassName(owner, group.name, v.name));
21747
+ if (variants.length === 1) return variants[0];
21748
+ return `T.any(${variants.join(", ")})`;
21749
+ };
21384
21750
  const sigParams = [];
21385
21751
  const seen = /* @__PURE__ */ new Set();
21386
21752
  for (const p of op.pathParams ?? []) {
@@ -21405,6 +21771,13 @@ function generateRbiFiles(spec, ctx) {
21405
21771
  seen.add(n);
21406
21772
  sigParams.push(`${n}: ${mapSorbetType(q.type)}`);
21407
21773
  }
21774
+ for (const group of parameterGroups) {
21775
+ if (group.optional) continue;
21776
+ const n = fieldName(group.name);
21777
+ if (seen.has(n)) continue;
21778
+ seen.add(n);
21779
+ sigParams.push(`${n}: ${groupSorbetType(group)}`);
21780
+ }
21408
21781
  for (const f of bodyFields) {
21409
21782
  if (hiddenParams.has(f.name)) continue;
21410
21783
  if (f.required) continue;
@@ -21421,6 +21794,13 @@ function generateRbiFiles(spec, ctx) {
21421
21794
  seen.add(n);
21422
21795
  sigParams.push(`${n}: T.nilable(${unwrapNilable(mapSorbetType(q.type))})`);
21423
21796
  }
21797
+ for (const group of parameterGroups) {
21798
+ if (!group.optional) continue;
21799
+ const n = fieldName(group.name);
21800
+ if (seen.has(n)) continue;
21801
+ seen.add(n);
21802
+ sigParams.push(`${n}: T.nilable(${groupSorbetType(group)})`);
21803
+ }
21424
21804
  sigParams.push("request_options: T::Hash[Symbol, T.untyped]");
21425
21805
  const retType = mapSorbetReturnType(op.response, listWrapperModels, modelNames);
21426
21806
  lines.push(" sig do");
@@ -21595,4 +21975,4 @@ const workosEmittersPlugin = {
21595
21975
  //#endregion
21596
21976
  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 };
21597
21977
 
21598
- //# sourceMappingURL=plugin-BgVrq-hM.mjs.map
21978
+ //# sourceMappingURL=plugin-Cmg_LFtm.mjs.map