@workos/oagen-emitters 0.12.0 → 0.12.2
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/.github/workflows/ci.yml +1 -1
- package/.github/workflows/lint-pr-title.yml +1 -1
- package/.github/workflows/lint.yml +1 -1
- package/.github/workflows/release-please.yml +2 -2
- package/.github/workflows/release.yml +1 -1
- package/.node-version +1 -1
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +14 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{plugin-C408Wh-o.mjs → plugin-eCuvoL1T.mjs} +3914 -2121
- package/dist/plugin-eCuvoL1T.mjs.map +1 -0
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +1 -1
- package/package.json +10 -10
- package/renovate.json +46 -6
- package/src/node/client.ts +19 -32
- package/src/node/enums.ts +67 -30
- package/src/node/errors.ts +2 -8
- package/src/node/field-plan.ts +188 -52
- package/src/node/fixtures.ts +11 -33
- package/src/node/index.ts +345 -20
- package/src/node/live-surface.ts +378 -0
- package/src/node/models.ts +540 -351
- package/src/node/naming.ts +119 -25
- package/src/node/node-overrides.ts +77 -0
- package/src/node/options.ts +41 -0
- package/src/node/resources.ts +455 -46
- package/src/node/sdk-errors.ts +0 -16
- package/src/node/tests.ts +108 -83
- package/src/node/type-map.ts +40 -18
- package/src/node/utils.ts +89 -102
- package/src/node/wrappers.ts +0 -20
- package/src/rust/fixtures.ts +87 -1
- package/src/rust/models.ts +17 -2
- package/src/rust/resources.ts +697 -62
- package/src/rust/tests.ts +540 -20
- package/test/node/client.test.ts +106 -1201
- package/test/node/enums.test.ts +59 -130
- package/test/node/errors.test.ts +2 -3
- package/test/node/live-surface.test.ts +240 -0
- package/test/node/models.test.ts +396 -765
- package/test/node/naming.test.ts +69 -234
- package/test/node/resources.test.ts +376 -2036
- package/test/node/tests.test.ts +119 -0
- package/test/node/type-map.test.ts +49 -54
- package/test/node/utils.test.ts +29 -80
- package/test/rust/fixtures.test.ts +227 -0
- package/test/rust/models.test.ts +38 -0
- package/test/rust/resources.test.ts +505 -2
- package/test/rust/tests.test.ts +504 -0
- package/dist/plugin-C408Wh-o.mjs.map +0 -1
- package/test/node/serializers.test.ts +0 -444
package/dist/plugin.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.mts","names":[],"sources":["../src/plugin.ts"],"mappings":";;;cA0Ba,oBAAA,EAAsB,
|
|
1
|
+
{"version":3,"file":"plugin.d.mts","names":[],"sources":["../src/plugin.ts"],"mappings":";;;cA0Ba,oBAAA,EAAsB,IAAI,CAAC,WAAA"}
|
package/dist/plugin.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as workosEmittersPlugin } from "./plugin-
|
|
1
|
+
import { t as workosEmittersPlugin } from "./plugin-eCuvoL1T.mjs";
|
|
2
2
|
export { workosEmittersPlugin };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workos/oagen-emitters",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.2",
|
|
4
4
|
"description": "WorkOS' oagen emitters",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "WorkOS",
|
|
@@ -38,22 +38,22 @@
|
|
|
38
38
|
"prepare": "husky"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@commitlint/cli": "^
|
|
42
|
-
"@commitlint/config-conventional": "^
|
|
43
|
-
"@types/node": "^25.
|
|
41
|
+
"@commitlint/cli": "^21.0.1",
|
|
42
|
+
"@commitlint/config-conventional": "^21.0.1",
|
|
43
|
+
"@types/node": "^25.8.0",
|
|
44
44
|
"husky": "^9.1.7",
|
|
45
|
-
"oxfmt": "^0.
|
|
46
|
-
"oxlint": "^1.
|
|
45
|
+
"oxfmt": "^0.50.0",
|
|
46
|
+
"oxlint": "^1.65.0",
|
|
47
47
|
"prettier": "^3.8.3",
|
|
48
|
-
"tsdown": "^0.
|
|
49
|
-
"tsx": "^4.
|
|
48
|
+
"tsdown": "^0.22.0",
|
|
49
|
+
"tsx": "^4.22.0",
|
|
50
50
|
"typescript": "^6.0.3",
|
|
51
|
-
"vitest": "^4.1.
|
|
51
|
+
"vitest": "^4.1.6"
|
|
52
52
|
},
|
|
53
53
|
"engines": {
|
|
54
54
|
"node": ">=24.10.0"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@workos/oagen": "^0.
|
|
57
|
+
"@workos/oagen": "^0.19.0"
|
|
58
58
|
}
|
|
59
59
|
}
|
package/renovate.json
CHANGED
|
@@ -1,25 +1,65 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
2
|
+
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
|
3
|
+
"extends": [
|
|
4
|
+
"config:recommended"
|
|
5
|
+
],
|
|
3
6
|
"dependencyDashboard": false,
|
|
4
|
-
"schedule": [
|
|
7
|
+
"schedule": [
|
|
8
|
+
"on the 15th day of the month before 12pm"
|
|
9
|
+
],
|
|
5
10
|
"timezone": "UTC",
|
|
6
11
|
"rebaseWhen": "conflicted",
|
|
7
12
|
"packageRules": [
|
|
8
13
|
{
|
|
9
|
-
"matchManagers": [
|
|
14
|
+
"matchManagers": [
|
|
15
|
+
"github-actions"
|
|
16
|
+
],
|
|
17
|
+
"pinDigests": true,
|
|
10
18
|
"extractVersion": "^v(?<version>\\d+\\.\\d+\\.\\d+)$"
|
|
11
19
|
},
|
|
12
20
|
{
|
|
13
|
-
"matchUpdateTypes": [
|
|
21
|
+
"matchUpdateTypes": [
|
|
22
|
+
"minor",
|
|
23
|
+
"patch"
|
|
24
|
+
],
|
|
14
25
|
"automerge": true,
|
|
15
26
|
"groupName": "minor and patch updates"
|
|
16
27
|
},
|
|
17
28
|
{
|
|
18
|
-
"matchUpdateTypes": [
|
|
29
|
+
"matchUpdateTypes": [
|
|
30
|
+
"major"
|
|
31
|
+
],
|
|
19
32
|
"automerge": false
|
|
20
33
|
},
|
|
21
34
|
{
|
|
22
|
-
"matchUpdateTypes": [
|
|
35
|
+
"matchUpdateTypes": [
|
|
36
|
+
"digest"
|
|
37
|
+
],
|
|
38
|
+
"automerge": false
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"matchManagers": [
|
|
42
|
+
"github-actions"
|
|
43
|
+
],
|
|
44
|
+
"matchUpdateTypes": [
|
|
45
|
+
"minor",
|
|
46
|
+
"patch",
|
|
47
|
+
"digest",
|
|
48
|
+
"pinDigest"
|
|
49
|
+
],
|
|
50
|
+
"groupName": "github actions non-major",
|
|
51
|
+
"groupSlug": "github-actions-non-major",
|
|
52
|
+
"automerge": true
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"matchManagers": [
|
|
56
|
+
"github-actions"
|
|
57
|
+
],
|
|
58
|
+
"matchUpdateTypes": [
|
|
59
|
+
"major"
|
|
60
|
+
],
|
|
61
|
+
"groupName": "github actions major",
|
|
62
|
+
"groupSlug": "github-actions-major",
|
|
23
63
|
"automerge": false
|
|
24
64
|
}
|
|
25
65
|
]
|
package/src/node/client.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import type { ApiSpec, AuthScheme, EmitterContext, GeneratedFile
|
|
3
|
+
import type { ApiSpec, AuthScheme, EmitterContext, GeneratedFile } from '@workos/oagen';
|
|
4
4
|
|
|
5
|
-
import { fileName,
|
|
5
|
+
import { fileName, servicePropertyName, resolveInterfaceName, wireInterfaceName } from './naming.js';
|
|
6
|
+
import { isInlineEnum } from './type-map.js';
|
|
6
7
|
import {
|
|
7
8
|
docComment,
|
|
8
9
|
createServiceDirResolver,
|
|
@@ -11,7 +12,9 @@ import {
|
|
|
11
12
|
isListWrapperModel,
|
|
12
13
|
computeNonEventReachable,
|
|
13
14
|
} from './utils.js';
|
|
14
|
-
import { resolveResourceClassName } from './resources.js';
|
|
15
|
+
import { resolveResourceClassName, resolveResourceDir } from './resources.js';
|
|
16
|
+
import { generatedResourceInterfaceModelNames } from './models.js';
|
|
17
|
+
import { assignEnumsToServices } from './enums.js';
|
|
15
18
|
|
|
16
19
|
export function generateClient(spec: ApiSpec, ctx: EmitterContext): GeneratedFile[] {
|
|
17
20
|
const files: GeneratedFile[] = [];
|
|
@@ -48,7 +51,7 @@ function generateWorkOSClient(spec: ApiSpec, ctx: EmitterContext): GeneratedFile
|
|
|
48
51
|
for (const service of spec.services) {
|
|
49
52
|
if (coveredServices.has(service.name)) continue;
|
|
50
53
|
const resolvedName = resolveResourceClassName(service, ctx);
|
|
51
|
-
const serviceDir =
|
|
54
|
+
const serviceDir = resolveResourceDir(service, ctx);
|
|
52
55
|
lines.push(`import { ${resolvedName} } from './${serviceDir}/${fileName(resolvedName)}';`);
|
|
53
56
|
}
|
|
54
57
|
|
|
@@ -126,6 +129,7 @@ function generateWorkOSClient(spec: ApiSpec, ctx: EmitterContext): GeneratedFile
|
|
|
126
129
|
function generateServiceBarrels(spec: ApiSpec, ctx: EmitterContext): GeneratedFile[] {
|
|
127
130
|
const files: GeneratedFile[] = [];
|
|
128
131
|
const { modelToService, resolveDir } = createServiceDirResolver(spec.models, spec.services, ctx);
|
|
132
|
+
const enumToService = assignEnumsToServices(spec.enums, spec.services, spec.models, ctx);
|
|
129
133
|
|
|
130
134
|
// Group interface files by directory, tracking exported symbol names
|
|
131
135
|
// to prevent TS2308 duplicate export errors when two files in the same
|
|
@@ -189,7 +193,8 @@ function generateServiceBarrels(spec: ApiSpec, ctx: EmitterContext): GeneratedFi
|
|
|
189
193
|
// from common utils, so no per-resource interface file is generated.
|
|
190
194
|
// Also skip unreachable models — use the same non-event reachability as model
|
|
191
195
|
// generation so every barrel entry has a corresponding generated file.
|
|
192
|
-
const barrelReachable =
|
|
196
|
+
const barrelReachable =
|
|
197
|
+
generatedResourceInterfaceModelNames(spec.models, ctx) ?? computeNonEventReachable(spec.services, spec.models);
|
|
193
198
|
for (const model of spec.models) {
|
|
194
199
|
if (isListMetadataModel(model) || isListWrapperModel(model)) continue;
|
|
195
200
|
if (!barrelReachable.has(model.name)) continue;
|
|
@@ -226,7 +231,10 @@ function generateServiceBarrels(spec: ApiSpec, ctx: EmitterContext): GeneratedFi
|
|
|
226
231
|
|
|
227
232
|
// Enums -> service directories
|
|
228
233
|
for (const enumDef of spec.enums) {
|
|
229
|
-
|
|
234
|
+
// Inlined enums have no file to re-export.
|
|
235
|
+
if (isInlineEnum(enumDef.name)) continue;
|
|
236
|
+
|
|
237
|
+
const enumService = enumToService.get(enumDef.name);
|
|
230
238
|
const dirName = resolveDir(enumService);
|
|
231
239
|
if (!dirExports.has(dirName)) {
|
|
232
240
|
dirExports.set(dirName, []);
|
|
@@ -368,6 +376,7 @@ function generateServiceBarrels(spec: ApiSpec, ctx: EmitterContext): GeneratedFi
|
|
|
368
376
|
function generateBarrel(spec: ApiSpec, ctx: EmitterContext): GeneratedFile {
|
|
369
377
|
const lines: string[] = [];
|
|
370
378
|
const { modelToService, resolveDir } = createServiceDirResolver(spec.models, spec.services, ctx);
|
|
379
|
+
const enumToService = assignEnumsToServices(spec.enums, spec.services, spec.models, ctx);
|
|
371
380
|
|
|
372
381
|
// Track all exported names to prevent duplicates.
|
|
373
382
|
// Pre-seed with names already exported by the existing SDK to avoid generating
|
|
@@ -492,7 +501,7 @@ function generateBarrel(spec: ApiSpec, ctx: EmitterContext): GeneratedFile {
|
|
|
492
501
|
// Per-service exports: service barrel + resource class
|
|
493
502
|
for (const service of spec.services) {
|
|
494
503
|
const resolvedName = resolveResourceClassName(service, ctx);
|
|
495
|
-
const serviceDir =
|
|
504
|
+
const serviceDir = resolveResourceDir(service, ctx);
|
|
496
505
|
// The interfaces directory may differ from the resource class directory when
|
|
497
506
|
// a service's class name is remapped (e.g., WebhooksEndpoints class lives in
|
|
498
507
|
// webhooks-endpoints/ but its model interfaces live in webhooks/).
|
|
@@ -508,7 +517,7 @@ function generateBarrel(spec: ApiSpec, ctx: EmitterContext): GeneratedFile {
|
|
|
508
517
|
return true;
|
|
509
518
|
});
|
|
510
519
|
const serviceEnums = spec.enums.filter((e) => {
|
|
511
|
-
const enumService =
|
|
520
|
+
const enumService = enumToService.get(e.name);
|
|
512
521
|
return enumService === service.name;
|
|
513
522
|
});
|
|
514
523
|
|
|
@@ -575,7 +584,7 @@ function generateBarrel(spec: ApiSpec, ctx: EmitterContext): GeneratedFile {
|
|
|
575
584
|
const reachable = computeNonEventReachable(spec.services, spec.models);
|
|
576
585
|
const unassignedModels = spec.models.filter((m) => !modelToService.has(m.name) && reachable.has(m.name));
|
|
577
586
|
const commonEnums = spec.enums.filter((e) => {
|
|
578
|
-
const enumService =
|
|
587
|
+
const enumService = enumToService.get(e.name);
|
|
579
588
|
return !enumService;
|
|
580
589
|
});
|
|
581
590
|
|
|
@@ -615,7 +624,7 @@ function generateBarrel(spec: ApiSpec, ctx: EmitterContext): GeneratedFile {
|
|
|
615
624
|
if (exportedNames.has(enumDef.name)) continue;
|
|
616
625
|
if (existingSdkExports.has(enumDef.name)) continue;
|
|
617
626
|
exportedNames.add(enumDef.name);
|
|
618
|
-
const enumService =
|
|
627
|
+
const enumService = enumToService.get(enumDef.name);
|
|
619
628
|
const dir = resolveDir(enumService);
|
|
620
629
|
if (!exportedDirs.has(dir)) {
|
|
621
630
|
const baselineEnum = ctx.apiSurface?.enums?.[enumDef.name];
|
|
@@ -643,28 +652,6 @@ function generateBarrel(spec: ApiSpec, ctx: EmitterContext): GeneratedFile {
|
|
|
643
652
|
};
|
|
644
653
|
}
|
|
645
654
|
|
|
646
|
-
function findEnumService(enumName: string, services: Service[]): string | undefined {
|
|
647
|
-
for (const service of services) {
|
|
648
|
-
for (const op of service.operations) {
|
|
649
|
-
const refs: string[] = [];
|
|
650
|
-
const collect = (ref: any) => {
|
|
651
|
-
if (ref?.kind === 'enum' && ref.name === enumName) refs.push(ref.name);
|
|
652
|
-
if (ref?.items) collect(ref.items);
|
|
653
|
-
if (ref?.inner) collect(ref.inner);
|
|
654
|
-
if (ref?.variants) ref.variants.forEach(collect);
|
|
655
|
-
if (ref?.valueType) collect(ref.valueType);
|
|
656
|
-
};
|
|
657
|
-
if (op.requestBody) collect(op.requestBody);
|
|
658
|
-
collect(op.response);
|
|
659
|
-
for (const p of [...op.pathParams, ...op.queryParams]) {
|
|
660
|
-
collect(p.type);
|
|
661
|
-
}
|
|
662
|
-
if (refs.length > 0) return service.name;
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
return undefined;
|
|
666
|
-
}
|
|
667
|
-
|
|
668
655
|
/**
|
|
669
656
|
* Determine whether the spec's auth scheme requires overriding the
|
|
670
657
|
* default bearer auth in WorkOSBase.setAuthHeaders().
|
package/src/node/enums.ts
CHANGED
|
@@ -1,42 +1,43 @@
|
|
|
1
|
-
import type { Enum, EmitterContext, GeneratedFile, Service } from '@workos/oagen';
|
|
2
|
-
import { toPascalCase, walkTypeRef } from '@workos/oagen';
|
|
1
|
+
import type { Enum, EmitterContext, GeneratedFile, Model, Service } from '@workos/oagen';
|
|
2
|
+
import { assignModelsToServices, collectFieldDependencies, toPascalCase, walkTypeRef } from '@workos/oagen';
|
|
3
3
|
import { fileName, resolveServiceDir, buildServiceNameMap } from './naming.js';
|
|
4
4
|
import { docComment } from './utils.js';
|
|
5
|
+
import { isInlineEnum } from './type-map.js';
|
|
6
|
+
import { liveSurfaceConstEnumMembers, liveSurfaceInterfacePath } from './live-surface.js';
|
|
5
7
|
|
|
6
8
|
export function generateEnums(enums: Enum[], ctx: EmitterContext): GeneratedFile[] {
|
|
7
9
|
if (enums.length === 0) return [];
|
|
8
10
|
|
|
9
|
-
const enumToService = assignEnumsToServices(enums, ctx.spec.services);
|
|
11
|
+
const enumToService = assignEnumsToServices(enums, ctx.spec.services, ctx.spec.models, ctx);
|
|
10
12
|
const serviceNameMap = buildServiceNameMap(ctx.spec.services, ctx);
|
|
11
13
|
const resolveDir = (irService: string | undefined) =>
|
|
12
14
|
irService ? resolveServiceDir(serviceNameMap.get(irService) ?? irService) : 'common';
|
|
13
15
|
const files: GeneratedFile[] = [];
|
|
14
16
|
|
|
15
17
|
for (const enumDef of enums) {
|
|
18
|
+
// Inlined enums get expanded at usage sites by `type-map`. No file needed.
|
|
19
|
+
if (isInlineEnum(enumDef.name)) continue;
|
|
20
|
+
|
|
16
21
|
const service = enumToService.get(enumDef.name);
|
|
17
22
|
const dirName = resolveDir(service);
|
|
18
23
|
|
|
19
|
-
// Check baseline surface for representation and values
|
|
20
24
|
const baselineEnum = ctx.apiSurface?.enums?.[enumDef.name];
|
|
21
25
|
const baselineAlias = ctx.apiSurface?.typeAliases?.[enumDef.name];
|
|
22
26
|
const generatedPath = `src/${dirName}/interfaces/${fileName(enumDef.name)}.interface.ts`;
|
|
23
27
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
const baselineSourceFile =
|
|
29
|
+
(baselineEnum as any)?.sourceFile ?? (baselineAlias as any)?.sourceFile ?? liveSurfaceInterfacePath(enumDef.name);
|
|
30
|
+
if (dirName === 'common' && !baselineSourceFile && (ctx.outputDir || ctx.targetDir || ctx.apiSurface)) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
27
33
|
if (baselineSourceFile && baselineSourceFile !== generatedPath) {
|
|
28
34
|
continue;
|
|
29
35
|
}
|
|
30
36
|
|
|
31
37
|
const lines: string[] = [];
|
|
32
|
-
|
|
33
|
-
// Track whether the generated content has new values not in the baseline.
|
|
34
|
-
// When it does, skipIfExists must be false so the file gets updated.
|
|
35
38
|
let hasNewValues = false;
|
|
36
39
|
|
|
37
40
|
if (baselineEnum?.members) {
|
|
38
|
-
// Generate TS `enum` using baseline member names and values, merging
|
|
39
|
-
// any new IR values that the baseline is missing.
|
|
40
41
|
const existingValues = new Set(Object.values(baselineEnum.members).map(String));
|
|
41
42
|
const irValues = enumDef.values.map((v) => String(v.value));
|
|
42
43
|
const missingValues = irValues.filter((v) => !existingValues.has(v));
|
|
@@ -47,21 +48,17 @@ export function generateEnums(enums: Enum[], ctx: EmitterContext): GeneratedFile
|
|
|
47
48
|
const valueStr = typeof memberValue === 'string' ? `'${memberValue}'` : String(memberValue);
|
|
48
49
|
lines.push(` ${memberName} = ${valueStr},`);
|
|
49
50
|
}
|
|
50
|
-
// Append new values from the spec that the baseline is missing
|
|
51
51
|
for (const val of missingValues) {
|
|
52
|
-
// Derive a PascalCase member name from the value
|
|
53
52
|
const memberName = toPascalCase(val);
|
|
54
53
|
lines.push(` ${memberName} = '${val}',`);
|
|
55
54
|
}
|
|
56
55
|
lines.push('}');
|
|
57
56
|
} else if (baselineAlias?.value) {
|
|
58
|
-
// Use the baseline type alias value, but merge in any new IR values the baseline is missing.
|
|
59
57
|
const baselineValues = extractLiteralUnionValues(baselineAlias.value);
|
|
60
58
|
const irValues = enumDef.values.map((v) => String(v.value));
|
|
61
59
|
const missing = irValues.filter((v) => !baselineValues.has(v));
|
|
62
60
|
hasNewValues = missing.length > 0;
|
|
63
61
|
if (missing.length > 0) {
|
|
64
|
-
// Baseline is missing values from the spec — regenerate with all values merged
|
|
65
62
|
const allValues = [...baselineValues, ...missing];
|
|
66
63
|
const parts = allValues.map((v) => `'${v}'`);
|
|
67
64
|
lines.push(`export type ${enumDef.name} = ${parts.join(' | ')};`);
|
|
@@ -69,11 +66,36 @@ export function generateEnums(enums: Enum[], ctx: EmitterContext): GeneratedFile
|
|
|
69
66
|
lines.push(`export type ${enumDef.name} = ${baselineAlias.value};`);
|
|
70
67
|
}
|
|
71
68
|
} else {
|
|
72
|
-
// No baseline
|
|
69
|
+
// No baseline form available — emit the workos-node house style:
|
|
70
|
+
//
|
|
71
|
+
// export const X = { Member: 'value', ... } as const;
|
|
72
|
+
// export type X = (typeof X)[keyof typeof X];
|
|
73
|
+
//
|
|
74
|
+
// This dual declaration lets callers use either the type (`X`) or the
|
|
75
|
+
// namespace (`X.Member`) without paying for a TypeScript `enum`'s
|
|
76
|
+
// runtime overhead. Emitting only the type alias would compile but
|
|
77
|
+
// break hand-written test files that import the enum as a value.
|
|
78
|
+
//
|
|
79
|
+
// Member name resolution, per value:
|
|
80
|
+
// 1. If the live SDK already declares this enum as a const-object
|
|
81
|
+
// with a member for this exact value, reuse the existing member
|
|
82
|
+
// name. This preserves acronym casing (`DSync`, `SAML`, `JWT`)
|
|
83
|
+
// that the simpler `toPascalCase` would otherwise flatten.
|
|
84
|
+
// 2. Otherwise PascalCase the value.
|
|
85
|
+
// 3. Skip duplicate values and duplicate member names — the union
|
|
86
|
+
// type derived from the const captures every kept value.
|
|
73
87
|
const values = enumDef.values;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
88
|
+
const existingMembers = liveSurfaceConstEnumMembers(enumDef.name);
|
|
89
|
+
const seenMembers = new Set<string>();
|
|
90
|
+
const seenValues = new Set<string>();
|
|
91
|
+
lines.push(`export const ${enumDef.name} = {`);
|
|
92
|
+
for (const v of values) {
|
|
93
|
+
const valueKey = String(v.value);
|
|
94
|
+
if (seenValues.has(valueKey)) continue;
|
|
95
|
+
seenValues.add(valueKey);
|
|
96
|
+
const memberName = existingMembers?.get(valueKey) ?? toPascalCase(valueKey);
|
|
97
|
+
if (seenMembers.has(memberName)) continue;
|
|
98
|
+
seenMembers.add(memberName);
|
|
77
99
|
const valueStr = typeof v.value === 'string' ? `'${v.value}'` : String(v.value);
|
|
78
100
|
if (v.description || v.deprecated) {
|
|
79
101
|
const parts: string[] = [];
|
|
@@ -81,16 +103,17 @@ export function generateEnums(enums: Enum[], ctx: EmitterContext): GeneratedFile
|
|
|
81
103
|
if (v.deprecated) parts.push('@deprecated');
|
|
82
104
|
lines.push(...docComment(parts.join('\n'), 2));
|
|
83
105
|
}
|
|
84
|
-
|
|
85
|
-
lines.push(` | ${valueStr}${suffix}`);
|
|
106
|
+
lines.push(` ${memberName}: ${valueStr},`);
|
|
86
107
|
}
|
|
108
|
+
lines.push(`} as const;`);
|
|
109
|
+
lines.push('');
|
|
110
|
+
lines.push(`export type ${enumDef.name} =`);
|
|
111
|
+
lines.push(` (typeof ${enumDef.name})[keyof typeof ${enumDef.name}];`);
|
|
87
112
|
}
|
|
88
113
|
|
|
89
114
|
files.push({
|
|
90
115
|
path: `src/${dirName}/interfaces/${fileName(enumDef.name)}.interface.ts`,
|
|
91
116
|
content: lines.join('\n'),
|
|
92
|
-
// When the spec has new values the baseline is missing, allow the file
|
|
93
|
-
// to be updated so the SDK picks up the full set of enum values.
|
|
94
117
|
skipIfExists: !hasNewValues,
|
|
95
118
|
});
|
|
96
119
|
}
|
|
@@ -98,13 +121,8 @@ export function generateEnums(enums: Enum[], ctx: EmitterContext): GeneratedFile
|
|
|
98
121
|
return files;
|
|
99
122
|
}
|
|
100
123
|
|
|
101
|
-
/**
|
|
102
|
-
* Parse a TypeScript string literal union type alias value (e.g., "'a' | 'b' | 'c'")
|
|
103
|
-
* into a set of its string values.
|
|
104
|
-
*/
|
|
105
124
|
function extractLiteralUnionValues(aliasValue: string): Set<string> {
|
|
106
125
|
const values = new Set<string>();
|
|
107
|
-
// Match all single-quoted string literals in the union
|
|
108
126
|
const regex = /'([^']+)'/g;
|
|
109
127
|
let match;
|
|
110
128
|
while ((match = regex.exec(aliasValue)) !== null) {
|
|
@@ -113,7 +131,12 @@ function extractLiteralUnionValues(aliasValue: string): Set<string> {
|
|
|
113
131
|
return values;
|
|
114
132
|
}
|
|
115
133
|
|
|
116
|
-
export function assignEnumsToServices(
|
|
134
|
+
export function assignEnumsToServices(
|
|
135
|
+
enums: Enum[],
|
|
136
|
+
services: Service[],
|
|
137
|
+
models: Model[] = [],
|
|
138
|
+
ctx?: EmitterContext,
|
|
139
|
+
): Map<string, string> {
|
|
117
140
|
const enumToService = new Map<string, string>();
|
|
118
141
|
const enumNames = new Set(enums.map((e) => e.name));
|
|
119
142
|
|
|
@@ -136,5 +159,19 @@ export function assignEnumsToServices(enums: Enum[], services: Service[]): Map<s
|
|
|
136
159
|
}
|
|
137
160
|
}
|
|
138
161
|
|
|
162
|
+
if (models.length > 0) {
|
|
163
|
+
const modelToService = assignModelsToServices(models, services, ctx?.modelHints);
|
|
164
|
+
for (const model of models) {
|
|
165
|
+
const service = modelToService.get(model.name);
|
|
166
|
+
if (!service) continue;
|
|
167
|
+
|
|
168
|
+
for (const name of collectFieldDependencies(model).enums) {
|
|
169
|
+
if (enumNames.has(name) && !enumToService.has(name)) {
|
|
170
|
+
enumToService.set(name, service);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
139
176
|
return enumToService;
|
|
140
177
|
}
|
package/src/node/errors.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { fileName } from './naming.js';
|
|
|
3
3
|
import { buildNodeStatusExceptions } from './sdk-errors.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Static exception classes are
|
|
6
|
+
* Static exception classes are hand-maintained in the target SDK.
|
|
7
7
|
* Only typed exceptions derived from spec error models are generated here.
|
|
8
8
|
*/
|
|
9
9
|
export function generateErrors(ctx?: EmitterContext): GeneratedFile[] {
|
|
@@ -45,8 +45,6 @@ export function generateErrors(ctx?: EmitterContext): GeneratedFile[] {
|
|
|
45
45
|
exportLines.push(`export { ${exceptionClassName} } from './${fileName(modelName)}.exception';`);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
// Generate a barrel for typed errors only (appended to existing exceptions/index.ts
|
|
49
|
-
// via region preservation if needed)
|
|
50
48
|
if (exportLines.length > 0) {
|
|
51
49
|
files.push({
|
|
52
50
|
path: 'src/common/exceptions/typed-errors.ts',
|
|
@@ -64,11 +62,7 @@ function collectTypedErrors(
|
|
|
64
62
|
): { modelName: string; statusCode: number; baseException: string | null }[] {
|
|
65
63
|
const statusToBase = buildNodeStatusExceptions(ctx.spec.sdk);
|
|
66
64
|
const seen = new Set<string>();
|
|
67
|
-
const results: {
|
|
68
|
-
modelName: string;
|
|
69
|
-
statusCode: number;
|
|
70
|
-
baseException: string | null;
|
|
71
|
-
}[] = [];
|
|
65
|
+
const results: { modelName: string; statusCode: number; baseException: string | null }[] = [];
|
|
72
66
|
|
|
73
67
|
for (const service of ctx.spec.services) {
|
|
74
68
|
for (const op of service.operations) {
|