@workos/oagen-emitters 0.2.1 → 0.4.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/.husky/pre-commit +1 -0
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +15 -0
- package/README.md +129 -0
- package/dist/index.d.mts +13 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +14549 -3385
- package/dist/index.mjs.map +1 -1
- package/docs/sdk-architecture/dotnet.md +336 -0
- package/docs/sdk-architecture/go.md +338 -0
- package/docs/sdk-architecture/php.md +315 -0
- package/docs/sdk-architecture/python.md +511 -0
- package/oagen.config.ts +328 -2
- package/package.json +9 -5
- package/scripts/generate-php.js +13 -0
- package/scripts/git-push-with-published-oagen.sh +21 -0
- package/smoke/sdk-dotnet.ts +45 -12
- package/smoke/sdk-go.ts +116 -42
- package/smoke/sdk-php.ts +28 -26
- package/smoke/sdk-python.ts +5 -2
- package/src/dotnet/client.ts +89 -0
- package/src/dotnet/enums.ts +323 -0
- package/src/dotnet/fixtures.ts +236 -0
- package/src/dotnet/index.ts +246 -0
- package/src/dotnet/manifest.ts +36 -0
- package/src/dotnet/models.ts +344 -0
- package/src/dotnet/naming.ts +330 -0
- package/src/dotnet/resources.ts +622 -0
- package/src/dotnet/tests.ts +693 -0
- package/src/dotnet/type-map.ts +201 -0
- package/src/dotnet/wrappers.ts +186 -0
- package/src/go/client.ts +141 -0
- package/src/go/enums.ts +196 -0
- package/src/go/fixtures.ts +212 -0
- package/src/go/index.ts +84 -0
- package/src/go/manifest.ts +36 -0
- package/src/go/models.ts +254 -0
- package/src/go/naming.ts +179 -0
- package/src/go/resources.ts +827 -0
- package/src/go/tests.ts +751 -0
- package/src/go/type-map.ts +82 -0
- package/src/go/wrappers.ts +261 -0
- package/src/index.ts +4 -0
- package/src/kotlin/client.ts +53 -0
- package/src/kotlin/enums.ts +162 -0
- package/src/kotlin/index.ts +92 -0
- package/src/kotlin/manifest.ts +55 -0
- package/src/kotlin/models.ts +395 -0
- package/src/kotlin/naming.ts +223 -0
- package/src/kotlin/overrides.ts +25 -0
- package/src/kotlin/resources.ts +667 -0
- package/src/kotlin/tests.ts +1019 -0
- package/src/kotlin/type-map.ts +123 -0
- package/src/kotlin/wrappers.ts +168 -0
- package/src/node/client.ts +128 -115
- package/src/node/enums.ts +9 -0
- package/src/node/errors.ts +37 -232
- package/src/node/field-plan.ts +726 -0
- package/src/node/fixtures.ts +9 -1
- package/src/node/index.ts +3 -9
- package/src/node/models.ts +178 -21
- package/src/node/naming.ts +49 -111
- package/src/node/resources.ts +527 -397
- package/src/node/sdk-errors.ts +41 -0
- package/src/node/tests.ts +69 -19
- package/src/node/type-map.ts +4 -2
- package/src/node/utils.ts +13 -71
- package/src/node/wrappers.ts +151 -0
- package/src/php/client.ts +179 -0
- package/src/php/enums.ts +67 -0
- package/src/php/errors.ts +9 -0
- package/src/php/fixtures.ts +181 -0
- package/src/php/index.ts +96 -0
- package/src/php/manifest.ts +36 -0
- package/src/php/models.ts +310 -0
- package/src/php/naming.ts +279 -0
- package/src/php/resources.ts +636 -0
- package/src/php/tests.ts +609 -0
- package/src/php/type-map.ts +90 -0
- package/src/php/utils.ts +18 -0
- package/src/php/wrappers.ts +152 -0
- package/src/python/client.ts +345 -0
- package/src/python/enums.ts +313 -0
- package/src/python/fixtures.ts +196 -0
- package/src/python/index.ts +95 -0
- package/src/python/manifest.ts +38 -0
- package/src/python/models.ts +688 -0
- package/src/python/naming.ts +189 -0
- package/src/python/resources.ts +1322 -0
- package/src/python/tests.ts +1335 -0
- package/src/python/type-map.ts +93 -0
- package/src/python/wrappers.ts +191 -0
- package/src/shared/model-utils.ts +472 -0
- package/src/shared/naming-utils.ts +154 -0
- package/src/shared/non-spec-services.ts +54 -0
- package/src/shared/resolved-ops.ts +109 -0
- package/src/shared/wrapper-utils.ts +70 -0
- package/test/dotnet/client.test.ts +121 -0
- package/test/dotnet/enums.test.ts +193 -0
- package/test/dotnet/errors.test.ts +9 -0
- package/test/dotnet/manifest.test.ts +82 -0
- package/test/dotnet/models.test.ts +260 -0
- package/test/dotnet/resources.test.ts +255 -0
- package/test/dotnet/tests.test.ts +202 -0
- package/test/go/client.test.ts +92 -0
- package/test/go/enums.test.ts +132 -0
- package/test/go/errors.test.ts +9 -0
- package/test/go/models.test.ts +265 -0
- package/test/go/resources.test.ts +408 -0
- package/test/go/tests.test.ts +143 -0
- package/test/kotlin/models.test.ts +135 -0
- package/test/kotlin/tests.test.ts +176 -0
- package/test/node/client.test.ts +92 -12
- package/test/node/enums.test.ts +2 -0
- package/test/node/errors.test.ts +2 -41
- package/test/node/models.test.ts +2 -0
- package/test/node/naming.test.ts +23 -0
- package/test/node/resources.test.ts +315 -84
- package/test/node/serializers.test.ts +3 -1
- package/test/node/type-map.test.ts +11 -0
- package/test/php/client.test.ts +95 -0
- package/test/php/enums.test.ts +173 -0
- package/test/php/errors.test.ts +9 -0
- package/test/php/models.test.ts +497 -0
- package/test/php/resources.test.ts +682 -0
- package/test/php/tests.test.ts +185 -0
- package/test/python/client.test.ts +200 -0
- package/test/python/enums.test.ts +228 -0
- package/test/python/errors.test.ts +16 -0
- package/test/python/manifest.test.ts +74 -0
- package/test/python/models.test.ts +716 -0
- package/test/python/resources.test.ts +617 -0
- package/test/python/tests.test.ts +202 -0
- package/src/node/common.ts +0 -273
- package/src/node/config.ts +0 -71
- package/src/node/serializers.ts +0 -746
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { TypeRef, PrimitiveType, UnionType } from '@workos/oagen';
|
|
2
|
+
import { mapTypeRef as irMapTypeRef } from '@workos/oagen';
|
|
3
|
+
import { className } from './naming.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Map an IR TypeRef to a Python type hint string.
|
|
7
|
+
* Uses standard library types: str, int, float, bool, List, Dict, Optional, Union.
|
|
8
|
+
*/
|
|
9
|
+
export function mapTypeRef(ref: TypeRef): string {
|
|
10
|
+
return irMapTypeRef<string>(ref, {
|
|
11
|
+
primitive: mapPrimitive,
|
|
12
|
+
array: (ref, items) => {
|
|
13
|
+
void ref;
|
|
14
|
+
return `List[${items}]`;
|
|
15
|
+
},
|
|
16
|
+
model: (r) => `"${className(r.name)}"`,
|
|
17
|
+
enum: (r) => `"${className(r.name)}"`,
|
|
18
|
+
union: (r, variants) => joinUnionVariants(r, variants),
|
|
19
|
+
nullable: (ref, inner) => {
|
|
20
|
+
void ref;
|
|
21
|
+
return `Optional[${inner}]`;
|
|
22
|
+
},
|
|
23
|
+
literal: (r) =>
|
|
24
|
+
typeof r.value === 'string' ? `Literal["${r.value}"]` : r.value === null ? 'None' : `Literal[${String(r.value)}]`,
|
|
25
|
+
map: (ref, value) => {
|
|
26
|
+
void ref;
|
|
27
|
+
return `Dict[str, ${value}]`;
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Map an IR TypeRef to a plain Python type string (no quotes around model/enum refs).
|
|
34
|
+
* Used for import collection and direct type references.
|
|
35
|
+
*/
|
|
36
|
+
export function mapTypeRefUnquoted(ref: TypeRef, knownEnums?: Set<string>, allowRawEnumStrings = false): string {
|
|
37
|
+
return irMapTypeRef<string>(ref, {
|
|
38
|
+
primitive: mapPrimitive,
|
|
39
|
+
array: (ref, items) => {
|
|
40
|
+
void ref;
|
|
41
|
+
return `List[${items}]`;
|
|
42
|
+
},
|
|
43
|
+
model: (r) => className(r.name),
|
|
44
|
+
enum: (r) => {
|
|
45
|
+
if (knownEnums && !knownEnums.has(r.name)) return 'str';
|
|
46
|
+
const enumType = className(r.name);
|
|
47
|
+
return allowRawEnumStrings ? `Union[${enumType}, str]` : enumType;
|
|
48
|
+
},
|
|
49
|
+
union: (r, variants) => joinUnionVariants(r, variants),
|
|
50
|
+
nullable: (ref, inner) => {
|
|
51
|
+
void ref;
|
|
52
|
+
return `Optional[${inner}]`;
|
|
53
|
+
},
|
|
54
|
+
literal: (r) =>
|
|
55
|
+
typeof r.value === 'string' ? `Literal["${r.value}"]` : r.value === null ? 'None' : `Literal[${String(r.value)}]`,
|
|
56
|
+
map: (ref, value) => {
|
|
57
|
+
void ref;
|
|
58
|
+
return `Dict[str, ${value}]`;
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function mapPrimitive(ref: PrimitiveType): string {
|
|
64
|
+
if (ref.format) {
|
|
65
|
+
switch (ref.format) {
|
|
66
|
+
case 'binary':
|
|
67
|
+
return 'bytes';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
switch (ref.type) {
|
|
71
|
+
case 'string':
|
|
72
|
+
return 'str';
|
|
73
|
+
case 'integer':
|
|
74
|
+
return 'int';
|
|
75
|
+
case 'number':
|
|
76
|
+
return 'float';
|
|
77
|
+
case 'boolean':
|
|
78
|
+
return 'bool';
|
|
79
|
+
case 'unknown':
|
|
80
|
+
return 'Any';
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function joinUnionVariants(ref: UnionType, variants: string[]): string {
|
|
85
|
+
if (ref.compositionKind === 'allOf') {
|
|
86
|
+
// Python doesn't have intersection types; use the first variant
|
|
87
|
+
return variants[0] ?? 'Any';
|
|
88
|
+
}
|
|
89
|
+
// Deduplicate identical variants (e.g., Union[Foo, Foo] -> Foo)
|
|
90
|
+
const unique = [...new Set(variants)];
|
|
91
|
+
if (unique.length === 1) return unique[0];
|
|
92
|
+
return `Union[${unique.join(', ')}]`;
|
|
93
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import type { EmitterContext, ResolvedOperation, ResolvedWrapper } from '@workos/oagen';
|
|
2
|
+
import { toSnakeCase } from '@workos/oagen';
|
|
3
|
+
import { className, fieldName } from './naming.js';
|
|
4
|
+
import { resolveWrapperParams, formatWrapperDescription } from '../shared/wrapper-utils.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Generate Python wrapper method lines for split operations.
|
|
8
|
+
*
|
|
9
|
+
* Each wrapper is a typed convenience method that:
|
|
10
|
+
* - Accepts only the exposed params (not the full union body)
|
|
11
|
+
* - Injects constant defaults (e.g., grant_type)
|
|
12
|
+
* - Reads inferred fields from client config (e.g., client_id)
|
|
13
|
+
* - Delegates to the HTTP client with the constructed body
|
|
14
|
+
*
|
|
15
|
+
* Generates both sync and async versions.
|
|
16
|
+
*/
|
|
17
|
+
export function generateSyncWrapperMethods(resolvedOp: ResolvedOperation, ctx: EmitterContext): string[] {
|
|
18
|
+
return generateWrapperMethodsInner(resolvedOp, ctx, false);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function generateAsyncWrapperMethods(resolvedOp: ResolvedOperation, ctx: EmitterContext): string[] {
|
|
22
|
+
return generateWrapperMethodsInner(resolvedOp, ctx, true);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function generateWrapperMethodsInner(resolvedOp: ResolvedOperation, ctx: EmitterContext, isAsync: boolean): string[] {
|
|
26
|
+
if (!resolvedOp.wrappers || resolvedOp.wrappers.length === 0) return [];
|
|
27
|
+
|
|
28
|
+
const lines: string[] = [];
|
|
29
|
+
|
|
30
|
+
for (const wrapper of resolvedOp.wrappers) {
|
|
31
|
+
lines.push('');
|
|
32
|
+
emitWrapperMethod(lines, resolvedOp, wrapper, ctx, isAsync);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return lines;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function emitWrapperMethod(
|
|
39
|
+
lines: string[],
|
|
40
|
+
resolvedOp: ResolvedOperation,
|
|
41
|
+
wrapper: ResolvedWrapper,
|
|
42
|
+
ctx: EmitterContext,
|
|
43
|
+
isAsync: boolean,
|
|
44
|
+
): void {
|
|
45
|
+
const op = resolvedOp.operation;
|
|
46
|
+
const method = wrapper.name; // already snake_case
|
|
47
|
+
const wrapperParams = resolveWrapperParams(wrapper, ctx);
|
|
48
|
+
|
|
49
|
+
// Build signature
|
|
50
|
+
const defKeyword = isAsync ? 'async def' : 'def';
|
|
51
|
+
lines.push(` ${defKeyword} ${method}(`);
|
|
52
|
+
lines.push(' self,');
|
|
53
|
+
|
|
54
|
+
// Path params as positional args
|
|
55
|
+
for (const param of op.pathParams) {
|
|
56
|
+
const paramName = fieldName(param.name);
|
|
57
|
+
const paramType = resolveSimpleType(param.type);
|
|
58
|
+
lines.push(` ${paramName}: ${paramType},`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
lines.push(' *,');
|
|
62
|
+
|
|
63
|
+
// Exposed params as keyword args
|
|
64
|
+
for (const { paramName, field, isOptional } of wrapperParams) {
|
|
65
|
+
const pyName = fieldName(paramName);
|
|
66
|
+
const pyType = field ? resolveSimpleType(field.type) : 'str';
|
|
67
|
+
|
|
68
|
+
if (isOptional) {
|
|
69
|
+
lines.push(` ${pyName}: Optional[${pyType}] = None,`);
|
|
70
|
+
} else {
|
|
71
|
+
lines.push(` ${pyName}: ${pyType},`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
lines.push(' request_options: Optional[RequestOptions] = None,');
|
|
76
|
+
|
|
77
|
+
// Return type
|
|
78
|
+
const responseType = wrapper.responseModelName ? className(wrapper.responseModelName) : 'None';
|
|
79
|
+
|
|
80
|
+
lines.push(` ) -> ${responseType}:`);
|
|
81
|
+
|
|
82
|
+
// Docstring
|
|
83
|
+
lines.push(` """${formatWrapperDescription(wrapper.name)}."""`);
|
|
84
|
+
|
|
85
|
+
// Build body dict
|
|
86
|
+
lines.push(' body: Dict[str, Any] = {');
|
|
87
|
+
|
|
88
|
+
// Constant defaults
|
|
89
|
+
for (const [key, value] of Object.entries(wrapper.defaults)) {
|
|
90
|
+
lines.push(` "${key}": ${pythonLiteral(value)},`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Exposed params (required ones go directly)
|
|
94
|
+
for (const { paramName, isOptional } of wrapperParams) {
|
|
95
|
+
if (isOptional) continue;
|
|
96
|
+
const pyName = fieldName(paramName);
|
|
97
|
+
lines.push(` "${paramName}": ${pyName},`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
lines.push(' }');
|
|
101
|
+
|
|
102
|
+
// Inferred fields from client config
|
|
103
|
+
for (const field of wrapper.inferFromClient) {
|
|
104
|
+
const expr = clientFieldExpression(field);
|
|
105
|
+
lines.push(` if ${expr} is not None:`);
|
|
106
|
+
lines.push(` body["${field}"] = ${expr}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Optional exposed params
|
|
110
|
+
for (const { paramName, isOptional } of wrapperParams) {
|
|
111
|
+
if (!isOptional) continue;
|
|
112
|
+
const pyName = fieldName(paramName);
|
|
113
|
+
lines.push(` if ${pyName} is not None:`);
|
|
114
|
+
lines.push(` body["${paramName}"] = ${pyName}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Build path expression
|
|
118
|
+
let pathExpr: string;
|
|
119
|
+
if (op.pathParams.length > 0) {
|
|
120
|
+
let path = op.path.replace(/^\//, '');
|
|
121
|
+
for (const p of op.pathParams) {
|
|
122
|
+
path = path.replace(`{${p.name}}`, `{${fieldName(p.name)}}`);
|
|
123
|
+
}
|
|
124
|
+
pathExpr = `f"${path}"`;
|
|
125
|
+
} else {
|
|
126
|
+
pathExpr = `"${op.path.replace(/^\//, '')}"`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Make the request
|
|
130
|
+
const awaitPrefix = isAsync ? 'await ' : '';
|
|
131
|
+
lines.push('');
|
|
132
|
+
|
|
133
|
+
if (wrapper.responseModelName) {
|
|
134
|
+
lines.push(` return ${awaitPrefix}self._client.request(`);
|
|
135
|
+
lines.push(` method="${op.httpMethod.toUpperCase()}",`);
|
|
136
|
+
lines.push(` path=${pathExpr},`);
|
|
137
|
+
lines.push(' body=body,');
|
|
138
|
+
lines.push(` model=${className(wrapper.responseModelName)},`);
|
|
139
|
+
lines.push(' request_options=request_options,');
|
|
140
|
+
lines.push(' )');
|
|
141
|
+
} else {
|
|
142
|
+
lines.push(` ${awaitPrefix}self._client.request(`);
|
|
143
|
+
lines.push(` method="${op.httpMethod.toUpperCase()}",`);
|
|
144
|
+
lines.push(` path=${pathExpr},`);
|
|
145
|
+
lines.push(' body=body,');
|
|
146
|
+
lines.push(' request_options=request_options,');
|
|
147
|
+
lines.push(' )');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Convert a value to a Python literal. */
|
|
152
|
+
export function pythonLiteral(value: string | number | boolean): string {
|
|
153
|
+
if (typeof value === 'string') return `"${value.replace(/"/g, '\\"')}"`;
|
|
154
|
+
if (typeof value === 'boolean') return value ? 'True' : 'False';
|
|
155
|
+
return String(value);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Get the Python expression for reading a client config field. */
|
|
159
|
+
export function clientFieldExpression(field: string): string {
|
|
160
|
+
switch (field) {
|
|
161
|
+
case 'client_id':
|
|
162
|
+
return 'self._client.client_id';
|
|
163
|
+
case 'client_secret':
|
|
164
|
+
return 'self._client._api_key';
|
|
165
|
+
default:
|
|
166
|
+
return `self._client.${toSnakeCase(field)}`;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Resolve a TypeRef to a simple Python type string. */
|
|
171
|
+
function resolveSimpleType(ref: any): string {
|
|
172
|
+
if (ref.kind === 'primitive') {
|
|
173
|
+
switch (ref.type) {
|
|
174
|
+
case 'string':
|
|
175
|
+
return 'str';
|
|
176
|
+
case 'integer':
|
|
177
|
+
return 'int';
|
|
178
|
+
case 'number':
|
|
179
|
+
return 'float';
|
|
180
|
+
case 'boolean':
|
|
181
|
+
return 'bool';
|
|
182
|
+
default:
|
|
183
|
+
return 'Any';
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (ref.kind === 'nullable') return resolveSimpleType(ref.inner);
|
|
187
|
+
if (ref.kind === 'array') return `List[${resolveSimpleType(ref.items)}]`;
|
|
188
|
+
if (ref.kind === 'model') return className(ref.name);
|
|
189
|
+
if (ref.kind === 'enum') return className(ref.name);
|
|
190
|
+
return 'Any';
|
|
191
|
+
}
|