@workos/oagen-emitters 0.2.0 → 0.2.1
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/.oxfmtrc.json +8 -1
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +7 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +633 -85
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/smoke/sdk-dotnet.ts +17 -3
- package/smoke/sdk-elixir.ts +17 -3
- package/smoke/sdk-go.ts +21 -4
- package/smoke/sdk-kotlin.ts +23 -4
- package/smoke/sdk-node.ts +15 -3
- package/smoke/sdk-ruby.ts +17 -3
- package/smoke/sdk-rust.ts +16 -3
- package/src/node/client.ts +94 -12
- package/src/node/common.ts +1 -1
- package/src/node/enums.ts +4 -4
- package/src/node/errors.ts +5 -1
- package/src/node/fixtures.ts +6 -4
- package/src/node/index.ts +65 -9
- package/src/node/models.ts +86 -75
- package/src/node/naming.ts +91 -2
- package/src/node/resources.ts +462 -23
- package/src/node/serializers.ts +3 -1
- package/src/node/tests.ts +39 -15
- package/src/node/utils.ts +52 -2
- package/test/node/client.test.ts +181 -82
- package/test/node/enums.test.ts +73 -3
- package/test/node/models.test.ts +107 -20
- package/test/node/naming.test.ts +14 -4
- package/test/node/resources.test.ts +627 -25
- package/test/node/serializers.test.ts +33 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workos/oagen-emitters",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "WorkOS' oagen emitters",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "WorkOS",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"sdk:extract:rust": "oagen extract --lang rust --sdk-path ../backend/workos-rust --output ./sdk-rust-surface.json"
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"@workos/oagen": "^0.
|
|
61
|
+
"@workos/oagen": "^0.3.0"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@commitlint/cli": "^20.5.0",
|
package/smoke/sdk-dotnet.ts
CHANGED
|
@@ -115,7 +115,12 @@ function createProxyServer(apiKey: string, captures: ProxyCapture[]): Promise<{
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
captures.push({
|
|
118
|
-
request: {
|
|
118
|
+
request: {
|
|
119
|
+
method: req.method!,
|
|
120
|
+
path: url.pathname,
|
|
121
|
+
queryParams,
|
|
122
|
+
body,
|
|
123
|
+
},
|
|
119
124
|
response: { status: proxyRes.statusCode!, body: resBody },
|
|
120
125
|
});
|
|
121
126
|
|
|
@@ -689,7 +694,11 @@ async function main(): Promise<void> {
|
|
|
689
694
|
|
|
690
695
|
// Build planned calls for this wave, resolving methods
|
|
691
696
|
const plannedCalls: PlannedCall[] = [];
|
|
692
|
-
const waveSkipped: Array<{
|
|
697
|
+
const waveSkipped: Array<{
|
|
698
|
+
op: Operation;
|
|
699
|
+
irService: string;
|
|
700
|
+
reason: string;
|
|
701
|
+
}> = [];
|
|
693
702
|
|
|
694
703
|
for (const { op, irService, pathParams } of wave.calls) {
|
|
695
704
|
const resolution = resolveMethod(op, irService, manifest);
|
|
@@ -858,7 +867,12 @@ function makeSkippedExchange(op: Operation, service: string, reason: string): Ca
|
|
|
858
867
|
operationId: op.name,
|
|
859
868
|
service,
|
|
860
869
|
operationName: op.name,
|
|
861
|
-
request: {
|
|
870
|
+
request: {
|
|
871
|
+
method: op.httpMethod.toUpperCase(),
|
|
872
|
+
path: op.path,
|
|
873
|
+
queryParams: {},
|
|
874
|
+
body: null,
|
|
875
|
+
},
|
|
862
876
|
response: { status: 0, body: null },
|
|
863
877
|
outcome: 'skipped',
|
|
864
878
|
error: reason,
|
package/smoke/sdk-elixir.ts
CHANGED
|
@@ -103,7 +103,12 @@ function startProxy(
|
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
const capturedReq: CapturedRequest = {
|
|
106
|
+
const capturedReq: CapturedRequest = {
|
|
107
|
+
method,
|
|
108
|
+
path,
|
|
109
|
+
queryParams,
|
|
110
|
+
body: reqBody,
|
|
111
|
+
};
|
|
107
112
|
|
|
108
113
|
// Forward to real API
|
|
109
114
|
const forwardHeaders: Record<string, string> = {
|
|
@@ -467,7 +472,11 @@ async function main(): Promise<void> {
|
|
|
467
472
|
|
|
468
473
|
// Build planned calls for this wave, resolving methods
|
|
469
474
|
const plannedCalls: PlannedCall[] = [];
|
|
470
|
-
const waveSkipped: Array<{
|
|
475
|
+
const waveSkipped: Array<{
|
|
476
|
+
op: Operation;
|
|
477
|
+
irService: string;
|
|
478
|
+
reason: string;
|
|
479
|
+
}> = [];
|
|
471
480
|
|
|
472
481
|
for (const { op, irService, pathParams } of wave.calls) {
|
|
473
482
|
const resolution = resolveMethod(op, irService, manifest);
|
|
@@ -726,7 +735,12 @@ function makeSkippedExchange(op: Operation, service: string, reason: string): Ca
|
|
|
726
735
|
operationId: op.name,
|
|
727
736
|
service,
|
|
728
737
|
operationName: op.name,
|
|
729
|
-
request: {
|
|
738
|
+
request: {
|
|
739
|
+
method: op.httpMethod.toUpperCase(),
|
|
740
|
+
path: op.path,
|
|
741
|
+
queryParams: {},
|
|
742
|
+
body: null,
|
|
743
|
+
},
|
|
730
744
|
response: { status: 0, body: null },
|
|
731
745
|
outcome: 'skipped',
|
|
732
746
|
error: reason,
|
package/smoke/sdk-go.ts
CHANGED
|
@@ -257,7 +257,12 @@ class CaptureProxy {
|
|
|
257
257
|
}
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
-
const capturedReq: CapturedRequest = {
|
|
260
|
+
const capturedReq: CapturedRequest = {
|
|
261
|
+
method,
|
|
262
|
+
path,
|
|
263
|
+
queryParams,
|
|
264
|
+
body: parsedReqBody,
|
|
265
|
+
};
|
|
261
266
|
|
|
262
267
|
// Forward to the real API
|
|
263
268
|
const targetUrl = new URL(path, this.targetBaseUrl);
|
|
@@ -580,7 +585,11 @@ async function main(): Promise<void> {
|
|
|
580
585
|
|
|
581
586
|
// Build planned calls for this wave, resolving methods
|
|
582
587
|
const plannedCalls: PlannedCall[] = [];
|
|
583
|
-
const waveSkipped: Array<{
|
|
588
|
+
const waveSkipped: Array<{
|
|
589
|
+
op: Operation;
|
|
590
|
+
irService: string;
|
|
591
|
+
reason: string;
|
|
592
|
+
}> = [];
|
|
584
593
|
|
|
585
594
|
for (const { op, irService, pathParams } of wave.calls) {
|
|
586
595
|
const resolution = resolveMethod(op, irService, manifest);
|
|
@@ -655,7 +664,10 @@ async function main(): Promise<void> {
|
|
|
655
664
|
execSync('go build -o smoke-driver main.go', {
|
|
656
665
|
cwd: tmpDir,
|
|
657
666
|
timeout: 120_000,
|
|
658
|
-
env: {
|
|
667
|
+
env: {
|
|
668
|
+
...process.env,
|
|
669
|
+
GOPATH: process.env.GOPATH || resolve(process.env.HOME || '~', 'go'),
|
|
670
|
+
},
|
|
659
671
|
encoding: 'utf-8',
|
|
660
672
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
661
673
|
});
|
|
@@ -903,7 +915,12 @@ function makeSkippedExchange(op: Operation, service: string, reason: string): Ca
|
|
|
903
915
|
operationId: op.name,
|
|
904
916
|
service,
|
|
905
917
|
operationName: op.name,
|
|
906
|
-
request: {
|
|
918
|
+
request: {
|
|
919
|
+
method: op.httpMethod.toUpperCase(),
|
|
920
|
+
path: op.path,
|
|
921
|
+
queryParams: {},
|
|
922
|
+
body: null,
|
|
923
|
+
},
|
|
907
924
|
response: { status: 0, body: null },
|
|
908
925
|
outcome: 'skipped',
|
|
909
926
|
error: reason,
|
package/smoke/sdk-kotlin.ts
CHANGED
|
@@ -123,7 +123,12 @@ function createProxyServer(
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
captures.push({
|
|
126
|
-
request: {
|
|
126
|
+
request: {
|
|
127
|
+
method: req.method!,
|
|
128
|
+
path: url.pathname,
|
|
129
|
+
queryParams,
|
|
130
|
+
body,
|
|
131
|
+
},
|
|
127
132
|
response: { status: proxyRes.statusCode!, body: resBody },
|
|
128
133
|
});
|
|
129
134
|
|
|
@@ -135,7 +140,12 @@ function createProxyServer(
|
|
|
135
140
|
proxyReq.on('error', (err) => {
|
|
136
141
|
console.error('Proxy request error:', err.message);
|
|
137
142
|
captures.push({
|
|
138
|
-
request: {
|
|
143
|
+
request: {
|
|
144
|
+
method: req.method!,
|
|
145
|
+
path: url.pathname,
|
|
146
|
+
queryParams,
|
|
147
|
+
body,
|
|
148
|
+
},
|
|
139
149
|
response: { status: 502, body: { error: err.message } },
|
|
140
150
|
});
|
|
141
151
|
res.writeHead(502);
|
|
@@ -499,7 +509,11 @@ async function main(): Promise<void> {
|
|
|
499
509
|
|
|
500
510
|
// Build planned calls for this wave, resolving methods
|
|
501
511
|
const plannedCalls: PlannedCall[] = [];
|
|
502
|
-
const waveSkipped: Array<{
|
|
512
|
+
const waveSkipped: Array<{
|
|
513
|
+
op: Operation;
|
|
514
|
+
irService: string;
|
|
515
|
+
reason: string;
|
|
516
|
+
}> = [];
|
|
503
517
|
|
|
504
518
|
for (const { op, irService, pathParams } of wave.calls) {
|
|
505
519
|
const resolution = resolveMethod(op, irService, manifest);
|
|
@@ -754,7 +768,12 @@ function makeSkippedExchange(op: Operation, service: string, reason: string): Ca
|
|
|
754
768
|
operationId: op.name,
|
|
755
769
|
service,
|
|
756
770
|
operationName: op.name,
|
|
757
|
-
request: {
|
|
771
|
+
request: {
|
|
772
|
+
method: op.httpMethod.toUpperCase(),
|
|
773
|
+
path: op.path,
|
|
774
|
+
queryParams: {},
|
|
775
|
+
body: null,
|
|
776
|
+
},
|
|
758
777
|
response: { status: 0, body: null },
|
|
759
778
|
outcome: 'skipped',
|
|
760
779
|
error: reason,
|
package/smoke/sdk-node.ts
CHANGED
|
@@ -52,7 +52,10 @@ interface CapturedResponse {
|
|
|
52
52
|
// HTTP Interception
|
|
53
53
|
// ---------------------------------------------------------------------------
|
|
54
54
|
|
|
55
|
-
let currentCapture: {
|
|
55
|
+
let currentCapture: {
|
|
56
|
+
request: CapturedRequest;
|
|
57
|
+
response: CapturedResponse;
|
|
58
|
+
} | null = null;
|
|
56
59
|
const originalFetch = globalThis.fetch;
|
|
57
60
|
|
|
58
61
|
function interceptFetch(): void {
|
|
@@ -259,7 +262,11 @@ async function main(): Promise<void> {
|
|
|
259
262
|
const groups = planOperations(spec);
|
|
260
263
|
const ids = new IdRegistry();
|
|
261
264
|
const exchanges: CapturedExchange[] = [];
|
|
262
|
-
const createdEntities: Array<{
|
|
265
|
+
const createdEntities: Array<{
|
|
266
|
+
service: string;
|
|
267
|
+
id: string;
|
|
268
|
+
deleteFn?: () => Promise<void>;
|
|
269
|
+
}> = [];
|
|
263
270
|
const delayMs = Number(process.env.SMOKE_DELAY_MS) || 200;
|
|
264
271
|
|
|
265
272
|
let successCount = 0;
|
|
@@ -452,7 +459,12 @@ function makeSkippedExchange(op: Operation, service: string, reason: string): Ca
|
|
|
452
459
|
operationId: op.name,
|
|
453
460
|
service,
|
|
454
461
|
operationName: op.name,
|
|
455
|
-
request: {
|
|
462
|
+
request: {
|
|
463
|
+
method: op.httpMethod.toUpperCase(),
|
|
464
|
+
path: op.path,
|
|
465
|
+
queryParams: {},
|
|
466
|
+
body: null,
|
|
467
|
+
},
|
|
456
468
|
response: { status: 0, body: null },
|
|
457
469
|
outcome: 'skipped',
|
|
458
470
|
error: reason,
|
package/smoke/sdk-ruby.ts
CHANGED
|
@@ -160,7 +160,12 @@ function startProxy(
|
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
const capturedReq: CapturedRequest = {
|
|
163
|
+
const capturedReq: CapturedRequest = {
|
|
164
|
+
method,
|
|
165
|
+
path,
|
|
166
|
+
queryParams,
|
|
167
|
+
body: reqBody,
|
|
168
|
+
};
|
|
164
169
|
|
|
165
170
|
// Forward to real API
|
|
166
171
|
const forwardHeaders: Record<string, string> = {
|
|
@@ -420,7 +425,11 @@ async function main(): Promise<void> {
|
|
|
420
425
|
|
|
421
426
|
// Build planned calls for this wave, resolving methods
|
|
422
427
|
const plannedCalls: PlannedCall[] = [];
|
|
423
|
-
const waveSkipped: Array<{
|
|
428
|
+
const waveSkipped: Array<{
|
|
429
|
+
op: Operation;
|
|
430
|
+
irService: string;
|
|
431
|
+
reason: string;
|
|
432
|
+
}> = [];
|
|
424
433
|
|
|
425
434
|
for (const { op, irService, pathParams } of wave.calls) {
|
|
426
435
|
const resolution = resolveMethod(op, irService, manifest);
|
|
@@ -678,7 +687,12 @@ function makeSkippedExchange(op: Operation, service: string, reason: string): Ca
|
|
|
678
687
|
operationId: op.name,
|
|
679
688
|
service,
|
|
680
689
|
operationName: op.name,
|
|
681
|
-
request: {
|
|
690
|
+
request: {
|
|
691
|
+
method: op.httpMethod.toUpperCase(),
|
|
692
|
+
path: op.path,
|
|
693
|
+
queryParams: {},
|
|
694
|
+
body: null,
|
|
695
|
+
},
|
|
682
696
|
response: { status: 0, body: null },
|
|
683
697
|
outcome: 'skipped',
|
|
684
698
|
error: reason,
|
package/smoke/sdk-rust.ts
CHANGED
|
@@ -452,7 +452,11 @@ serde_json = "1"
|
|
|
452
452
|
|
|
453
453
|
// Build planned calls for this wave, resolving methods
|
|
454
454
|
const plannedCalls: PlannedCall[] = [];
|
|
455
|
-
const waveSkipped: Array<{
|
|
455
|
+
const waveSkipped: Array<{
|
|
456
|
+
op: Operation;
|
|
457
|
+
irService: string;
|
|
458
|
+
reason: string;
|
|
459
|
+
}> = [];
|
|
456
460
|
|
|
457
461
|
for (const { op, irService, pathParams } of wave.calls) {
|
|
458
462
|
const resolution = resolveMethod(op, irService, manifest);
|
|
@@ -534,7 +538,11 @@ serde_json = "1"
|
|
|
534
538
|
await new Promise<void>((resolvePromise, rejectPromise) => {
|
|
535
539
|
const child = spawn(join(tmpDir, 'target', 'debug', 'smoke-driver'), [], {
|
|
536
540
|
cwd: tmpDir,
|
|
537
|
-
env: {
|
|
541
|
+
env: {
|
|
542
|
+
...process.env,
|
|
543
|
+
WORKOS_API_KEY: apiKey,
|
|
544
|
+
WORKOS_BASE_URL: `http://localhost:${proxy.port}`,
|
|
545
|
+
},
|
|
538
546
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
539
547
|
});
|
|
540
548
|
|
|
@@ -729,7 +737,12 @@ function makeSkippedExchange(op: Operation, service: string, reason: string): Ca
|
|
|
729
737
|
operationId: op.name,
|
|
730
738
|
service,
|
|
731
739
|
operationName: op.name,
|
|
732
|
-
request: {
|
|
740
|
+
request: {
|
|
741
|
+
method: op.httpMethod.toUpperCase(),
|
|
742
|
+
path: op.path,
|
|
743
|
+
queryParams: {},
|
|
744
|
+
body: null,
|
|
745
|
+
},
|
|
733
746
|
response: { status: 0, body: null },
|
|
734
747
|
outcome: 'skipped',
|
|
735
748
|
error: reason,
|
package/src/node/client.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ApiSpec, AuthScheme, EmitterContext, GeneratedFile, Service } from '@workos/oagen';
|
|
2
|
-
import { fileName,
|
|
2
|
+
import { fileName, resolveServiceDir, servicePropertyName, resolveInterfaceName, wireInterfaceName } from './naming.js';
|
|
3
3
|
import {
|
|
4
4
|
docComment,
|
|
5
5
|
createServiceDirResolver,
|
|
@@ -46,7 +46,7 @@ function generateWorkOSClient(spec: ApiSpec, ctx: EmitterContext): GeneratedFile
|
|
|
46
46
|
for (const service of spec.services) {
|
|
47
47
|
if (coveredServices.has(service.name)) continue;
|
|
48
48
|
const resolvedName = resolveResourceClassName(service, ctx);
|
|
49
|
-
const serviceDir =
|
|
49
|
+
const serviceDir = resolveServiceDir(resolvedName);
|
|
50
50
|
lines.push(`import { ${resolvedName} } from './${serviceDir}/${fileName(resolvedName)}';`);
|
|
51
51
|
}
|
|
52
52
|
|
|
@@ -99,7 +99,11 @@ function generateWorkOSClient(spec: ApiSpec, ctx: EmitterContext): GeneratedFile
|
|
|
99
99
|
|
|
100
100
|
lines.push('}');
|
|
101
101
|
|
|
102
|
-
return {
|
|
102
|
+
return {
|
|
103
|
+
path: 'src/workos.ts',
|
|
104
|
+
content: lines.join('\n'),
|
|
105
|
+
skipIfExists: true,
|
|
106
|
+
};
|
|
103
107
|
}
|
|
104
108
|
|
|
105
109
|
/**
|
|
@@ -315,10 +319,68 @@ function generateBarrel(spec: ApiSpec, ctx: EmitterContext): GeneratedFile {
|
|
|
315
319
|
// Track directories that have already been wildcard-exported
|
|
316
320
|
const exportedDirs = new Set<string>();
|
|
317
321
|
|
|
322
|
+
// Pre-compute all names per interfaces directory (generated + baseline) to detect
|
|
323
|
+
// cross-directory conflicts before emitting any star exports. A star export is
|
|
324
|
+
// unsafe when two different directories export the same name (e.g., Factor in
|
|
325
|
+
// both mfa/interfaces and user-management/interfaces).
|
|
326
|
+
const dirAllNames = new Map<string, Set<string>>();
|
|
327
|
+
for (const service of spec.services) {
|
|
328
|
+
const iDir = resolveDir(service.name);
|
|
329
|
+
if (!dirAllNames.has(iDir)) dirAllNames.set(iDir, new Set());
|
|
330
|
+
const names = dirAllNames.get(iDir)!;
|
|
331
|
+
for (const model of spec.models) {
|
|
332
|
+
if (modelToService.get(model.name) !== service.name) continue;
|
|
333
|
+
if (isListMetadataModel(model) || isListWrapperModel(model)) continue;
|
|
334
|
+
names.add(resolveInterfaceName(model.name, ctx));
|
|
335
|
+
names.add(wireInterfaceName(resolveInterfaceName(model.name, ctx)));
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
// Add baseline names per directory
|
|
339
|
+
if (ctx.apiSurface?.interfaces) {
|
|
340
|
+
for (const [name, iface] of Object.entries(ctx.apiSurface.interfaces)) {
|
|
341
|
+
const sourceFile = (iface as any).sourceFile as string | undefined;
|
|
342
|
+
if (!sourceFile) continue;
|
|
343
|
+
const match = sourceFile.match(/^src\/([^/]+)\/interfaces\//);
|
|
344
|
+
if (match) {
|
|
345
|
+
const dirName = match[1];
|
|
346
|
+
if (!dirAllNames.has(dirName)) dirAllNames.set(dirName, new Set());
|
|
347
|
+
dirAllNames.get(dirName)!.add(name);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
if (ctx.apiSurface?.typeAliases) {
|
|
352
|
+
for (const [name, alias] of Object.entries(ctx.apiSurface.typeAliases)) {
|
|
353
|
+
const sourceFile = (alias as any).sourceFile as string | undefined;
|
|
354
|
+
if (!sourceFile) continue;
|
|
355
|
+
const match = sourceFile.match(/^src\/([^/]+)\/interfaces\//);
|
|
356
|
+
if (match) {
|
|
357
|
+
const dirName = match[1];
|
|
358
|
+
if (!dirAllNames.has(dirName)) dirAllNames.set(dirName, new Set());
|
|
359
|
+
dirAllNames.get(dirName)!.add(name);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
// Detect directories with cross-directory name conflicts
|
|
364
|
+
const unsafeStarDirs = new Set<string>();
|
|
365
|
+
const allDirEntries = [...dirAllNames.entries()];
|
|
366
|
+
for (let i = 0; i < allDirEntries.length; i++) {
|
|
367
|
+
for (let j = i + 1; j < allDirEntries.length; j++) {
|
|
368
|
+
const [dirA, namesA] = allDirEntries[i];
|
|
369
|
+
const [dirB, namesB] = allDirEntries[j];
|
|
370
|
+
for (const name of namesA) {
|
|
371
|
+
if (namesB.has(name)) {
|
|
372
|
+
unsafeStarDirs.add(dirA);
|
|
373
|
+
unsafeStarDirs.add(dirB);
|
|
374
|
+
break;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
318
380
|
// Per-service exports: service barrel + resource class
|
|
319
381
|
for (const service of spec.services) {
|
|
320
382
|
const resolvedName = resolveResourceClassName(service, ctx);
|
|
321
|
-
const serviceDir =
|
|
383
|
+
const serviceDir = resolveServiceDir(resolvedName);
|
|
322
384
|
// The interfaces directory may differ from the resource class directory when
|
|
323
385
|
// a service's class name is remapped (e.g., WebhooksEndpoints class lives in
|
|
324
386
|
// webhooks-endpoints/ but its model interfaces live in webhooks/).
|
|
@@ -338,13 +400,25 @@ function generateBarrel(spec: ApiSpec, ctx: EmitterContext): GeneratedFile {
|
|
|
338
400
|
return enumService === service.name;
|
|
339
401
|
});
|
|
340
402
|
|
|
341
|
-
// Check whether any model or enum in this service conflicts with
|
|
342
|
-
//
|
|
403
|
+
// Check whether any model or enum in this service conflicts with names already
|
|
404
|
+
// exported (from earlier star exports or the existing SDK baseline). If so, fall
|
|
405
|
+
// back to individual named exports to avoid duplicate-export TS2308 errors.
|
|
343
406
|
const hasConflict =
|
|
344
|
-
serviceModels.some((m) =>
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
407
|
+
serviceModels.some((m) => {
|
|
408
|
+
const name = resolveInterfaceName(m.name, ctx);
|
|
409
|
+
return existingSdkExports.has(name) || exportedNames.has(name) || exportedNames.has(wireInterfaceName(name));
|
|
410
|
+
}) || serviceEnums.some((e) => existingSdkExports.has(e.name) || exportedNames.has(e.name));
|
|
411
|
+
|
|
412
|
+
// Skip star export for covered services — their directory may have hand-written types
|
|
413
|
+
// (e.g., Factor in mfa/) that conflict with types in the covering service's directory.
|
|
414
|
+
const isCovered = coveredServicesBarrel.has(service.name);
|
|
415
|
+
if (
|
|
416
|
+
(serviceModels.length > 0 || serviceEnums.length > 0) &&
|
|
417
|
+
!exportedDirs.has(interfacesDir) &&
|
|
418
|
+
!hasConflict &&
|
|
419
|
+
!unsafeStarDirs.has(interfacesDir) &&
|
|
420
|
+
!isCovered
|
|
421
|
+
) {
|
|
348
422
|
exportedDirs.add(interfacesDir);
|
|
349
423
|
lines.push(`export * from './${interfacesDir}/interfaces';`);
|
|
350
424
|
// Track the individual names so they don't get re-exported below
|
|
@@ -446,7 +520,11 @@ function generateBarrel(spec: ApiSpec, ctx: EmitterContext): GeneratedFile {
|
|
|
446
520
|
lines.push("export { WorkOS } from './workos';");
|
|
447
521
|
}
|
|
448
522
|
|
|
449
|
-
return {
|
|
523
|
+
return {
|
|
524
|
+
path: 'src/index.ts',
|
|
525
|
+
content: lines.join('\n'),
|
|
526
|
+
skipIfExists: true,
|
|
527
|
+
};
|
|
450
528
|
}
|
|
451
529
|
|
|
452
530
|
/**
|
|
@@ -459,7 +537,11 @@ function generateWorkerBarrel(_spec: ApiSpec, _ctx: EmitterContext): GeneratedFi
|
|
|
459
537
|
// Re-export everything from the main index — keeps type exports in sync
|
|
460
538
|
lines.push("export * from './index';");
|
|
461
539
|
|
|
462
|
-
return {
|
|
540
|
+
return {
|
|
541
|
+
path: 'src/index.worker.ts',
|
|
542
|
+
content: lines.join('\n'),
|
|
543
|
+
skipIfExists: true,
|
|
544
|
+
};
|
|
463
545
|
}
|
|
464
546
|
|
|
465
547
|
function findEnumService(enumName: string, services: Service[]): string | undefined {
|
package/src/node/common.ts
CHANGED
|
@@ -225,7 +225,7 @@ export function fetchBody({ raw = false } = {}): any {
|
|
|
225
225
|
export function testUnauthorized(fn: () => Promise<any>) {
|
|
226
226
|
it('throws on unauthorized', async () => {
|
|
227
227
|
fetchOnce({ message: 'Unauthorized' }, { status: 401 });
|
|
228
|
-
await expect(fn()).rejects.toThrow('
|
|
228
|
+
await expect(fn()).rejects.toThrow('Could not authorize the request');
|
|
229
229
|
});
|
|
230
230
|
}
|
|
231
231
|
|
package/src/node/enums.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Enum, EmitterContext, GeneratedFile, Service } from '@workos/oagen';
|
|
2
|
-
import { walkTypeRef } from '@workos/oagen';
|
|
3
|
-
import { fileName,
|
|
2
|
+
import { toPascalCase, walkTypeRef } from '@workos/oagen';
|
|
3
|
+
import { fileName, resolveServiceDir, buildServiceNameMap } from './naming.js';
|
|
4
4
|
import { docComment } from './utils.js';
|
|
5
5
|
|
|
6
6
|
export function generateEnums(enums: Enum[], ctx: EmitterContext): GeneratedFile[] {
|
|
@@ -9,7 +9,7 @@ export function generateEnums(enums: Enum[], ctx: EmitterContext): GeneratedFile
|
|
|
9
9
|
const enumToService = assignEnumsToServices(enums, ctx.spec.services);
|
|
10
10
|
const serviceNameMap = buildServiceNameMap(ctx.spec.services, ctx);
|
|
11
11
|
const resolveDir = (irService: string | undefined) =>
|
|
12
|
-
irService ?
|
|
12
|
+
irService ? resolveServiceDir(serviceNameMap.get(irService) ?? irService) : 'common';
|
|
13
13
|
const files: GeneratedFile[] = [];
|
|
14
14
|
|
|
15
15
|
for (const enumDef of enums) {
|
|
@@ -41,7 +41,7 @@ export function generateEnums(enums: Enum[], ctx: EmitterContext): GeneratedFile
|
|
|
41
41
|
// Append new values from the spec that the baseline is missing
|
|
42
42
|
for (const val of missingValues) {
|
|
43
43
|
// Derive a PascalCase member name from the value
|
|
44
|
-
const memberName = val
|
|
44
|
+
const memberName = toPascalCase(val);
|
|
45
45
|
lines.push(` ${memberName} = '${val}',`);
|
|
46
46
|
}
|
|
47
47
|
lines.push('}');
|
package/src/node/errors.ts
CHANGED
|
@@ -258,7 +258,11 @@ function collectTypedErrors(
|
|
|
258
258
|
ctx: EmitterContext,
|
|
259
259
|
): { modelName: string; statusCode: number; baseException: string | null }[] {
|
|
260
260
|
const seen = new Set<string>();
|
|
261
|
-
const results: {
|
|
261
|
+
const results: {
|
|
262
|
+
modelName: string;
|
|
263
|
+
statusCode: number;
|
|
264
|
+
baseException: string | null;
|
|
265
|
+
}[] = [];
|
|
262
266
|
|
|
263
267
|
for (const service of ctx.spec.services) {
|
|
264
268
|
for (const op of service.operations) {
|
package/src/node/fixtures.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Model, TypeRef, Enum, EmitterContext } from '@workos/oagen';
|
|
2
|
-
import { wireFieldName, fileName,
|
|
2
|
+
import { wireFieldName, fileName, resolveServiceDir } from './naming.js';
|
|
3
3
|
import { resolveResourceClassName } from './resources.js';
|
|
4
4
|
import { createServiceDirResolver, assignModelsToServices, isListMetadataModel, isListWrapperModel } from './utils.js';
|
|
5
5
|
|
|
@@ -42,7 +42,7 @@ export function generateFixtures(
|
|
|
42
42
|
? createServiceDirResolver(spec.models, ctx.spec.services, ctx)
|
|
43
43
|
: {
|
|
44
44
|
modelToService: assignModelsToServices(spec.models, spec.services),
|
|
45
|
-
resolveDir: (irService: string | undefined) => (irService ?
|
|
45
|
+
resolveDir: (irService: string | undefined) => (irService ? resolveServiceDir(irService) : 'common'),
|
|
46
46
|
};
|
|
47
47
|
const modelMap = new Map(spec.models.map((m) => [m.name, m]));
|
|
48
48
|
const enumMap = new Map(spec.enums.map((e) => [e.name, e]));
|
|
@@ -66,7 +66,7 @@ export function generateFixtures(
|
|
|
66
66
|
// Generate list fixtures for models that appear in paginated responses
|
|
67
67
|
for (const service of spec.services) {
|
|
68
68
|
const resolvedName = ctx ? resolveResourceClassName(service, ctx) : service.name;
|
|
69
|
-
const serviceDir =
|
|
69
|
+
const serviceDir = resolveServiceDir(resolvedName);
|
|
70
70
|
for (const op of service.operations) {
|
|
71
71
|
if (op.pagination) {
|
|
72
72
|
let itemModel = op.pagination.itemType.kind === 'model' ? modelMap.get(op.pagination.itemType.name) : null;
|
|
@@ -175,7 +175,9 @@ function generateFieldValue(
|
|
|
175
175
|
}
|
|
176
176
|
return null;
|
|
177
177
|
case 'map':
|
|
178
|
-
return {
|
|
178
|
+
return {
|
|
179
|
+
key: generateFieldValue(ref.valueType, 'value', modelName, modelMap, enumMap),
|
|
180
|
+
};
|
|
179
181
|
}
|
|
180
182
|
}
|
|
181
183
|
|