@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.
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +19 -0
- package/dist/index.mjs +1 -1
- package/dist/{plugin-DOE0FqrZ.mjs → plugin-Dh9JSScr.mjs} +586 -86
- package/dist/plugin-Dh9JSScr.mjs.map +1 -0
- package/dist/plugin.mjs +1 -1
- package/package.json +4 -4
- package/src/kotlin/client.ts +12 -6
- package/src/kotlin/enums.ts +12 -1
- package/src/kotlin/index.ts +8 -6
- package/src/kotlin/models.ts +99 -4
- package/src/kotlin/naming.ts +58 -4
- package/src/kotlin/resources.ts +436 -53
- package/src/kotlin/suspend.ts +96 -0
- package/src/kotlin/tests.ts +33 -5
- package/src/kotlin/wrappers.ts +104 -21
- package/test/kotlin/resources.test.ts +94 -1
- package/dist/plugin-DOE0FqrZ.mjs.map +0 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
18210
|
-
|
|
18211
|
-
const
|
|
18212
|
-
|
|
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
|
-
|
|
18216
|
-
|
|
18217
|
-
|
|
18218
|
-
|
|
18219
|
-
|
|
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
|
-
|
|
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
|
|
18230
|
-
|
|
18231
|
-
|
|
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
|
-
|
|
18392
|
-
|
|
18393
|
-
|
|
18394
|
-
|
|
18395
|
-
|
|
18396
|
-
|
|
18397
|
-
|
|
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
|
-
|
|
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)
|
|
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)
|
|
18475
|
-
else
|
|
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
|
-
|
|
18485
|
-
} else
|
|
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
|
-
|
|
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
|
-
|
|
18548
|
-
for (const
|
|
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)
|
|
18675
|
-
for (const qp of queryParams)
|
|
18676
|
-
for (const bf of bodyFields)
|
|
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
|
-
|
|
18691
|
-
|
|
18692
|
-
|
|
18693
|
-
|
|
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)
|
|
18725
|
-
|
|
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 (
|
|
18728
|
-
return [`${indent}${prop}?.let {
|
|
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}
|
|
18731
|
-
if (inner.kind === "primitive" && inner.type === "string") return [`${indent}
|
|
18732
|
-
return [`${indent}${prop}?.let {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
18948
|
-
*
|
|
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 [${
|
|
18960
|
-
accessorLines.push(` val ${prop}: ${
|
|
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(` ${
|
|
19436
|
+
accessorLines.push(` ${apiCls}::class`);
|
|
18964
19437
|
accessorLines.push(" ) {");
|
|
18965
|
-
accessorLines.push(` ${
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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-
|
|
22803
|
+
//# sourceMappingURL=plugin-Dh9JSScr.mjs.map
|