@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.
Files changed (136) hide show
  1. package/.husky/pre-commit +1 -0
  2. package/.release-please-manifest.json +1 -1
  3. package/CHANGELOG.md +15 -0
  4. package/README.md +129 -0
  5. package/dist/index.d.mts +13 -1
  6. package/dist/index.d.mts.map +1 -1
  7. package/dist/index.mjs +14549 -3385
  8. package/dist/index.mjs.map +1 -1
  9. package/docs/sdk-architecture/dotnet.md +336 -0
  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 +328 -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 +45 -12
  18. package/smoke/sdk-go.ts +116 -42
  19. package/smoke/sdk-php.ts +28 -26
  20. package/smoke/sdk-python.ts +5 -2
  21. package/src/dotnet/client.ts +89 -0
  22. package/src/dotnet/enums.ts +323 -0
  23. package/src/dotnet/fixtures.ts +236 -0
  24. package/src/dotnet/index.ts +246 -0
  25. package/src/dotnet/manifest.ts +36 -0
  26. package/src/dotnet/models.ts +344 -0
  27. package/src/dotnet/naming.ts +330 -0
  28. package/src/dotnet/resources.ts +622 -0
  29. package/src/dotnet/tests.ts +693 -0
  30. package/src/dotnet/type-map.ts +201 -0
  31. package/src/dotnet/wrappers.ts +186 -0
  32. package/src/go/client.ts +141 -0
  33. package/src/go/enums.ts +196 -0
  34. package/src/go/fixtures.ts +212 -0
  35. package/src/go/index.ts +84 -0
  36. package/src/go/manifest.ts +36 -0
  37. package/src/go/models.ts +254 -0
  38. package/src/go/naming.ts +179 -0
  39. package/src/go/resources.ts +827 -0
  40. package/src/go/tests.ts +751 -0
  41. package/src/go/type-map.ts +82 -0
  42. package/src/go/wrappers.ts +261 -0
  43. package/src/index.ts +4 -0
  44. package/src/kotlin/client.ts +53 -0
  45. package/src/kotlin/enums.ts +162 -0
  46. package/src/kotlin/index.ts +92 -0
  47. package/src/kotlin/manifest.ts +55 -0
  48. package/src/kotlin/models.ts +395 -0
  49. package/src/kotlin/naming.ts +223 -0
  50. package/src/kotlin/overrides.ts +25 -0
  51. package/src/kotlin/resources.ts +667 -0
  52. package/src/kotlin/tests.ts +1019 -0
  53. package/src/kotlin/type-map.ts +123 -0
  54. package/src/kotlin/wrappers.ts +168 -0
  55. package/src/node/client.ts +128 -115
  56. package/src/node/enums.ts +9 -0
  57. package/src/node/errors.ts +37 -232
  58. package/src/node/field-plan.ts +726 -0
  59. package/src/node/fixtures.ts +9 -1
  60. package/src/node/index.ts +3 -9
  61. package/src/node/models.ts +178 -21
  62. package/src/node/naming.ts +49 -111
  63. package/src/node/resources.ts +527 -397
  64. package/src/node/sdk-errors.ts +41 -0
  65. package/src/node/tests.ts +69 -19
  66. package/src/node/type-map.ts +4 -2
  67. package/src/node/utils.ts +13 -71
  68. package/src/node/wrappers.ts +151 -0
  69. package/src/php/client.ts +179 -0
  70. package/src/php/enums.ts +67 -0
  71. package/src/php/errors.ts +9 -0
  72. package/src/php/fixtures.ts +181 -0
  73. package/src/php/index.ts +96 -0
  74. package/src/php/manifest.ts +36 -0
  75. package/src/php/models.ts +310 -0
  76. package/src/php/naming.ts +279 -0
  77. package/src/php/resources.ts +636 -0
  78. package/src/php/tests.ts +609 -0
  79. package/src/php/type-map.ts +90 -0
  80. package/src/php/utils.ts +18 -0
  81. package/src/php/wrappers.ts +152 -0
  82. package/src/python/client.ts +345 -0
  83. package/src/python/enums.ts +313 -0
  84. package/src/python/fixtures.ts +196 -0
  85. package/src/python/index.ts +95 -0
  86. package/src/python/manifest.ts +38 -0
  87. package/src/python/models.ts +688 -0
  88. package/src/python/naming.ts +189 -0
  89. package/src/python/resources.ts +1322 -0
  90. package/src/python/tests.ts +1335 -0
  91. package/src/python/type-map.ts +93 -0
  92. package/src/python/wrappers.ts +191 -0
  93. package/src/shared/model-utils.ts +472 -0
  94. package/src/shared/naming-utils.ts +154 -0
  95. package/src/shared/non-spec-services.ts +54 -0
  96. package/src/shared/resolved-ops.ts +109 -0
  97. package/src/shared/wrapper-utils.ts +70 -0
  98. package/test/dotnet/client.test.ts +121 -0
  99. package/test/dotnet/enums.test.ts +193 -0
  100. package/test/dotnet/errors.test.ts +9 -0
  101. package/test/dotnet/manifest.test.ts +82 -0
  102. package/test/dotnet/models.test.ts +260 -0
  103. package/test/dotnet/resources.test.ts +255 -0
  104. package/test/dotnet/tests.test.ts +202 -0
  105. package/test/go/client.test.ts +92 -0
  106. package/test/go/enums.test.ts +132 -0
  107. package/test/go/errors.test.ts +9 -0
  108. package/test/go/models.test.ts +265 -0
  109. package/test/go/resources.test.ts +408 -0
  110. package/test/go/tests.test.ts +143 -0
  111. package/test/kotlin/models.test.ts +135 -0
  112. package/test/kotlin/tests.test.ts +176 -0
  113. package/test/node/client.test.ts +92 -12
  114. package/test/node/enums.test.ts +2 -0
  115. package/test/node/errors.test.ts +2 -41
  116. package/test/node/models.test.ts +2 -0
  117. package/test/node/naming.test.ts +23 -0
  118. package/test/node/resources.test.ts +315 -84
  119. package/test/node/serializers.test.ts +3 -1
  120. package/test/node/type-map.test.ts +11 -0
  121. package/test/php/client.test.ts +95 -0
  122. package/test/php/enums.test.ts +173 -0
  123. package/test/php/errors.test.ts +9 -0
  124. package/test/php/models.test.ts +497 -0
  125. package/test/php/resources.test.ts +682 -0
  126. package/test/php/tests.test.ts +185 -0
  127. package/test/python/client.test.ts +200 -0
  128. package/test/python/enums.test.ts +228 -0
  129. package/test/python/errors.test.ts +16 -0
  130. package/test/python/manifest.test.ts +74 -0
  131. package/test/python/models.test.ts +716 -0
  132. package/test/python/resources.test.ts +617 -0
  133. package/test/python/tests.test.ts +202 -0
  134. package/src/node/common.ts +0 -273
  135. package/src/node/config.ts +0 -71
  136. 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
+ }