@workos/oagen-emitters 0.8.1 → 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.
@@ -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("");
@@ -17749,6 +17800,40 @@ const KOTLIN_SRC_PREFIX$2 = "src/main/kotlin/";
17749
17800
  const MODELS_PACKAGE = "com.workos.models";
17750
17801
  const MODELS_DIR = "com/workos/models";
17751
17802
  /**
17803
+ * Some specs leave string fields without `format: date-time` even though the
17804
+ * description (or the example) makes clear they carry an ISO-8601 timestamp.
17805
+ * Detect that here so we can promote the type to `OffsetDateTime` in the
17806
+ * Kotlin output.
17807
+ */
17808
+ const ISO_8601_DESCRIPTION_RE$2 = /\bISO[-_ ]?8601\b/i;
17809
+ function looksLikeIso8601String$2(description) {
17810
+ if (!description) return false;
17811
+ return ISO_8601_DESCRIPTION_RE$2.test(description);
17812
+ }
17813
+ function promoteIso8601TypeRef$2(type, description) {
17814
+ if (!looksLikeIso8601String$2(description)) return type;
17815
+ const promote = (t) => {
17816
+ if (t.kind === "primitive" && t.type === "string" && !t.format) return {
17817
+ kind: "primitive",
17818
+ type: "string",
17819
+ format: "date-time"
17820
+ };
17821
+ if (t.kind === "nullable") return {
17822
+ kind: "nullable",
17823
+ inner: promote(t.inner)
17824
+ };
17825
+ return t;
17826
+ };
17827
+ return promote(type);
17828
+ }
17829
+ function promoteFieldType$1(f) {
17830
+ const promoted = promoteIso8601TypeRef$2(f.type, f.description);
17831
+ return promoted === f.type ? f : {
17832
+ ...f,
17833
+ type: promoted
17834
+ };
17835
+ }
17836
+ /**
17752
17837
  * Generate Kotlin `data class` models. Each model becomes a separate `.kt`
17753
17838
  * file under `com.workos.models`. Discriminated unions emit a sealed class
17754
17839
  * with Jackson `@JsonTypeInfo` / `@JsonSubTypes` annotations so the base type
@@ -17878,6 +17963,7 @@ function emitDataClass(model) {
17878
17963
  for (let i = 0; i < rendered.length; i++) {
17879
17964
  const suffix = i === rendered.length - 1 ? "" : ",";
17880
17965
  lines.push(`${rendered[i]}${suffix}`);
17966
+ if (i < rendered.length - 1) lines.push("");
17881
17967
  }
17882
17968
  lines.push(`)${implClause}`);
17883
17969
  lines.push("");
@@ -17895,7 +17981,29 @@ function emitSealedUnion(typeName, disc) {
17895
17981
  lines.push("import com.fasterxml.jackson.annotation.JsonSubTypes");
17896
17982
  lines.push("import com.fasterxml.jackson.annotation.JsonTypeInfo");
17897
17983
  lines.push("");
17898
- 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(" */");
17899
18007
  lines.push("@JsonTypeInfo(");
17900
18008
  lines.push(" use = JsonTypeInfo.Id.NAME,");
17901
18009
  lines.push(" include = JsonTypeInfo.As.EXISTING_PROPERTY,");
