@workos/oagen-emitters 0.8.2 → 0.9.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.
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.8.2"
2
+ ".": "0.9.0"
3
3
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.9.0](https://github.com/workos/oagen-emitters/compare/v0.8.2...v0.9.0) (2026-05-06)
4
+
5
+
6
+ ### Features
7
+
8
+ * **kotlin:** improve emitter output for SDK review feedback ([#90](https://github.com/workos/oagen-emitters/issues/90)) ([14180eb](https://github.com/workos/oagen-emitters/commit/14180eb7919a89a9b08406bc6bc06a35df887e65))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **kotlin:** make ktlintFormat actually run after generation ([#88](https://github.com/workos/oagen-emitters/issues/88)) ([6082a45](https://github.com/workos/oagen-emitters/commit/6082a45ab2b247bbf4630cd0acc6dba5238f5d26))
14
+
3
15
  ## [0.8.2](https://github.com/workos/oagen-emitters/compare/v0.8.1...v0.8.2) (2026-05-05)
4
16
 
5
17
 
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-CeNME04k.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-Dh9JSScr.mjs";
2
2
  export { dotnetEmitter, dotnetExtractor, elixirExtractor, goEmitter, goExtractor, kotlinEmitter, kotlinExtractor, nodeEmitter, nodeExtractor, phpEmitter, phpExtractor, pythonEmitter, pythonExtractor, rubyEmitter, rubyExtractor, rustExtractor, workosEmittersPlugin };
@@ -17397,9 +17397,29 @@ function findDotnetWorkspace(targetDir) {
17397
17397
  }
17398
17398
  //#endregion
17399
17399
  //#region src/kotlin/naming.ts
17400
+ /**
17401
+ * Acronyms that should appear fully uppercase in PascalCase identifiers.
17402
+ * `toPascalCase` would otherwise titlecase them (e.g. `Sso`, `Pkce`, `Mfa`).
17403
+ * Both [className] and [apiClassName] consult this list so model and service
17404
+ * class names stay consistent (e.g. `SsoConnection` -> `SSOConnection`).
17405
+ */
17406
+ const PASCAL_ACRONYMS = [
17407
+ "SSO",
17408
+ "PKCE",
17409
+ "MFA"
17410
+ ];
17411
+ function uppercaseAcronyms(pascal) {
17412
+ let result = pascal;
17413
+ for (const acronym of PASCAL_ACRONYMS) {
17414
+ const titled = acronym.charAt(0) + acronym.slice(1).toLowerCase();
17415
+ const re = new RegExp(`${titled}(?=[A-Z0-9]|$)`, "g");
17416
+ result = result.replace(re, acronym);
17417
+ }
17418
+ return result;
17419
+ }
17400
17420
  /** PascalCase class/type name. */
17401
17421
  function className$1(name) {
17402
- return toPascalCase(stripUrnPrefix(name));
17422
+ return uppercaseAcronyms(toPascalCase(stripUrnPrefix(name)));
17403
17423
  }
17404
17424
  /** camelCase method name. */
17405
17425
  function methodName(name) {
@@ -17417,7 +17437,6 @@ function packageSegment(name) {
17417
17437
  }
17418
17438
  /** Kotlin service class name for a mount group (e.g., `Organizations`). */
17419
17439
  function apiClassName(name) {
17420
- if (className$1(name) === "SSO") return "Sso";
17421
17440
  return className$1(name);
17422
17441
  }
17423
17442
  /** Accessor property exposed on the WorkOS client (camelCase). */
@@ -17528,6 +17547,33 @@ function clientFieldExpression(field) {
17528
17547
  function humanize(name) {
17529
17548
  return name.replace(/_/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase();
17530
17549
  }
17550
+ /**
17551
+ * If the parameter is typed as an enum and its description is long enough
17552
+ * that repeating it across every method's KDoc adds noise, return a short
17553
+ * description that defers to the enum's own KDoc with a `See [EnumName].`
17554
+ * reference. Otherwise return null and the caller keeps the spec text.
17555
+ *
17556
+ * Currently only `PaginationOrder` is shortened — the SSO/MFA/etc. enums
17557
+ * have brief descriptions already. Generalize when other long-description
17558
+ * enums appear in the spec.
17559
+ */
17560
+ const LONG_ENUM_DESC_THRESHOLD = 120;
17561
+ function maybeShortenEnumParamDescription(type, description) {
17562
+ if (!type || !description) return null;
17563
+ const enumName = extractEnumName(type);
17564
+ if (!enumName) return null;
17565
+ if (description.length <= LONG_ENUM_DESC_THRESHOLD) return null;
17566
+ if (className$1(enumName) !== "PaginationOrder") return null;
17567
+ return {
17568
+ description: `the order to return records in. See [${className$1(enumName)}].`,
17569
+ enumRef: className$1(enumName)
17570
+ };
17571
+ }
17572
+ function extractEnumName(type) {
17573
+ if (type.kind === "enum") return type.name;
17574
+ if (type.kind === "nullable") return extractEnumName(type.inner);
17575
+ return null;
17576
+ }
17531
17577
  //#endregion
17532
17578
  //#region src/kotlin/enums.ts
17533
17579
  const KOTLIN_SRC_PREFIX$3 = "src/main/kotlin/";
@@ -17632,15 +17678,20 @@ function generateEnums$1(enums, _ctx) {
17632
17678
  if (v.deprecated) members.push(" @Deprecated(\"Deprecated enum value\")");
17633
17679
  members.push(` ${memberName}(${ktStringLiteral(wire)})`);
17634
17680
  }
17681
+ let firstValueEmitted = false;
17635
17682
  for (let i = 0; i < members.length; i++) {
17636
17683
  const isLast = i === members.length - 1;
17637
17684
  const line = members[i];
17638
17685
  const trimmedStart = line.trimStart();
17639
- if (trimmedStart.startsWith("/**") || trimmedStart.startsWith("@")) {
17686
+ const isDocStart = trimmedStart.startsWith("/**");
17687
+ const isAnnotation = trimmedStart.startsWith("@");
17688
+ if (isDocStart && firstValueEmitted) lines.push("");
17689
+ if (isDocStart || isAnnotation) {
17640
17690
  lines.push(line);
17641
17691
  continue;
17642
17692
  }
17643
17693
  lines.push(isLast ? line : `${line},`);
17694
+ firstValueEmitted = true;
17644
17695
  }
17645
17696
  lines.push("}");
17646
17697
  lines.push("");
@@ -17912,6 +17963,7 @@ function emitDataClass(model) {
17912
17963
  for (let i = 0; i < rendered.length; i++) {
17913
17964
  const suffix = i === rendered.length - 1 ? "" : ",";
17914
17965
  lines.push(`${rendered[i]}${suffix}`);
17966
+ if (i < rendered.length - 1) lines.push("");
17915
17967
  }
17916
17968
  lines.push(`)${implClause}`);
17917
17969
  lines.push("");
@@ -17929,7 +17981,29 @@ function emitSealedUnion(typeName, disc) {
17929
17981
  lines.push("import com.fasterxml.jackson.annotation.JsonSubTypes");
17930
17982
  lines.push("import com.fasterxml.jackson.annotation.JsonTypeInfo");
17931
17983
  lines.push("");
17932
- appendKdoc(lines, `Discriminated union over ${typeName} variants. Selected by \`${disc.property}\`.`, 0);
17984
+ const exampleVariantWire = Object.keys(disc.mapping)[0];
17985
+ const exampleVariantType = exampleVariantWire ? className$1(disc.mapping[exampleVariantWire]) : null;
17986
+ lines.push("/**");
17987
+ lines.push(` * Discriminated union over ${typeName} variants. Selected by \`${disc.property}\`.`);
17988
+ if (exampleVariantType) {
17989
+ lines.push(" *");
17990
+ lines.push(" * Usage from Kotlin:");
17991
+ lines.push(" * ```kotlin");
17992
+ lines.push(` * when (val v: ${typeName} = receivedFromApi()) {`);
17993
+ lines.push(` * is ${exampleVariantType} -> handle(v)`);
17994
+ lines.push(" * else -> handleOther(v)");
17995
+ lines.push(" * }");
17996
+ lines.push(" * ```");
17997
+ lines.push(" *");
17998
+ lines.push(" * Usage from Java:");
17999
+ lines.push(" * ```java");
18000
+ lines.push(` * ${typeName} v = receivedFromApi();`);
18001
+ lines.push(` * if (v instanceof ${exampleVariantType}) {`);
18002
+ lines.push(` * handle((${exampleVariantType}) v);`);
18003
+ lines.push(" * }");
18004
+ lines.push(" * ```");
18005
+ }
18006
+ lines.push(" */");
17933
18007
  lines.push("@JsonTypeInfo(");
17934
18008
  lines.push(" use = JsonTypeInfo.Id.NAME,");
17935
18009
  lines.push(" include = JsonTypeInfo.As.EXISTING_PROPERTY,");
@@ -18230,6 +18304,81 @@ function escapeKotlinStringLiteral(literal) {
18230
18304
  return literal.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\$/g, "\\$");
18231
18305
  }
18232
18306
  //#endregion
18307
+ //#region src/kotlin/suspend.ts
18308
+ /**
18309
+ * Helpers for emitting `suspend` overloads alongside every generated
18310
+ * blocking SDK method. The suspend variant simply delegates to the blocking
18311
+ * implementation under `withContext(Dispatchers.IO)`, so callers can invoke
18312
+ * any operation from a coroutine context without blocking the calling
18313
+ * dispatcher.
18314
+ *
18315
+ * Naming: the suspend variant uses a `Suspend`-suffixed Kotlin source name
18316
+ * (e.g. `deleteEndpointSuspend`). This matters because Kotlin does not let
18317
+ * a `suspend` function and a non-`suspend` function with the same name and
18318
+ * identical value parameters coexist in the same scope — they are not
18319
+ * distinguishable at call sites, even with `@JvmName`. (`@JvmName` only
18320
+ * disambiguates JVM signatures, not Kotlin source names.) Naming the suspend
18321
+ * variant explicitly sidesteps the conflict and makes the choice between
18322
+ * blocking and suspending callable obvious at the call site. The matching
18323
+ * `@JvmName("...Suspend")` is now technically redundant but kept for
18324
+ * explicit clarity in Java interop / tooling.
18325
+ */
18326
+ /**
18327
+ * Emit a suspend overload that delegates to a blocking method.
18328
+ *
18329
+ * The emitted lines preserve the parameter declarations (including default
18330
+ * values) so callers can invoke the suspend variant with named arguments and
18331
+ * skip optional parameters, just as they do with the blocking version.
18332
+ *
18333
+ * The emitted suspend method is named `${methodName}Suspend` (a distinct
18334
+ * Kotlin source name from the blocking method) — see the file-level KDoc for
18335
+ * why this is required.
18336
+ */
18337
+ function emitSuspendVariant(opts) {
18338
+ const { methodName, params, returnType, deprecated } = opts;
18339
+ const suspendName = `${methodName}Suspend`;
18340
+ const lines = [];
18341
+ lines.push(" /**");
18342
+ lines.push(` * Coroutine-aware variant of [${escapeReserved(methodName)}]. Use this from`);
18343
+ lines.push(" * a `suspend` function or coroutine scope.");
18344
+ lines.push(" *");
18345
+ lines.push(` * Delegates to the blocking [${escapeReserved(methodName)}] under`);
18346
+ lines.push(" * `withContext(Dispatchers.IO)`, so this is safe to call from any");
18347
+ lines.push(" * coroutine dispatcher (including `Dispatchers.Main`).");
18348
+ lines.push(" */");
18349
+ if (deprecated) lines.push(" @Deprecated(\"Deprecated operation\")");
18350
+ lines.push(` @JvmName(${jvmNameLiteral(suspendName)})`);
18351
+ const returnClause = returnType === "Unit" ? "" : `: ${returnType}`;
18352
+ const callArgs = params.map((p) => p.name).join(", ");
18353
+ if (params.length === 0) {
18354
+ lines.push(` suspend fun ${escapeReserved(suspendName)}()${returnClause} = withContext(Dispatchers.IO) {`);
18355
+ lines.push(` ${escapeReserved(methodName)}()`);
18356
+ lines.push(" }");
18357
+ return lines;
18358
+ }
18359
+ if (params.length === 1) {
18360
+ const single = params[0].decl.replace(/^\s+/, "");
18361
+ lines.push(` suspend fun ${escapeReserved(suspendName)}(${single})${returnClause} = withContext(Dispatchers.IO) {`);
18362
+ } else {
18363
+ lines.push(` suspend fun ${escapeReserved(suspendName)}(`);
18364
+ for (let i = 0; i < params.length; i++) {
18365
+ const suffix = i === params.length - 1 ? "" : ",";
18366
+ lines.push(`${params[i].decl}${suffix}`);
18367
+ }
18368
+ lines.push(` )${returnClause} = withContext(Dispatchers.IO) {`);
18369
+ }
18370
+ lines.push(` ${escapeReserved(methodName)}(${callArgs})`);
18371
+ lines.push(" }");
18372
+ return lines;
18373
+ }
18374
+ /**
18375
+ * Imports a service file needs to declare any suspend overloads.
18376
+ */
18377
+ const SUSPEND_IMPORTS = ["kotlinx.coroutines.Dispatchers", "kotlinx.coroutines.withContext"];
18378
+ function jvmNameLiteral(name) {
18379
+ return JSON.stringify(name);
18380
+ }
18381
+ //#endregion
18233
18382
  //#region src/kotlin/wrappers.ts
18234
18383
  /**
18235
18384
  * Emit Kotlin wrapper methods for a union-split operation. Each wrapper
@@ -18262,14 +18411,16 @@ function emitWrapperMethod$1(resolvedOp, wrapper, ctx) {
18262
18411
  if (opDesc) kdocLines.push(opDesc.split("\n")[0]);
18263
18412
  else kdocLines.push(`${wrapperHumanName.charAt(0).toUpperCase()}${wrapperHumanName.slice(1)}.`);
18264
18413
  const paramDocs = [];
18265
- const pushParamDoc = (kotlinName, sourceName, description) => {
18414
+ const pushParamDoc = (kotlinName, sourceName, description, type) => {
18266
18415
  const firstLine = description?.split("\n").find((l) => l.trim())?.trim() ?? "";
18267
18416
  const fallback = `the ${humanize(sourceName)} of the request.`;
18268
- const text = firstLine || fallback;
18417
+ let text = firstLine || fallback;
18418
+ const shortened = maybeShortenEnumParamDescription(type, text);
18419
+ if (shortened) text = shortened.description;
18269
18420
  paramDocs.push(`@param ${kotlinName} ${escapeKdoc$1(text)}`);
18270
18421
  };
18271
- for (const pp of pathParams) pushParamDoc(propertyName(pp.name), pp.name, pp.description);
18272
- for (const rp of resolvedParams) pushParamDoc(propertyName(rp.paramName), rp.paramName, rp.field?.description);
18422
+ for (const pp of pathParams) pushParamDoc(propertyName(pp.name), pp.name, pp.description, pp.type);
18423
+ for (const rp of resolvedParams) pushParamDoc(propertyName(rp.paramName), rp.paramName, rp.field?.description, rp.field?.type);
18273
18424
  pushParamDoc("requestOptions", "request_options", "per-request overrides (idempotency key, API key, headers, timeout)");
18274
18425
  if (responseClass) paramDocs.push(`@return the ${responseClass}`);
18275
18426
  lines.push(" /**");
@@ -18279,14 +18430,29 @@ function emitWrapperMethod$1(resolvedOp, wrapper, ctx) {
18279
18430
  lines.push(" */");
18280
18431
  lines.push(" @JvmOverloads");
18281
18432
  const params = [];
18282
- for (const pp of pathParams) params.push(` ${propertyName(pp.name)}: String`);
18433
+ const suspendParams = [];
18434
+ for (const pp of pathParams) {
18435
+ const decl = ` ${propertyName(pp.name)}: String`;
18436
+ params.push(decl);
18437
+ suspendParams.push({
18438
+ decl,
18439
+ name: propertyName(pp.name)
18440
+ });
18441
+ }
18283
18442
  for (const rp of resolvedParams) {
18284
18443
  const paramName = propertyName(rp.paramName);
18285
- const kotlinType = rp.field ? rp.isOptional ? mapTypeRefOptional(rp.field.type) : mapTypeRef$1(rp.field.type) : rp.isOptional ? "String?" : "String";
18286
- const trailer = rp.isOptional ? " = null" : "";
18287
- params.push(` ${paramName}: ${kotlinType}${trailer}`);
18444
+ const decl = ` ${paramName}: ${rp.field ? rp.isOptional ? mapTypeRefOptional(rp.field.type) : mapTypeRef$1(rp.field.type) : rp.isOptional ? "String?" : "String"}${rp.isOptional ? " = null" : ""}`;
18445
+ params.push(decl);
18446
+ suspendParams.push({
18447
+ decl,
18448
+ name: paramName
18449
+ });
18288
18450
  }
18289
18451
  params.push(" requestOptions: RequestOptions? = null");
18452
+ suspendParams.push({
18453
+ decl: " requestOptions: RequestOptions? = null",
18454
+ name: "requestOptions"
18455
+ });
18290
18456
  const returnClause = responseClass ? `: ${responseClass}` : "";
18291
18457
  if (params.length === 1) {
18292
18458
  const single = params[0].replace(/^\s+/, "");
@@ -18316,6 +18482,7 @@ function emitWrapperMethod$1(resolvedOp, wrapper, ctx) {
18316
18482
  }
18317
18483
  lines.push(` )`);
18318
18484
  lines.push(" }");
18485
+ appendSuspendVariant(lines, method, suspendParams, responseClass ?? "Unit");
18319
18486
  return lines;
18320
18487
  }
18321
18488
  const bodyEntries = [];
@@ -18347,8 +18514,17 @@ function emitWrapperMethod$1(resolvedOp, wrapper, ctx) {
18347
18514
  if (responseClass) lines.push(` return workos.baseClient.request(config, ${responseClass}::class.java)`);
18348
18515
  else lines.push(` workos.baseClient.requestVoid(config)`);
18349
18516
  lines.push(" }");
18517
+ appendSuspendVariant(lines, method, suspendParams, responseClass ?? "Unit");
18350
18518
  return lines;
18351
18519
  }
18520
+ function appendSuspendVariant(lines, method, suspendParams, returnType) {
18521
+ lines.push("");
18522
+ for (const ln of emitSuspendVariant({
18523
+ methodName: method,
18524
+ params: suspendParams,
18525
+ returnType
18526
+ })) lines.push(ln);
18527
+ }
18352
18528
  function escapeKdoc$1(s) {
18353
18529
  return s.replace(/\*\//g, "*​/");
18354
18530
  }
@@ -18453,6 +18629,7 @@ function generateApiClass(mountName, operations, ctx, resolvedLookup) {
18453
18629
  imports.add("com.workos.common.http.Page");
18454
18630
  imports.add("com.workos.common.http.RequestConfig");
18455
18631
  imports.add("com.workos.common.http.RequestOptions");
18632
+ for (const imp of SUSPEND_IMPORTS) imports.add(imp);
18456
18633
  const body = [];
18457
18634
  const seenMethods = /* @__PURE__ */ new Set();
18458
18635
  if (operations.some((op) => op.path === "/user_management/authenticate" && op.httpMethod.toUpperCase() === "POST")) {
@@ -18507,15 +18684,21 @@ function generateApiClass(mountName, operations, ctx, resolvedLookup) {
18507
18684
  lines.push("");
18508
18685
  for (const line of sealedLines) lines.push(line);
18509
18686
  const serviceDescription = resolveServiceDescription(ctx, mountName, operations);
18687
+ const suspendNote = "Every operation on this class is available in two flavors: a blocking variant (`<methodName>`) and a coroutine-aware variant (`<methodName>Suspend`). The `Suspend` variants delegate to the blocking ones under `withContext(Dispatchers.IO)`, so they are safe to call from any coroutine dispatcher (including `Dispatchers.Main`).";
18510
18688
  if (serviceDescription) {
18511
18689
  const docLines = serviceDescription.trim().split("\n");
18512
- if (docLines.length === 1) lines.push(`/** ${escapeKdoc(docLines[0].trim())} */`);
18513
- else {
18514
- lines.push("/**");
18515
- for (const l of docLines) lines.push(l ? ` * ${escapeKdoc(l)}` : " *");
18516
- lines.push(" */");
18517
- }
18518
- } else lines.push(`/** API accessor for ${mountName}. */`);
18690
+ lines.push("/**");
18691
+ for (const l of docLines) lines.push(l ? ` * ${escapeKdoc(l)}` : " *");
18692
+ lines.push(" *");
18693
+ lines.push(` * ${escapeKdoc(suspendNote)}`);
18694
+ lines.push(" */");
18695
+ } else {
18696
+ lines.push("/**");
18697
+ lines.push(` * API accessor for ${mountName}.`);
18698
+ lines.push(" *");
18699
+ lines.push(` * ${escapeKdoc(suspendNote)}`);
18700
+ lines.push(" */");
18701
+ }
18519
18702
  lines.push(`class ${apiClass}(`);
18520
18703
  lines.push(" internal val workos: WorkOS");
18521
18704
  lines.push(`) {`);
@@ -18586,14 +18769,22 @@ function renderMethod(_mountName, method, op, ctx, resolvedOp, imports) {
18586
18769
  }
18587
18770
  const groupParamNames = assignGroupParameterNames$1(op, paramNames);
18588
18771
  const params = [];
18589
- for (const pp of pathParams) params.push(` ${propertyName(pp.name)}: String`);
18772
+ const suspendParams = [];
18773
+ const pushParam = (decl, name) => {
18774
+ params.push(decl);
18775
+ suspendParams.push({
18776
+ decl,
18777
+ name
18778
+ });
18779
+ };
18780
+ for (const pp of pathParams) pushParam(` ${propertyName(pp.name)}: String`, propertyName(pp.name));
18590
18781
  const sortedQuery = [...uniqueQuery].sort((a, b) => a.required === b.required ? 0 : a.required ? -1 : 1);
18591
- for (const qp of sortedQuery) params.push(renderParam(qp.name, qp.type, qp.required, method.startsWith("list") && qp.name === "limit"));
18782
+ for (const qp of sortedQuery) pushParam(renderParam(qp.name, qp.type, qp.required, method.startsWith("list") && qp.name === "limit"), propertyName(qp.name));
18592
18783
  for (const group of op.parameterGroups ?? []) {
18593
18784
  const sealedName = sealedGroupName$1(group.name);
18594
18785
  const prop = groupParamNames.get(group.name);
18595
- if (group.optional) params.push(` ${prop}: ${sealedName}? = null`);
18596
- else params.push(` ${prop}: ${sealedName}`);
18786
+ if (group.optional) pushParam(` ${prop}: ${sealedName}? = null`, prop);
18787
+ else pushParam(` ${prop}: ${sealedName}`, prop);
18597
18788
  }
18598
18789
  const isPatch = httpMethod === "PATCH";
18599
18790
  const sortedBodyFields = [...bodyFields].sort((a, b) => a.required === b.required ? 0 : a.required ? -1 : 1);
@@ -18602,10 +18793,10 @@ function renderMethod(_mountName, method, op, ctx, resolvedOp, imports) {
18602
18793
  if (isPatch && !bf.required) {
18603
18794
  const baseType = mapTypeRef$1(bf.type);
18604
18795
  imports.add("com.workos.common.http.PatchField");
18605
- params.push(` ${bodyParamNames.get(bf.name)}: PatchField<${baseType}> = PatchField.Absent`);
18606
- } else params.push(renderParamNamed(bodyParamNames.get(bf.name), bf.type, bf.required));
18796
+ pushParam(` ${bodyParamNames.get(bf.name)}: PatchField<${baseType}> = PatchField.Absent`, bodyParamNames.get(bf.name));
18797
+ } else pushParam(renderParamNamed(bodyParamNames.get(bf.name), bf.type, bf.required), bodyParamNames.get(bf.name));
18607
18798
  }
18608
- params.push(" requestOptions: RequestOptions? = null");
18799
+ pushParam(" requestOptions: RequestOptions? = null", "requestOptions");
18609
18800
  const returnType = resolveReturnType(plan, imports, ctx);
18610
18801
  const isPaginated = plan.isPaginated && paginatedItemName !== null;
18611
18802
  const lines = [];
@@ -18649,6 +18840,7 @@ function renderMethod(_mountName, method, op, ctx, resolvedOp, imports) {
18649
18840
  }
18650
18841
  lines.push(` )`);
18651
18842
  lines.push(" }");
18843
+ appendSuspendVariantLines(lines, method, suspendParams, returnType, op.deprecated);
18652
18844
  return lines.join("\n");
18653
18845
  }
18654
18846
  if (isPaginated) {
@@ -18732,8 +18924,123 @@ function renderMethod(_mountName, method, op, ctx, resolvedOp, imports) {
18732
18924
  else lines.push(` workos.baseClient.requestVoid(config)`);
18733
18925
  }
18734
18926
  lines.push(" }");
18927
+ appendSuspendVariantLines(lines, method, suspendParams, returnType, op.deprecated);
18928
+ appendJavaFriendlyVariantOverloads(lines, {
18929
+ method,
18930
+ op,
18931
+ canonicalParams: suspendParams,
18932
+ groupParamNames,
18933
+ returnType,
18934
+ deprecated: op.deprecated
18935
+ });
18735
18936
  return lines.join("\n");
18736
18937
  }
18938
+ /**
18939
+ * For each variant of every sealed-class parameter group on `op`, append a
18940
+ * Java-discoverable overload that takes the variant's flat fields directly
18941
+ * and forwards to the canonical (sealed-class) method.
18942
+ *
18943
+ * Implementation scope: when an operation has multiple parameter groups, we
18944
+ * emit overloads for each variant of each group while keeping the *other*
18945
+ * groups' sealed-class params unchanged. This avoids combinatorial explosion
18946
+ * for ops with several groups while still flattening the common case.
18947
+ *
18948
+ * Method-name derivation handles the most common variant shapes (`ById`,
18949
+ * `ByExternalId`, etc.). Other shapes (e.g. `Plaintext`, `Imported`) are
18950
+ * skipped with a comment so unusual unions don't compile-fail the SDK; if
18951
+ * one becomes important we can add an explicit case here.
18952
+ */
18953
+ function appendJavaFriendlyVariantOverloads(lines, ctx) {
18954
+ const groups = ctx.op.parameterGroups ?? [];
18955
+ if (groups.length === 0) return;
18956
+ for (const group of groups) for (const variant of group.variants) {
18957
+ const overloadMethodName = deriveOverloadMethodName(ctx.method, variant.name);
18958
+ if (overloadMethodName === null) continue;
18959
+ emitOneJavaOverload(lines, ctx, group, variant, overloadMethodName);
18960
+ }
18961
+ }
18962
+ function deriveOverloadMethodName(baseMethod, variantName) {
18963
+ const v = className$1(variantName);
18964
+ if (v === "ById") return baseMethod;
18965
+ if (/^By[A-Z]/.test(v)) {
18966
+ if (baseMethod.toLowerCase().endsWith(v.toLowerCase())) return baseMethod;
18967
+ return `${baseMethod}${v}`;
18968
+ }
18969
+ return null;
18970
+ }
18971
+ function emitOneJavaOverload(lines, ctx, group, variant, overloadMethodName) {
18972
+ const sealedName = sealedGroupName$1(group.name);
18973
+ const variantClass = className$1(variant.name);
18974
+ const targetGroupProp = ctx.groupParamNames.get(group.name);
18975
+ const decls = [];
18976
+ const existingNames = /* @__PURE__ */ new Set();
18977
+ for (const cp of ctx.canonicalParams) if (cp.name !== targetGroupProp) existingNames.add(cp.name);
18978
+ const variantFieldDecls = [];
18979
+ const variantArgPairs = [];
18980
+ for (const p of variant.parameters) {
18981
+ const sealedField = deriveShortPropertyName(p.name, group.name);
18982
+ let localName = sealedField;
18983
+ if (existingNames.has(localName)) localName = `${propertyName(group.name)}${localName.charAt(0).toUpperCase()}${localName.slice(1)}`;
18984
+ existingNames.add(localName);
18985
+ const decl = renderParamNamed(localName, p.type, true);
18986
+ variantFieldDecls.push({
18987
+ decl,
18988
+ name: localName
18989
+ });
18990
+ variantArgPairs.push({
18991
+ sealedField,
18992
+ localName
18993
+ });
18994
+ }
18995
+ for (const cp of ctx.canonicalParams) if (cp.name === targetGroupProp) for (const v of variantFieldDecls) decls.push(v);
18996
+ else decls.push({
18997
+ decl: cp.decl,
18998
+ name: cp.name
18999
+ });
19000
+ const returnClause = ctx.returnType === "Unit" ? "" : `: ${ctx.returnType}`;
19001
+ const sealedConstruct = `${sealedName}.${variantClass}(${variantArgPairs.map((p) => `${p.sealedField} = ${p.localName}`).join(", ")})`;
19002
+ const forwardArgs = [];
19003
+ for (const cp of ctx.canonicalParams) if (cp.name === targetGroupProp) forwardArgs.push(`${cp.name} = ${sealedConstruct}`);
19004
+ else forwardArgs.push(`${cp.name} = ${cp.name}`);
19005
+ lines.push("");
19006
+ lines.push(" /**");
19007
+ lines.push(` * Java-friendly overload — equivalent to`);
19008
+ lines.push(` * \`${ctx.method}(${sealedName}.${variantClass}(...))\` from Kotlin.`);
19009
+ lines.push(` *`);
19010
+ lines.push(` * Accepts the discriminating fields directly so Java callers don't`);
19011
+ lines.push(` * need to construct \`${sealedName}.${variantClass}\` explicitly.`);
19012
+ lines.push(" */");
19013
+ if (ctx.deprecated) lines.push(" @Deprecated(\"Deprecated operation\")");
19014
+ if (decls.length === 1) {
19015
+ const single = decls[0].decl.replace(/^\s+/, "");
19016
+ lines.push(` fun ${escapeReserved(overloadMethodName)}(${single})${returnClause} = ${ctx.method}(`);
19017
+ } else {
19018
+ lines.push(` fun ${escapeReserved(overloadMethodName)}(`);
19019
+ for (let i = 0; i < decls.length; i++) {
19020
+ const sep = i === decls.length - 1 ? "" : ",";
19021
+ lines.push(`${decls[i].decl}${sep}`);
19022
+ }
19023
+ lines.push(` )${returnClause} = ${ctx.method}(`);
19024
+ }
19025
+ for (let i = 0; i < forwardArgs.length; i++) {
19026
+ const sep = i === forwardArgs.length - 1 ? "" : ",";
19027
+ lines.push(` ${forwardArgs[i]}${sep}`);
19028
+ }
19029
+ lines.push(" )");
19030
+ appendSuspendVariantLines(lines, overloadMethodName, decls.map((d) => ({
19031
+ decl: d.decl,
19032
+ name: d.name
19033
+ })), ctx.returnType, ctx.deprecated);
19034
+ }
19035
+ function appendSuspendVariantLines(lines, method, suspendParams, returnType, deprecated) {
19036
+ lines.push("");
19037
+ for (const ln of emitSuspendVariant({
19038
+ methodName: method,
19039
+ params: suspendParams,
19040
+ returnType,
19041
+ deprecated
19042
+ })) lines.push(ln);
19043
+ }
18737
19044
  function resolveReturnType(plan, imports, ctx) {
18738
19045
  const itemName = plan.isPaginated ? resolvePaginatedItemName(plan.paginatedItemModelName, ctx) ?? plan.paginatedItemModelName : null;
18739
19046
  if (plan.isPaginated && itemName) {
@@ -18786,14 +19093,14 @@ function buildMethodKdoc(op, pathParams, queryParams, bodyFields, bodyParamNames
18786
19093
  if (descriptionRaw) for (const l of descriptionRaw.split("\n")) textLines.push(escapeKdoc(l));
18787
19094
  const paramDocs = [];
18788
19095
  const seenParamDocs = /* @__PURE__ */ new Set();
18789
- const pushParamDoc = (name, sourceName, description, deprecated) => {
19096
+ const pushParamDoc = (name, sourceName, description, deprecated, type) => {
18790
19097
  if (seenParamDocs.has(name)) return;
18791
19098
  seenParamDocs.add(name);
18792
- paramDocs.push(formatParamDoc(name, description, deprecated, sourceName));
19099
+ paramDocs.push(formatParamDoc(name, description, deprecated, sourceName, type));
18793
19100
  };
18794
- for (const pp of pathParams) pushParamDoc(propertyName(pp.name), pp.name, pp.description, pp.deprecated);
18795
- for (const qp of queryParams) pushParamDoc(propertyName(qp.name), qp.name, qp.description, qp.deprecated);
18796
- for (const bf of bodyFields) pushParamDoc(bodyParamNames.get(bf.name), bf.name, bf.description, bf.deprecated);
19101
+ for (const pp of pathParams) pushParamDoc(propertyName(pp.name), pp.name, pp.description, pp.deprecated, pp.type);
19102
+ for (const qp of queryParams) pushParamDoc(propertyName(qp.name), qp.name, qp.description, qp.deprecated, qp.type);
19103
+ for (const bf of bodyFields) pushParamDoc(bodyParamNames.get(bf.name), bf.name, bf.description, bf.deprecated, bf.type);
18797
19104
  pushParamDoc("requestOptions", "request_options", REQUEST_OPTIONS_PARAM_DESCRIPTION);
18798
19105
  const returnDoc = plan.isPaginated ? "@return a [com.workos.common.http.Page] of results" : plan.responseModelName ? `@return the ${plan.isArrayResponse ? `list of ${className$1(plan.responseModelName)}` : className$1(plan.responseModelName)}` : null;
18799
19106
  if (!(textLines.length > 0 || paramDocs.length > 0 || returnDoc !== null)) return [];
@@ -18814,11 +19121,14 @@ function buildMethodKdoc(op, pathParams, queryParams, bodyFields, bodyParamNames
18814
19121
  * appears across resource methods, wrapper methods, and union-split helpers.
18815
19122
  */
18816
19123
  const REQUEST_OPTIONS_PARAM_DESCRIPTION = "per-request overrides (idempotency key, API key, headers, timeout)";
18817
- function formatParamDoc(kotlinName, description, deprecated, sourceName) {
19124
+ function formatParamDoc(kotlinName, description, deprecated, sourceName, type) {
18818
19125
  const specText = (description?.split("\n").find((l) => l.trim()) ?? "").trim();
18819
19126
  const deprecationNote = deprecated ? "**Deprecated.**" : "";
18820
19127
  const fallback = `the ${humanize(sourceName ?? kotlinName)} of the request.`;
18821
- return `@param ${kotlinName} ${escapeKdoc([deprecationNote, specText || fallback].filter(Boolean).join(" "))}`;
19128
+ let text = specText || fallback;
19129
+ const shortened = maybeShortenEnumParamDescription(type, text);
19130
+ if (shortened) text = shortened.description;
19131
+ return `@param ${kotlinName} ${escapeKdoc([deprecationNote, text].filter(Boolean).join(" "))}`;
18822
19132
  }
18823
19133
  /**
18824
19134
  * Unwrap a possibly-nullable type to check if the inner type is an array,
@@ -18961,7 +19271,26 @@ function deriveShortPropertyName(paramName, groupName) {
18961
19271
  function generateSealedClass(group, bodyFieldTypes) {
18962
19272
  const lines = [];
18963
19273
  const sealedName = sealedGroupName$1(group.name);
18964
- lines.push(`/** Mutually exclusive ${humanize(group.name)} parameter variants. */`);
19274
+ const example = group.variants[0];
19275
+ if (example) {
19276
+ const exampleVariant = className$1(example.name);
19277
+ const exampleArgs = example.parameters.map((p) => `${deriveShortPropertyName(p.name, group.name)} = "..."`).join(", ");
19278
+ lines.push("/**");
19279
+ lines.push(` * Mutually exclusive ${humanize(group.name)} parameter variants.`);
19280
+ lines.push(" *");
19281
+ lines.push(" * Usage from Kotlin:");
19282
+ lines.push(" * ```kotlin");
19283
+ lines.push(` * val target: ${sealedName} = ${sealedName}.${exampleVariant}(${exampleArgs})`);
19284
+ lines.push(" * ```");
19285
+ lines.push(" *");
19286
+ lines.push(" * Usage from Java:");
19287
+ lines.push(" * ```java");
19288
+ lines.push(` * ${sealedName} target = new ${sealedName}.${exampleVariant}(${example.parameters.map(() => "\"...\"").join(", ")});`);
19289
+ lines.push(" * ```");
19290
+ lines.push(" *");
19291
+ lines.push(` * Java callers may also use the per-variant overloads on the surrounding API class to skip variant construction entirely.`);
19292
+ lines.push(" */");
19293
+ } else lines.push(`/** Mutually exclusive ${humanize(group.name)} parameter variants. */`);
18965
19294
  lines.push(`sealed class ${sealedName} {`);
18966
19295
  for (let vi = 0; vi < group.variants.length; vi++) {
18967
19296
  const variant = group.variants[vi];
@@ -19084,30 +19413,36 @@ const KOTLIN_SRC_PREFIX = "src/main/kotlin/";
19084
19413
  * contains a `WorkOS` class stub with only these properties — the oagen
19085
19414
  * merger deep-merges them into the existing hand-written `WorkOS.kt`.
19086
19415
  *
19087
- * Accessors use fully-qualified type names so the merger doesn't need to
19088
- * inject imports into the hand-written file.
19416
+ * Each referenced service class is hoisted into a top-level `import` so the
19417
+ * accessor bodies use the short class name. The live `WorkOS.kt` is marked
19418
+ * `@oagen-ignore-file` and won't be regenerated, but the emitter still emits
19419
+ * the cleaner shape so future regenerations don't reintroduce the FQN noise.
19089
19420
  */
19090
19421
  function generateClient$1(spec, ctx) {
19091
19422
  const targets = deduplicateByMount(spec.services, ctx);
19092
19423
  if (targets.length === 0) return [];
19424
+ const imports = /* @__PURE__ */ new Set();
19093
19425
  const accessorLines = [];
19094
19426
  for (const mount of targets) {
19095
19427
  const apiCls = apiClassName(mount);
19096
19428
  const fqn = `com.workos.${packageSegment(mount)}.${apiCls}`;
19429
+ imports.add(fqn);
19097
19430
  const prop = servicePropertyName$1(mount);
19098
19431
  accessorLines.push("");
19099
- accessorLines.push(` /** Lazily-constructed [${fqn}] accessor for this [WorkOS] client. */`);
19100
- accessorLines.push(` val ${prop}: ${fqn}`);
19432
+ accessorLines.push(` /** Lazily-constructed [${apiCls}] accessor for this [WorkOS] client. */`);
19433
+ accessorLines.push(` val ${prop}: ${apiCls}`);
19101
19434
  accessorLines.push(" get() =");
19102
19435
  accessorLines.push(" service(");
19103
- accessorLines.push(` ${fqn}::class`);
19436
+ accessorLines.push(` ${apiCls}::class`);
19104
19437
  accessorLines.push(" ) {");
19105
- accessorLines.push(` ${fqn}(this)`);
19438
+ accessorLines.push(` ${apiCls}(this)`);
19106
19439
  accessorLines.push(" }");
19107
19440
  }
19108
19441
  const lines = [];
19109
19442
  lines.push("package com.workos");
19110
19443
  lines.push("");
19444
+ for (const imp of [...imports].sort()) lines.push(`import ${imp}`);
19445
+ if (imports.size > 0) lines.push("");
19111
19446
  lines.push("open class WorkOS {");
19112
19447
  for (const line of accessorLines) lines.push(line);
19113
19448
  lines.push("}");
@@ -20055,11 +20390,7 @@ const kotlinEmitter = {
20055
20390
  if (!fs.existsSync(path.join(targetDir, "gradlew"))) return null;
20056
20391
  return {
20057
20392
  cmd: "bash",
20058
- args: [
20059
- "-c",
20060
- `cd ${JSON.stringify(targetDir)} && ./gradlew ktlintFormat --quiet 2>/dev/null; true`,
20061
- "--"
20062
- ]
20393
+ args: ["-c", "./gradlew ktlintFormat --quiet >/dev/null 2>&1; true"]
20063
20394
  };
20064
20395
  }
20065
20396
  };
@@ -22469,4 +22800,4 @@ const workosEmittersPlugin = {
22469
22800
  //#endregion
22470
22801
  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 };
22471
22802
 
22472
- //# sourceMappingURL=plugin-CeNME04k.mjs.map
22803
+ //# sourceMappingURL=plugin-Dh9JSScr.mjs.map