@workos/oagen-emitters 0.17.0 → 0.18.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/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +14 -0
- package/dist/index.mjs +1 -1
- package/dist/{plugin-BLnR-FMi.mjs → plugin-CtU_wbid.mjs} +182 -49
- package/dist/plugin-CtU_wbid.mjs.map +1 -0
- package/dist/plugin.mjs +1 -1
- package/package.json +6 -6
- package/src/node/client.ts +2 -2
- package/src/node/discriminated-models.ts +197 -50
- package/src/node/models.ts +44 -5
- package/src/node/options.ts +23 -0
- package/src/node/resources.ts +56 -12
- package/src/node/tests.ts +5 -3
- package/test/node/discriminated-pure-oneof.test.ts +108 -0
- package/dist/plugin-BLnR-FMi.mjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.18.1](https://github.com/workos/oagen-emitters/compare/v0.18.0...v0.18.1) (2026-06-17)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **node:** support owning services with hand-owned generic types ([#152](https://github.com/workos/oagen-emitters/issues/152)) ([aa8223d](https://github.com/workos/oagen-emitters/commit/aa8223dbb6b56f5c2ab12ac42e5a1fc9f9e99943))
|
|
9
|
+
|
|
10
|
+
## [0.18.0](https://github.com/workos/oagen-emitters/compare/v0.17.0...v0.18.0) (2026-06-16)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* **node:** emit discriminated unions for pure oneOf token response ([#150](https://github.com/workos/oagen-emitters/issues/150)) ([1433bcc](https://github.com/workos/oagen-emitters/commit/1433bcc2f61db52aeadd7b947b79bb90c184c0a1))
|
|
16
|
+
|
|
3
17
|
## [0.17.0](https://github.com/workos/oagen-emitters/compare/v0.16.1...v0.17.0) (2026-06-15)
|
|
4
18
|
|
|
5
19
|
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as fieldName$4, B as servicePropertyName, C as apiClassName, D as dotnetEmitter, E as propertyName, F as fieldName$3, H as fieldName$1, I as methodName, L as trimMountedResourceFromMethod, M as trimMountedResourceFromMethod$1, N as goEmitter, O as appendAsyncSuffix, P as className, R as phpEmitter, S as kotlinEmitter, T as packageSegment, U as safeParamName$1, V as pythonEmitter, W as nodeEmitter, _ as rubyEmitter, a as rustExtractor, b as resolveServiceTarget, c as pythonExtractor, d as rustEmitter, f as fieldName$5, g as typeName, h as resourceAccessorName, i as kotlinExtractor, j as methodName$1, k as className$1, l as rubyExtractor, m as moduleName, n as elixirExtractor, o as goExtractor, p as methodName$3, r as dotnetExtractor, s as phpExtractor, t as workosEmittersPlugin, u as nodeExtractor, v as buildExportedClassNameSet, w as methodName$2, x as safeParamName, y as fieldName, z as fieldName$2 } from "./plugin-
|
|
1
|
+
import { A as fieldName$4, B as servicePropertyName, C as apiClassName, D as dotnetEmitter, E as propertyName, F as fieldName$3, H as fieldName$1, I as methodName, L as trimMountedResourceFromMethod, M as trimMountedResourceFromMethod$1, N as goEmitter, O as appendAsyncSuffix, P as className, R as phpEmitter, S as kotlinEmitter, T as packageSegment, U as safeParamName$1, V as pythonEmitter, W as nodeEmitter, _ as rubyEmitter, a as rustExtractor, b as resolveServiceTarget, c as pythonExtractor, d as rustEmitter, f as fieldName$5, g as typeName, h as resourceAccessorName, i as kotlinExtractor, j as methodName$1, k as className$1, l as rubyExtractor, m as moduleName, n as elixirExtractor, o as goExtractor, p as methodName$3, r as dotnetExtractor, s as phpExtractor, t as workosEmittersPlugin, u as nodeExtractor, v as buildExportedClassNameSet, w as methodName$2, x as safeParamName, y as fieldName, z as fieldName$2 } from "./plugin-CtU_wbid.mjs";
|
|
2
2
|
import { collectSnippetArgs, collectWrapperArgs, toSnakeCase } from "@workos/oagen";
|
|
3
3
|
//#region src/snippets/ruby.ts
|
|
4
4
|
const INDENT$6 = " ";
|
|
@@ -3626,6 +3626,17 @@ function isNodeOwnedService(ctx, ...names) {
|
|
|
3626
3626
|
const owned = new Set(configured.map(normalizeServiceName));
|
|
3627
3627
|
return names.some((name) => name !== void 0 ? ownedLookupNames(name).some((candidate) => owned.has(normalizeServiceName(candidate))) : false);
|
|
3628
3628
|
}
|
|
3629
|
+
/**
|
|
3630
|
+
* True when `name` is a hand-owned type (see {@link NodeEmitterOptions.handOwnedTypes}).
|
|
3631
|
+
* Hand-owned types are never generated; the emitter defers to the existing
|
|
3632
|
+
* hand-written declaration and routes imports/barrel exports to it.
|
|
3633
|
+
*/
|
|
3634
|
+
function isHandOwnedType(ctx, name) {
|
|
3635
|
+
if (name === void 0) return false;
|
|
3636
|
+
const configured = nodeOptions(ctx).handOwnedTypes;
|
|
3637
|
+
if (!configured || configured.length === 0) return false;
|
|
3638
|
+
return configured.includes(name);
|
|
3639
|
+
}
|
|
3629
3640
|
//#endregion
|
|
3630
3641
|
//#region src/node/live-surface.ts
|
|
3631
3642
|
const SRC_DIR = "src";
|
|
@@ -5582,9 +5593,15 @@ function optionsObjectInfo(service, method, op, plan, ctx, baselineMethod, resol
|
|
|
5582
5593
|
generated: baselineTypeSourceFile(ctx, overrideType) === void 0
|
|
5583
5594
|
};
|
|
5584
5595
|
if (!operationHasOptionsInput(op, plan, resolvedOp)) return void 0;
|
|
5596
|
+
const resolvedService = resolveResourceClassName$3(service, ctx);
|
|
5597
|
+
let optionsType = methodOptionsName$1(method, resolvedService);
|
|
5598
|
+
if (method !== "list") {
|
|
5599
|
+
const baselineDir = baselineTypeSourceFile(ctx, optionsType)?.match(/^src\/([^/]+)\//)?.[1];
|
|
5600
|
+
if (baselineDir && baselineDir !== resolveResourceDir(service, ctx)) optionsType = `${toPascalCase(resolvedService)}${toPascalCase(method)}Options`;
|
|
5601
|
+
}
|
|
5585
5602
|
return {
|
|
5586
5603
|
name: "options",
|
|
5587
|
-
type:
|
|
5604
|
+
type: optionsType,
|
|
5588
5605
|
optional: optionsObjectShouldBeOptional(op, plan, resolvedOp),
|
|
5589
5606
|
generated: true
|
|
5590
5607
|
};
|
|
@@ -5603,6 +5620,17 @@ function renderOptionsParam(param) {
|
|
|
5603
5620
|
function isValidTypeIdentifier(name) {
|
|
5604
5621
|
return /^[A-Za-z_$][\w$]*$/.test(name);
|
|
5605
5622
|
}
|
|
5623
|
+
/**
|
|
5624
|
+
* Extract candidate named type references from a compound type expression such
|
|
5625
|
+
* as `GetAccessTokenOptions & { provider: string }`. PascalCase tokens are type
|
|
5626
|
+
* names worth importing; lowercase tokens are property keys or primitives and
|
|
5627
|
+
* are skipped. The caller only imports the ones that resolve to a known source
|
|
5628
|
+
* file, so unrecognized PascalCase tokens (e.g. `Date`, `Record`) are harmless.
|
|
5629
|
+
*/
|
|
5630
|
+
function extractNamedTypeRefs(typeExpr) {
|
|
5631
|
+
const matches = typeExpr.match(/\b[A-Z][A-Za-z0-9_$]*\b/g) ?? [];
|
|
5632
|
+
return [...new Set(matches)];
|
|
5633
|
+
}
|
|
5606
5634
|
function autoPaginatableItemType$1(returnType) {
|
|
5607
5635
|
return returnType?.match(/\b(?:AutoPaginatable|List)<\s*([A-Za-z_$][\w$]*)/)?.[1];
|
|
5608
5636
|
}
|
|
@@ -5937,12 +5965,21 @@ function generateResourceClass(service, ctx) {
|
|
|
5937
5965
|
if (hasIdempotentPost || hasCustomEncoding) lines.push("import type { PostOptions } from '../common/interfaces/post-options.interface';");
|
|
5938
5966
|
const importedTypeNames = /* @__PURE__ */ new Set();
|
|
5939
5967
|
for (const optionType of optionObjectTypes) {
|
|
5940
|
-
if (
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5968
|
+
if (isValidTypeIdentifier(optionType)) {
|
|
5969
|
+
if (importedTypeNames.has(optionType)) continue;
|
|
5970
|
+
importedTypeNames.add(optionType);
|
|
5971
|
+
const sourceFile = baselineTypeSourceFile(ctx, optionType);
|
|
5972
|
+
const relPath = sourceFile ? relativeImport(resourcePath, sourceFile) : `./interfaces/${fileName$3(optionType)}.interface`;
|
|
5973
|
+
lines.push(`import type { ${optionType} } from '${relPath}';`);
|
|
5974
|
+
continue;
|
|
5975
|
+
}
|
|
5976
|
+
for (const typeName of extractNamedTypeRefs(optionType)) {
|
|
5977
|
+
if (importedTypeNames.has(typeName)) continue;
|
|
5978
|
+
const sourceFile = baselineTypeSourceFile(ctx, typeName) ?? liveSurfaceInterfacePath(typeName);
|
|
5979
|
+
if (!sourceFile) continue;
|
|
5980
|
+
importedTypeNames.add(typeName);
|
|
5981
|
+
lines.push(`import type { ${typeName} } from '${relativeImport(resourcePath, sourceFile)}';`);
|
|
5982
|
+
}
|
|
5946
5983
|
}
|
|
5947
5984
|
for (const typeName of returnTypeImports) {
|
|
5948
5985
|
if (importedTypeNames.has(typeName)) continue;
|
|
@@ -6059,7 +6096,7 @@ function generateResourceClass(service, ctx) {
|
|
|
6059
6096
|
const resolved = lookupResolved(op, resolvedLookup);
|
|
6060
6097
|
const optionInfo = optionsObjectInfo(service, method, op, plan, ctx, baselineMethodFor$1(service, method, ctx), resolved);
|
|
6061
6098
|
if (plan.isPaginated) {
|
|
6062
|
-
const extraParams = op.queryParams.filter((p) => !PAGINATION_PARAM_NAMES.has(p.name));
|
|
6099
|
+
const extraParams = op.queryParams.filter((p) => !PAGINATION_PARAM_NAMES.has(p.name) && !p.deprecated);
|
|
6063
6100
|
if (extraParams.length > 0) {
|
|
6064
6101
|
const optionsName = optionInfo?.type ?? paginatedOptionsName(method, resolvedName);
|
|
6065
6102
|
if (extraParams.some((p) => fieldName$6(p.name) !== wireFieldName(p.name))) {
|
|
@@ -7316,6 +7353,7 @@ function generateModels$7(models, ctx, shared) {
|
|
|
7316
7353
|
if (isListMetadataModel(model) && !listMetadataNeeded.has(model.name)) continue;
|
|
7317
7354
|
if (isListWrapperModel(model) && !nonPaginatedRefs.has(model.name)) continue;
|
|
7318
7355
|
if (discriminatedSkip?.has(model.name)) continue;
|
|
7356
|
+
if (isHandOwnedType(ctx, model.name) || isHandOwnedType(ctx, resolveInterfaceName(model.name, ctx, { skipTypeAlias: true }))) continue;
|
|
7319
7357
|
const service = modelToService.get(model.name);
|
|
7320
7358
|
const isOwnedModel = isNodeOwnedService(ctx, service);
|
|
7321
7359
|
if (!isOwnedModel && !modelHasNewFields(model, ctx) && !forceGenerate.has(model.name)) continue;
|
|
@@ -7390,6 +7428,7 @@ function generateModels$7(models, ctx, shared) {
|
|
|
7390
7428
|
const crossServiceImports = /* @__PURE__ */ new Map();
|
|
7391
7429
|
const unresolvableNames = /* @__PURE__ */ new Set();
|
|
7392
7430
|
const enumToService = assignEnumsToServices(ctx.spec.enums, ctx.spec.services, ctx.spec.models, ctx);
|
|
7431
|
+
const serviceNameMap = buildServiceNameMap(ctx.spec.services, ctx);
|
|
7393
7432
|
const resolvedEnumNames = /* @__PURE__ */ new Map();
|
|
7394
7433
|
for (const e of ctx.spec.enums) resolvedEnumNames.set(resolveInterfaceName(e.name, ctx), e.name);
|
|
7395
7434
|
for (const field of model.fields) {
|
|
@@ -7409,7 +7448,7 @@ function generateModels$7(models, ctx, shared) {
|
|
|
7409
7448
|
const eDir = resolveDir(eService);
|
|
7410
7449
|
const bEnum = ctx.apiSurface?.enums?.[irEnumName];
|
|
7411
7450
|
const bAlias = ctx.apiSurface?.typeAliases?.[irEnumName];
|
|
7412
|
-
const bSrc = isNodeOwnedService(ctx, eService) ? void 0 : bEnum?.sourceFile ?? bAlias?.sourceFile;
|
|
7451
|
+
const bSrc = isNodeOwnedService(ctx, eService, eService ? serviceNameMap.get(eService) : void 0) ? void 0 : bEnum?.sourceFile ?? bAlias?.sourceFile;
|
|
7413
7452
|
const gPath = `src/${eDir}/interfaces/${fileName$3(irEnumName)}.interface.ts`;
|
|
7414
7453
|
const cPath = `src/${dirName}/interfaces/${fileName$3(model.name)}.interface.ts`;
|
|
7415
7454
|
if (bSrc === cPath) {
|
|
@@ -7454,7 +7493,7 @@ function generateModels$7(models, ctx, shared) {
|
|
|
7454
7493
|
const baselineEnum = ctx.apiSurface?.enums?.[dep];
|
|
7455
7494
|
const baselineAlias = ctx.apiSurface?.typeAliases?.[dep];
|
|
7456
7495
|
const depService = enumToService.get(dep);
|
|
7457
|
-
const baselineSrc = isNodeOwnedService(ctx, depService) ? void 0 : baselineEnum?.sourceFile ?? baselineAlias?.sourceFile ?? liveSurfaceInterfacePath(dep);
|
|
7496
|
+
const baselineSrc = isNodeOwnedService(ctx, depService, depService ? serviceNameMap.get(depService) : void 0) ? void 0 : baselineEnum?.sourceFile ?? baselineAlias?.sourceFile ?? liveSurfaceInterfacePath(dep);
|
|
7458
7497
|
const depDir = resolveDir(depService);
|
|
7459
7498
|
const generatedPath = `src/${depDir}/interfaces/${fileName$3(dep)}.interface.ts`;
|
|
7460
7499
|
const currentFilePath = `src/${dirName}/interfaces/${fileName$3(model.name)}.interface.ts`;
|
|
@@ -7677,6 +7716,7 @@ function generateSerializers(models, ctx, shared) {
|
|
|
7677
7716
|
if (isListMetadataModel(model) && !serializerListMetadataNeeded.has(model.name)) continue;
|
|
7678
7717
|
if (isListWrapperModel(model) && !serializerNonPaginatedRefs.has(model.name)) continue;
|
|
7679
7718
|
if (discriminatedSerializerSkip?.has(model.name)) continue;
|
|
7719
|
+
if (isHandOwnedType(ctx, model.name) || isHandOwnedType(ctx, resolveInterfaceName(model.name, ctx, { skipTypeAlias: true }))) continue;
|
|
7680
7720
|
if (!isNodeOwnedService(ctx, modelToService.get(model.name)) && !modelHasNewFields(model, ctx) && !forceGenerateSerializer.has(model.name)) continue;
|
|
7681
7721
|
eligibleModels.push(model);
|
|
7682
7722
|
}
|
|
@@ -7760,12 +7800,20 @@ function generateSerializers(models, ctx, shared) {
|
|
|
7760
7800
|
}
|
|
7761
7801
|
const liveRootForBarrel = ctx.outputDir ?? ctx.targetDir;
|
|
7762
7802
|
for (const [dir, stems] of serializersByDir) {
|
|
7763
|
-
if (liveRootForBarrel
|
|
7803
|
+
if (liveRootForBarrel) {
|
|
7804
|
+
const dirIsOwned = isNodeOwnedService(ctx, dir);
|
|
7764
7805
|
const serializersDir = path.join(liveRootForBarrel, "src", dir, "serializers");
|
|
7765
7806
|
try {
|
|
7766
7807
|
for (const entry of fs.readdirSync(serializersDir)) {
|
|
7767
7808
|
if (!entry.endsWith(".serializer.ts")) continue;
|
|
7768
|
-
|
|
7809
|
+
const stem = entry.replace(/\.serializer\.ts$/, "");
|
|
7810
|
+
if (stems.has(stem)) continue;
|
|
7811
|
+
if (dirIsOwned) {
|
|
7812
|
+
const content = fs.readFileSync(path.join(serializersDir, entry), "utf-8");
|
|
7813
|
+
if (/auto-generated by oagen/i.test(content.slice(0, 400))) continue;
|
|
7814
|
+
if (![...content.matchAll(/export\s+(?:const|function)\s+((?:de)?serialize[A-Za-z0-9_]+)/g)].map((m) => m[1].replace(/^(?:de)?serialize/, "")).some((typeName) => isHandOwnedType(ctx, typeName))) continue;
|
|
7815
|
+
}
|
|
7816
|
+
stems.add(stem);
|
|
7769
7817
|
}
|
|
7770
7818
|
} catch {}
|
|
7771
7819
|
}
|
|
@@ -8049,12 +8097,12 @@ function exportedNamesForSource(ctx, sourceFile) {
|
|
|
8049
8097
|
*/
|
|
8050
8098
|
function generateServiceBarrels(spec, ctx) {
|
|
8051
8099
|
const files = [];
|
|
8052
|
-
const { modelToService, resolveDir } = createServiceDirResolver(spec.models, spec.services, ctx);
|
|
8100
|
+
const { modelToService, resolveDir, serviceNameMap } = createServiceDirResolver(spec.models, spec.services, ctx);
|
|
8053
8101
|
const enumToService = assignEnumsToServices(spec.enums, spec.services, spec.models, ctx);
|
|
8054
8102
|
const dirExports = /* @__PURE__ */ new Map();
|
|
8055
8103
|
const dirSymbols = /* @__PURE__ */ new Map();
|
|
8056
8104
|
const ownedDirNames = /* @__PURE__ */ new Set();
|
|
8057
|
-
for (const service of spec.services) if (isNodeOwnedService(ctx, service.name)) {
|
|
8105
|
+
for (const service of spec.services) if (isNodeOwnedService(ctx, service.name, serviceNameMap.get(service.name))) {
|
|
8058
8106
|
const dir = resolveDir(service.name);
|
|
8059
8107
|
ownedDirNames.add(dir);
|
|
8060
8108
|
if (!dirExports.has(dir)) {
|
|
@@ -8825,12 +8873,12 @@ function buildOptionsObjectTestArg(op, plan, baselineMethod, modelMap, ctx) {
|
|
|
8825
8873
|
entries.push(`${optionField}: ${JSON.stringify(pathParamTestValue(param, localName))}`);
|
|
8826
8874
|
}
|
|
8827
8875
|
if (plan.isPaginated) entries.push("order: 'desc'");
|
|
8828
|
-
const queryParams = plan.isPaginated ? op.queryParams.filter((param) => ![
|
|
8876
|
+
const queryParams = (plan.isPaginated ? op.queryParams.filter((param) => ![
|
|
8829
8877
|
"limit",
|
|
8830
8878
|
"before",
|
|
8831
8879
|
"after",
|
|
8832
8880
|
"order"
|
|
8833
|
-
].includes(param.name)) : op.queryParams;
|
|
8881
|
+
].includes(param.name)) : op.queryParams).filter((param) => !param.deprecated);
|
|
8834
8882
|
for (const param of queryParams) {
|
|
8835
8883
|
const localName = fieldName$6(param.name);
|
|
8836
8884
|
const value = queryParamTestValue(param, modelMap);
|
|
@@ -9374,10 +9422,11 @@ function dedupeLiteralTypes(types) {
|
|
|
9374
9422
|
//#region src/node/discriminated-models.ts
|
|
9375
9423
|
function detectDiscriminatedShape(modelName, rawSchemas) {
|
|
9376
9424
|
const schema = rawSchemas[modelName];
|
|
9377
|
-
if (!schema
|
|
9425
|
+
if (!schema) return null;
|
|
9378
9426
|
let baseObject = null;
|
|
9379
9427
|
let oneOfVariants = null;
|
|
9380
|
-
|
|
9428
|
+
let inlineUnion = false;
|
|
9429
|
+
if (schema.allOf) for (const member of schema.allOf) {
|
|
9381
9430
|
const resolved = resolveRef(member, rawSchemas);
|
|
9382
9431
|
if (resolved.oneOf) {
|
|
9383
9432
|
if (oneOfVariants) return null;
|
|
@@ -9388,16 +9437,22 @@ function detectDiscriminatedShape(modelName, rawSchemas) {
|
|
|
9388
9437
|
baseObject = mergeBase(baseObject, nestedBase);
|
|
9389
9438
|
}
|
|
9390
9439
|
}
|
|
9440
|
+
else if (schema.oneOf && schema.oneOf.length >= 2) {
|
|
9441
|
+
if (!schema.oneOf.every((v) => v.properties && !v.$ref && (v.type === "object" || !v.type) && !v.allOf && !v.oneOf)) return null;
|
|
9442
|
+
oneOfVariants = schema.oneOf;
|
|
9443
|
+
inlineUnion = true;
|
|
9444
|
+
} else return null;
|
|
9391
9445
|
if (!oneOfVariants || oneOfVariants.length < 2) return null;
|
|
9392
9446
|
const flattenedVariants = oneOfVariants.map((v) => flattenVariant(v, rawSchemas));
|
|
9393
9447
|
const discProp = findSharedDiscriminator(flattenedVariants);
|
|
9394
9448
|
if (!discProp) return null;
|
|
9395
9449
|
const variants = flattenedVariants.map((fv) => {
|
|
9396
|
-
const discValue =
|
|
9397
|
-
if (
|
|
9450
|
+
const discValue = readConst(fv.alwaysProperties.get(discProp));
|
|
9451
|
+
if (discValue === null) return null;
|
|
9398
9452
|
return {
|
|
9399
|
-
nameSuffix: variantNameSuffix(discValue),
|
|
9400
|
-
discriminatorValue: discValue,
|
|
9453
|
+
nameSuffix: variantNameSuffix(String(discValue)),
|
|
9454
|
+
discriminatorValue: String(discValue),
|
|
9455
|
+
discriminatorIsBoolean: typeof discValue === "boolean",
|
|
9401
9456
|
fields: variantFields(fv, discProp, modelName)
|
|
9402
9457
|
};
|
|
9403
9458
|
}).filter((v) => v !== null);
|
|
@@ -9410,7 +9465,8 @@ function detectDiscriminatedShape(modelName, rawSchemas) {
|
|
|
9410
9465
|
discriminatorProperty: discProp,
|
|
9411
9466
|
discriminatorPropertyDomain: toCamelCase(discProp),
|
|
9412
9467
|
discriminatorDescription,
|
|
9413
|
-
variants
|
|
9468
|
+
variants,
|
|
9469
|
+
inlineUnion
|
|
9414
9470
|
};
|
|
9415
9471
|
}
|
|
9416
9472
|
function mergeBase(prev, next) {
|
|
@@ -9525,21 +9581,29 @@ function findSharedDiscriminator(variants) {
|
|
|
9525
9581
|
let allConst = true;
|
|
9526
9582
|
const values = [];
|
|
9527
9583
|
for (const v of variants) {
|
|
9528
|
-
const val =
|
|
9584
|
+
const val = readConst(v.alwaysProperties.get(propName));
|
|
9529
9585
|
if (val === null) {
|
|
9530
9586
|
allConst = false;
|
|
9531
9587
|
break;
|
|
9532
9588
|
}
|
|
9533
|
-
values.push(val);
|
|
9589
|
+
values.push(String(val));
|
|
9534
9590
|
}
|
|
9535
9591
|
if (allConst && new Set(values).size === values.length) return propName;
|
|
9536
9592
|
}
|
|
9537
9593
|
return null;
|
|
9538
9594
|
}
|
|
9539
|
-
|
|
9595
|
+
/**
|
|
9596
|
+
* Read a discriminator value pinned by `const` (or a single-value `enum`).
|
|
9597
|
+
* Supports string and boolean literals — the latter drives the
|
|
9598
|
+
* `active: true | false` style token response union.
|
|
9599
|
+
*/
|
|
9600
|
+
function readConst(schema) {
|
|
9540
9601
|
if (!schema) return null;
|
|
9541
|
-
if (typeof schema.const === "string") return schema.const;
|
|
9542
|
-
if (Array.isArray(schema.enum) && schema.enum.length === 1
|
|
9602
|
+
if (typeof schema.const === "string" || typeof schema.const === "boolean") return schema.const;
|
|
9603
|
+
if (Array.isArray(schema.enum) && schema.enum.length === 1) {
|
|
9604
|
+
const only = schema.enum[0];
|
|
9605
|
+
if (typeof only === "string" || typeof only === "boolean") return only;
|
|
9606
|
+
}
|
|
9543
9607
|
return null;
|
|
9544
9608
|
}
|
|
9545
9609
|
function variantNameSuffix(constValue) {
|
|
@@ -9568,6 +9632,7 @@ function buildField(rawName, schema, required, parentName) {
|
|
|
9568
9632
|
const modelDeps = /* @__PURE__ */ new Set();
|
|
9569
9633
|
const domainType = rawSchemaToTS(schema, parentName, rawName, false, modelDeps);
|
|
9570
9634
|
const wireType = rawSchemaToTS(schema, parentName, rawName, true, modelDeps);
|
|
9635
|
+
const enumValues = Array.isArray(schema.enum) && schema.enum.length > 0 && schema.enum.every((e) => typeof e === "string") ? schema.enum : void 0;
|
|
9571
9636
|
return {
|
|
9572
9637
|
name: rawName,
|
|
9573
9638
|
description: schema.description,
|
|
@@ -9575,7 +9640,8 @@ function buildField(rawName, schema, required, parentName) {
|
|
|
9575
9640
|
domainType,
|
|
9576
9641
|
wireType,
|
|
9577
9642
|
modelDeps,
|
|
9578
|
-
hasDateTime: schemaHasDateTime(schema)
|
|
9643
|
+
hasDateTime: schemaHasDateTime(schema),
|
|
9644
|
+
enumValues
|
|
9579
9645
|
};
|
|
9580
9646
|
}
|
|
9581
9647
|
function schemaHasDateTime(schema) {
|
|
@@ -9644,13 +9710,15 @@ function planDiscriminatedModels(models, ctx) {
|
|
|
9644
9710
|
const stripped = rawName.replace(/Dto/g, "").replace(/DTO/g, "").replace(/Json$/, "");
|
|
9645
9711
|
if (stripped !== rawName && irModelDir.has(stripped)) depDirMap.set(rawName, irModelDir.get(stripped));
|
|
9646
9712
|
}
|
|
9713
|
+
for (const [irName, dir] of irModelDir) if (!depDirMap.has(irName)) depDirMap.set(irName, dir);
|
|
9647
9714
|
for (const model of models) {
|
|
9648
9715
|
const shape = detectDiscriminatedShape(model.name, rawSchemas);
|
|
9649
9716
|
if (!shape) continue;
|
|
9650
9717
|
const allDeps = /* @__PURE__ */ new Set();
|
|
9651
9718
|
for (const field of shape.baseFields) for (const d of field.modelDeps) allDeps.add(d);
|
|
9652
9719
|
for (const variant of shape.variants) for (const field of variant.fields) for (const d of field.modelDeps) allDeps.add(d);
|
|
9653
|
-
|
|
9720
|
+
const resolvable = (dep) => depDirMap.has(dep) || irModelDir.has(dep) || depDirMap.has(toPascalCase(dep)) || irModelDir.has(toPascalCase(dep));
|
|
9721
|
+
if ([...allDeps].some((dep) => !resolvable(dep))) continue;
|
|
9654
9722
|
const modelDir = resolveDir(modelToService.get(model.name));
|
|
9655
9723
|
plans.set(model.name, {
|
|
9656
9724
|
shape,
|
|
@@ -9670,6 +9738,7 @@ function generateDiscriminatedFiles(plans, ctx) {
|
|
|
9670
9738
|
}
|
|
9671
9739
|
function buildInterfaceFile(plan, _ctx) {
|
|
9672
9740
|
const { shape, modelDir } = plan;
|
|
9741
|
+
if (shape.inlineUnion) return buildInlineUnionFile(plan);
|
|
9673
9742
|
const domain = toPascalCase(shape.modelName);
|
|
9674
9743
|
const wire = wireInterfaceName(domain);
|
|
9675
9744
|
const lines = [];
|
|
@@ -9697,6 +9766,45 @@ function buildInterfaceFile(plan, _ctx) {
|
|
|
9697
9766
|
overwriteExisting: true
|
|
9698
9767
|
};
|
|
9699
9768
|
}
|
|
9769
|
+
/**
|
|
9770
|
+
* Emit a pure-`oneOf` discriminated union as a single inline type alias
|
|
9771
|
+
* (`export type X = { … } | { … }`) for both the domain and wire shapes. Used
|
|
9772
|
+
* instead of named per-variant interfaces, which read poorly for small
|
|
9773
|
+
* (often two-variant, boolean-discriminated) unions like the token response.
|
|
9774
|
+
*/
|
|
9775
|
+
function buildInlineUnionFile(plan) {
|
|
9776
|
+
const { shape, modelDir } = plan;
|
|
9777
|
+
const domain = toPascalCase(shape.modelName);
|
|
9778
|
+
const wire = wireInterfaceName(domain);
|
|
9779
|
+
const lines = [];
|
|
9780
|
+
const imports = collectImports$2(plan);
|
|
9781
|
+
if (imports.length > 0) {
|
|
9782
|
+
for (const imp of imports) lines.push(`import type { ${imp.symbols.join(", ")} } from '${imp.path}';`);
|
|
9783
|
+
lines.push("");
|
|
9784
|
+
}
|
|
9785
|
+
lines.push(...buildInlineUnionAlias(domain, shape, false));
|
|
9786
|
+
lines.push("");
|
|
9787
|
+
lines.push(...buildInlineUnionAlias(wire, shape, true));
|
|
9788
|
+
return {
|
|
9789
|
+
path: `src/${modelDir}/interfaces/${fileName$3(shape.modelName)}.interface.ts`,
|
|
9790
|
+
content: lines.join("\n") + "\n",
|
|
9791
|
+
overwriteExisting: true
|
|
9792
|
+
};
|
|
9793
|
+
}
|
|
9794
|
+
function buildInlineUnionAlias(name, shape, isWire) {
|
|
9795
|
+
const lines = [`export type ${name} =`];
|
|
9796
|
+
shape.variants.forEach((variant, idx) => {
|
|
9797
|
+
const isLast = idx === shape.variants.length - 1;
|
|
9798
|
+
const members = [`${isWire ? shape.discriminatorProperty : shape.discriminatorPropertyDomain}: ${discLiteral(variant)}`];
|
|
9799
|
+
for (const field of variant.fields) {
|
|
9800
|
+
const key = isWire ? field.name : toCamelCase(field.name);
|
|
9801
|
+
const opt = field.required ? "" : "?";
|
|
9802
|
+
members.push(`${key}${opt}: ${inlineFieldType(field, isWire)}`);
|
|
9803
|
+
}
|
|
9804
|
+
lines.push(` | { ${members.join("; ")} }${isLast ? ";" : ""}`);
|
|
9805
|
+
});
|
|
9806
|
+
return lines;
|
|
9807
|
+
}
|
|
9700
9808
|
function buildInterfaceBody(name, shape, variant, isWire) {
|
|
9701
9809
|
const lines = [];
|
|
9702
9810
|
lines.push(`export interface ${name} {`);
|
|
@@ -9708,11 +9816,32 @@ function buildInterfaceBody(name, shape, variant, isWire) {
|
|
|
9708
9816
|
}
|
|
9709
9817
|
const discKey = isWire ? shape.discriminatorProperty : shape.discriminatorPropertyDomain;
|
|
9710
9818
|
if (shape.discriminatorDescription) lines.push(` /** ${shape.discriminatorDescription} */`);
|
|
9711
|
-
lines.push(` ${discKey}:
|
|
9819
|
+
lines.push(` ${discKey}: ${discLiteral(variant)};`);
|
|
9712
9820
|
for (const field of variant.fields) pushFieldLine(lines, field, isWire);
|
|
9713
9821
|
lines.push("}");
|
|
9714
9822
|
return lines;
|
|
9715
9823
|
}
|
|
9824
|
+
/**
|
|
9825
|
+
* The discriminator value as a TS literal: quoted for strings (`'oauth'`),
|
|
9826
|
+
* bare for booleans (`true`).
|
|
9827
|
+
*/
|
|
9828
|
+
function discLiteral(variant) {
|
|
9829
|
+
return variant.discriminatorIsBoolean ? variant.discriminatorValue : `'${variant.discriminatorValue}'`;
|
|
9830
|
+
}
|
|
9831
|
+
/**
|
|
9832
|
+
* Field type for an inline-union member: a literal union for inline string
|
|
9833
|
+
* enums, otherwise the resolved domain/wire type.
|
|
9834
|
+
*
|
|
9835
|
+
* The `enumValues` branch deliberately ignores `isWire`: `enumValues` is only
|
|
9836
|
+
* populated for string enums (see `buildField`), whose domain and wire
|
|
9837
|
+
* representations are identical literal unions. Any field whose domain/wire
|
|
9838
|
+
* types actually differ (model refs, dates, non-string enums) leaves
|
|
9839
|
+
* `enumValues` undefined and falls through to the type-specific branch.
|
|
9840
|
+
*/
|
|
9841
|
+
function inlineFieldType(field, isWire) {
|
|
9842
|
+
if (field.enumValues) return field.enumValues.map((v) => `'${v}'`).join(" | ");
|
|
9843
|
+
return isWire ? field.wireType : field.domainType;
|
|
9844
|
+
}
|
|
9716
9845
|
function pushFieldLine(lines, field, isWire) {
|
|
9717
9846
|
const key = isWire ? field.name : toCamelCase(field.name);
|
|
9718
9847
|
const opt = field.required ? "" : "?";
|
|
@@ -9729,7 +9858,7 @@ function collectImports$2(plan) {
|
|
|
9729
9858
|
const domain = toPascalCase(dep);
|
|
9730
9859
|
const wire = wireInterfaceName(domain);
|
|
9731
9860
|
const symbols = wire !== domain ? [domain, wire] : [domain];
|
|
9732
|
-
const depDir = plan.depDirMap.get(dep);
|
|
9861
|
+
const depDir = plan.depDirMap.get(dep) ?? plan.depDirMap.get(domain);
|
|
9733
9862
|
const baseName = fileName$3(toSnakeFromPascal(domain));
|
|
9734
9863
|
let importPath;
|
|
9735
9864
|
if (!depDir || depDir === plan.modelDir) importPath = `./${baseName}.interface`;
|
|
@@ -9756,39 +9885,43 @@ function buildSerializerFile(plan, _ctx) {
|
|
|
9756
9885
|
const allDeps = /* @__PURE__ */ new Set();
|
|
9757
9886
|
for (const field of shape.baseFields) for (const d of field.modelDeps) allDeps.add(d);
|
|
9758
9887
|
for (const variant of shape.variants) for (const field of variant.fields) for (const d of field.modelDeps) allDeps.add(d);
|
|
9888
|
+
const deserializeOnly = shape.inlineUnion === true;
|
|
9759
9889
|
for (const dep of [...allDeps].sort()) {
|
|
9760
9890
|
const depDomain = toPascalCase(dep);
|
|
9761
9891
|
const depFile = fileName$3(toSnakeFromPascal(depDomain));
|
|
9762
|
-
|
|
9892
|
+
const helpers = deserializeOnly ? `deserialize${depDomain}` : `deserialize${depDomain}, serialize${depDomain}`;
|
|
9893
|
+
lines.push(`import { ${helpers} } from './${depFile}.serializer';`);
|
|
9763
9894
|
}
|
|
9764
9895
|
lines.push("");
|
|
9765
9896
|
lines.push(`export const deserialize${domain} = (response: ${wire}): ${domain} => {`);
|
|
9766
9897
|
lines.push(` switch (response.${shape.discriminatorProperty}) {`);
|
|
9767
9898
|
for (const variant of shape.variants) {
|
|
9768
|
-
lines.push(` case
|
|
9899
|
+
lines.push(` case ${discLiteral(variant)}:`);
|
|
9769
9900
|
lines.push(` return {`);
|
|
9770
9901
|
for (const field of shape.baseFields) lines.push(` ${assignmentLine(field, false, allDeps)},`);
|
|
9771
|
-
lines.push(` ${shape.discriminatorPropertyDomain}:
|
|
9902
|
+
lines.push(` ${shape.discriminatorPropertyDomain}: ${discLiteral(variant)},`);
|
|
9772
9903
|
for (const field of variant.fields) lines.push(` ${assignmentLine(field, false, allDeps)},`);
|
|
9773
9904
|
lines.push(` };`);
|
|
9774
9905
|
}
|
|
9775
9906
|
lines.push(` default:`);
|
|
9776
|
-
lines.push(` throw new Error(\`Unknown ${shape.discriminatorProperty}: \${(response as
|
|
9907
|
+
lines.push(` throw new Error(\`Unknown ${shape.discriminatorProperty}: \${String((response as Record<string, unknown>).${shape.discriminatorProperty})}\`);`);
|
|
9777
9908
|
lines.push(` }`);
|
|
9778
9909
|
lines.push(`};`);
|
|
9779
|
-
|
|
9780
|
-
|
|
9781
|
-
|
|
9782
|
-
|
|
9783
|
-
|
|
9784
|
-
|
|
9785
|
-
|
|
9786
|
-
|
|
9787
|
-
|
|
9788
|
-
|
|
9910
|
+
if (!deserializeOnly) {
|
|
9911
|
+
lines.push("");
|
|
9912
|
+
lines.push(`export const serialize${domain} = (model: ${domain}): ${wire} => {`);
|
|
9913
|
+
lines.push(` switch (model.${shape.discriminatorPropertyDomain}) {`);
|
|
9914
|
+
for (const variant of shape.variants) {
|
|
9915
|
+
lines.push(` case ${discLiteral(variant)}:`);
|
|
9916
|
+
lines.push(` return {`);
|
|
9917
|
+
for (const field of shape.baseFields) lines.push(` ${assignmentLine(field, true, allDeps)},`);
|
|
9918
|
+
lines.push(` ${shape.discriminatorProperty}: ${discLiteral(variant)},`);
|
|
9919
|
+
for (const field of variant.fields) lines.push(` ${assignmentLine(field, true, allDeps)},`);
|
|
9920
|
+
lines.push(` };`);
|
|
9921
|
+
}
|
|
9922
|
+
lines.push(` }`);
|
|
9923
|
+
lines.push(`};`);
|
|
9789
9924
|
}
|
|
9790
|
-
lines.push(` }`);
|
|
9791
|
-
lines.push(`};`);
|
|
9792
9925
|
return {
|
|
9793
9926
|
path: `src/${modelDir}/serializers/${fileName$3(shape.modelName)}.serializer.ts`,
|
|
9794
9927
|
content: lines.join("\n") + "\n",
|
|
@@ -29764,4 +29897,4 @@ const workosEmittersPlugin = {
|
|
|
29764
29897
|
//#endregion
|
|
29765
29898
|
export { fieldName$2 as A, servicePropertyName$2 as B, apiClassName as C, dotnetEmitter as D, propertyName as E, fieldName$3 as F, fieldName$5 as H, methodName$3 as I, trimMountedResourceFromMethod$2 as L, trimMountedResourceFromMethod$1 as M, goEmitter as N, appendAsyncSuffix as O, className$3 as P, phpEmitter as R, kotlinEmitter as S, packageSegment as T, safeParamName$1 as U, pythonEmitter as V, nodeEmitter as W, rubyEmitter as _, rustExtractor as a, resolveServiceTarget as b, pythonExtractor as c, rustEmitter as d, fieldName as f, typeName as g, resourceAccessorName as h, kotlinExtractor as i, methodName$2 as j, className$2 as k, rubyExtractor as l, moduleName as m, elixirExtractor as n, goExtractor as o, methodName as p, dotnetExtractor as r, phpExtractor as s, workosEmittersPlugin as t, nodeExtractor as u, buildExportedClassNameSet as v, methodName$1 as w, safeParamName as x, fieldName$1 as y, fieldName$4 as z };
|
|
29766
29899
|
|
|
29767
|
-
//# sourceMappingURL=plugin-
|
|
29900
|
+
//# sourceMappingURL=plugin-CtU_wbid.mjs.map
|