@@ -17985,7 +18093,8 @@ function emitWorkOSEvent(eventMapping) {
17985
18093
  function renderFields(fields, overrideFields = /* @__PURE__ */ new Set()) {
17986
18094
  const seen = /* @__PURE__ */ new Set();
17987
18095
  const lines = [];
17988
- for (const field of fields) {
18096
+ for (const rawField of fields) {
18097
+ const field = promoteFieldType$1(rawField);
17989
18098
  const kotlinName = propertyName(field.name);
17990
18099
  if (seen.has(kotlinName)) continue;
17991
18100
  seen.add(kotlinName);
@@ -18004,7 +18113,7 @@ function renderFields(fields, overrideFields = /* @__PURE__ */ new Set()) {
18004
18113
  const isOverride = overrideFields.has(kotlinName);
18005
18114
  const annotations = [];
18006
18115
  annotations.push(`@JsonProperty(${ktStringLiteral(field.name)})`);
18007
- if (field.deprecated) annotations.push("@Deprecated(\"Deprecated field\")");
18116
+ if (field.deprecated) annotations.push(buildDeprecatedAnnotation(field.description));
18008
18117
  const paramParts = [];
18009
18118
  if (field.description?.trim()) {
18010
18119
  const line = field.description.split("\n").find((l) => l.trim()) ?? "";
@@ -18035,6 +18144,26 @@ function collapseFieldEntries(rawLines) {
18035
18144
  return entries;
18036
18145
  }
18037
18146
  /**
18147
+ * Pull the most useful free-form deprecation hint out of a field description
18148
+ * and lift it into the `@Deprecated(...)` message argument. Most WorkOS
18149
+ * deprecations are written as a description that begins with "Deprecated"
18150
+ * (e.g. "Deprecated. Use `domain_data` instead."). When the description
18151
+ * doesn't carry a hint we fall back to a short, self-explanatory message
18152
+ * rather than the generic "Deprecated field" placeholder.
18153
+ */
18154
+ function deprecationMessageFromDescription(description) {
18155
+ if (!description) return "Deprecated.";
18156
+ const firstLine = description.split("\n").map((l) => l.trim()).find((l) => l.length > 0);
18157
+ if (!firstLine) return "Deprecated.";
18158
+ const collapsed = firstLine.replace(/\s+/g, " ").trim();
18159
+ if (collapsed.length === 0) return "Deprecated.";
18160
+ if (/\bdeprecat/i.test(collapsed)) return collapsed;
18161
+ return "Deprecated.";
18162
+ }
18163
+ function buildDeprecatedAnnotation(description) {
18164
+ return `@Deprecated(${ktStringLiteral(deprecationMessageFromDescription(description))})`;
18165
+ }
18166
+ /**
18038
18167
  * If the TypeRef is a literal (const) with a string, number, or boolean value,
18039
18168
  * return the Kotlin expression for that default. Otherwise return null.
18040
18169
  */
@@ -18049,7 +18178,8 @@ function collectImports(fields) {
18049
18178
  const imports = /* @__PURE__ */ new Set();
18050
18179
  if (fields.length === 0) return imports;
18051
18180
  imports.add("com.fasterxml.jackson.annotation.JsonProperty");
18052
- for (const field of fields) {
18181
+ for (const rawField of fields) {
18182
+ const field = promoteFieldType$1(rawField);
18053
18183
  const mapped = mapTypeRef$1(field.type);
18054
18184
  if (/\bOffsetDateTime\b/.test(mapped)) imports.add("java.time.OffsetDateTime");
18055
18185
  for (const enumName of collectEnumNames(field.type)) {
@@ -18174,6 +18304,81 @@ function escapeKotlinStringLiteral(literal) {
18174
18304
  return literal.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\$/g, "\\$");
18175
18305
  }
18176
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
18177
18382
  //#region src/kotlin/wrappers.ts
18178
18383
  /**
18179
18384
  * Emit Kotlin wrapper methods for a union-split operation. Each wrapper
@@ -18206,31 +18411,48 @@ function emitWrapperMethod$1(resolvedOp, wrapper, ctx) {
18206
18411
  if (opDesc) kdocLines.push(opDesc.split("\n")[0]);
18207
18412
  else kdocLines.push(`${wrapperHumanName.charAt(0).toUpperCase()}${wrapperHumanName.slice(1)}.`);
18208
18413
  const paramDocs = [];
18209
- for (const pp of pathParams) if (pp.description?.trim()) paramDocs.push(`@param ${propertyName(pp.name)} ${escapeKdoc$1(pp.description.split("\n")[0].trim())}`);
18210
- for (const rp of resolvedParams) {
18211
- const desc = rp.field?.description?.trim();
18212
- if (desc) paramDocs.push(`@param ${propertyName(rp.paramName)} ${escapeKdoc$1(desc.split("\n")[0])}`);
18213
- }
18414
+ const pushParamDoc = (kotlinName, sourceName, description, type) => {
18415
+ const firstLine = description?.split("\n").find((l) => l.trim())?.trim() ?? "";
18416
+ const fallback = `the ${humanize(sourceName)} of the request.`;
18417
+ let text = firstLine || fallback;
18418
+ const shortened = maybeShortenEnumParamDescription(type, text);
18419
+ if (shortened) text = shortened.description;
18420
+ paramDocs.push(`@param ${kotlinName} ${escapeKdoc$1(text)}`);
18421
+ };
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);
18424
+ pushParamDoc("requestOptions", "request_options", "per-request overrides (idempotency key, API key, headers, timeout)");
18214
18425
  if (responseClass) paramDocs.push(`@return the ${responseClass}`);
18215
- if (paramDocs.length > 0 || kdocLines.length > 0) {
18216
- lines.push(" /**");
18217
- for (const l of kdocLines) lines.push(` * ${escapeKdoc$1(l)}`);
18218
- if (paramDocs.length > 0) {
18219
- lines.push(" *");
18220
- for (const p of paramDocs) lines.push(` * ${p}`);
18221
- }
18222
- lines.push(" */");
18223
- }
18426
+ lines.push(" /**");
18427
+ for (const l of kdocLines) lines.push(` * ${escapeKdoc$1(l)}`);
18428
+ lines.push(" *");
18429
+ for (const p of paramDocs) lines.push(` * ${p}`);
18430
+ lines.push(" */");
18224
18431
  lines.push(" @JvmOverloads");
18225
18432
  const params = [];
18226
- 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
+ }
18227
18442
  for (const rp of resolvedParams) {
18228
18443
  const paramName = propertyName(rp.paramName);
18229
- const kotlinType = rp.field ? rp.isOptional ? mapTypeRefOptional(rp.field.type) : mapTypeRef$1(rp.field.type) : rp.isOptional ? "String?" : "String";
18230
- const trailer = rp.isOptional ? " = null" : "";
18231
- 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
+ });
18232
18450
  }
18233
18451
  params.push(" requestOptions: RequestOptions? = null");
18452
+ suspendParams.push({
18453
+ decl: " requestOptions: RequestOptions? = null",
18454
+ name: "requestOptions"
18455
+ });
18234
18456
  const returnClause = responseClass ? `: ${responseClass}` : "";
18235
18457
  if (params.length === 1) {
18236
18458
  const single = params[0].replace(/^\s+/, "");
@@ -18243,6 +18465,26 @@ function emitWrapperMethod$1(resolvedOp, wrapper, ctx) {
18243
18465
  }
18244
18466
  lines.push(` )${returnClause} {`);
18245
18467
  }
18468
+ const inferred = wrapper.inferFromClient ?? [];
18469
+ const usesStandardClientCreds = inferred.includes("client_id") && inferred.includes("client_secret");
18470
+ if (op.path === "/user_management/authenticate" && op.httpMethod.toUpperCase() === "POST" && responseClass === "AuthenticateResponse" && typeof wrapper.defaults?.grant_type === "string" && usesStandardClientCreds) {
18471
+ const grantType = wrapper.defaults.grant_type;
18472
+ lines.push(` return authenticate(`);
18473
+ lines.push(` grantType = ${ktLiteral(grantType)},`);
18474
+ lines.push(` requestOptions = requestOptions,`);
18475
+ const entryLines = resolvedParams.map((rp) => {
18476
+ const paramName = propertyName(rp.paramName);
18477
+ return ` ${ktLiteral(rp.paramName)} to ${paramName}`;
18478
+ });
18479
+ for (let i = 0; i < entryLines.length; i++) {
18480
+ const sep = i === entryLines.length - 1 ? "" : ",";
18481
+ lines.push(`${entryLines[i]}${sep}`);
18482
+ }
18483
+ lines.push(` )`);
18484
+ lines.push(" }");
18485
+ appendSuspendVariant(lines, method, suspendParams, responseClass ?? "Unit");
18486
+ return lines;
18487
+ }
18246
18488
  const bodyEntries = [];
18247
18489
  for (const rp of resolvedParams) {
18248
18490
  const paramName = propertyName(rp.paramName);
@@ -18272,8 +18514,17 @@ function emitWrapperMethod$1(resolvedOp, wrapper, ctx) {
18272
18514
  if (responseClass) lines.push(` return workos.baseClient.request(config, ${responseClass}::class.java)`);
18273
18515
  else lines.push(` workos.baseClient.requestVoid(config)`);
18274
18516
  lines.push(" }");
18517
+ appendSuspendVariant(lines, method, suspendParams, responseClass ?? "Unit");
18275
18518
  return lines;
18276
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
+ }
18277
18528
  function escapeKdoc$1(s) {
18278
18529
  return s.replace(/\*\//g, "*​/");
18279
18530
  }
@@ -18301,6 +18552,52 @@ function isHandwrittenOverride(op) {
18301
18552
  //#region src/kotlin/resources.ts
18302
18553
  const KOTLIN_SRC_PREFIX$1 = "src/main/kotlin/";
18303
18554
  /**
18555
+ * Some specs leave query params / fields typed as plain `string` even though
18556
+ * the description (or the field name) makes clear they carry an ISO-8601
18557
+ * timestamp. Detecting that here lets us emit `OffsetDateTime` so callers
18558
+ * don't have to format the wire string themselves.
18559
+ */
18560
+ const ISO_8601_DESCRIPTION_RE$1 = /\bISO[-_ ]?8601\b/i;
18561
+ function looksLikeIso8601String$1(description) {
18562
+ if (!description) return false;
18563
+ return ISO_8601_DESCRIPTION_RE$1.test(description);
18564
+ }
18565
+ /**
18566
+ * Promote a string `TypeRef` to a `format: date-time` primitive when the
18567
+ * accompanying description identifies it as an ISO-8601 timestamp. Leaves
18568
+ * non-string types untouched.
18569
+ */
18570
+ function promoteIso8601TypeRef$1(type, description) {
18571
+ if (!looksLikeIso8601String$1(description)) return type;
18572
+ const promote = (t) => {
18573
+ if (t.kind === "primitive" && t.type === "string" && !t.format) return {
18574
+ kind: "primitive",
18575
+ type: "string",
18576
+ format: "date-time"
18577
+ };
18578
+ if (t.kind === "nullable") return {
18579
+ kind: "nullable",
18580
+ inner: promote(t.inner)
18581
+ };
18582
+ return t;
18583
+ };
18584
+ return promote(type);
18585
+ }
18586
+ function promoteParameterType(p) {
18587
+ const promoted = promoteIso8601TypeRef$1(p.type, p.description);
18588
+ return promoted === p.type ? p : {
18589
+ ...p,
18590
+ type: promoted
18591
+ };
18592
+ }
18593
+ function promoteFieldType(f) {
18594
+ const promoted = promoteIso8601TypeRef$1(f.type, f.description);
18595
+ return promoted === f.type ? f : {
18596
+ ...f,
18597
+ type: promoted
18598
+ };
18599
+ }
18600
+ /**
18304
18601
  * Generate one API class per mount group. Methods map 1:1 to IR operations.
18305
18602
  * Path params, query params, and body fields are flattened into the method
18306
18603
  * signature so callers never need to construct an intermediate options object.
@@ -18332,6 +18629,7 @@ function generateApiClass(mountName, operations, ctx, resolvedLookup) {
18332
18629
  imports.add("com.workos.common.http.Page");
18333
18630
  imports.add("com.workos.common.http.RequestConfig");
18334
18631
  imports.add("com.workos.common.http.RequestOptions");
18632
+ for (const imp of SUSPEND_IMPORTS) imports.add(imp);
18335
18633
  const body = [];
18336
18634
  const seenMethods = /* @__PURE__ */ new Set();
18337
18635
  if (operations.some((op) => op.path === "/user_management/authenticate" && op.httpMethod.toUpperCase() === "POST")) {
@@ -18386,15 +18684,21 @@ function generateApiClass(mountName, operations, ctx, resolvedLookup) {
18386
18684
  lines.push("");
18387
18685
  for (const line of sealedLines) lines.push(line);
18388
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`).";
18389
18688
  if (serviceDescription) {
18390
18689
  const docLines = serviceDescription.trim().split("\n");
18391
- if (docLines.length === 1) lines.push(`/** ${escapeKdoc(docLines[0].trim())} */`);
18392
- else {
18393
- lines.push("/**");
18394
- for (const l of docLines) lines.push(l ? ` * ${escapeKdoc(l)}` : " *");
18395
- lines.push(" */");
18396
- }
18397
- } 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
+ }
18398
18702
  lines.push(`class ${apiClass}(`);
18399
18703
  lines.push(" internal val workos: WorkOS");
18400
18704
  lines.push(`) {`);
@@ -18431,9 +18735,9 @@ function renderMethod(_mountName, method, op, ctx, resolvedOp, imports) {
18431
18735
  const pathParams = sortPathParamsByTemplateOrder(op);
18432
18736
  const groupedParamNames = collectGroupedParamNames(op);
18433
18737
  const hasGroups = (op.parameterGroups?.length ?? 0) > 0;
18434
- const queryParams = op.queryParams.filter((p) => !hidden.has(p.name) && !groupedParamNames.has(p.name));
18738
+ const queryParams = op.queryParams.filter((p) => !hidden.has(p.name) && !groupedParamNames.has(p.name)).map(promoteParameterType);
18435
18739
  const bodyModel = resolveBodyModel$2(op, ctx);
18436
- const bodyFields = bodyModel ? bodyModel.fields.filter((f) => !hidden.has(f.name) && !groupedParamNames.has(f.name)) : [];
18740
+ const bodyFields = bodyModel ? bodyModel.fields.filter((f) => !hidden.has(f.name) && !groupedParamNames.has(f.name)).map(promoteFieldType) : [];
18437
18741
  for (const p of [...pathParams, ...queryParams]) registerTypeImports(p.type, imports, ctx);
18438
18742
  for (const f of bodyFields) registerTypeImports(f.type, imports, ctx);
18439
18743
  const paginatedItemName = resolvePaginatedItemName(plan.paginatedItemModelName, ctx);
@@ -18465,14 +18769,22 @@ function renderMethod(_mountName, method, op, ctx, resolvedOp, imports) {
18465
18769
  }
18466
18770
  const groupParamNames = assignGroupParameterNames$1(op, paramNames);
18467
18771
  const params = [];
18468
- 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));
18469
18781
  const sortedQuery = [...uniqueQuery].sort((a, b) => a.required === b.required ? 0 : a.required ? -1 : 1);
18470
- 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));
18471
18783
  for (const group of op.parameterGroups ?? []) {
18472
18784
  const sealedName = sealedGroupName$1(group.name);
18473
18785
  const prop = groupParamNames.get(group.name);
18474
- if (group.optional) params.push(` ${prop}: ${sealedName}? = null`);
18475
- else params.push(` ${prop}: ${sealedName}`);
18786
+ if (group.optional) pushParam(` ${prop}: ${sealedName}? = null`, prop);
18787
+ else pushParam(` ${prop}: ${sealedName}`, prop);
18476
18788
  }
18477
18789
  const isPatch = httpMethod === "PATCH";
18478
18790
  const sortedBodyFields = [...bodyFields].sort((a, b) => a.required === b.required ? 0 : a.required ? -1 : 1);
@@ -18481,10 +18793,10 @@ function renderMethod(_mountName, method, op, ctx, resolvedOp, imports) {
18481
18793
  if (isPatch && !bf.required) {
18482
18794
  const baseType = mapTypeRef$1(bf.type);
18483
18795
  imports.add("com.workos.common.http.PatchField");
18484
- params.push(` ${bodyParamNames.get(bf.name)}: PatchField<${baseType}> = PatchField.Absent`);
18485
- } 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));
18486
18798
  }
18487
- params.push(" requestOptions: RequestOptions? = null");
18799
+ pushParam(" requestOptions: RequestOptions? = null", "requestOptions");
18488
18800
  const returnType = resolveReturnType(plan, imports, ctx);
18489
18801
  const isPaginated = plan.isPaginated && paginatedItemName !== null;
18490
18802
  const lines = [];
@@ -18528,6 +18840,7 @@ function renderMethod(_mountName, method, op, ctx, resolvedOp, imports) {
18528
18840
  }
18529
18841
  lines.push(` )`);
18530
18842
  lines.push(" }");
18843
+ appendSuspendVariantLines(lines, method, suspendParams, returnType, op.deprecated);
18531
18844
  return lines.join("\n");
18532
18845
  }
18533
18846
  if (isPaginated) {
@@ -18544,9 +18857,8 @@ function renderMethod(_mountName, method, op, ctx, resolvedOp, imports) {
18544
18857
  lines.push(` before = ${pickNamedQueryParam(sortedQuery, "before")},`);
18545
18858
  lines.push(` after = ${pickNamedQueryParam(sortedQuery, "after")}`);
18546
18859
  lines.push(` ) {`);
18547
- lines.push(` val params = this`);
18548
- for (const qp of sortedQuery.filter((p) => p.name !== "after" && p.name !== "before")) for (const ln of emitQueryParam(qp, " ")) lines.push(ln);
18549
- for (const group of op.parameterGroups ?? []) for (const ln of emitGroupQueryDispatch(group, groupParamNames.get(group.name), " ")) lines.push(ln);
18860
+ for (const qp of sortedQuery.filter((p) => p.name !== "after" && p.name !== "before")) for (const ln of emitQueryParam(qp, " ", true)) lines.push(ln);
18861
+ for (const group of op.parameterGroups ?? []) for (const ln of emitGroupQueryDispatch(group, groupParamNames.get(group.name), " ", true)) lines.push(ln);
18550
18862
  lines.push(` }`);
18551
18863
  } else {
18552
18864
  const groupsGoToQuery = hasGroups && !hasBody;
@@ -18612,8 +18924,123 @@ function renderMethod(_mountName, method, op, ctx, resolvedOp, imports) {
18612
18924
  else lines.push(` workos.baseClient.requestVoid(config)`);
18613
18925
  }
18614
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
+ });
18615
18936
  return lines.join("\n");
18616
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
+ }
18617
19044
  function resolveReturnType(plan, imports, ctx) {
18618
19045
  const itemName = plan.isPaginated ? resolvePaginatedItemName(plan.paginatedItemModelName, ctx) ?? plan.paginatedItemModelName : null;
18619
19046
  if (plan.isPaginated && itemName) {
@@ -18666,14 +19093,15 @@ function buildMethodKdoc(op, pathParams, queryParams, bodyFields, bodyParamNames
18666
19093
  if (descriptionRaw) for (const l of descriptionRaw.split("\n")) textLines.push(escapeKdoc(l));
18667
19094
  const paramDocs = [];
18668
19095
  const seenParamDocs = /* @__PURE__ */ new Set();
18669
- const pushParamDoc = (name, description, deprecated) => {
19096
+ const pushParamDoc = (name, sourceName, description, deprecated, type) => {
18670
19097
  if (seenParamDocs.has(name)) return;
18671
19098
  seenParamDocs.add(name);
18672
- paramDocs.push(formatParamDoc(name, description, deprecated));
19099
+ paramDocs.push(formatParamDoc(name, description, deprecated, sourceName, type));
18673
19100
  };
18674
- for (const pp of pathParams) if (pp.description?.trim() || pp.deprecated) pushParamDoc(propertyName(pp.name), pp.description, pp.deprecated);
18675
- for (const qp of queryParams) if (qp.description?.trim() || qp.deprecated) pushParamDoc(propertyName(qp.name), qp.description, qp.deprecated);
18676
- for (const bf of bodyFields) if (bf.description?.trim() || bf.deprecated) pushParamDoc(bodyParamNames.get(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);
19104
+ pushParamDoc("requestOptions", "request_options", REQUEST_OPTIONS_PARAM_DESCRIPTION);
18677
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;
18678
19106
  if (!(textLines.length > 0 || paramDocs.length > 0 || returnDoc !== null)) return [];
18679
19107
  const out = [" /**"];
@@ -18687,10 +19115,20 @@ function buildMethodKdoc(op, pathParams, queryParams, bodyFields, bodyParamNames
18687
19115
  out.push(" */");
18688
19116
  return out;
18689
19117
  }
18690
- function formatParamDoc(kotlinName, description, deprecated) {
18691
- const text = (description?.split("\n").find((l) => l.trim()) ?? "").trim();
18692
- const parts = [deprecated ? "**Deprecated.**" : "", text].filter(Boolean).join(" ");
18693
- return `@param ${kotlinName}${parts ? ` ${escapeKdoc(parts)}` : ""}`;
19118
+ /**
19119
+ * Stable, canned description for the trailing `requestOptions` parameter that
19120
+ * every generated method exposes. Kept as a constant so the same phrasing
19121
+ * appears across resource methods, wrapper methods, and union-split helpers.
19122
+ */
19123
+ const REQUEST_OPTIONS_PARAM_DESCRIPTION = "per-request overrides (idempotency key, API key, headers, timeout)";
19124
+ function formatParamDoc(kotlinName, description, deprecated, sourceName, type) {
19125
+ const specText = (description?.split("\n").find((l) => l.trim()) ?? "").trim();
19126
+ const deprecationNote = deprecated ? "**Deprecated.**" : "";
19127
+ const fallback = `the ${humanize(sourceName ?? kotlinName)} of the request.`;
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(" "))}`;
18694
19132
  }
18695
19133
  /**
18696
19134
  * Unwrap a possibly-nullable type to check if the inner type is an array,
@@ -18709,32 +19147,43 @@ function unwrapArray(t) {
18709
19147
  function valueExprForQuery(type) {
18710
19148
  const inner = type.kind === "nullable" ? type.inner : type;
18711
19149
  if (inner.kind === "enum") return "it.value";
18712
- if (inner.kind === "primitive" && inner.type === "string") return "it";
19150
+ if (inner.kind === "primitive" && inner.type === "string") return inner.format === "date-time" ? "it.toString()" : "it";
18713
19151
  return "it.toString()";
18714
19152
  }
18715
- function emitQueryParam(p, indent) {
19153
+ function emitQueryParam(p, indent, receiverMode = false) {
18716
19154
  const prop = propertyName(p.name);
18717
19155
  const rendered = queryParamToString(p.type, prop);
18718
19156
  const inner = p.type.kind === "nullable" ? p.type.inner : p.type;
18719
19157
  const arrayItem = unwrapArray(p.type);
19158
+ const callPrefix = receiverMode ? "" : "params.";
19159
+ const addPair = (pair) => receiverMode ? `add(${pair})` : `params += ${pair}`;
18720
19160
  if (arrayItem) {
18721
19161
  const explode = p.explode ?? true;
18722
19162
  const itemExpr = valueExprForQuery(arrayItem);
19163
+ const isIdentity = itemExpr === "it";
18723
19164
  if (!explode) {
18724
- if (p.required) return [`${indent}params.addJoinedIfNotNull(${ktLiteral(p.name)}, ${prop}.map { ${itemExpr} })`];
18725
- return [`${indent}params.addJoinedIfNotNull(${ktLiteral(p.name)}, ${prop}?.map { ${itemExpr} })`];
19165
+ if (p.required) {
19166
+ const arg = isIdentity ? prop : `${prop}.map { ${itemExpr} }`;
19167
+ return [`${indent}${callPrefix}addJoinedIfNotNull(${ktLiteral(p.name)}, ${arg})`];
19168
+ }
19169
+ const arg = isIdentity ? prop : `${prop}?.map { ${itemExpr} }`;
19170
+ return [`${indent}${callPrefix}addJoinedIfNotNull(${ktLiteral(p.name)}, ${arg})`];
19171
+ }
19172
+ if (p.required) {
19173
+ const arg = isIdentity ? prop : `${prop}.map { ${itemExpr} }`;
19174
+ return [`${indent}${callPrefix}addEach(${ktLiteral(p.name)}, ${arg})`];
18726
19175
  }
18727
- if (p.required) return [`${indent}params.addEach(${ktLiteral(p.name)}, ${prop}.map { ${itemExpr} })`];
18728
- return [`${indent}${prop}?.let { params.addEach(${ktLiteral(p.name)}, it.map { ${itemExpr} }) }`];
19176
+ if (isIdentity) return [`${indent}${prop}?.let { ${callPrefix}addEach(${ktLiteral(p.name)}, it) }`];
19177
+ return [`${indent}${prop}?.let { ${callPrefix}addEach(${ktLiteral(p.name)}, it.map { ${itemExpr} }) }`];
18729
19178
  }
18730
- if (p.required) return [`${indent}params += ${ktLiteral(p.name)} to ${rendered}`];
18731
- if (inner.kind === "primitive" && inner.type === "string") return [`${indent}params.addIfNotNull(${ktLiteral(p.name)}, ${prop})`];
18732
- return [`${indent}${prop}?.let { params += ${ktLiteral(p.name)} to ${queryParamToString(inner, "it")} }`];
19179
+ if (p.required) return [`${indent}${addPair(`${ktLiteral(p.name)} to ${rendered}`)}`];
19180
+ if (inner.kind === "primitive" && inner.type === "string" && inner.format !== "date-time") return [`${indent}${callPrefix}addIfNotNull(${ktLiteral(p.name)}, ${prop})`];
19181
+ return [`${indent}${prop}?.let { ${addPair(`${ktLiteral(p.name)} to ${queryParamToString(inner, "it")}`)} }`];
18733
19182
  }
18734
19183
  function queryParamToString(type, varName) {
18735
19184
  if (type.kind === "enum") return `${varName}.value`;
18736
19185
  if (type.kind === "nullable") return queryParamToString(type.inner, varName);
18737
- if (type.kind === "primitive" && type.type === "string") return varName;
19186
+ if (type.kind === "primitive" && type.type === "string") return type.format === "date-time" ? `${varName}.toString()` : varName;
18738
19187
  return `${varName}.toString()`;
18739
19188
  }
18740
19189
  function pickNamedQueryParam(sorted, name) {
@@ -18822,7 +19271,26 @@ function deriveShortPropertyName(paramName, groupName) {
18822
19271
  function generateSealedClass(group, bodyFieldTypes) {
18823
19272
  const lines = [];
18824
19273
  const sealedName = sealedGroupName$1(group.name);
18825
- 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. */`);
18826
19294
  lines.push(`sealed class ${sealedName} {`);
18827
19295
  for (let vi = 0; vi < group.variants.length; vi++) {
18828
19296
  const variant = group.variants[vi];
@@ -18848,14 +19316,14 @@ function generateSealedClass(group, bodyFieldTypes) {
18848
19316
  return lines;
18849
19317
  }
18850
19318
  /** Emit `when` dispatch that serializes a parameter group into query params. */
18851
- function emitGroupQueryDispatch(group, prop, indent) {
19319
+ function emitGroupQueryDispatch(group, prop, indent, receiverMode = false) {
18852
19320
  const sealedName = sealedGroupName$1(group.name);
18853
19321
  const lines = [];
18854
19322
  if (group.optional) {
18855
19323
  lines.push(`${indent}if (${prop} != null) {`);
18856
- emitWhenBlock(lines, group, sealedName, prop, `${indent} `);
19324
+ emitWhenBlock(lines, group, sealedName, prop, `${indent} `, receiverMode);
18857
19325
  lines.push(`${indent}}`);
18858
- } else emitWhenBlock(lines, group, sealedName, prop, indent);
19326
+ } else emitWhenBlock(lines, group, sealedName, prop, indent, receiverMode);
18859
19327
  return lines;
18860
19328
  }
18861
19329
  function assignGroupParameterNames$1(op, occupiedNames) {
@@ -18882,13 +19350,14 @@ function reserveUniqueGroupParameterName$1(base, occupiedNames) {
18882
19350
  occupiedNames.add(fallback);
18883
19351
  return fallback;
18884
19352
  }
18885
- function emitWhenBlock(lines, group, sealedName, prop, indent) {
19353
+ function emitWhenBlock(lines, group, sealedName, prop, indent, receiverMode = false) {
18886
19354
  lines.push(`${indent}when (${prop}) {`);
18887
19355
  for (const variant of group.variants) {
18888
19356
  const variantName = className$1(variant.name);
18889
19357
  const entries = variant.parameters.map((p) => {
18890
19358
  const fieldProp = deriveShortPropertyName(p.name, group.name);
18891
- return `params += ${ktLiteral(p.name)} to ${prop}.${fieldProp}`;
19359
+ const pair = `${ktLiteral(p.name)} to ${prop}.${fieldProp}`;
19360
+ return receiverMode ? `add(${pair})` : `params += ${pair}`;
18892
19361
  });
18893
19362
  if (entries.length === 1) lines.push(`${indent} is ${sealedName}.${variantName} -> ${entries[0]}`);
18894
19363
  else {
@@ -18944,30 +19413,36 @@ const KOTLIN_SRC_PREFIX = "src/main/kotlin/";
18944
19413
  * contains a `WorkOS` class stub with only these properties — the oagen
18945
19414
  * merger deep-merges them into the existing hand-written `WorkOS.kt`.
18946
19415
  *
18947
- * Accessors use fully-qualified type names so the merger doesn't need to
18948
- * 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.
18949
19420
  */
18950
19421
  function generateClient$1(spec, ctx) {
18951
19422
  const targets = deduplicateByMount(spec.services, ctx);
18952
19423
  if (targets.length === 0) return [];
19424
+ const imports = /* @__PURE__ */ new Set();
18953
19425
  const accessorLines = [];
18954
19426
  for (const mount of targets) {
18955
19427
  const apiCls = apiClassName(mount);
18956
19428
  const fqn = `com.workos.${packageSegment(mount)}.${apiCls}`;
19429
+ imports.add(fqn);
18957
19430
  const prop = servicePropertyName$1(mount);
18958
19431
  accessorLines.push("");
18959
- accessorLines.push(` /** Lazily-constructed [${fqn}] accessor for this [WorkOS] client. */`);
18960
- accessorLines.push(` val ${prop}: ${fqn}`);
19432
+ accessorLines.push(` /** Lazily-constructed [${apiCls}] accessor for this [WorkOS] client. */`);
19433
+ accessorLines.push(` val ${prop}: ${apiCls}`);
18961
19434
  accessorLines.push(" get() =");
18962
19435
  accessorLines.push(" service(");
18963
- accessorLines.push(` ${fqn}::class`);
19436
+ accessorLines.push(` ${apiCls}::class`);
18964
19437
  accessorLines.push(" ) {");
18965
- accessorLines.push(` ${fqn}(this)`);
19438
+ accessorLines.push(` ${apiCls}(this)`);
18966
19439
  accessorLines.push(" }");
18967
19440
  }
18968
19441
  const lines = [];
18969
19442
  lines.push("package com.workos");
18970
19443
  lines.push("");
19444
+ for (const imp of [...imports].sort()) lines.push(`import ${imp}`);
19445
+ if (imports.size > 0) lines.push("");
18971
19446
  lines.push("open class WorkOS {");
18972
19447
  for (const line of accessorLines) lines.push(line);
18973
19448
  lines.push("}");
@@ -18986,6 +19461,33 @@ function deduplicateByMount(services, ctx) {
18986
19461
  //#region src/kotlin/tests.ts
18987
19462
  const TEST_PREFIX = "src/test/kotlin/";
18988
19463
  /**
19464
+ * Mirror the ISO-8601 hint promotion the resource/model emitters use so tests
19465
+ * synthesize values whose Kotlin type matches the generated method signature.
19466
+ * Kept in sync with the helpers in `resources.ts` / `models.ts`; if the
19467
+ * detection rule changes, update all three.
19468
+ */
19469
+ const ISO_8601_DESCRIPTION_RE = /\bISO[-_ ]?8601\b/i;
19470
+ function looksLikeIso8601String(description) {
19471
+ if (!description) return false;
19472
+ return ISO_8601_DESCRIPTION_RE.test(description);
19473
+ }
19474
+ function promoteIso8601TypeRef(type, description) {
19475
+ if (!looksLikeIso8601String(description)) return type;
19476
+ const promote = (t) => {
19477
+ if (t.kind === "primitive" && t.type === "string" && !t.format) return {
19478
+ kind: "primitive",
19479
+ type: "string",
19480
+ format: "date-time"
19481
+ };
19482
+ if (t.kind === "nullable") return {
19483
+ kind: "nullable",
19484
+ inner: promote(t.inner)
19485
+ };
19486
+ return t;
19487
+ };
19488
+ return promote(type);
19489
+ }
19490
+ /**
18989
19491
  * Generate one JUnit 5 + WireMock test class per API mount group, plus a
18990
19492
  * cross-cutting model round-trip test.
18991
19493
  *
@@ -19120,10 +19622,11 @@ function buildOperationTest(op, resolved, ctx) {
19120
19622
  }
19121
19623
  for (const qp of sortedQuery) {
19122
19624
  if (!qp.required) break;
19123
- const val = synthValue(qp.type, ctx, imports);
19625
+ const promotedType = promoteIso8601TypeRef(qp.type, qp.description);
19626
+ const val = synthValue(promotedType, ctx, imports);
19124
19627
  if (val === null) return null;
19125
19628
  argParts.push(val);
19126
- const regex = queryValueRegexFor(qp.type);
19629
+ const regex = queryValueRegexFor(promotedType);
19127
19630
  if (regex !== null) requiredQueryAssertions.push({
19128
19631
  name: qp.name,
19129
19632
  valueRegex: regex
@@ -19143,10 +19646,11 @@ function buildOperationTest(op, resolved, ctx) {
19143
19646
  for (const bf of sortedBody) {
19144
19647
  if (sharedQueryBodyParams.has(bf.name)) continue;
19145
19648
  if (!bf.required) break;
19146
- const val = synthValue(bf.type, ctx, imports);
19649
+ const promotedType = promoteIso8601TypeRef(bf.type, bf.description);
19650
+ const val = synthValue(promotedType, ctx, imports);
19147
19651
  if (val === null) return null;
19148
19652
  argParts.push(val);
19149
- if (isScalarBodyField(bf.type)) requiredBodyPaths.push(bf.name);
19653
+ if (isScalarBodyField(promotedType)) requiredBodyPaths.push(bf.name);
19150
19654
  }
19151
19655
  }
19152
19656
  const plan2 = plan;
@@ -19264,7 +19768,7 @@ function buildWrapperTest(op, wrapper, ctx) {
19264
19768
  argParts.push(ktStringLiteral("sample-arg"));
19265
19769
  continue;
19266
19770
  }
19267
- const val = synthValue(rp.field.type, ctx, imports);
19771
+ const val = synthValue(promoteIso8601TypeRef(rp.field.type, rp.field.description), ctx, imports);
19268
19772
  if (val === null) return null;
19269
19773
  argParts.push(val);
19270
19774
  }
@@ -19886,11 +20390,7 @@ const kotlinEmitter = {
19886
20390
  if (!fs.existsSync(path.join(targetDir, "gradlew"))) return null;
19887
20391
  return {
19888
20392
  cmd: "bash",
19889
- args: [
19890
- "-c",
19891
- `cd ${JSON.stringify(targetDir)} && ./gradlew ktlintFormat --quiet 2>/dev/null; true`,
19892
- "--"
19893
- ]
20393
+ args: ["-c", "./gradlew ktlintFormat --quiet >/dev/null 2>&1; true"]
19894
20394
  };
19895
20395
  }
19896
20396
  };
@@ -22300,4 +22800,4 @@ const workosEmittersPlugin = {
22300
22800
  //#endregion
22301
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 };
22302
22802
 
22303
- //# sourceMappingURL=plugin-DOE0FqrZ.mjs.map
22803
+ //# sourceMappingURL=plugin-Dh9JSScr.mjs.map