@workos/oagen-emitters 0.2.0 → 0.3.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/.oxfmtrc.json +8 -1
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +15 -0
- package/README.md +129 -0
- package/dist/index.d.mts +10 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +11943 -2728
- package/dist/index.mjs.map +1 -1
- 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 +298 -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 +17 -3
- package/smoke/sdk-elixir.ts +17 -3
- package/smoke/sdk-go.ts +137 -46
- package/smoke/sdk-kotlin.ts +23 -4
- package/smoke/sdk-node.ts +15 -3
- package/smoke/sdk-php.ts +28 -26
- package/smoke/sdk-python.ts +5 -2
- package/smoke/sdk-ruby.ts +17 -3
- package/smoke/sdk-rust.ts +16 -3
- 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 +81 -0
- package/src/go/manifest.ts +36 -0
- package/src/go/models.ts +254 -0
- package/src/go/naming.ts +191 -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 +3 -0
- package/src/node/client.ts +167 -122
- package/src/node/enums.ts +13 -4
- package/src/node/errors.ts +42 -233
- package/src/node/field-plan.ts +726 -0
- package/src/node/fixtures.ts +15 -5
- package/src/node/index.ts +65 -16
- package/src/node/models.ts +264 -96
- package/src/node/naming.ts +52 -25
- package/src/node/resources.ts +621 -172
- package/src/node/sdk-errors.ts +41 -0
- package/src/node/tests.ts +71 -27
- package/src/node/type-map.ts +4 -2
- package/src/node/utils.ts +56 -64
- package/src/node/wrappers.ts +151 -0
- package/src/php/client.ts +171 -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 +298 -0
- package/src/php/resources.ts +561 -0
- package/src/php/tests.ts +533 -0
- package/src/php/type-map.ts +90 -0
- package/src/php/utils.ts +18 -0
- package/src/php/wrappers.ts +151 -0
- package/src/python/client.ts +337 -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 +209 -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 +255 -0
- package/src/shared/naming-utils.ts +107 -0
- package/src/shared/non-spec-services.ts +54 -0
- package/src/shared/resolved-ops.ts +109 -0
- package/src/shared/wrapper-utils.ts +59 -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/node/client.test.ts +199 -94
- package/test/node/enums.test.ts +75 -3
- package/test/node/errors.test.ts +2 -41
- package/test/node/models.test.ts +109 -20
- package/test/node/naming.test.ts +37 -4
- package/test/node/resources.test.ts +662 -30
- package/test/node/serializers.test.ts +36 -7
- package/test/node/type-map.test.ts +11 -0
- package/test/php/client.test.ts +94 -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 +644 -0
- package/test/php/tests.test.ts +118 -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 -744
|
@@ -0,0 +1,82 @@
|
|
|
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 Go type string.
|
|
7
|
+
*/
|
|
8
|
+
export function mapTypeRef(ref: TypeRef, asPointer = false): string {
|
|
9
|
+
const base = irMapTypeRef<string>(ref, {
|
|
10
|
+
primitive: mapPrimitive,
|
|
11
|
+
array: (_ref, items) => `[]${items}`,
|
|
12
|
+
model: (r) => `*${className(r.name)}`,
|
|
13
|
+
enum: (r) => className(r.name),
|
|
14
|
+
union: (_r, variants) => joinUnionVariants(_r, variants),
|
|
15
|
+
nullable: (_ref, inner) => {
|
|
16
|
+
// If inner is already a pointer type (model), don't double-pointer
|
|
17
|
+
if (inner.startsWith('*')) return inner;
|
|
18
|
+
return `*${inner}`;
|
|
19
|
+
},
|
|
20
|
+
literal: (r) => {
|
|
21
|
+
if (r.value === null) return 'interface{}';
|
|
22
|
+
if (typeof r.value === 'string') return 'string';
|
|
23
|
+
if (typeof r.value === 'number') return Number.isInteger(r.value) ? 'int' : 'float64';
|
|
24
|
+
if (typeof r.value === 'boolean') return 'bool';
|
|
25
|
+
return 'interface{}';
|
|
26
|
+
},
|
|
27
|
+
map: (_ref, value) => `map[string]${value}`,
|
|
28
|
+
});
|
|
29
|
+
if (asPointer && !base.startsWith('*') && !base.startsWith('[]') && !base.startsWith('map[')) {
|
|
30
|
+
return `*${base}`;
|
|
31
|
+
}
|
|
32
|
+
return base;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Map an IR TypeRef to a Go type string without pointer wrapping for models.
|
|
37
|
+
* Used for response type references where we don't want a double pointer.
|
|
38
|
+
*/
|
|
39
|
+
export function mapTypeRefValue(ref: TypeRef): string {
|
|
40
|
+
return irMapTypeRef<string>(ref, {
|
|
41
|
+
primitive: mapPrimitive,
|
|
42
|
+
array: (_ref, items) => `[]${items}`,
|
|
43
|
+
model: (r) => className(r.name),
|
|
44
|
+
enum: (r) => className(r.name),
|
|
45
|
+
union: (_r, variants) => joinUnionVariants(_r, variants),
|
|
46
|
+
nullable: (_ref, inner) => `*${inner}`,
|
|
47
|
+
literal: (r) => {
|
|
48
|
+
if (r.value === null) return 'interface{}';
|
|
49
|
+
if (typeof r.value === 'string') return 'string';
|
|
50
|
+
if (typeof r.value === 'number') return Number.isInteger(r.value) ? 'int' : 'float64';
|
|
51
|
+
if (typeof r.value === 'boolean') return 'bool';
|
|
52
|
+
return 'interface{}';
|
|
53
|
+
},
|
|
54
|
+
map: (_ref, value) => `map[string]${value}`,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function mapPrimitive(ref: PrimitiveType): string {
|
|
59
|
+
if (ref.format === 'binary') return '[]byte';
|
|
60
|
+
switch (ref.type) {
|
|
61
|
+
case 'string':
|
|
62
|
+
return 'string';
|
|
63
|
+
case 'integer':
|
|
64
|
+
return 'int';
|
|
65
|
+
case 'number':
|
|
66
|
+
return 'float64';
|
|
67
|
+
case 'boolean':
|
|
68
|
+
return 'bool';
|
|
69
|
+
case 'unknown':
|
|
70
|
+
return 'interface{}';
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function joinUnionVariants(_ref: UnionType, variants: string[]): string {
|
|
75
|
+
if (_ref.compositionKind === 'allOf') {
|
|
76
|
+
return variants[0] ?? 'interface{}';
|
|
77
|
+
}
|
|
78
|
+
const unique = [...new Set(variants)];
|
|
79
|
+
if (unique.length === 1) return unique[0];
|
|
80
|
+
// Go doesn't have union types; use interface{}
|
|
81
|
+
return 'interface{}';
|
|
82
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import type { EmitterContext, ResolvedOperation, ResolvedWrapper } from '@workos/oagen';
|
|
2
|
+
import {
|
|
3
|
+
className as goClassName,
|
|
4
|
+
fieldName as goFieldName,
|
|
5
|
+
methodName as goMethodName,
|
|
6
|
+
unexportedName,
|
|
7
|
+
} from './naming.js';
|
|
8
|
+
import { sortPathParamsByTemplateOrder } from './resources.js';
|
|
9
|
+
import { resolveWrapperParams, formatWrapperDescription, type ResolvedWrapperParam } from '../shared/wrapper-utils.js';
|
|
10
|
+
import { lowerFirstForDoc, fieldDocComment } from '../shared/naming-utils.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Generate Go wrapper method lines for union split operations.
|
|
14
|
+
*
|
|
15
|
+
* Each wrapper is a typed convenience method that:
|
|
16
|
+
* - Accepts only the exposed params (not the full union body)
|
|
17
|
+
* - Injects constant defaults (e.g., grant_type)
|
|
18
|
+
* - Reads inferred fields from client config (e.g., client_id)
|
|
19
|
+
* - Delegates to the HTTP client with the constructed body
|
|
20
|
+
*/
|
|
21
|
+
export function generateWrapperMethods(
|
|
22
|
+
serviceType: string,
|
|
23
|
+
resolvedOp: ResolvedOperation,
|
|
24
|
+
ctx: EmitterContext,
|
|
25
|
+
): string[] {
|
|
26
|
+
if (!resolvedOp.wrappers || resolvedOp.wrappers.length === 0) return [];
|
|
27
|
+
|
|
28
|
+
const lines: string[] = [];
|
|
29
|
+
|
|
30
|
+
for (const wrapper of resolvedOp.wrappers) {
|
|
31
|
+
const wrapperParams = resolveWrapperParams(wrapper, ctx);
|
|
32
|
+
lines.push('');
|
|
33
|
+
emitWrapperParamsStruct(lines, wrapper, wrapperParams);
|
|
34
|
+
lines.push('');
|
|
35
|
+
emitWrapperMethod(lines, serviceType, resolvedOp, wrapper, wrapperParams);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return lines;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function emitWrapperParamsStruct(
|
|
42
|
+
lines: string[],
|
|
43
|
+
wrapper: ResolvedWrapper,
|
|
44
|
+
wrapperParams: ResolvedWrapperParam[],
|
|
45
|
+
): void {
|
|
46
|
+
const structName = `${goMethodName(wrapper.name)}Params`;
|
|
47
|
+
|
|
48
|
+
lines.push(`// ${structName} contains the parameters for ${goMethodName(wrapper.name)}.`);
|
|
49
|
+
lines.push(`type ${structName} struct {`);
|
|
50
|
+
|
|
51
|
+
for (const { paramName, field, isOptional } of wrapperParams) {
|
|
52
|
+
const goField = goFieldName(paramName);
|
|
53
|
+
const goType = field ? resolveSimpleGoType(field.type) : 'string';
|
|
54
|
+
|
|
55
|
+
if (field?.description) {
|
|
56
|
+
const fdLines = field.description.split('\n').filter((l: string) => l.trim());
|
|
57
|
+
lines.push(`\t// ${fieldDocComment(goField, fdLines[0])}`);
|
|
58
|
+
for (let i = 1; i < fdLines.length; i++) {
|
|
59
|
+
lines.push(`\t// ${fdLines[i].trim()}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (isOptional) {
|
|
63
|
+
const optType = goType.startsWith('*') || goType.startsWith('[]') ? goType : `*${goType}`;
|
|
64
|
+
lines.push(`\t${goField} ${optType} \`json:"${paramName},omitempty"\``);
|
|
65
|
+
} else {
|
|
66
|
+
lines.push(`\t${goField} ${goType} \`json:"${paramName}"\``);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
lines.push('}');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function emitWrapperMethod(
|
|
74
|
+
lines: string[],
|
|
75
|
+
serviceType: string,
|
|
76
|
+
resolvedOp: ResolvedOperation,
|
|
77
|
+
wrapper: ResolvedWrapper,
|
|
78
|
+
wrapperParams: ResolvedWrapperParam[],
|
|
79
|
+
): void {
|
|
80
|
+
const op = resolvedOp.operation;
|
|
81
|
+
const method = goMethodName(wrapper.name);
|
|
82
|
+
const paramsStruct = `${method}Params`;
|
|
83
|
+
|
|
84
|
+
// Return type
|
|
85
|
+
const responseType = wrapper.responseModelName ? goClassName(wrapper.responseModelName) : null;
|
|
86
|
+
|
|
87
|
+
// GoDoc
|
|
88
|
+
lines.push(`// ${method} ${formatWrapperDescription(wrapper.name)}.`);
|
|
89
|
+
|
|
90
|
+
// Signature
|
|
91
|
+
const sigParams: string[] = ['ctx context.Context'];
|
|
92
|
+
|
|
93
|
+
// Path params as positional args (sorted by template order)
|
|
94
|
+
for (const p of sortPathParamsByTemplateOrder(op)) {
|
|
95
|
+
sigParams.push(`${lowerFirstSafe(goFieldName(p.name))} ${resolveSimpleGoType(p.type)}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
sigParams.push(`params *${paramsStruct}`);
|
|
99
|
+
sigParams.push('opts ...RequestOption');
|
|
100
|
+
|
|
101
|
+
if (responseType) {
|
|
102
|
+
lines.push(`func (s *${serviceType}) ${method}(${sigParams.join(', ')}) (*${responseType}, error) {`);
|
|
103
|
+
} else {
|
|
104
|
+
lines.push(`func (s *${serviceType}) ${method}(${sigParams.join(', ')}) error {`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Build body map with defaults + exposed params
|
|
108
|
+
lines.push('\tbody := map[string]interface{}{');
|
|
109
|
+
|
|
110
|
+
// Constant defaults (e.g., grant_type)
|
|
111
|
+
for (const [key, value] of Object.entries(wrapper.defaults)) {
|
|
112
|
+
lines.push(`\t\t"${key}": ${goLiteral(value)},`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Required exposed params
|
|
116
|
+
for (const { paramName, isOptional } of wrapperParams) {
|
|
117
|
+
if (isOptional) continue;
|
|
118
|
+
const goField = goFieldName(paramName);
|
|
119
|
+
lines.push(`\t\t"${paramName}": params.${goField},`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
lines.push('\t}');
|
|
123
|
+
|
|
124
|
+
// Inferred fields from client config
|
|
125
|
+
for (const field of wrapper.inferFromClient) {
|
|
126
|
+
const expr = clientFieldExpression(field);
|
|
127
|
+
lines.push(`\tif ${expr} != "" {`);
|
|
128
|
+
lines.push(`\t\tbody["${field}"] = ${expr}`);
|
|
129
|
+
lines.push('\t}');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Optional exposed params
|
|
133
|
+
for (const { paramName, isOptional } of wrapperParams) {
|
|
134
|
+
if (!isOptional) continue;
|
|
135
|
+
const goField = goFieldName(paramName);
|
|
136
|
+
lines.push(`\tif params.${goField} != nil {`);
|
|
137
|
+
lines.push(`\t\tbody["${paramName}"] = *params.${goField}`);
|
|
138
|
+
lines.push('\t}');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Build path expression
|
|
142
|
+
let pathExpr: string;
|
|
143
|
+
if (op.pathParams.length > 0) {
|
|
144
|
+
let fmtStr = op.path;
|
|
145
|
+
const fmtArgs: string[] = [];
|
|
146
|
+
for (const p of sortPathParamsByTemplateOrder(op)) {
|
|
147
|
+
fmtStr = fmtStr.replace(`{${p.name}}`, '%s');
|
|
148
|
+
fmtArgs.push(lowerFirstSafe(goFieldName(p.name)));
|
|
149
|
+
}
|
|
150
|
+
pathExpr = `fmt.Sprintf("${fmtStr}", ${fmtArgs.join(', ')})`;
|
|
151
|
+
} else {
|
|
152
|
+
pathExpr = `"${op.path}"`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Make the request
|
|
156
|
+
if (responseType) {
|
|
157
|
+
lines.push(`\tvar result ${responseType}`);
|
|
158
|
+
lines.push(
|
|
159
|
+
`\t_, err := s.client.request(ctx, "${op.httpMethod.toUpperCase()}", ${pathExpr}, nil, body, &result, opts)`,
|
|
160
|
+
);
|
|
161
|
+
lines.push('\tif err != nil {');
|
|
162
|
+
lines.push('\t\treturn nil, err');
|
|
163
|
+
lines.push('\t}');
|
|
164
|
+
lines.push('\treturn &result, nil');
|
|
165
|
+
} else {
|
|
166
|
+
lines.push(
|
|
167
|
+
`\t_, err := s.client.request(ctx, "${op.httpMethod.toUpperCase()}", ${pathExpr}, nil, body, nil, opts)`,
|
|
168
|
+
);
|
|
169
|
+
lines.push('\treturn err');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
lines.push('}');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Convert a value to a Go literal. */
|
|
176
|
+
function goLiteral(value: string | number | boolean): string {
|
|
177
|
+
if (typeof value === 'string') return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
178
|
+
if (typeof value === 'boolean') return value ? 'true' : 'false';
|
|
179
|
+
return String(value);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Get the Go expression for reading a client config field. */
|
|
183
|
+
function clientFieldExpression(field: string): string {
|
|
184
|
+
switch (field) {
|
|
185
|
+
case 'client_id':
|
|
186
|
+
return 's.client.clientID';
|
|
187
|
+
case 'client_secret':
|
|
188
|
+
return 's.client.apiKey';
|
|
189
|
+
default:
|
|
190
|
+
return `s.client.${lowerFirstSafe(goFieldName(field))}`;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Resolve a TypeRef to a simple Go type string. */
|
|
195
|
+
function resolveSimpleGoType(ref: any): string {
|
|
196
|
+
if (ref.kind === 'primitive') {
|
|
197
|
+
switch (ref.type) {
|
|
198
|
+
case 'string':
|
|
199
|
+
return 'string';
|
|
200
|
+
case 'integer':
|
|
201
|
+
return 'int';
|
|
202
|
+
case 'number':
|
|
203
|
+
return 'float64';
|
|
204
|
+
case 'boolean':
|
|
205
|
+
return 'bool';
|
|
206
|
+
default:
|
|
207
|
+
return 'interface{}';
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (ref.kind === 'nullable') return `*${resolveSimpleGoType(ref.inner)}`;
|
|
211
|
+
if (ref.kind === 'array') return `[]${resolveSimpleGoType(ref.items)}`;
|
|
212
|
+
if (ref.kind === 'model') return `*${goClassName(ref.name)}`;
|
|
213
|
+
if (ref.kind === 'enum') return goClassName(ref.name);
|
|
214
|
+
if (ref.kind === 'union') {
|
|
215
|
+
// For oneOf with a single non-null variant, use that variant's type
|
|
216
|
+
const nonNull = ref.variants.filter((v: any) => v.kind !== 'literal' || v.value !== null);
|
|
217
|
+
if (nonNull.length === 1) return resolveSimpleGoType(nonNull[0]);
|
|
218
|
+
return 'interface{}';
|
|
219
|
+
}
|
|
220
|
+
return 'interface{}';
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Go reserved words set. */
|
|
224
|
+
const GO_RESERVED = new Set([
|
|
225
|
+
'break',
|
|
226
|
+
'case',
|
|
227
|
+
'chan',
|
|
228
|
+
'const',
|
|
229
|
+
'continue',
|
|
230
|
+
'default',
|
|
231
|
+
'defer',
|
|
232
|
+
'else',
|
|
233
|
+
'fallthrough',
|
|
234
|
+
'for',
|
|
235
|
+
'func',
|
|
236
|
+
'go',
|
|
237
|
+
'goto',
|
|
238
|
+
'if',
|
|
239
|
+
'import',
|
|
240
|
+
'interface',
|
|
241
|
+
'map',
|
|
242
|
+
'package',
|
|
243
|
+
'range',
|
|
244
|
+
'return',
|
|
245
|
+
'select',
|
|
246
|
+
'struct',
|
|
247
|
+
'switch',
|
|
248
|
+
'type',
|
|
249
|
+
'var',
|
|
250
|
+
]);
|
|
251
|
+
|
|
252
|
+
function lowerFirstSafe(s: string): string {
|
|
253
|
+
if (!s) return s;
|
|
254
|
+
const result = unexportedName(s);
|
|
255
|
+
if (GO_RESERVED.has(result)) return `${result}Param`;
|
|
256
|
+
return result;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function _lowerFirstField(s: string): string {
|
|
260
|
+
return lowerFirstForDoc(s);
|
|
261
|
+
}
|
package/src/index.ts
CHANGED