@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/src/kotlin/wrappers.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { EmitterContext, ResolvedOperation, ResolvedWrapper } from '@workos/oagen';
|
|
2
|
-
import { className, propertyName, ktLiteral, clientFieldExpression, escapeReserved } from './naming.js';
|
|
2
|
+
import { className, propertyName, ktLiteral, clientFieldExpression, escapeReserved, humanize } from './naming.js';
|
|
3
3
|
import { mapTypeRef, mapTypeRefOptional } from './type-map.js';
|
|
4
4
|
import { resolveWrapperParams } from '../shared/wrapper-utils.js';
|
|
5
5
|
import { sortPathParamsByTemplateOrder } from './resources.js';
|
|
@@ -35,7 +35,11 @@ function emitWrapperMethod(resolvedOp: ResolvedOperation, wrapper: ResolvedWrapp
|
|
|
35
35
|
|
|
36
36
|
const lines: string[] = [];
|
|
37
37
|
|
|
38
|
-
// Build KDoc
|
|
38
|
+
// Build KDoc: operation description + a `@param` line for *every* parameter
|
|
39
|
+
// (Dokka does not flag missing @param blocks, so coverage has to be enforced
|
|
40
|
+
// at emit time) + `@return` when there's a response model. Spec-provided
|
|
41
|
+
// descriptions are preferred; the fallback is templated from the parameter
|
|
42
|
+
// name so the SDK still compiles cleanly under failOnWarning.
|
|
39
43
|
const kdocLines: string[] = [];
|
|
40
44
|
const opDesc = (op.description ?? '').trim();
|
|
41
45
|
const wrapperHumanName = method.replace(/([a-z])([A-Z])/g, '$1 $2').toLowerCase();
|
|
@@ -45,29 +49,36 @@ function emitWrapperMethod(resolvedOp: ResolvedOperation, wrapper: ResolvedWrapp
|
|
|
45
49
|
kdocLines.push(`${wrapperHumanName.charAt(0).toUpperCase()}${wrapperHumanName.slice(1)}.`);
|
|
46
50
|
}
|
|
47
51
|
const paramDocs: string[] = [];
|
|
52
|
+
const pushParamDoc = (kotlinName: string, sourceName: string, description: string | undefined) => {
|
|
53
|
+
const firstLine =
|
|
54
|
+
description
|
|
55
|
+
?.split('\n')
|
|
56
|
+
.find((l) => l.trim())
|
|
57
|
+
?.trim() ?? '';
|
|
58
|
+
const fallback = `the ${humanize(sourceName)} of the request.`;
|
|
59
|
+
const text = firstLine || fallback;
|
|
60
|
+
paramDocs.push(`@param ${kotlinName} ${escapeKdoc(text)}`);
|
|
61
|
+
};
|
|
48
62
|
for (const pp of pathParams) {
|
|
49
|
-
|
|
50
|
-
paramDocs.push(`@param ${propertyName(pp.name)} ${escapeKdoc(pp.description.split('\n')[0].trim())}`);
|
|
51
|
-
}
|
|
63
|
+
pushParamDoc(propertyName(pp.name), pp.name, pp.description);
|
|
52
64
|
}
|
|
53
65
|
for (const rp of resolvedParams) {
|
|
54
|
-
|
|
55
|
-
if (desc) {
|
|
56
|
-
paramDocs.push(`@param ${propertyName(rp.paramName)} ${escapeKdoc(desc.split('\n')[0])}`);
|
|
57
|
-
}
|
|
66
|
+
pushParamDoc(propertyName(rp.paramName), rp.paramName, rp.field?.description);
|
|
58
67
|
}
|
|
68
|
+
// Trailing `requestOptions` parameter — stable canned phrasing.
|
|
69
|
+
pushParamDoc(
|
|
70
|
+
'requestOptions',
|
|
71
|
+
'request_options',
|
|
72
|
+
'per-request overrides (idempotency key, API key, headers, timeout)',
|
|
73
|
+
);
|
|
59
74
|
if (responseClass) {
|
|
60
75
|
paramDocs.push(`@return the ${responseClass}`);
|
|
61
76
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
for (const p of paramDocs) lines.push(` * ${p}`);
|
|
68
|
-
}
|
|
69
|
-
lines.push(' */');
|
|
70
|
-
}
|
|
77
|
+
lines.push(' /**');
|
|
78
|
+
for (const l of kdocLines) lines.push(` * ${escapeKdoc(l)}`);
|
|
79
|
+
lines.push(' *');
|
|
80
|
+
for (const p of paramDocs) lines.push(` * ${p}`);
|
|
81
|
+
lines.push(' */');
|
|
71
82
|
|
|
72
83
|
lines.push(' @JvmOverloads');
|
|
73
84
|
|
|
@@ -101,6 +112,37 @@ function emitWrapperMethod(resolvedOp: ResolvedOperation, wrapper: ResolvedWrapp
|
|
|
101
112
|
lines.push(` )${returnClause} {`);
|
|
102
113
|
}
|
|
103
114
|
|
|
115
|
+
// The /user_management/authenticate endpoint is union-split into one
|
|
116
|
+
// wrapper per grant_type. Every variant posts the same shape (caller
|
|
117
|
+
// params + grant_type + client_id + client_secret) to the same path with
|
|
118
|
+
// the same response model, so we route through a single `authenticate(...)`
|
|
119
|
+
// private helper instead of duplicating the request boilerplate per grant.
|
|
120
|
+
const inferred = wrapper.inferFromClient ?? [];
|
|
121
|
+
const usesStandardClientCreds = inferred.includes('client_id') && inferred.includes('client_secret');
|
|
122
|
+
if (
|
|
123
|
+
op.path === '/user_management/authenticate' &&
|
|
124
|
+
op.httpMethod.toUpperCase() === 'POST' &&
|
|
125
|
+
responseClass === 'AuthenticateResponse' &&
|
|
126
|
+
typeof wrapper.defaults?.grant_type === 'string' &&
|
|
127
|
+
usesStandardClientCreds
|
|
128
|
+
) {
|
|
129
|
+
const grantType = wrapper.defaults.grant_type;
|
|
130
|
+
lines.push(` return authenticate(`);
|
|
131
|
+
lines.push(` grantType = ${ktLiteral(grantType)},`);
|
|
132
|
+
lines.push(` requestOptions = requestOptions,`);
|
|
133
|
+
const entryLines = resolvedParams.map((rp) => {
|
|
134
|
+
const paramName = propertyName(rp.paramName);
|
|
135
|
+
return ` ${ktLiteral(rp.paramName)} to ${paramName}`;
|
|
136
|
+
});
|
|
137
|
+
for (let i = 0; i < entryLines.length; i++) {
|
|
138
|
+
const sep = i === entryLines.length - 1 ? '' : ',';
|
|
139
|
+
lines.push(`${entryLines[i]}${sep}`);
|
|
140
|
+
}
|
|
141
|
+
lines.push(` )`);
|
|
142
|
+
lines.push(' }');
|
|
143
|
+
return lines;
|
|
144
|
+
}
|
|
145
|
+
|
|
104
146
|
// Build body using bodyOf() — consistent with non-wrapper methods.
|
|
105
147
|
// bodyOf() automatically drops null optional values.
|
|
106
148
|
const bodyEntries: string[] = [];
|
package/src/python/client.ts
CHANGED
|
@@ -286,12 +286,10 @@ function generateServiceInits(spec: ApiSpec, ctx: EmitterContext): GeneratedFile
|
|
|
286
286
|
overwriteExisting: true,
|
|
287
287
|
});
|
|
288
288
|
|
|
289
|
-
//
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
skipIfExists: true,
|
|
294
|
-
});
|
|
289
|
+
// models/__init__.py is emitted unconditionally by `models.ts` — including
|
|
290
|
+
// an empty barrel for services with no models — so we don't need a safety
|
|
291
|
+
// net here. (A `skipIfExists` safety net previously caused stale imports
|
|
292
|
+
// to survive when the underlying module was pruned.)
|
|
295
293
|
}
|
|
296
294
|
|
|
297
295
|
return files;
|
package/src/python/models.ts
CHANGED
|
@@ -447,6 +447,24 @@ export function generateModels(models: Model[], ctx: EmitterContext): GeneratedF
|
|
|
447
447
|
serviceDirModelPaths.add(`src/${ctx.namespace}/${dirName}/models`);
|
|
448
448
|
}
|
|
449
449
|
|
|
450
|
+
// Emit an empty barrel for every service-models dir that has no symbols of
|
|
451
|
+
// its own (e.g. a service whose models live in another package via
|
|
452
|
+
// cross-domain aliases). Otherwise the live SDK can keep a stale
|
|
453
|
+
// `__init__.py` from a previous spec revision — when the underlying module
|
|
454
|
+
// gets pruned the dangling re-export survives and breaks pyright. Done here
|
|
455
|
+
// (not in client.ts) so a subsequent emission for the same path with real
|
|
456
|
+
// content always wins last-write-wins.
|
|
457
|
+
for (const dirPath of serviceDirModelPaths) {
|
|
458
|
+
if (!symbolsByDir.has(dirPath)) {
|
|
459
|
+
files.push({
|
|
460
|
+
path: `${dirPath}/__init__.py`,
|
|
461
|
+
content: '',
|
|
462
|
+
integrateTarget: true,
|
|
463
|
+
overwriteExisting: true,
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
450
468
|
for (const [dirPath, names] of symbolsByDir) {
|
|
451
469
|
// Use `import X as X` syntax for explicit re-exports (required by pyright strict)
|
|
452
470
|
const uniqueNames = [...new Set(names)].sort();
|
|
@@ -719,7 +737,12 @@ function deserializeField(ref: any, accessor: string, isRequired: boolean, walru
|
|
|
719
737
|
const dispatchMap = entries.map(([value, modelName]) => `"${value}": ${className(modelName)}`).join(', ');
|
|
720
738
|
const dataExpr = isRequired ? accessor : walrusVar;
|
|
721
739
|
const dataCast = `cast(Dict[str, Any], ${dataExpr})`;
|
|
722
|
-
|
|
740
|
+
// The dispatch dict has `str` keys, so pyright (strict) rejects the
|
|
741
|
+
// raw `Any | None` returned by `.get(prop)` even though `dict.get`
|
|
742
|
+
// accepts any hashable. Cast through `str` to satisfy the parameter
|
|
743
|
+
// type — runtime semantics are unchanged because a missing/`None`
|
|
744
|
+
// discriminator simply misses the dispatch and falls through.
|
|
745
|
+
const lookupExpr = `{${dispatchMap}}.get(cast(str, ${dataCast}.get("${ref.discriminator.property}")))`;
|
|
723
746
|
const branch = `(_disc.from_dict(${dataCast}) if (_disc := ${lookupExpr}) is not None else ${dataExpr})`;
|
|
724
747
|
if (isRequired) return branch;
|
|
725
748
|
return `(${branch}) if (${walrusVar} := ${accessor}) is not None else None`;
|
|
@@ -760,6 +783,12 @@ function serializeField(ref: any, accessor: string): string {
|
|
|
760
783
|
if (uniqueModels.length === 1) {
|
|
761
784
|
return `${accessor}.to_dict()`;
|
|
762
785
|
}
|
|
786
|
+
// Discriminated union: deserialize produced a dataclass instance for
|
|
787
|
+
// known discriminator values and the raw dict for unknowns. Round-trip
|
|
788
|
+
// both — call `.to_dict()` if it exists, otherwise pass through.
|
|
789
|
+
if (ref.discriminator && ref.discriminator.mapping && modelVariants.length > 0) {
|
|
790
|
+
return `${accessor}.to_dict() if hasattr(${accessor}, "to_dict") else ${accessor}`;
|
|
791
|
+
}
|
|
763
792
|
return accessor;
|
|
764
793
|
}
|
|
765
794
|
default:
|