@workos/oagen-emitters 0.2.1 → 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 (103) hide show
  1. package/.husky/pre-commit +1 -0
  2. package/.release-please-manifest.json +1 -1
  3. package/CHANGELOG.md +8 -0
  4. package/README.md +129 -0
  5. package/dist/index.d.mts +10 -1
  6. package/dist/index.d.mts.map +1 -1
  7. package/dist/index.mjs +11893 -3226
  8. package/dist/index.mjs.map +1 -1
  9. package/docs/sdk-architecture/go.md +338 -0
  10. package/docs/sdk-architecture/php.md +315 -0
  11. package/docs/sdk-architecture/python.md +511 -0
  12. package/oagen.config.ts +298 -2
  13. package/package.json +9 -5
  14. package/scripts/generate-php.js +13 -0
  15. package/scripts/git-push-with-published-oagen.sh +21 -0
  16. package/smoke/sdk-go.ts +116 -42
  17. package/smoke/sdk-php.ts +28 -26
  18. package/smoke/sdk-python.ts +5 -2
  19. package/src/go/client.ts +141 -0
  20. package/src/go/enums.ts +196 -0
  21. package/src/go/fixtures.ts +212 -0
  22. package/src/go/index.ts +81 -0
  23. package/src/go/manifest.ts +36 -0
  24. package/src/go/models.ts +254 -0
  25. package/src/go/naming.ts +191 -0
  26. package/src/go/resources.ts +827 -0
  27. package/src/go/tests.ts +751 -0
  28. package/src/go/type-map.ts +82 -0
  29. package/src/go/wrappers.ts +261 -0
  30. package/src/index.ts +3 -0
  31. package/src/node/client.ts +78 -115
  32. package/src/node/enums.ts +9 -0
  33. package/src/node/errors.ts +37 -232
  34. package/src/node/field-plan.ts +726 -0
  35. package/src/node/fixtures.ts +9 -1
  36. package/src/node/index.ts +2 -9
  37. package/src/node/models.ts +178 -21
  38. package/src/node/naming.ts +49 -111
  39. package/src/node/resources.ts +374 -364
  40. package/src/node/sdk-errors.ts +41 -0
  41. package/src/node/tests.ts +32 -12
  42. package/src/node/type-map.ts +4 -2
  43. package/src/node/utils.ts +13 -71
  44. package/src/node/wrappers.ts +151 -0
  45. package/src/php/client.ts +171 -0
  46. package/src/php/enums.ts +67 -0
  47. package/src/php/errors.ts +9 -0
  48. package/src/php/fixtures.ts +181 -0
  49. package/src/php/index.ts +96 -0
  50. package/src/php/manifest.ts +36 -0
  51. package/src/php/models.ts +310 -0
  52. package/src/php/naming.ts +298 -0
  53. package/src/php/resources.ts +561 -0
  54. package/src/php/tests.ts +533 -0
  55. package/src/php/type-map.ts +90 -0
  56. package/src/php/utils.ts +18 -0
  57. package/src/php/wrappers.ts +151 -0
  58. package/src/python/client.ts +337 -0
  59. package/src/python/enums.ts +313 -0
  60. package/src/python/fixtures.ts +196 -0
  61. package/src/python/index.ts +95 -0
  62. package/src/python/manifest.ts +38 -0
  63. package/src/python/models.ts +688 -0
  64. package/src/python/naming.ts +209 -0
  65. package/src/python/resources.ts +1322 -0
  66. package/src/python/tests.ts +1335 -0
  67. package/src/python/type-map.ts +93 -0
  68. package/src/python/wrappers.ts +191 -0
  69. package/src/shared/model-utils.ts +255 -0
  70. package/src/shared/naming-utils.ts +107 -0
  71. package/src/shared/non-spec-services.ts +54 -0
  72. package/src/shared/resolved-ops.ts +109 -0
  73. package/src/shared/wrapper-utils.ts +59 -0
  74. package/test/go/client.test.ts +92 -0
  75. package/test/go/enums.test.ts +132 -0
  76. package/test/go/errors.test.ts +9 -0
  77. package/test/go/models.test.ts +265 -0
  78. package/test/go/resources.test.ts +408 -0
  79. package/test/go/tests.test.ts +143 -0
  80. package/test/node/client.test.ts +18 -12
  81. package/test/node/enums.test.ts +2 -0
  82. package/test/node/errors.test.ts +2 -41
  83. package/test/node/models.test.ts +2 -0
  84. package/test/node/naming.test.ts +23 -0
  85. package/test/node/resources.test.ts +99 -69
  86. package/test/node/serializers.test.ts +3 -1
  87. package/test/node/type-map.test.ts +11 -0
  88. package/test/php/client.test.ts +94 -0
  89. package/test/php/enums.test.ts +173 -0
  90. package/test/php/errors.test.ts +9 -0
  91. package/test/php/models.test.ts +497 -0
  92. package/test/php/resources.test.ts +644 -0
  93. package/test/php/tests.test.ts +118 -0
  94. package/test/python/client.test.ts +200 -0
  95. package/test/python/enums.test.ts +228 -0
  96. package/test/python/errors.test.ts +16 -0
  97. package/test/python/manifest.test.ts +74 -0
  98. package/test/python/models.test.ts +716 -0
  99. package/test/python/resources.test.ts +617 -0
  100. package/test/python/tests.test.ts +202 -0
  101. package/src/node/common.ts +0 -273
  102. package/src/node/config.ts +0 -71
  103. package/src/node/serializers.ts +0 -746
