@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.
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +14 -0
- package/dist/index.mjs +1 -1
- package/dist/{plugin-bCMdV7KX.mjs → plugin-CeNME04k.mjs} +239 -64
- package/dist/plugin-CeNME04k.mjs.map +1 -0
- package/dist/plugin.mjs +1 -1
- package/package.json +1 -1
- package/src/dotnet/index.ts +6 -2
- package/src/dotnet/models.ts +19 -7
- package/src/go/fixtures.ts +10 -2
- package/src/kotlin/models.ts +67 -3
- package/src/kotlin/resources.ts +128 -37
- package/src/kotlin/tests.ts +33 -5
- package/src/kotlin/wrappers.ts +60 -18
- package/src/python/client.ts +4 -6
- package/src/python/models.ts +30 -1
- package/dist/plugin-bCMdV7KX.mjs.map +0 -1
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-
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
18204
|
-
|
|
18205
|
-
const
|
|
18206
|
-
|
|
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
|
-
|
|
18210
|
-
|
|
18211
|
-
|
|
18212
|
-
|
|
18213
|
-
|
|
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
|
-
|
|
18542
|
-
for (const
|
|
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)
|
|
18669
|
-
for (const qp of queryParams)
|
|
18670
|
-
for (const bf of bodyFields)
|
|
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
|
-
|
|
18685
|
-
|
|
18686
|
-
|
|
18687
|
-
|
|
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)
|
|
18719
|
-
|
|
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 (
|
|
18722
|
-
return [`${indent}${prop}?.let {
|
|
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}
|
|
18725
|
-
if (inner.kind === "primitive" && inner.type === "string") return [`${indent}
|
|
18726
|
-
return [`${indent}${prop}?.let {
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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-
|
|
22472
|
+
//# sourceMappingURL=plugin-CeNME04k.mjs.map
|