@workos/oagen-emitters 0.8.0 → 0.8.2

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.0"
2
+ ".": "0.8.2"
3
3
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.8.2](https://github.com/workos/oagen-emitters/compare/v0.8.1...v0.8.2) (2026-05-05)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **kotlin:** improve emitter output quality ([#86](https://github.com/workos/oagen-emitters/issues/86)) ([6dd9b2a](https://github.com/workos/oagen-emitters/commit/6dd9b2ad6904ebb088cba65801d4987a2af61482))
9
+
10
+ ## [0.8.1](https://github.com/workos/oagen-emitters/compare/v0.8.0...v0.8.1) (2026-05-05)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **emitters:** repair regressions surfaced by shared-schema spec rev ([#84](https://github.com/workos/oagen-emitters/issues/84)) ([a04d317](https://github.com/workos/oagen-emitters/commit/a04d3170707adea21f19632f2a149b735be91d50))
16
+
3
17
  ## [0.8.0](https://github.com/workos/oagen-emitters/compare/v0.7.5...v0.8.0) (2026-05-05)
4
18
 
5
19
 
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-bCMdV7KX.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-CeNME04k.mjs";
2
2
  export { dotnetEmitter, dotnetExtractor, elixirExtractor, goEmitter, goExtractor, kotlinEmitter, kotlinExtractor, nodeEmitter, nodeExtractor, phpEmitter, phpExtractor, pythonEmitter, pythonExtractor, rubyEmitter, rubyExtractor, rustExtractor, workosEmittersPlugin };
@@ -7456,6 +7456,12 @@ function generateModels$5(models, ctx) {
7456
7456
  const dirName = mountDirMap.get(service.name) ?? resolveDir(service.name);
7457
7457
  serviceDirModelPaths.add(`src/${ctx.namespace}/${dirName}/models`);
7458
7458
  }
7459
+ for (const dirPath of serviceDirModelPaths) if (!symbolsByDir.has(dirPath)) files.push({
7460
+ path: `${dirPath}/__init__.py`,
7461
+ content: "",
7462
+ integrateTarget: true,
7463
+ overwriteExisting: true
7464
+ });
7459
7465
  for (const [dirPath, names] of symbolsByDir) {
7460
7466
  const uniqueNames = [...new Set(names)].sort();
7461
7467
  const importLines = [];
@@ -7639,7 +7645,7 @@ function deserializeField(ref, accessor, isRequired, walrusVar = "_v") {
7639
7645
  const dispatchMap = entries.map(([value, modelName]) => `"${value}": ${className$5(modelName)}`).join(", ");
7640
7646
  const dataExpr = isRequired ? accessor : walrusVar;
7641
7647
  const dataCast = `cast(Dict[str, Any], ${dataExpr})`;
7642
- const branch = `(_disc.from_dict(${dataCast}) if (_disc := ${`{${dispatchMap}}.get(${dataCast}.get("${ref.discriminator.property}"))`}) is not None else ${dataExpr})`;
7648
+ const branch = `(_disc.from_dict(${dataCast}) if (_disc := ${`{${dispatchMap}}.get(cast(str, ${dataCast}.get("${ref.discriminator.property}")))`}) is not None else ${dataExpr})`;
7643
7649
  if (isRequired) return branch;
7644
7650
  return `(${branch}) if (${walrusVar} := ${accessor}) is not None else None`;
7645
7651
  }
@@ -7665,6 +7671,7 @@ function serializeField(ref, accessor) {
7665
7671
  case "union": {
7666
7672
  const modelVariants = (ref.variants ?? []).filter((v) => v.kind === "model");
7667
7673
  if ([...new Set(modelVariants.map((v) => v.name))].length === 1) return `${accessor}.to_dict()`;
7674
+ if (ref.discriminator && ref.discriminator.mapping && modelVariants.length > 0) return `${accessor}.to_dict() if hasattr(${accessor}, "to_dict") else ${accessor}`;
7668
7675
  return accessor;
7669
7676
  }
7670
7677
  default: return accessor;
@@ -9067,11 +9074,6 @@ function generateServiceInits(spec, ctx) {
9067
9074
  integrateTarget: true,
9068
9075
  overwriteExisting: true
9069
9076
  });
9070
- files.push({
9071
- path: `src/${ctx.namespace}/${dirName}/models/__init__.py`,
9072
- content: "",
9073
- skipIfExists: true
9074
- });
9075
9077
  }
9076
9078
  return files;
9077
9079
  }
@@ -14071,12 +14073,16 @@ function generateFixtures$1(spec) {
14071
14073
  content: JSON.stringify(fixture, null, 2)
14072
14074
  });
14073
14075
  }