@@ -1,223 +1,31 @@
1
1
  import type { EmitterContext, GeneratedFile } from '@workos/oagen';
2
2
  import { fileName } from './naming.js';
3
+ import { buildNodeStatusExceptions } from './sdk-errors.js';
3
4
 
5
+ /**
6
+ * Static exception classes are now hand-maintained in the target SDK.
7
+ * Only typed exceptions derived from spec error models are generated here.
8
+ */
4
9
  export function generateErrors(ctx?: EmitterContext): GeneratedFile[] {
5
- const files: GeneratedFile[] = [
6
- {
7
- path: 'src/common/exceptions/bad-request.exception.ts',
8
- content: `export class BadRequestException extends Error {
9
- readonly status = 400;
10
- readonly name = 'BadRequestException';
11
- readonly requestID: string;
12
- readonly code?: string;
10
+ if (!ctx?.spec) return [];
13
11
 
14
- constructor({
15
- code,
16
- message,
17
- requestID,
18
- }: {
19
- code?: string;
20
- message?: string;
21
- requestID: string;
22
- }) {
23
- super();
24
- this.message = message ?? 'Bad request';
25
- this.requestID = requestID;
26
- if (code) this.code = code;
27
- }
28
- }`,
29
- skipIfExists: true,
30
- integrateTarget: false,
31
- },
32
- {
33
- path: 'src/common/exceptions/unauthorized.exception.ts',
34
- content: `export class UnauthorizedException extends Error {
35
- readonly status = 401;
36
- readonly name = 'UnauthorizedException';
37
- readonly requestID: string;
38
-
39
- constructor(requestID: string) {
40
- super();
41
- this.message = 'Unauthorized';
42
- this.requestID = requestID;
43
- }
44
- }`,
45
- skipIfExists: true,
46
- integrateTarget: false,
47
- },
48
- {
49
- path: 'src/common/exceptions/not-found.exception.ts',
50
- content: `export class NotFoundException extends Error {
51
- readonly status = 404;
52
- readonly name = 'NotFoundException';
53
- readonly requestID: string;
54
- readonly code?: string;
55
-
56
- constructor({
57
- code,
58
- message,
59
- path,
60
- requestID,
61
- }: {
62
- code?: string;
63
- message?: string;
64
- path: string;
65
- requestID: string;
66
- }) {
67
- super();
68
- this.message =
69
- message ?? \`The requested path '\${path}' could not be found.\`;
70
- this.requestID = requestID;
71
- if (code) this.code = code;
72
- }
73
- }`,
74
- skipIfExists: true,
75
- integrateTarget: false,
76
- },
77
- {
78
- path: 'src/common/exceptions/conflict.exception.ts',
79
- content: `export class ConflictException extends Error {
80
- readonly status = 409;
81
- readonly name = 'ConflictException';
82
- readonly requestID: string;
12
+ const typedErrors = collectTypedErrors(ctx);
13
+ if (typedErrors.length === 0) return [];
83
14
 
84
- constructor({
85
- message,
86
- requestID,
87
- }: {
88
- message?: string;
89
- error?: string;
90
- requestID: string;
91
- }) {
92
- super();
93
- this.message = message ?? 'Conflict';
94
- this.requestID = requestID;
95
- }
96
- }`,
97
- skipIfExists: true,
98
- integrateTarget: false,
99
- },
100
- {
101
- path: 'src/common/exceptions/unprocessable-entity.exception.ts',
102
- content: `export interface UnprocessableEntityError {
103
- code: string;
104
- }
15
+ const files: GeneratedFile[] = [];
16
+ const exportLines: string[] = [];
105
17
 
106
- export class UnprocessableEntityException extends Error {
107
- readonly status = 422;
108
- readonly name = 'UnprocessableEntityException';
109
- readonly requestID: string;
110
- readonly code?: string;
18
+ for (const { modelName, statusCode, baseException } of typedErrors) {
19
+ const exceptionClassName = `${modelName}Exception`;
20
+ const filePath = `src/common/exceptions/${fileName(modelName)}.exception.ts`;
21
+ const baseImport = baseException
22
+ ? `import { ${baseException} } from './${fileName(baseException.replace(/Exception$/, '')).replace(/^/, '')}.exception';\n\n`
23
+ : '';
24
+ const baseClass = baseException ?? 'Error';
111
25
 
112
- constructor({
113
- code,
114
- errors,
115
- message,
116
- requestID,
117
- }: {
118
- code?: string;
119
- errors?: UnprocessableEntityError[];
120
- message?: string;
121
- requestID: string;
122
- }) {
123
- super();
124
- this.requestID = requestID;
125
- this.message = message ?? 'Unprocessable entity';
126
- if (code) this.code = code;
127
- if (errors) {
128
- const requirement =
129
- errors.length === 1 ? 'requirement' : 'requirements';
130
- this.message = \`The following \${requirement} must be met:\\n\`;
131
- for (const { code: errCode } of errors) {
132
- this.message = this.message.concat(\`\\t\${errCode}\\n\`);
133
- }
134
- }
135
- }
136
- }`,
137
- skipIfExists: true,
138
- integrateTarget: false,
139
- },
140
- {
141
- path: 'src/common/exceptions/rate-limit-exceeded.exception.ts',
142
- content: `export class RateLimitExceededException extends Error {
143
- readonly status = 429;
144
- readonly name = 'RateLimitExceededException';
145
- readonly requestID: string;
146
- readonly retryAfter?: number;
147
-
148
- constructor(message: string, requestID: string, retryAfter?: number) {
149
- super();
150
- this.message = message ?? 'Too many requests';
151
- this.requestID = requestID;
152
- this.retryAfter = retryAfter;
153
- }
154
- }`,
155
- skipIfExists: true,
156
- integrateTarget: false,
157
- },
158
- {
159
- path: 'src/common/exceptions/generic-server.exception.ts',
160
- content: `export class GenericServerException extends Error {
161
- readonly status: number;
162
- readonly name = 'GenericServerException';
163
- readonly requestID: string;
164
-
165
- constructor(status: number, message: string, requestID: string) {
166
- super();
167
- this.status = status;
168
- this.message = message ?? 'Server error';
169
- this.requestID = requestID;
170
- }
171
- }`,
172
- skipIfExists: true,
173
- integrateTarget: false,
174
- },
175
- {
176
- path: 'src/common/exceptions/no-api-key-provided.exception.ts',
177
- content: `export class NoApiKeyProvidedException extends Error {
178
- readonly name = 'NoApiKeyProvidedException';
179
-
180
- constructor() {
181
- super();
182
- this.message =
183
- 'No API key provided. Pass it to the WorkOS constructor or set the WORKOS_API_KEY environment variable.';
184
- }
185
- }`,
186
- skipIfExists: true,
187
- integrateTarget: false,
188
- },
189
- {
190
- path: 'src/common/exceptions/index.ts',
191
- content: `export { BadRequestException } from './bad-request.exception';
192
- export { UnauthorizedException } from './unauthorized.exception';
193
- export { NotFoundException } from './not-found.exception';
194
- export { ConflictException } from './conflict.exception';
195
- export {
196
- UnprocessableEntityException,
197
- type UnprocessableEntityError,
198
- } from './unprocessable-entity.exception';
199
- export { RateLimitExceededException } from './rate-limit-exceeded.exception';
200
- export { GenericServerException } from './generic-server.exception';
201
- export { NoApiKeyProvidedException } from './no-api-key-provided.exception';`,
202
- skipIfExists: true,
203
- integrateTarget: false,
204
- },
205
- ];
206
-
207
- // Fix 6: Generate typed exception classes from ErrorResponse.type
208
- if (ctx?.spec) {
209
- const typedErrors = collectTypedErrors(ctx);
210
- for (const { modelName, statusCode, baseException } of typedErrors) {
211
- const exceptionClassName = `${modelName}Exception`;
212
- const filePath = `src/common/exceptions/${fileName(modelName)}.exception.ts`;
213
- const baseImport = baseException
214
- ? `import { ${baseException} } from './${fileName(baseException.replace(/Exception$/, '')).replace(/^/, '')}.exception';\n\n`
215
- : '';
216
- const baseClass = baseException ?? 'Error';
217
-
218
- files.push({
219
- path: filePath,
220
- content: `${baseImport}export class ${exceptionClassName} extends ${baseClass} {
26
+ files.push({
27
+ path: filePath,
28
+ content: `${baseImport}export class ${exceptionClassName} extends ${baseClass} {
221
29
  readonly status = ${statusCode};
222
30
  readonly name = '${exceptionClassName}';
223
31
  readonly requestID: string;
@@ -230,33 +38,31 @@ export { NoApiKeyProvidedException } from './no-api-key-provided.exception';`,
230
38
  if (data) this.data = data;
231
39
  }
232
40
  }`,
233
- skipIfExists: true,
234
- integrateTarget: false,
235
- });
41
+ skipIfExists: true,
42
+ integrateTarget: false,
43
+ });
236
44
 
237
- // Update the barrel index to export the new exception
238
- const existingIndex = files.find((f) => f.path === 'src/common/exceptions/index.ts');
239
- if (existingIndex) {
240
- existingIndex.content += `\nexport { ${exceptionClassName} } from './${fileName(modelName)}.exception';`;
241
- }
242
- }
45
+ exportLines.push(`export { ${exceptionClassName} } from './${fileName(modelName)}.exception';`);
46
+ }
47
+
48
+ // Generate a barrel for typed errors only (appended to existing exceptions/index.ts
49
+ // via region preservation if needed)
50
+ if (exportLines.length > 0) {
51
+ files.push({
52
+ path: 'src/common/exceptions/typed-errors.ts',
53
+ content: exportLines.join('\n'),
54
+ skipIfExists: true,
55
+ integrateTarget: false,
56
+ });
243
57
  }
