@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.
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.17.0"
2
+ ".": "0.18.1"
3
3
  }
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-BLnR-FMi.mjs";
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: methodOptionsName$1(method, resolveResourceClassName$3(service, ctx)),
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 (!isValidTypeIdentifier(optionType)) continue;
5941
- if (importedTypeNames.has(optionType)) continue;
5942
- importedTypeNames.add(optionType);
5943
- const sourceFile = baselineTypeSourceFile(ctx, optionType);
5944
- const relPath = sourceFile ? relativeImport(resourcePath, sourceFile) : `./interfaces/${fileName$3(optionType)}.interface`;
5945
- lines.push(`import type { ${optionType} } from '${relPath}';`);
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 && !isNodeOwnedService(ctx, dir)) {
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
- stems.add(entry.replace(/\.serializer\.ts$/, ""));
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?.allOf) return null;
9425
+ if (!schema) return null;
9378
9426
  let baseObject = null;
9379
9427
  let oneOfVariants = null;
9380
- for (const member of schema.allOf) {
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 = readConstString(fv.alwaysProperties.get(discProp));
9397
- if (!discValue) return null;
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 = readConstString(v.alwaysProperties.get(propName));
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
- function readConstString(schema) {
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 && typeof schema.enum[0] === "string") return schema.enum[0];
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
- if ([...allDeps].some((dep) => !depDirMap.has(dep) && !irModelDir.has(dep))) continue;
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}: '${variant.discriminatorValue}';`);
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
- lines.push(`import { deserialize${depDomain}, serialize${depDomain} } from './${depFile}.serializer';`);
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 '${variant.discriminatorValue}':`);
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}: '${variant.discriminatorValue}',`);
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 { ${shape.discriminatorProperty}: string }).${shape.discriminatorProperty}}\`);`);
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
- lines.push("");
9780
- lines.push(`export const serialize${domain} = (model: ${domain}): ${wire} => {`);
9781
- lines.push(` switch (model.${shape.discriminatorPropertyDomain}) {`);
9782
- for (const variant of shape.variants) {
9783
- lines.push(` case '${variant.discriminatorValue}':`);
9784
- lines.push(` return {`);
9785
- for (const field of shape.baseFields) lines.push(` ${assignmentLine(field, true, allDeps)},`);
9786
- lines.push(` ${shape.discriminatorProperty}: '${variant.discriminatorValue}',`);
9787
- for (const field of variant.fields) lines.push(` ${assignmentLine(field, true, allDeps)},`);
9788
- lines.push(` };`);
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-BLnR-FMi.mjs.map
29900
+ //# sourceMappingURL=plugin-CtU_wbid.mjs.map