14076
+ const seenListPaths = /* @__PURE__ */ new Set();
14074
14077
  for (const service of spec.services) for (const op of service.operations) if (op.pagination) {
14075
14078
  let itemModel = op.pagination.itemType.kind === "model" ? modelMap.get(op.pagination.itemType.name) : null;
14076
14079
  if (itemModel) {
14077
14080
  const unwrapped = unwrapListModel$1(itemModel, modelMap);
14078
14081
  if (unwrapped) itemModel = unwrapped;
14079
14082
  if (itemModel.fields.length === 0) continue;
14083
+ const path = `testdata/list_${fileName$1(itemModel.name)}.json`;
14084
+ if (seenListPaths.has(path)) continue;
14085
+ seenListPaths.add(path);
14080
14086
  const listFixture = {
14081
14087
  data: [generateModelFixture$1(itemModel, modelMap, enumMap)],
14082
14088
  list_metadata: {
@@ -14085,7 +14091,7 @@ function generateFixtures$1(spec) {
14085
14091
  }
14086
14092
  };
14087
14093
  files.push({
14088
- path: `testdata/list_${fileName$1(itemModel.name)}.json`,
14094
+ path,
14089
14095
  content: JSON.stringify(listFixture, null, 2)
14090
14096
  });
14091
14097
  }
@@ -15199,9 +15205,9 @@ function generateModels$2(models, ctx, discCtx) {
15199
15205
  if (models.length === 0) return [];
15200
15206
  const enumConstByName = /* @__PURE__ */ new Map();
15201
15207
  for (const e of ctx.spec.enums) if (e.values.length === 1) enumConstByName.set(e.name, String(e.values[0].value));
15202
- const requestBodyOnlyNames = collectRequestBodyOnlyModelNames(ctx.spec.services, models);
15203
15208
  const files = [];
15204
15209
  primeModelAliases(models);
15210
+ const requestBodyOnlyNames = collectRequestBodyOnlyModelNames(ctx.spec.services, models);
15205
15211
  const baseFieldLookup = /* @__PURE__ */ new Map();
15206
15212
  if (discCtx) {
15207
15213
  for (const model of models) if (discCtx.discriminatorBases.has(model.name)) {
@@ -15489,10 +15495,10 @@ function collectRequestBodyOnlyModelNames(services, models) {
15489
15495
  const otherReferences = /* @__PURE__ */ new Set();
15490
15496
  const collect = (ref, into) => {
15491
15497
  if (!ref) return;
15492
- walkTypeRef(ref, { model: (r) => into.add(r.name) });
15498
+ walkTypeRef(ref, { model: (r) => into.add(resolveModelName(r.name)) });
15493
15499
  };
15494
15500
  for (const service of services) for (const op of service.operations) {
15495
- if (op.requestBody?.kind === "model") requestBodyNames.add(op.requestBody.name);
15501
+ if (op.requestBody?.kind === "model") requestBodyNames.add(resolveModelName(op.requestBody.name));
15496
15502
  collect(op.response, otherReferences);
15497
15503
  if (op.pagination) collect(op.pagination.itemType, otherReferences);
15498
15504
  for (const p of [
@@ -17227,7 +17233,7 @@ const dotnetEmitter = {
17227
17233
  lines.push(" {");
17228
17234
  lines.push(" public override bool CanConvert(Type objectType) => objectType == typeof(object);");
17229
17235
  lines.push("");
17230
- lines.push(" public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object? existingValue, Newtonsoft.Json.JsonSerializer serializer)");
17236
+ lines.push(" public override object? ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object? existingValue, Newtonsoft.Json.JsonSerializer serializer)");
17231
17237
  lines.push(" {");
17232
17238
  lines.push(" var jObject = JObject.Load(reader);");
17233
17239
  lines.push(` var discriminatorValue = jObject["${disc.property}"]?.ToString();`);
@@ -17273,7 +17279,7 @@ const dotnetEmitter = {
17273
17279
  lines.push("");
17274
17280
  lines.push(` public override bool CanConvert(Type objectType) => typeof(${baseClass}).IsAssignableFrom(objectType);`);
17275
17281
  lines.push("");
17276
- lines.push(" public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object? existingValue, Newtonsoft.Json.JsonSerializer serializer)");
17282
+ lines.push(" public override object? ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object? existingValue, Newtonsoft.Json.JsonSerializer serializer)");
17277
17283
  lines.push(" {");
17278
17284
  lines.push(" var jObject = JObject.Load(reader);");
17279
17285
  lines.push(` var discriminatorValue = jObject["${disc.property}"]?.ToString();`);
@@ -17743,6 +17749,40 @@ const KOTLIN_SRC_PREFIX$2 = "src/main/kotlin/";
17743
17749
  const MODELS_PACKAGE = "com.workos.models";
17744
17750
  const MODELS_DIR = "com/workos/models";
17745
17751
  /**
17752
+ * Some specs leave string fields without `format: date-time` even though the
17753
+ * description (or the example) makes clear they carry an ISO-8601 timestamp.
17754
+ * Detect that here so we can promote the type to `OffsetDateTime` in the
17755
+ * Kotlin output.
17756
+ */
17757
+ const ISO_8601_DESCRIPTION_RE$2 = /\bISO[-_ ]?8601\b/i;
17758
+ function looksLikeIso8601String$2(description) {
17759
+ if (!description) return false;
17760
+ return ISO_8601_DESCRIPTION_RE$2.test(description);
17761
+ }
17762
+ function promoteIso8601TypeRef$2(type, description) {
17763
+ if (!looksLikeIso8601String$2(description)) return type;
17764
+ const promote = (t) => {
17765
+ if (t.kind === "primitive" && t.type === "string" && !t.format) return {
17766
+ kind: "primitive",
17767
+ type: "string",
17768
+ format: "date-time"
17769
+ };
17770
+ if (t.kind === "nullable") return {
17771
+ kind: "nullable",
17772
+ inner: promote(t.inner)
17773
+ };
17774
+ return t;
17775
+ };
17776
+ return promote(type);
17777
+ }
17778
+ function promoteFieldType$1(f) {
17779
+ const promoted = promoteIso8601TypeRef$2(f.type, f.description);
17780
+ return promoted === f.type ? f : {
17781
+ ...f,
17782
+ type: promoted
17783
+ };
17784
+ }
17785
+ /**
17746
17786
  * Generate Kotlin `data class` models. Each model becomes a separate `.kt`
17747
17787
  * file under `com.workos.models`. Discriminated unions emit a sealed class
17748
17788
  * with Jackson `@JsonTypeInfo` / `@JsonSubTypes` annotations so the base type
@@ -17979,7 +18019,8 @@ function emitWorkOSEvent(eventMapping) {
17979
18019
  function renderFields(fields, overrideFields = /* @__PURE__ */ new Set()) {
17980
18020
  const seen = /* @__PURE__ */ new Set();
17981
18021
  const lines = [];
17982
- for (const field of fields) {
18022
+ for (const rawField of fields) {
18023
+ const field = promoteFieldType$1(rawField);
17983
18024
  const kotlinName = propertyName(field.name);
17984
18025
  if (seen.has(kotlinName)) continue;
17985
18026
  seen.add(kotlinName);
@@ -17998,7 +18039,7 @@ function renderFields(fields, overrideFields = /* @__PURE__ */ new Set()) {
17998
18039
  const isOverride = overrideFields.has(kotlinName);
17999
18040
  const annotations = [];
18000
18041
  annotations.push(`@JsonProperty(${ktStringLiteral(field.name)})`);
18001
- if (field.deprecated) annotations.push("@Deprecated(\"Deprecated field\")");
18042
+ if (field.deprecated) annotations.push(buildDeprecatedAnnotation(field.description));
18002
18043
  const paramParts = [];
18003
18044
  if (field.description?.trim()) {
18004
18045
  const line = field.description.split("\n").find((l) => l.trim()) ?? "";
@@ -18029,6 +18070,26 @@ function collapseFieldEntries(rawLines) {
18029
18070
  return entries;
18030
18071
  }
18031
18072
  /**
18073
+ * Pull the most useful free-form deprecation hint out of a field description
18074
+ * and lift it into the `@Deprecated(...)` message argument. Most WorkOS
18075
+ * deprecations are written as a description that begins with "Deprecated"
18076
+ * (e.g. "Deprecated. Use `domain_data` instead."). When the description
18077
+ * doesn't carry a hint we fall back to a short, self-explanatory message
18078
+ * rather than the generic "Deprecated field" placeholder.
18079
+ */
18080
+ function deprecationMessageFromDescription(description) {
18081
+ if (!description) return "Deprecated.";
18082
+ const firstLine = description.split("\n").map((l) => l.trim()).find((l) => l.length > 0);
18083
+ if (!firstLine) return "Deprecated.";
18084
+ const collapsed = firstLine.replace(/\s+/g, " ").trim();
18085
+ if (collapsed.length === 0) return "Deprecated.";
18086
+ if (/\bdeprecat/i.test(collapsed)) return collapsed;
18087
+ return "Deprecated.";
18088
+ }
18089
+ function buildDeprecatedAnnotation(description) {
18090
+ return `@Deprecated(${ktStringLiteral(deprecationMessageFromDescription(description))})`;
18091
+ }
18092
+ /**
18032
18093
  * If the TypeRef is a literal (const) with a string, number, or boolean value,
18033
18094
  * return the Kotlin expression for that default. Otherwise return null.
18034
18095
  */
@@ -18043,7 +18104,8 @@ function collectImports(fields) {
18043
18104
  const imports = /* @__PURE__ */ new Set();
18044
18105
  if (fields.length === 0) return imports;
18045
18106
  imports.add("com.fasterxml.jackson.annotation.JsonProperty");
18046
- for (const field of fields) {
18107
+ for (const rawField of fields) {
18108
+ const field = promoteFieldType$1(rawField);
18047
18109
  const mapped = mapTypeRef$1(field.type);
18048
18110
  if (/\bOffsetDateTime\b/.test(mapped)) imports.add("java.time.OffsetDateTime");
18049
18111
  for (const enumName of collectEnumNames(field.type)) {
@@ -18200,21 +18262,21 @@ function emitWrapperMethod$1(resolvedOp, wrapper, ctx) {
18200
18262
  if (opDesc) kdocLines.push(opDesc.split("\n")[0]);
18201
18263
  else kdocLines.push(`${wrapperHumanName.charAt(0).toUpperCase()}${wrapperHumanName.slice(1)}.`);
18202
18264
  const paramDocs = [];
18203
- for (const pp of pathParams) if (pp.description?.trim()) paramDocs.push(`@param ${propertyName(pp.name)} ${escapeKdoc$1(pp.description.split("\n")[0].trim())}`);
18204
- for (const rp of resolvedParams) {
18205
- const desc = rp.field?.description?.trim();
18206
- if (desc) paramDocs.push(`@param ${propertyName(rp.paramName)} ${escapeKdoc$1(desc.split("\n")[0])}`);
18207
- }
18265
+ const pushParamDoc = (kotlinName, sourceName, description) => {
18266
+ const firstLine = description?.split("\n").find((l) => l.trim())?.trim() ?? "";
18267
+ const fallback = `the ${humanize(sourceName)} of the request.`;
18268
+ const text = firstLine || fallback;
18269
+ paramDocs.push(`@param ${kotlinName} ${escapeKdoc$1(text)}`);
18270
+ };
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);
18273
+ pushParamDoc("requestOptions", "request_options", "per-request overrides (idempotency key, API key, headers, timeout)");
18208
18274
  if (responseClass) paramDocs.push(`@return the ${responseClass}`);
18209
- if (paramDocs.length > 0 || kdocLines.length > 0) {
18210
- lines.push(" /**");
18211
- for (const l of kdocLines) lines.push(` * ${escapeKdoc$1(l)}`);
18212
- if (paramDocs.length > 0) {
18213
- lines.push(" *");
18214
- for (const p of paramDocs) lines.push(` * ${p}`);
18215
- }
18216
- lines.push(" */");
18217
- }
18275
+ lines.push(" /**");
18276
+ for (const l of kdocLines) lines.push(` * ${escapeKdoc$1(l)}`);
18277
+ lines.push(" *");
18278
+ for (const p of paramDocs) lines.push(` * ${p}`);
18279
+ lines.push(" */");
18218
18280
  lines.push(" @JvmOverloads");
18219
18281
  const params = [];
18220
18282
  for (const pp of pathParams) params.push(` ${propertyName(pp.name)}: String`);
@@ -18237,6 +18299,25 @@ function emitWrapperMethod$1(resolvedOp, wrapper, ctx) {
18237
18299
  }
18238
18300
  lines.push(` )${returnClause} {`);
18239
18301
  }
18302
+ const inferred = wrapper.inferFromClient ?? [];
18303
+ const usesStandardClientCreds = inferred.includes("client_id") && inferred.includes("client_secret");
18304
+ if (op.path === "/user_management/authenticate" && op.httpMethod.toUpperCase() === "POST" && responseClass === "AuthenticateResponse" && typeof wrapper.defaults?.grant_type === "string" && usesStandardClientCreds) {
18305
+ const grantType = wrapper.defaults.grant_type;
18306
+ lines.push(` return authenticate(`);
18307
+ lines.push(` grantType = ${ktLiteral(grantType)},`);
18308
+ lines.push(` requestOptions = requestOptions,`);
18309
+ const entryLines = resolvedParams.map((rp) => {
18310
+ const paramName = propertyName(rp.paramName);
18311
+ return ` ${ktLiteral(rp.paramName)} to ${paramName}`;
18312
+ });
18313
+ for (let i = 0; i < entryLines.length; i++) {
18314
+ const sep = i === entryLines.length - 1 ? "" : ",";
18315
+ lines.push(`${entryLines[i]}${sep}`);
18316
+ }
18317
+ lines.push(` )`);
18318
+ lines.push(" }");
18319
+ return lines;
18320
+ }
18240
18321
  const bodyEntries = [];
18241
18322
  for (const rp of resolvedParams) {
18242
18323
  const paramName = propertyName(rp.paramName);
@@ -18295,6 +18376,52 @@ function isHandwrittenOverride(op) {
18295
18376
  //#region src/kotlin/resources.ts
18296
18377
  const KOTLIN_SRC_PREFIX$1 = "src/main/kotlin/";
18297
18378
  /**
18379
+ * Some specs leave query params / fields typed as plain `string` even though
18380
+ * the description (or the field name) makes clear they carry an ISO-8601
18381
+ * timestamp. Detecting that here lets us emit `OffsetDateTime` so callers
18382
+ * don't have to format the wire string themselves.
18383
+ */
18384
+ const ISO_8601_DESCRIPTION_RE$1 = /\bISO[-_ ]?8601\b/i;
18385
+ function looksLikeIso8601String$1(description) {
18386
+ if (!description) return false;
18387
+ return ISO_8601_DESCRIPTION_RE$1.test(description);
18388
+ }
18389
+ /**
18390
+ * Promote a string `TypeRef` to a `format: date-time` primitive when the
18391
+ * accompanying description identifies it as an ISO-8601 timestamp. Leaves
18392
+ * non-string types untouched.
18393
+ */
18394
+ function promoteIso8601TypeRef$1(type, description) {
18395
+ if (!looksLikeIso8601String$1(description)) return type;
18396
+ const promote = (t) => {
18397
+ if (t.kind === "primitive" && t.type === "string" && !t.format) return {
18398
+ kind: "primitive",
18399
+ type: "string",
18400
+ format: "date-time"
18401
+ };
18402
+ if (t.kind === "nullable") return {
18403
+ kind: "nullable",
18404
+ inner: promote(t.inner)
18405
+ };
18406
+ return t;
18407
+ };
18408
+ return promote(type);
18409
+ }
18410
+ function promoteParameterType(p) {
18411
+ const promoted = promoteIso8601TypeRef$1(p.type, p.description);
18412
+ return promoted === p.type ? p : {
18413
+ ...p,
18414
+ type: promoted
18415
+ };
18416
+ }
18417
+ function promoteFieldType(f) {
18418
+ const promoted = promoteIso8601TypeRef$1(f.type, f.description);
18419
+ return promoted === f.type ? f : {
18420
+ ...f,
18421
+ type: promoted
18422
+ };
18423
+ }
18424
+ /**
18298
18425
  * Generate one API class per mount group. Methods map 1:1 to IR operations.
18299
18426
  * Path params, query params, and body fields are flattened into the method
18300
18427
  * signature so callers never need to construct an intermediate options object.
@@ -18425,9 +18552,9 @@ function renderMethod(_mountName, method, op, ctx, resolvedOp, imports) {
18425
18552
  const pathParams = sortPathParamsByTemplateOrder(op);
18426
18553
  const groupedParamNames = collectGroupedParamNames(op);
18427
18554
  const hasGroups = (op.parameterGroups?.length ?? 0) > 0;
18428
- const queryParams = op.queryParams.filter((p) => !hidden.has(p.name) && !groupedParamNames.has(p.name));
18555
+ const queryParams = op.queryParams.filter((p) => !hidden.has(p.name) && !groupedParamNames.has(p.name)).map(promoteParameterType);
18429
18556
  const bodyModel = resolveBodyModel$2(op, ctx);
18430
- const bodyFields = bodyModel ? bodyModel.fields.filter((f) => !hidden.has(f.name) && !groupedParamNames.has(f.name)) : [];
18557
+ const bodyFields = bodyModel ? bodyModel.fields.filter((f) => !hidden.has(f.name) && !groupedParamNames.has(f.name)).map(promoteFieldType) : [];
18431
18558
  for (const p of [...pathParams, ...queryParams]) registerTypeImports(p.type, imports, ctx);
18432
18559
  for (const f of bodyFields) registerTypeImports(f.type, imports, ctx);
18433
18560
  const paginatedItemName = resolvePaginatedItemName(plan.paginatedItemModelName, ctx);
@@ -18538,9 +18665,8 @@ function renderMethod(_mountName, method, op, ctx, resolvedOp, imports) {
18538
18665
  lines.push(` before = ${pickNamedQueryParam(sortedQuery, "before")},`);
18539
18666
  lines.push(` after = ${pickNamedQueryParam(sortedQuery, "after")}`);
18540
18667
  lines.push(` ) {`);
18541
- lines.push(` val params = this`);
18542
- for (const qp of sortedQuery.filter((p) => p.name !== "after" && p.name !== "before")) for (const ln of emitQueryParam(qp, " ")) lines.push(ln);
18543
- for (const group of op.parameterGroups ?? []) for (const ln of emitGroupQueryDispatch(group, groupParamNames.get(group.name), " ")) lines.push(ln);
18668
+ for (const qp of sortedQuery.filter((p) => p.name !== "after" && p.name !== "before")) for (const ln of emitQueryParam(qp, " ", true)) lines.push(ln);
18669
+ for (const group of op.parameterGroups ?? []) for (const ln of emitGroupQueryDispatch(group, groupParamNames.get(group.name), " ", true)) lines.push(ln);
18544
18670
  lines.push(` }`);
18545
18671
  } else {
18546
18672
  const groupsGoToQuery = hasGroups && !hasBody;
@@ -18660,14 +18786,15 @@ function buildMethodKdoc(op, pathParams, queryParams, bodyFields, bodyParamNames
18660
18786
  if (descriptionRaw) for (const l of descriptionRaw.split("\n")) textLines.push(escapeKdoc(l));
18661
18787
  const paramDocs = [];
18662
18788
  const seenParamDocs = /* @__PURE__ */ new Set();
18663
- const pushParamDoc = (name, description, deprecated) => {
18789
+ const pushParamDoc = (name, sourceName, description, deprecated) => {
18664
18790
  if (seenParamDocs.has(name)) return;
18665
18791
  seenParamDocs.add(name);
18666
- paramDocs.push(formatParamDoc(name, description, deprecated));
18792
+ paramDocs.push(formatParamDoc(name, description, deprecated, sourceName));
18667
18793
  };
18668
- for (const pp of pathParams) if (pp.description?.trim() || pp.deprecated) pushParamDoc(propertyName(pp.name), pp.description, pp.deprecated);
18669
- for (const qp of queryParams) if (qp.description?.trim() || qp.deprecated) pushParamDoc(propertyName(qp.name), qp.description, qp.deprecated);
18670
- for (const bf of bodyFields) if (bf.description?.trim() || bf.deprecated) pushParamDoc(bodyParamNames.get(bf.name), bf.description, bf.deprecated);
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);
18797
+ pushParamDoc("requestOptions", "request_options", REQUEST_OPTIONS_PARAM_DESCRIPTION);
18671
18798
  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;
18672
18799
  if (!(textLines.length > 0 || paramDocs.length > 0 || returnDoc !== null)) return [];
18673
18800
  const out = [" /**"];
@@ -18681,10 +18808,17 @@ function buildMethodKdoc(op, pathParams, queryParams, bodyFields, bodyParamNames
18681
18808
  out.push(" */");
18682
18809
  return out;
18683
18810
  }
18684
- function formatParamDoc(kotlinName, description, deprecated) {
18685
- const text = (description?.split("\n").find((l) => l.trim()) ?? "").trim();
18686
- const parts = [deprecated ? "**Deprecated.**" : "", text].filter(Boolean).join(" ");
18687
- return `@param ${kotlinName}${parts ? ` ${escapeKdoc(parts)}` : ""}`;
18811
+ /**
18812
+ * Stable, canned description for the trailing `requestOptions` parameter that
18813
+ * every generated method exposes. Kept as a constant so the same phrasing
18814
+ * appears across resource methods, wrapper methods, and union-split helpers.
18815
+ */
18816
+ const REQUEST_OPTIONS_PARAM_DESCRIPTION = "per-request overrides (idempotency key, API key, headers, timeout)";
18817
+ function formatParamDoc(kotlinName, description, deprecated, sourceName) {
18818
+ const specText = (description?.split("\n").find((l) => l.trim()) ?? "").trim();
18819
+ const deprecationNote = deprecated ? "**Deprecated.**" : "";
18820
+ const fallback = `the ${humanize(sourceName ?? kotlinName)} of the request.`;
18821
+ return `@param ${kotlinName} ${escapeKdoc([deprecationNote, specText || fallback].filter(Boolean).join(" "))}`;
18688
18822
  }
18689
18823
  /**
18690
18824
  * Unwrap a possibly-nullable type to check if the inner type is an array,
@@ -18703,32 +18837,43 @@ function unwrapArray(t) {
18703
18837
  function valueExprForQuery(type) {
18704
18838
  const inner = type.kind === "nullable" ? type.inner : type;
18705
18839
  if (inner.kind === "enum") return "it.value";
18706
- if (inner.kind === "primitive" && inner.type === "string") return "it";
18840
+ if (inner.kind === "primitive" && inner.type === "string") return inner.format === "date-time" ? "it.toString()" : "it";
18707
18841
  return "it.toString()";
18708
18842
  }
18709
- function emitQueryParam(p, indent) {
18843
+ function emitQueryParam(p, indent, receiverMode = false) {
18710
18844
  const prop = propertyName(p.name);
18711
18845
  const rendered = queryParamToString(p.type, prop);
18712
18846
  const inner = p.type.kind === "nullable" ? p.type.inner : p.type;
18713
18847
  const arrayItem = unwrapArray(p.type);
18848
+ const callPrefix = receiverMode ? "" : "params.";
18849
+ const addPair = (pair) => receiverMode ? `add(${pair})` : `params += ${pair}`;
18714
18850
  if (arrayItem) {
18715
18851
  const explode = p.explode ?? true;
18716
18852
  const itemExpr = valueExprForQuery(arrayItem);
18853
+ const isIdentity = itemExpr === "it";
18717
18854
  if (!explode) {
18718
- if (p.required) return [`${indent}params.addJoinedIfNotNull(${ktLiteral(p.name)}, ${prop}.map { ${itemExpr} })`];
18719
- return [`${indent}params.addJoinedIfNotNull(${ktLiteral(p.name)}, ${prop}?.map { ${itemExpr} })`];
18855
+ if (p.required) {
18856
+ const arg = isIdentity ? prop : `${prop}.map { ${itemExpr} }`;
18857
+ return [`${indent}${callPrefix}addJoinedIfNotNull(${ktLiteral(p.name)}, ${arg})`];
18858
+ }
18859
+ const arg = isIdentity ? prop : `${prop}?.map { ${itemExpr} }`;
18860
+ return [`${indent}${callPrefix}addJoinedIfNotNull(${ktLiteral(p.name)}, ${arg})`];
18861
+ }
18862
+ if (p.required) {
18863
+ const arg = isIdentity ? prop : `${prop}.map { ${itemExpr} }`;
18864
+ return [`${indent}${callPrefix}addEach(${ktLiteral(p.name)}, ${arg})`];
18720
18865
  }
18721
- if (p.required) return [`${indent}params.addEach(${ktLiteral(p.name)}, ${prop}.map { ${itemExpr} })`];
18722
- return [`${indent}${prop}?.let { params.addEach(${ktLiteral(p.name)}, it.map { ${itemExpr} }) }`];
18866
+ if (isIdentity) return [`${indent}${prop}?.let { ${callPrefix}addEach(${ktLiteral(p.name)}, it) }`];
18867
+ return [`${indent}${prop}?.let { ${callPrefix}addEach(${ktLiteral(p.name)}, it.map { ${itemExpr} }) }`];
18723
18868
  }
18724
- if (p.required) return [`${indent}params += ${ktLiteral(p.name)} to ${rendered}`];
18725
- if (inner.kind === "primitive" && inner.type === "string") return [`${indent}params.addIfNotNull(${ktLiteral(p.name)}, ${prop})`];
18726
- return [`${indent}${prop}?.let { params += ${ktLiteral(p.name)} to ${queryParamToString(inner, "it")} }`];
18869
+ if (p.required) return [`${indent}${addPair(`${ktLiteral(p.name)} to ${rendered}`)}`];
18870
+ if (inner.kind === "primitive" && inner.type === "string" && inner.format !== "date-time") return [`${indent}${callPrefix}addIfNotNull(${ktLiteral(p.name)}, ${prop})`];
18871
+ return [`${indent}${prop}?.let { ${addPair(`${ktLiteral(p.name)} to ${queryParamToString(inner, "it")}`)} }`];
18727
18872
  }
18728
18873
  function queryParamToString(type, varName) {
18729
18874
  if (type.kind === "enum") return `${varName}.value`;
18730
18875
  if (type.kind === "nullable") return queryParamToString(type.inner, varName);
18731
- if (type.kind === "primitive" && type.type === "string") return varName;
18876
+ if (type.kind === "primitive" && type.type === "string") return type.format === "date-time" ? `${varName}.toString()` : varName;
18732
18877
  return `${varName}.toString()`;
18733
18878
  }
18734
18879
  function pickNamedQueryParam(sorted, name) {
@@ -18842,14 +18987,14 @@ function generateSealedClass(group, bodyFieldTypes) {
18842
18987
  return lines;
18843
18988
  }
18844
18989
  /** Emit `when` dispatch that serializes a parameter group into query params. */
18845
- function emitGroupQueryDispatch(group, prop, indent) {
18990
+ function emitGroupQueryDispatch(group, prop, indent, receiverMode = false) {
18846
18991
  const sealedName = sealedGroupName$1(group.name);
18847
18992
  const lines = [];
18848
18993
  if (group.optional) {
18849
18994
  lines.push(`${indent}if (${prop} != null) {`);
18850
- emitWhenBlock(lines, group, sealedName, prop, `${indent} `);
18995
+ emitWhenBlock(lines, group, sealedName, prop, `${indent} `, receiverMode);
18851
18996
  lines.push(`${indent}}`);
18852
- } else emitWhenBlock(lines, group, sealedName, prop, indent);
18997
+ } else emitWhenBlock(lines, group, sealedName, prop, indent, receiverMode);
18853
18998
  return lines;
18854
18999
  }
18855
19000
  function assignGroupParameterNames$1(op, occupiedNames) {
@@ -18876,13 +19021,14 @@ function reserveUniqueGroupParameterName$1(base, occupiedNames) {
18876
19021
  occupiedNames.add(fallback);
18877
19022
  return fallback;
18878
19023
  }
18879
- function emitWhenBlock(lines, group, sealedName, prop, indent) {
19024
+ function emitWhenBlock(lines, group, sealedName, prop, indent, receiverMode = false) {
18880
19025
  lines.push(`${indent}when (${prop}) {`);
18881
19026
  for (const variant of group.variants) {
18882
19027
  const variantName = className$1(variant.name);
18883
19028
  const entries = variant.parameters.map((p) => {
18884
19029
  const fieldProp = deriveShortPropertyName(p.name, group.name);
18885
- return `params += ${ktLiteral(p.name)} to ${prop}.${fieldProp}`;
19030
+ const pair = `${ktLiteral(p.name)} to ${prop}.${fieldProp}`;
19031
+ return receiverMode ? `add(${pair})` : `params += ${pair}`;
18886
19032
  });
18887
19033
  if (entries.length === 1) lines.push(`${indent} is ${sealedName}.${variantName} -> ${entries[0]}`);
18888
19034
  else {
@@ -18980,6 +19126,33 @@ function deduplicateByMount(services, ctx) {
18980
19126
  //#region src/kotlin/tests.ts
18981
19127
  const TEST_PREFIX = "src/test/kotlin/";
18982
19128
  /**
19129
+ * Mirror the ISO-8601 hint promotion the resource/model emitters use so tests
19130
+ * synthesize values whose Kotlin type matches the generated method signature.
19131
+ * Kept in sync with the helpers in `resources.ts` / `models.ts`; if the
19132
+ * detection rule changes, update all three.
19133
+ */
19134
+ const ISO_8601_DESCRIPTION_RE = /\bISO[-_ ]?8601\b/i;
19135
+ function looksLikeIso8601String(description) {
19136
+ if (!description) return false;
19137
+ return ISO_8601_DESCRIPTION_RE.test(description);
19138
+ }
19139
+ function promoteIso8601TypeRef(type, description) {
19140
+ if (!looksLikeIso8601String(description)) return type;
19141
+ const promote = (t) => {
19142
+ if (t.kind === "primitive" && t.type === "string" && !t.format) return {
19143
+ kind: "primitive",
19144
+ type: "string",
19145
+ format: "date-time"
19146
+ };
19147
+ if (t.kind === "nullable") return {
19148
+ kind: "nullable",
19149
+ inner: promote(t.inner)
19150
+ };
19151
+ return t;
19152
+ };
19153
+ return promote(type);
19154
+ }
19155
+ /**
18983
19156
  * Generate one JUnit 5 + WireMock test class per API mount group, plus a
18984
19157
  * cross-cutting model round-trip test.
18985
19158
  *
@@ -19114,10 +19287,11 @@ function buildOperationTest(op, resolved, ctx) {
19114
19287
  }
19115
19288
  for (const qp of sortedQuery) {
19116
19289
  if (!qp.required) break;
19117
- const val = synthValue(qp.type, ctx, imports);
19290
+ const promotedType = promoteIso8601TypeRef(qp.type, qp.description);
19291
+ const val = synthValue(promotedType, ctx, imports);
19118
19292
  if (val === null) return null;
19119
19293
  argParts.push(val);
19120
- const regex = queryValueRegexFor(qp.type);
19294
+ const regex = queryValueRegexFor(promotedType);
19121
19295
  if (regex !== null) requiredQueryAssertions.push({
19122
19296
  name: qp.name,
19123
19297
  valueRegex: regex
@@ -19137,10 +19311,11 @@ function buildOperationTest(op, resolved, ctx) {
19137
19311
  for (const bf of sortedBody) {
19138
19312
  if (sharedQueryBodyParams.has(bf.name)) continue;
19139
19313
  if (!bf.required) break;
19140
- const val = synthValue(bf.type, ctx, imports);
19314
+ const promotedType = promoteIso8601TypeRef(bf.type, bf.description);
19315
+ const val = synthValue(promotedType, ctx, imports);
19141
19316
  if (val === null) return null;
19142
19317
  argParts.push(val);
19143
- if (isScalarBodyField(bf.type)) requiredBodyPaths.push(bf.name);
19318
+ if (isScalarBodyField(promotedType)) requiredBodyPaths.push(bf.name);
19144
19319
  }
19145
19320
  }
19146
19321
  const plan2 = plan;
@@ -19258,7 +19433,7 @@ function buildWrapperTest(op, wrapper, ctx) {
19258
19433
  argParts.push(ktStringLiteral("sample-arg"));
19259
19434
  continue;
19260
19435
  }
19261
- const val = synthValue(rp.field.type, ctx, imports);
19436
+ const val = synthValue(promoteIso8601TypeRef(rp.field.type, rp.field.description), ctx, imports);
19262
19437
  if (val === null) return null;
19263
19438
  argParts.push(val);
19264
19439
  }
@@ -22294,4 +22469,4 @@ const workosEmittersPlugin = {
22294
22469
  //#endregion
22295
22470
  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 };
22296
22471
 
22297
- //# sourceMappingURL=plugin-bCMdV7KX.mjs.map
22472
+ //# sourceMappingURL=plugin-CeNME04k.mjs.map