244
58
 
245
59
  return files;
246
60
  }
247
61
 
248
- const STATUS_TO_BASE_EXCEPTION: Record<number, string> = {
249
- 400: 'BadRequestException',
250
- 401: 'UnauthorizedException',
251
- 404: 'NotFoundException',
252
- 409: 'ConflictException',
253
- 422: 'UnprocessableEntityException',
254
- 429: 'RateLimitExceededException',
255
- };
256
-
257
62
  function collectTypedErrors(
258
63
  ctx: EmitterContext,
259
64
  ): { modelName: string; statusCode: number; baseException: string | null }[] {
65
+ const statusToBase = buildNodeStatusExceptions(ctx.spec.sdk);
260
66
  const seen = new Set<string>();
261
67
  const results: {
262
68
  modelName: string;
@@ -272,8 +78,7 @@ function collectTypedErrors(
272
78
  results.push({
273
79
  modelName: err.type.name,
274
80
  statusCode: err.statusCode,
275
- baseException:
276
- STATUS_TO_BASE_EXCEPTION[err.statusCode] ?? (err.statusCode >= 500 ? 'GenericServerException' : null),
81
+ baseException: statusToBase[err.statusCode] ?? (err.statusCode >= 500 ? 'GenericServerException' : null),
277
82
  });
278
83
  }
279
84
  }