@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.
Files changed (110) hide show
  1. package/.husky/pre-commit +1 -0
  2. package/.oxfmtrc.json +8 -1
  3. package/.release-please-manifest.json +1 -1
  4. package/CHANGELOG.md +15 -0
  5. package/README.md +129 -0
  6. package/dist/index.d.mts +10 -1
  7. package/dist/index.d.mts.map +1 -1
  8. package/dist/index.mjs +11943 -2728
  9. package/dist/index.mjs.map +1 -1
  10. package/docs/sdk-architecture/go.md +338 -0
  11. package/docs/sdk-architecture/php.md +315 -0
  12. package/docs/sdk-architecture/python.md +511 -0
  13. package/oagen.config.ts +298 -2
  14. package/package.json +9 -5
  15. package/scripts/generate-php.js +13 -0
  16. package/scripts/git-push-with-published-oagen.sh +21 -0
  17. package/smoke/sdk-dotnet.ts +17 -3
  18. package/smoke/sdk-elixir.ts +17 -3
  19. package/smoke/sdk-go.ts +137 -46
  20. package/smoke/sdk-kotlin.ts +23 -4
  21. package/smoke/sdk-node.ts +15 -3
  22. package/smoke/sdk-php.ts +28 -26
  23. package/smoke/sdk-python.ts +5 -2
  24. package/smoke/sdk-ruby.ts +17 -3
  25. package/smoke/sdk-rust.ts +16 -3
  26. package/src/go/client.ts +141 -0
  27. package/src/go/enums.ts +196 -0
  28. package/src/go/fixtures.ts +212 -0
  29. package/src/go/index.ts +81 -0
  30. package/src/go/manifest.ts +36 -0
  31. package/src/go/models.ts +254 -0
  32. package/src/go/naming.ts +191 -0
  33. package/src/go/resources.ts +827 -0
  34. package/src/go/tests.ts +751 -0
  35. package/src/go/type-map.ts +82 -0
  36. package/src/go/wrappers.ts +261 -0
  37. package/src/index.ts +3 -0
  38. package/src/node/client.ts +167 -122
  39. package/src/node/enums.ts +13 -4
  40. package/src/node/errors.ts +42 -233
  41. package/src/node/field-plan.ts +726 -0
  42. package/src/node/fixtures.ts +15 -5
  43. package/src/node/index.ts +65 -16
  44. package/src/node/models.ts +264 -96
  45. package/src/node/naming.ts +52 -25
  46. package/src/node/resources.ts +621 -172
  47. package/src/node/sdk-errors.ts +41 -0
  48. package/src/node/tests.ts +71 -27
  49. package/src/node/type-map.ts +4 -2
  50. package/src/node/utils.ts +56 -64
  51. package/src/node/wrappers.ts +151 -0
  52. package/src/php/client.ts +171 -0
  53. package/src/php/enums.ts +67 -0
  54. package/src/php/errors.ts +9 -0
  55. package/src/php/fixtures.ts +181 -0
  56. package/src/php/index.ts +96 -0
  57. package/src/php/manifest.ts +36 -0
  58. package/src/php/models.ts +310 -0
  59. package/src/php/naming.ts +298 -0
  60. package/src/php/resources.ts +561 -0
  61. package/src/php/tests.ts +533 -0
  62. package/src/php/type-map.ts +90 -0
  63. package/src/php/utils.ts +18 -0
  64. package/src/php/wrappers.ts +151 -0
  65. package/src/python/client.ts +337 -0
  66. package/src/python/enums.ts +313 -0
  67. package/src/python/fixtures.ts +196 -0
  68. package/src/python/index.ts +95 -0
  69. package/src/python/manifest.ts +38 -0
  70. package/src/python/models.ts +688 -0
  71. package/src/python/naming.ts +209 -0
  72. package/src/python/resources.ts +1322 -0
  73. package/src/python/tests.ts +1335 -0
  74. package/src/python/type-map.ts +93 -0
  75. package/src/python/wrappers.ts +191 -0
  76. package/src/shared/model-utils.ts +255 -0
  77. package/src/shared/naming-utils.ts +107 -0
  78. package/src/shared/non-spec-services.ts +54 -0
  79. package/src/shared/resolved-ops.ts +109 -0
  80. package/src/shared/wrapper-utils.ts +59 -0
  81. package/test/go/client.test.ts +92 -0
  82. package/test/go/enums.test.ts +132 -0
  83. package/test/go/errors.test.ts +9 -0
  84. package/test/go/models.test.ts +265 -0
  85. package/test/go/resources.test.ts +408 -0
  86. package/test/go/tests.test.ts +143 -0
  87. package/test/node/client.test.ts +199 -94
  88. package/test/node/enums.test.ts +75 -3
  89. package/test/node/errors.test.ts +2 -41
  90. package/test/node/models.test.ts +109 -20
  91. package/test/node/naming.test.ts +37 -4
  92. package/test/node/resources.test.ts +662 -30
  93. package/test/node/serializers.test.ts +36 -7
  94. package/test/node/type-map.test.ts +11 -0
  95. package/test/php/client.test.ts +94 -0
  96. package/test/php/enums.test.ts +173 -0
  97. package/test/php/errors.test.ts +9 -0
  98. package/test/php/models.test.ts +497 -0
  99. package/test/php/resources.test.ts +644 -0
  100. package/test/php/tests.test.ts +118 -0
  101. package/test/python/client.test.ts +200 -0
  102. package/test/python/enums.test.ts +228 -0
  103. package/test/python/errors.test.ts +16 -0
  104. package/test/python/manifest.test.ts +74 -0
  105. package/test/python/models.test.ts +716 -0
  106. package/test/python/resources.test.ts +617 -0
  107. package/test/python/tests.test.ts +202 -0
  108. package/src/node/common.ts +0 -273
  109. package/src/node/config.ts +0 -71
  110. 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
@@ -1 +1,4 @@
1
1
  export { nodeEmitter } from './node/index.js';
2
+ export { pythonEmitter } from './python/index.js';
3
+ export { phpEmitter } from './php/index.js';
4
+ export { goEmitter } from './go/index.js';