@workos/oagen-emitters 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2692,6 +2692,7 @@ function enrichModelsFromSpec(models) {
2692
2692
  _lastSyntheticEnums = collector.enums.map((e) => ({
2693
2693
  name: e.name,
2694
2694
  values: e.values.map((v) => ({
2695
+ name: toUpperSnakeCase(String(v.value)),
2695
2696
  value: v.value,
2696
2697
  description: v.description
2697
2698
  }))
@@ -2985,7 +2986,7 @@ function mapTypeRef$7(ref, opts) {
2985
2986
  const genericDefaults = opts?.genericDefaults;
2986
2987
  return mapTypeRef(ref, {
2987
2988
  primitive: mapPrimitive$6,
2988
- array: (_r, items) => `${parenthesizeUnion(items)}[]`,
2989
+ array: (_r, items) => `${parenthesizeUnion$1(items)}[]`,
2989
2990
  model: (r) => resolveDomainName(r.name) + (genericDefaults?.get(r.name) ?? ""),
2990
2991
  enum: (r) => inlineEnumUnions.get(r.name) ?? r.name,
2991
2992
  union: (r, variants) => joinUnionVariants$5(r, variants),
@@ -3002,7 +3003,7 @@ function mapWireTypeRef(ref, opts) {
3002
3003
  const genericDefaults = opts?.genericDefaults;
3003
3004
  return mapTypeRef(ref, {
3004
3005
  primitive: mapWirePrimitive,
3005
- array: (_r, items) => `${parenthesizeUnion(items)}[]`,
3006
+ array: (_r, items) => `${parenthesizeUnion$1(items)}[]`,
3006
3007
  model: (r) => wireInterfaceName(resolveDomainName(r.name)) + (genericDefaults?.get(r.name) ?? ""),
3007
3008
  enum: (r) => inlineEnumUnions.get(r.name) ?? r.name,
3008
3009
  union: (r, variants) => joinUnionVariants$5(r, variants),
@@ -3039,7 +3040,7 @@ function joinUnionVariants$5(ref, variants) {
3039
3040
  if (unique.length === 1) return unique[0];
3040
3041
  return unique.join(" | ");
3041
3042
  }
3042
- function parenthesizeUnion(type) {
3043
+ function parenthesizeUnion$1(type) {
3043
3044
  return type.includes(" | ") || type.includes(" & ") ? `(${type})` : type;
3044
3045
  }
3045
3046
  //#endregion
@@ -4627,7 +4628,7 @@ function splitCamelWords(name) {
4627
4628
  return parts;
4628
4629
  }
4629
4630
  /** Naive singularize: strip trailing 's' unless it ends in 'ss'. */
4630
- function singularize$3(word) {
4631
+ function singularize$4(word) {
4631
4632
  return word.endsWith("s") && !word.endsWith("ss") ? word.slice(0, -1) : word;
4632
4633
  }
4633
4634
  /**
@@ -4644,11 +4645,11 @@ function deduplicateMethodNames(plans, _ctx) {
4644
4645
  if (count <= 1) continue;
4645
4646
  const dupes = plans.filter((p) => p.method === name);
4646
4647
  if (new Set(dupes.map((d) => d.op.path.replace(/\/\{[^}]+\}$/, ""))).size <= 1) continue;
4647
- const nameWords = new Set(splitCamelWords(name).map(singularize$3));
4648
+ const nameWords = new Set(splitCamelWords(name).map(singularize$4));
4648
4649
  const scored = dupes.map((d) => {
4649
4650
  return {
4650
4651
  plan: d,
4651
- score: d.op.path.split("/").filter((s) => s && !s.startsWith("{")).flatMap((s) => s.split("_")).map(singularize$3).filter((w) => nameWords.has(w)).length
4652
+ score: d.op.path.split("/").filter((s) => s && !s.startsWith("{")).flatMap((s) => s.split("_")).map(singularize$4).filter((w) => nameWords.has(w)).length
4652
4653
  };
4653
4654
  });
4654
4655
  scored.sort((a, b) => b.score - a.score);
@@ -5942,6 +5943,7 @@ function isSupportedFieldType(ref, ownerModelName, shared, ctx) {
5942
5943
  const resolvedName = resolveInterfaceName(ref.name, ctx);
5943
5944
  if (ctx.apiSurface?.interfaces?.[resolvedName] || ctx.apiSurface?.typeAliases?.[resolvedName]) return true;
5944
5945
  if (isAdoptedModelName(ref.name)) return true;
5946
+ if (shared.modelToService.has(ref.name)) return true;
5945
5947
  return liveSurfaceHasManagedFile(`src/${shared.resolveDir(shared.modelToService.get(ref.name))}/interfaces/${fileName$3(ref.name)}.interface.ts`);
5946
5948
  }
5947
5949
  case "enum": {
@@ -5988,12 +5990,14 @@ function generateModels$7(models, ctx, shared) {
5988
5990
  if ((ctx.apiSurface?.interfaces?.[depName])?.sourceFile === parentPath) forceGenerate.add(dep);
5989
5991
  }
5990
5992
  }
5993
+ const discriminatedSkip = ctx._discriminatedModelNames;
5991
5994
  for (const originalModel of models) {
5992
5995
  const model = projectedByName.get(originalModel.name) ?? originalModel;
5993
5996
  if (!reachableModels.has(model.name)) continue;
5994
5997
  if (interfaceEligibleModels && !interfaceEligibleModels.has(model.name)) continue;
5995
5998
  if (isListMetadataModel(model)) continue;
5996
5999
  if (isListWrapperModel(model)) continue;
6000
+ if (discriminatedSkip?.has(model.name)) continue;
5997
6001
  const service = modelToService.get(model.name);
5998
6002
  const isOwnedModel = isNodeOwnedService(ctx, service);
5999
6003
  if (!isOwnedModel && !modelHasNewFields(model, ctx) && !forceGenerate.has(model.name)) continue;
@@ -6325,6 +6329,7 @@ function generateSerializers(models, ctx, shared) {
6325
6329
  if ((ctx.apiSurface?.interfaces?.[depName])?.sourceFile === parentPath) forceGenerateSerializer.add(dep);
6326
6330
  }
6327
6331
  }
6332
+ const discriminatedSerializerSkip = ctx._discriminatedModelNames;
6328
6333
  const eligibleModels = [];
6329
6334
  for (const originalModel of models) {
6330
6335
  const model = projectedByName.get(originalModel.name) ?? originalModel;
@@ -6332,6 +6337,7 @@ function generateSerializers(models, ctx, shared) {
6332
6337
  if (serializerEligibleModels && !serializerEligibleModels.has(model.name)) continue;
6333
6338
  if (isListMetadataModel(model)) continue;
6334
6339
  if (isListWrapperModel(model)) continue;
6340
+ if (discriminatedSerializerSkip?.has(model.name)) continue;
6335
6341
  if (!isNodeOwnedService(ctx, modelToService.get(model.name)) && !modelHasNewFields(model, ctx) && !forceGenerateSerializer.has(model.name)) continue;
6336
6342
  eligibleModels.push(model);
6337
6343
  }
@@ -7643,6 +7649,445 @@ function generateSerializerTests(spec, ctx) {
7643
7649
  return files;
7644
7650
  }
7645
7651
  //#endregion
7652
+ //#region src/node/discriminated-models.ts
7653
+ function detectDiscriminatedShape(modelName, rawSchemas) {
7654
+ const schema = rawSchemas[modelName];
7655
+ if (!schema?.allOf) return null;
7656
+ let baseObject = null;
7657
+ let oneOfVariants = null;
7658
+ for (const member of schema.allOf) {
7659
+ const resolved = resolveRef(member, rawSchemas);
7660
+ if (resolved.oneOf) {
7661
+ if (oneOfVariants) return null;
7662
+ oneOfVariants = resolved.oneOf;
7663
+ } else if (resolved.properties) baseObject = mergeBase(baseObject, resolved);
7664
+ else if (resolved.allOf) {
7665
+ const nestedBase = flattenObjectAllOf(resolved, rawSchemas);
7666
+ baseObject = mergeBase(baseObject, nestedBase);
7667
+ }
7668
+ }
7669
+ if (!oneOfVariants || oneOfVariants.length < 2) return null;
7670
+ const flattenedVariants = oneOfVariants.map((v) => flattenVariant(v, rawSchemas));
7671
+ const discProp = findSharedDiscriminator(flattenedVariants);
7672
+ if (!discProp) return null;
7673
+ const variants = flattenedVariants.map((fv) => {
7674
+ const discValue = readConstString(fv.alwaysProperties.get(discProp));
7675
+ if (!discValue) return null;
7676
+ return {
7677
+ nameSuffix: variantNameSuffix(discValue),
7678
+ discriminatorValue: discValue,
7679
+ fields: variantFields(fv, discProp, modelName, rawSchemas)
7680
+ };
7681
+ }).filter((v) => v !== null);
7682
+ if (variants.length !== flattenedVariants.length) return null;
7683
+ return {
7684
+ modelName,
7685
+ baseFields: baseObject ? collectObjectFields(baseObject, modelName, rawSchemas) : [],
7686
+ discriminatorProperty: discProp,
7687
+ discriminatorPropertyDomain: toCamelCase(discProp),
7688
+ variants
7689
+ };
7690
+ }
7691
+ function mergeBase(prev, next) {
7692
+ if (!prev) return next;
7693
+ return {
7694
+ type: "object",
7695
+ properties: {
7696
+ ...prev.properties ?? {},
7697
+ ...next.properties ?? {}
7698
+ },
7699
+ required: [...new Set([...prev.required ?? [], ...next.required ?? []])]
7700
+ };
7701
+ }
7702
+ function flattenObjectAllOf(schema, rawSchemas) {
7703
+ let merged = {
7704
+ type: "object",
7705
+ properties: {},
7706
+ required: []
7707
+ };
7708
+ for (const sub of schema.allOf ?? []) {
7709
+ const resolved = resolveRef(sub, rawSchemas);
7710
+ if (resolved.properties) merged = mergeBase(merged, resolved);
7711
+ else if (resolved.allOf) merged = mergeBase(merged, flattenObjectAllOf(resolved, rawSchemas));
7712
+ }
7713
+ return merged;
7714
+ }
7715
+ function flattenVariant(variant, rawSchemas) {
7716
+ const resolved = resolveRef(variant, rawSchemas);
7717
+ if (resolved.properties && !resolved.allOf && !resolved.oneOf) {
7718
+ const props = /* @__PURE__ */ new Map();
7719
+ for (const [k, v] of Object.entries(resolved.properties)) props.set(k, v);
7720
+ return {
7721
+ alwaysProperties: props,
7722
+ optionalProperties: /* @__PURE__ */ new Map(),
7723
+ required: new Set(resolved.required ?? [])
7724
+ };
7725
+ }
7726
+ if (resolved.allOf) {
7727
+ let agg = {
7728
+ alwaysProperties: /* @__PURE__ */ new Map(),
7729
+ optionalProperties: /* @__PURE__ */ new Map(),
7730
+ required: /* @__PURE__ */ new Set()
7731
+ };
7732
+ let initialized = false;
7733
+ for (const member of resolved.allOf) {
7734
+ const memberView = flattenVariant(member, rawSchemas);
7735
+ if (!initialized) {
7736
+ agg = {
7737
+ alwaysProperties: new Map(memberView.alwaysProperties),
7738
+ optionalProperties: new Map(memberView.optionalProperties),
7739
+ required: new Set(memberView.required)
7740
+ };
7741
+ initialized = true;
7742
+ continue;
7743
+ }
7744
+ for (const [k, v] of memberView.alwaysProperties) if (!agg.alwaysProperties.has(k) && !agg.optionalProperties.has(k)) agg.alwaysProperties.set(k, v);
7745
+ for (const [k, v] of memberView.optionalProperties) if (!agg.alwaysProperties.has(k) && !agg.optionalProperties.has(k)) agg.optionalProperties.set(k, v);
7746
+ for (const r of memberView.required) agg.required.add(r);
7747
+ }
7748
+ return agg;
7749
+ }
7750
+ if (resolved.oneOf) {
7751
+ const memberViews = resolved.oneOf.map((m) => flattenVariant(m, rawSchemas));
7752
+ const allKeys = /* @__PURE__ */ new Set();
7753
+ for (const view of memberViews) {
7754
+ for (const k of view.alwaysProperties.keys()) allKeys.add(k);
7755
+ for (const k of view.optionalProperties.keys()) allKeys.add(k);
7756
+ }
7757
+ const always = /* @__PURE__ */ new Map();
7758
+ const optional = /* @__PURE__ */ new Map();
7759
+ const requiredEverywhere = /* @__PURE__ */ new Set();
7760
+ for (const key of allKeys) {
7761
+ const inAll = memberViews.every((v) => v.alwaysProperties.has(key) || v.optionalProperties.has(key));
7762
+ const merged = mergeFieldSchemas(memberViews.map((v) => v.alwaysProperties.get(key) ?? v.optionalProperties.get(key)).filter((s) => Boolean(s)));
7763
+ if (inAll) always.set(key, merged);
7764
+ else optional.set(key, merged);
7765
+ if (inAll && memberViews.every((v) => v.required.has(key))) requiredEverywhere.add(key);
7766
+ }
7767
+ return {
7768
+ alwaysProperties: always,
7769
+ optionalProperties: optional,
7770
+ required: requiredEverywhere
7771
+ };
7772
+ }
7773
+ return {
7774
+ alwaysProperties: /* @__PURE__ */ new Map(),
7775
+ optionalProperties: /* @__PURE__ */ new Map(),
7776
+ required: /* @__PURE__ */ new Set()
7777
+ };
7778
+ }
7779
+ function mergeFieldSchemas(schemas) {
7780
+ if (schemas.length === 0) return {};
7781
+ if (schemas.length === 1) return schemas[0];
7782
+ if (schemas.every((s) => s.type === "boolean" && typeof s.const === "boolean")) return {
7783
+ type: "boolean",
7784
+ description: schemas[0].description
7785
+ };
7786
+ if (schemas.every((s) => s.type === "string" && typeof s.const === "string")) {
7787
+ const values = schemas.map((s) => s.const);
7788
+ if (new Set(values).size === 1) return schemas[0];
7789
+ return {
7790
+ type: "string",
7791
+ description: schemas[0].description
7792
+ };
7793
+ }
7794
+ return schemas[0];
7795
+ }
7796
+ function findSharedDiscriminator(variants) {
7797
+ if (variants.length < 2) return null;
7798
+ const firstAlways = variants[0].alwaysProperties;
7799
+ for (const propName of firstAlways.keys()) {
7800
+ let allConst = true;
7801
+ const values = [];
7802
+ for (const v of variants) {
7803
+ const val = readConstString(v.alwaysProperties.get(propName));
7804
+ if (val === null) {
7805
+ allConst = false;
7806
+ break;
7807
+ }
7808
+ values.push(val);
7809
+ }
7810
+ if (allConst && new Set(values).size === values.length) return propName;
7811
+ }
7812
+ return null;
7813
+ }
7814
+ function readConstString(schema) {
7815
+ if (!schema) return null;
7816
+ if (typeof schema.const === "string") return schema.const;
7817
+ if (Array.isArray(schema.enum) && schema.enum.length === 1 && typeof schema.enum[0] === "string") return schema.enum[0];
7818
+ return null;
7819
+ }
7820
+ function variantNameSuffix(constValue) {
7821
+ return toPascalCase(constValue);
7822
+ }
7823
+ function collectObjectFields(schema, parentName, rawSchemas) {
7824
+ const props = schema.properties ?? {};
7825
+ const required = new Set(schema.required ?? []);
7826
+ const fields = [];
7827
+ for (const [name, propSchema] of Object.entries(props)) fields.push(buildField(name, propSchema, required.has(name), parentName, rawSchemas));
7828
+ return fields;
7829
+ }
7830
+ function variantFields(fv, discriminatorProperty, parentName, rawSchemas) {
7831
+ const fields = [];
7832
+ for (const [name, propSchema] of fv.alwaysProperties) {
7833
+ if (name === discriminatorProperty) continue;
7834
+ fields.push(buildField(name, propSchema, fv.required.has(name), parentName, rawSchemas));
7835
+ }
7836
+ for (const [name, propSchema] of fv.optionalProperties) {
7837
+ if (name === discriminatorProperty) continue;
7838
+ fields.push(buildField(name, propSchema, false, parentName, rawSchemas));
7839
+ }
7840
+ return fields;
7841
+ }
7842
+ function buildField(rawName, schema, required, parentName, rawSchemas) {
7843
+ const modelDeps = /* @__PURE__ */ new Set();
7844
+ const domainType = rawSchemaToTS(schema, parentName, rawName, false, modelDeps, rawSchemas);
7845
+ const wireType = rawSchemaToTS(schema, parentName, rawName, true, modelDeps, rawSchemas);
7846
+ return {
7847
+ name: rawName,
7848
+ description: schema.description,
7849
+ required,
7850
+ domainType,
7851
+ wireType,
7852
+ modelDeps,
7853
+ hasDateTime: schemaHasDateTime(schema)
7854
+ };
7855
+ }
7856
+ function schemaHasDateTime(schema) {
7857
+ if (schema.format === "date-time" && typeOf(schema) === "string") return true;
7858
+ if (schema.items && schemaHasDateTime(schema.items)) return true;
7859
+ return false;
7860
+ }
7861
+ function typeOf(schema) {
7862
+ if (Array.isArray(schema.type)) return schema.type.find((t) => t !== "null");
7863
+ return schema.type;
7864
+ }
7865
+ function isNullable(schema) {
7866
+ return Array.isArray(schema.type) && schema.type.includes("null");
7867
+ }
7868
+ function rawSchemaToTS(schema, parentName, fieldName, isWire, modelDeps, rawSchemas) {
7869
+ if (schema.$ref) {
7870
+ const refName = schema.$ref.split("/").pop();
7871
+ modelDeps.add(refName);
7872
+ const domain = toPascalCase(refName);
7873
+ return isWire ? wireInterfaceName(domain) : domain;
7874
+ }
7875
+ if (typeof schema.const === "string") return `'${schema.const}'`;
7876
+ if (typeof schema.const === "boolean") return String(schema.const);
7877
+ const baseType = typeOf(schema);
7878
+ const nullable = isNullable(schema);
7879
+ let core;
7880
+ if (baseType === "string") core = !isWire && schema.format === "date-time" ? "Date" : "string";
7881
+ else if (baseType === "integer" || baseType === "number") core = "number";
7882
+ else if (baseType === "boolean") core = "boolean";
7883
+ else if (baseType === "array" && schema.items) core = `${parenthesizeUnion(rawSchemaToTS(schema.items, parentName, singularize$3(fieldName), isWire, modelDeps, rawSchemas))}[]`;
7884
+ else if (baseType === "object" && schema.properties) {
7885
+ const synthName = `${parentName}_${singularize$3(fieldName)}`;
7886
+ modelDeps.add(synthName);
7887
+ const domain = toPascalCase(synthName);
7888
+ return isWire ? wireInterfaceName(domain) : domain;
7889
+ } else core = "unknown";
7890
+ return nullable ? `${core} | null` : core;
7891
+ }
7892
+ function parenthesizeUnion(t) {
7893
+ return /\s\|\s/.test(t) ? `(${t})` : t;
7894
+ }
7895
+ function singularize$3(name) {
7896
+ if (name.endsWith("ies") && name.length > 3) return `${name.slice(0, -3)}y`;
7897
+ if (name.endsWith("s") && !name.endsWith("ss")) return name.slice(0, -1);
7898
+ return name;
7899
+ }
7900
+ function resolveRef(schema, rawSchemas) {
7901
+ if (!schema.$ref) return schema;
7902
+ const segments = schema.$ref.split("/");
7903
+ return rawSchemas[segments[segments.length - 1]] ?? schema;
7904
+ }
7905
+ function planDiscriminatedModels(models, ctx) {
7906
+ const plans = /* @__PURE__ */ new Map();
7907
+ const spec = loadRawSpec();
7908
+ if (!spec?.components?.schemas) return plans;
7909
+ const rawSchemas = spec.components.schemas;
7910
+ const { modelToService, resolveDir } = createServiceDirResolver(models, ctx.spec.services, ctx);
7911
+ for (const model of models) {
7912
+ const shape = detectDiscriminatedShape(model.name, rawSchemas);
7913
+ if (!shape) continue;
7914
+ const modelDir = resolveDir(modelToService.get(model.name));
7915
+ plans.set(model.name, {
7916
+ shape,
7917
+ modelDir
7918
+ });
7919
+ }
7920
+ return plans;
7921
+ }
7922
+ function generateDiscriminatedFiles(plans, ctx) {
7923
+ const files = [];
7924
+ for (const plan of plans.values()) {
7925
+ files.push(buildInterfaceFile(plan, ctx));
7926
+ files.push(buildSerializerFile(plan, ctx));
7927
+ }
7928
+ return files;
7929
+ }
7930
+ function buildInterfaceFile(plan, _ctx) {
7931
+ const { shape, modelDir } = plan;
7932
+ const domain = toPascalCase(shape.modelName);
7933
+ const wire = wireInterfaceName(domain);
7934
+ const lines = [];
7935
+ const imports = collectImports$2(plan);
7936
+ if (imports.length > 0) {
7937
+ for (const imp of imports) lines.push(`import type { ${imp.symbols.join(", ")} } from '${imp.path}';`);
7938
+ lines.push("");
7939
+ }
7940
+ for (const variant of shape.variants) {
7941
+ const variantDomain = `${domain}${variant.nameSuffix}`;
7942
+ const variantWire = `${variantDomain}Response`;
7943
+ lines.push(...buildInterfaceBody(variantDomain, shape, variant, false));
7944
+ lines.push("");
7945
+ lines.push(...buildInterfaceBody(variantWire, shape, variant, true));
7946
+ lines.push("");
7947
+ }
7948
+ const variantNames = shape.variants.map((v) => `${domain}${v.nameSuffix}`);
7949
+ lines.push(`export type ${domain} = ${variantNames.join(" | ")};`);
7950
+ lines.push("");
7951
+ const wireVariantNames = shape.variants.map((v) => `${domain}${v.nameSuffix}Response`);
7952
+ lines.push(`export type ${wire} = ${wireVariantNames.join(" | ")};`);
7953
+ return {
7954
+ path: `src/${modelDir}/interfaces/${fileName$3(shape.modelName)}.interface.ts`,
7955
+ content: lines.join("\n") + "\n",
7956
+ overwriteExisting: true
7957
+ };
7958
+ }
7959
+ function buildInterfaceBody(name, shape, variant, isWire) {
7960
+ const lines = [];
7961
+ lines.push(`export interface ${name} {`);
7962
+ for (const field of shape.baseFields) pushFieldLine(lines, field, isWire);
7963
+ const discKey = isWire ? shape.discriminatorProperty : shape.discriminatorPropertyDomain;
7964
+ lines.push(` ${discKey}: '${variant.discriminatorValue}';`);
7965
+ for (const field of variant.fields) pushFieldLine(lines, field, isWire);
7966
+ lines.push("}");
7967
+ return lines;
7968
+ }
7969
+ function pushFieldLine(lines, field, isWire) {
7970
+ const key = isWire ? field.name : toCamelCase(field.name);
7971
+ const opt = field.required ? "" : "?";
7972
+ const type = isWire ? field.wireType : field.domainType;
7973
+ if (field.description) lines.push(` /** ${field.description} */`);
7974
+ lines.push(` ${key}${opt}: ${type};`);
7975
+ }
7976
+ function collectImports$2(plan) {
7977
+ const deps = /* @__PURE__ */ new Set();
7978
+ for (const field of plan.shape.baseFields) for (const d of field.modelDeps) deps.add(d);
7979
+ for (const variant of plan.shape.variants) for (const field of variant.fields) for (const d of field.modelDeps) deps.add(d);
7980
+ const symbols = [];
7981
+ for (const dep of [...deps].sort()) {
7982
+ const domain = toPascalCase(dep);
7983
+ symbols.push(domain);
7984
+ const wire = wireInterfaceName(domain);
7985
+ if (wire !== domain) symbols.push(wire);
7986
+ }
7987
+ if (symbols.length === 0) return [];
7988
+ return symbols.map((sym) => {
7989
+ return {
7990
+ path: `./${fileName$3(toSnakeFromPascal(sym.replace(/Response$/, "")))}.interface`,
7991
+ symbols: [sym]
7992
+ };
7993
+ }).reduce((acc, cur) => {
7994
+ const existing = acc.find((a) => a.path === cur.path);
7995
+ if (existing) existing.symbols.push(...cur.symbols);
7996
+ else acc.push(cur);
7997
+ return acc;
7998
+ }, []);
7999
+ }
8000
+ function toSnakeFromPascal(s) {
8001
+ return s.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").toLowerCase();
8002
+ }
8003
+ function buildSerializerFile(plan, _ctx) {
8004
+ const { shape, modelDir } = plan;
8005
+ const domain = toPascalCase(shape.modelName);
8006
+ const wire = wireInterfaceName(domain);
8007
+ const lines = [];
8008
+ const interfaceImportPath = `../interfaces/${fileName$3(shape.modelName)}.interface`;
8009
+ lines.push(`import type { ${domain}, ${wire} } from '${interfaceImportPath}';`);
8010
+ const allDeps = /* @__PURE__ */ new Set();
8011
+ for (const field of shape.baseFields) for (const d of field.modelDeps) allDeps.add(d);
8012
+ for (const variant of shape.variants) for (const field of variant.fields) for (const d of field.modelDeps) allDeps.add(d);
8013
+ for (const dep of [...allDeps].sort()) {
8014
+ const depDomain = toPascalCase(dep);
8015
+ const depFile = fileName$3(toSnakeFromPascal(depDomain));
8016
+ lines.push(`import { deserialize${depDomain}, serialize${depDomain} } from './${depFile}.serializer';`);
8017
+ }
8018
+ lines.push("");
8019
+ lines.push(`export const deserialize${domain} = (response: ${wire}): ${domain} => {`);
8020
+ lines.push(` switch (response.${shape.discriminatorProperty}) {`);
8021
+ for (const variant of shape.variants) {
8022
+ lines.push(` case '${variant.discriminatorValue}':`);
8023
+ lines.push(` return {`);
8024
+ for (const field of shape.baseFields) lines.push(` ${assignmentLine(field, false, allDeps)},`);
8025
+ lines.push(` ${shape.discriminatorPropertyDomain}: '${variant.discriminatorValue}',`);
8026
+ for (const field of variant.fields) lines.push(` ${assignmentLine(field, false, allDeps)},`);
8027
+ lines.push(` };`);
8028
+ }
8029
+ lines.push(` default:`);
8030
+ lines.push(` throw new Error(\`Unknown ${shape.discriminatorProperty}: \${(response as { ${shape.discriminatorProperty}: string }).${shape.discriminatorProperty}}\`);`);
8031
+ lines.push(` }`);
8032
+ lines.push(`};`);
8033
+ lines.push("");
8034
+ lines.push(`export const serialize${domain} = (model: ${domain}): ${wire} => {`);
8035
+ lines.push(` switch (model.${shape.discriminatorPropertyDomain}) {`);
8036
+ for (const variant of shape.variants) {
8037
+ lines.push(` case '${variant.discriminatorValue}':`);
8038
+ lines.push(` return {`);
8039
+ for (const field of shape.baseFields) lines.push(` ${assignmentLine(field, true, allDeps)},`);
8040
+ lines.push(` ${shape.discriminatorProperty}: '${variant.discriminatorValue}',`);
8041
+ for (const field of variant.fields) lines.push(` ${assignmentLine(field, true, allDeps)},`);
8042
+ lines.push(` };`);
8043
+ }
8044
+ lines.push(` }`);
8045
+ lines.push(`};`);
8046
+ return {
8047
+ path: `src/${modelDir}/serializers/${fileName$3(shape.modelName)}.serializer.ts`,
8048
+ content: lines.join("\n") + "\n",
8049
+ overwriteExisting: true
8050
+ };
8051
+ }
8052
+ function assignmentLine(field, serialize, _allDeps) {
8053
+ const camel = toCamelCase(field.name);
8054
+ const snake = field.name;
8055
+ const lhs = serialize ? snake : camel;
8056
+ const rhsKey = serialize ? camel : snake;
8057
+ const source = serialize ? `model.${rhsKey}` : `response.${rhsKey}`;
8058
+ if (field.hasDateTime) {
8059
+ if (serialize) {
8060
+ if (field.required) return `${lhs}: ${source}.toISOString()`;
8061
+ return `${lhs}: ${source} != null ? ${source}.toISOString() : undefined`;
8062
+ }
8063
+ if (field.required) return `${lhs}: new Date(${source})`;
8064
+ return `${lhs}: ${source} != null ? new Date(${source}) : undefined`;
8065
+ }
8066
+ const arrayDep = arrayItemModelDep(field);
8067
+ if (arrayDep) {
8068
+ const fn = serialize ? `serialize${arrayDep}` : `deserialize${arrayDep}`;
8069
+ if (field.required) return `${lhs}: ${source}.map(${fn})`;
8070
+ return `${lhs}: ${source} != null ? ${source}.map(${fn}) : undefined`;
8071
+ }
8072
+ const scalarDep = scalarModelDepName(field);
8073
+ if (scalarDep) {
8074
+ const fn = serialize ? `serialize${scalarDep}` : `deserialize${scalarDep}`;
8075
+ if (field.required) return `${lhs}: ${fn}(${source})`;
8076
+ return `${lhs}: ${source} != null ? ${fn}(${source}) : undefined`;
8077
+ }
8078
+ return `${lhs}: ${source}`;
8079
+ }
8080
+ function arrayItemModelDep(field) {
8081
+ const m = field.domainType.match(/^([A-Z]\w*)\[\]$/);
8082
+ if (m && field.modelDeps.size > 0) return m[1];
8083
+ return null;
8084
+ }
8085
+ function scalarModelDepName(field) {
8086
+ const stripped = field.domainType.replace(/\s*\|\s*null$/, "");
8087
+ if (/^[A-Z]\w*$/.test(stripped) && field.modelDeps.size === 1) return toPascalCase([...field.modelDeps][0]);
8088
+ return null;
8089
+ }
8090
+ //#endregion
7646
8091
  //#region src/node/node-overrides.ts
7647
8092
  const OPERATION_OVERRIDES = {
7648
8093
  "POST /organizations/{organizationId}/groups": { methodName: "create_group" },
@@ -7662,31 +8107,71 @@ const contextCache = /* @__PURE__ */ new WeakMap();
7662
8107
  function operationKey(resolved) {
7663
8108
  return `${resolved.operation.httpMethod.toUpperCase()} ${resolved.operation.path}`;
7664
8109
  }
8110
+ /**
8111
+ * Apply oneOf / allOf+oneOf enrichment (flattening variant fields onto the
8112
+ * parent model, plus synthetic models/enums for inline shapes) so the rest
8113
+ * of the Node emitter sees a richer `spec.models`.
8114
+ *
8115
+ * Without this, `ConnectApplication` (and any other `allOf [base, oneOf [...]]`
8116
+ * schema whose first variant is itself wrapped in `allOf`) loses every
8117
+ * non-M2M field — the IR parser's discriminator detection silently skips
8118
+ * variants whose properties live behind another `allOf`. Mirrors what the
8119
+ * Go / Kotlin / .NET emitters already do.
8120
+ *
8121
+ * Discriminated bases produced by `enrichModelsFromSpec` get their original
8122
+ * fields restored — Node emits flat interfaces today, not TS sum types, so
8123
+ * an empty base would otherwise drop the common fields.
8124
+ */
8125
+ function enrichSpecModels(models) {
8126
+ const enriched = enrichModelsFromSpec(models);
8127
+ const originalByName = new Map(models.map((m) => [m.name, m]));
8128
+ return enriched.map((m) => {
8129
+ if (m.discriminator && m.fields.length === 0) {
8130
+ const original = originalByName.get(m.name);
8131
+ if (original && original.fields.length > 0) return {
8132
+ ...m,
8133
+ fields: original.fields
8134
+ };
8135
+ }
8136
+ return m;
8137
+ });
8138
+ }
7665
8139
  function withNodeOperationOverrides(ctx) {
7666
8140
  const cached = contextCache.get(ctx);
7667
8141
  if (cached) return cached;
8142
+ const enrichedModels = enrichSpecModels(ctx.spec.models);
8143
+ const specChanged = enrichedModels.length !== ctx.spec.models.length || enrichedModels.some((m, i) => m !== ctx.spec.models[i]);
8144
+ const enrichedSpec = specChanged ? {
8145
+ ...ctx.spec,
8146
+ models: enrichedModels
8147
+ } : ctx.spec;
7668
8148
  const resolvedOperations = ctx.resolvedOperations;
7669
8149
  if (!resolvedOperations?.length) {
7670
- contextCache.set(ctx, ctx);
7671
- return ctx;
8150
+ const next = specChanged ? {
8151
+ ...ctx,
8152
+ spec: enrichedSpec
8153
+ } : ctx;
8154
+ contextCache.set(ctx, next);
8155
+ return next;
7672
8156
  }
7673
- let changed = false;
8157
+ let opsChanged = false;
7674
8158
  const nextResolved = resolvedOperations.map((resolved) => {
7675
8159
  const override = OPERATION_OVERRIDES[operationKey(resolved)];
7676
8160
  if (!override) return resolved;
7677
8161
  const methodName = override.methodName ?? resolved.methodName;
7678
8162
  const mountOn = override.mountOn ?? resolved.mountOn;
7679
8163
  if (methodName === resolved.methodName && mountOn === resolved.mountOn) return resolved;
7680
- changed = true;
8164
+ opsChanged = true;
7681
8165
  return {
7682
8166
  ...resolved,
7683
8167
  methodName,
7684
8168
  mountOn
7685
8169
  };
7686
8170
  });
7687
- const next = changed ? {
8171
+ const next = opsChanged || specChanged ? {
7688
8172
  ...ctx,
7689
- resolvedOperations: nextResolved
8173
+ ...opsChanged ? { resolvedOperations: nextResolved } : {},
8174
+ ...specChanged ? { spec: enrichedSpec } : {}
7690
8175
  } : ctx;
7691
8176
  contextCache.set(ctx, next);
7692
8177
  return next;
@@ -7897,17 +8382,51 @@ function applyLiveSurface(files, ctx, surface) {
7897
8382
  }
7898
8383
  return out;
7899
8384
  }
8385
+ /**
8386
+ * Flatten oneOf / allOf+oneOf variant fields from the raw spec onto each
8387
+ * model. `enrichModelsFromSpec` produces (a) extra optional fields on models
8388
+ * whose schema is `allOf [base, oneOf [...]]`, and (b) synthetic models /
8389
+ * enums for inline objects encountered inside variants (e.g. the inline
8390
+ * `redirect_uris` item shape on `ConnectApplication`).
8391
+ *
8392
+ * Node, like Go / Kotlin / .NET, emits flat interfaces rather than a sum
8393
+ * type, so on `enrichModelsFromSpec`-marked discriminated bases we restore
8394
+ * the original IR fields — otherwise the base interface would be empty.
8395
+ * A future change can emit a real TS discriminated union; for now the goal
8396
+ * is parity with the other flat-emit languages so every variant field is
8397
+ * at least reachable.
8398
+ */
8399
+ function enrichModelsForNode(models) {
8400
+ const enriched = enrichModelsFromSpec(models);
8401
+ const originalByName = new Map(models.map((m) => [m.name, m]));
8402
+ return enriched.map((m) => {
8403
+ if (m.discriminator && m.fields.length === 0) {
8404
+ const original = originalByName.get(m.name);
8405
+ if (original && original.fields.length > 0) return {
8406
+ ...m,
8407
+ fields: original.fields
8408
+ };
8409
+ }
8410
+ return m;
8411
+ });
8412
+ }
7900
8413
  const nodeEmitter = {
7901
8414
  language: "node",
7902
8415
  generateModels(models, ctx) {
7903
8416
  const nodeCtx = withNodeOperationOverrides(ctx);
7904
8417
  const surface = getSurface(nodeCtx);
7905
- return applyLiveSurface(generateModelsAndSerializers(models, nodeCtx), nodeCtx, surface);
8418
+ const enriched = enrichModelsForNode(models);
8419
+ const discPlans = planDiscriminatedModels(enriched, nodeCtx);
8420
+ nodeCtx._discriminatedModelNames = new Set(discPlans.keys());
8421
+ const standardFiles = generateModelsAndSerializers(enriched, nodeCtx);
8422
+ const discFiles = generateDiscriminatedFiles(discPlans, nodeCtx);
8423
+ return applyLiveSurface([...standardFiles, ...discFiles], nodeCtx, surface);
7906
8424
  },
7907
8425
  generateEnums(enums, ctx) {
7908
8426
  const nodeCtx = withNodeOperationOverrides(ctx);
7909
8427
  const surface = getSurface(nodeCtx);
7910
- return applyLiveSurface(generateEnums$7(enums, nodeCtx), nodeCtx, surface);
8428
+ const syntheticEnums = getSyntheticEnums();
8429
+ return applyLiveSurface(generateEnums$7([...enums, ...syntheticEnums], nodeCtx), nodeCtx, surface);
7911
8430
  },
7912
8431
  generateResources(services, ctx) {
7913
8432
  const nodeCtx = withNodeOperationOverrides(ctx);
@@ -7917,7 +8436,7 @@ const nodeEmitter = {
7917
8436
  generateClient(spec, ctx) {
7918
8437
  const nodeCtx = withNodeOperationOverrides(ctx);
7919
8438
  const surface = getSurface(nodeCtx);
7920
- return applyLiveSurface(generateClient$7(spec, nodeCtx), nodeCtx, surface);
8439
+ return applyLiveSurface(generateClient$7(nodeCtx.spec, nodeCtx), nodeCtx, surface);
7921
8440
  },
7922
8441
  generateErrors(_ctx) {
7923
8442
  return [];
@@ -13845,15 +14364,37 @@ function ensureTrailingNewlines$5(files) {
13845
14364
  for (const f of files) if (f.content && !f.content.endsWith("\n")) f.content += "\n";
13846
14365
  return files;
13847
14366
  }
14367
+ /**
14368
+ * Flatten oneOf / allOf+oneOf variant fields onto each base model and pull
14369
+ * in synthetic models / enums for inline variant shapes. PHP emits flat
14370
+ * classes (no sum types), so a discriminated base whose IR fields the
14371
+ * parser stripped (post-allOf-aware detection) gets its original fields
14372
+ * restored to avoid silently dropping variant data.
14373
+ */
14374
+ function enrichModelsForPhp(models) {
14375
+ const enriched = enrichModelsFromSpec(models);
14376
+ const originalByName = new Map(models.map((m) => [m.name, m]));
14377
+ return enriched.map((m) => {
14378
+ if (m.discriminator && m.fields.length === 0) {
14379
+ const original = originalByName.get(m.name);
14380
+ if (original && original.fields.length > 0) return {
14381
+ ...m,
14382
+ fields: original.fields
14383
+ };
14384
+ }
14385
+ return m;
14386
+ });
14387
+ }
13848
14388
  const phpEmitter = {
13849
14389
  language: "php",
13850
14390
  generateModels(models, ctx) {
13851
14391
  ensureNamingInitialized(ctx);
13852
- return ensureTrailingNewlines$5(generateModels$5(models, ctx));
14392
+ return ensureTrailingNewlines$5(generateModels$5(enrichModelsForPhp(models), ctx));
13853
14393
  },
13854
14394
  generateEnums(enums, ctx) {
13855
14395
  ensureNamingInitialized(ctx);
13856
- return ensureTrailingNewlines$5(generateEnums$5(enums, ctx));
14396
+ const syntheticEnums = getSyntheticEnums();
14397
+ return ensureTrailingNewlines$5(generateEnums$5([...enums, ...syntheticEnums], ctx));
13857
14398
  },
13858
14399
  generateResources(services, ctx) {
13859
14400
  ensureNamingInitialized(ctx);
@@ -24436,13 +24977,37 @@ function ensureTrailingNewlines$1(files) {
24436
24977
  for (const f of files) if (f.content && !f.content.endsWith("\n")) f.content += "\n";
24437
24978
  return files;
24438
24979
  }
24980
+ /**
24981
+ * Flatten oneOf / allOf+oneOf variant fields onto each base model and pick
24982
+ * up the synthetic models / enums `enrichModelsFromSpec` produces for inline
24983
+ * variant shapes. Ruby emits flat hash-backed models, not sum types, so a
24984
+ * discriminated base whose IR fields were stripped (the new EventSchema-
24985
+ * style behaviour after the parser learned to walk allOf-wrapped variants)
24986
+ * has its original fields restored — otherwise `ConnectApplication`-style
24987
+ * bases would silently lose every variant field they had previously.
24988
+ */
24989
+ function enrichModelsForRuby(models) {
24990
+ const enriched = enrichModelsFromSpec(models);
24991
+ const originalByName = new Map(models.map((m) => [m.name, m]));
24992
+ return enriched.map((m) => {
24993
+ if (m.discriminator && m.fields.length === 0) {
24994
+ const original = originalByName.get(m.name);
24995
+ if (original && original.fields.length > 0) return {
24996
+ ...m,
24997
+ fields: original.fields
24998
+ };
24999
+ }
25000
+ return m;
25001
+ });
25002
+ }
24439
25003
  const rubyEmitter = {
24440
25004
  language: "ruby",
24441
25005
  generateModels(models, ctx) {
24442
- return ensureTrailingNewlines$1(generateModels$1(models, ctx));
25006
+ return ensureTrailingNewlines$1(generateModels$1(enrichModelsForRuby(models), ctx));
24443
25007
  },
24444
25008
  generateEnums(enums, ctx) {
24445
- return ensureTrailingNewlines$1(generateEnums$1(enums, ctx));
25009
+ const syntheticEnums = getSyntheticEnums();
25010
+ return ensureTrailingNewlines$1(generateEnums$1([...enums, ...syntheticEnums], ctx));
24446
25011
  },
24447
25012
  generateResources(services, ctx) {
24448
25013
  return ensureTrailingNewlines$1(generateResources$1(services, ctx));
@@ -26731,14 +27296,37 @@ function ensureTrailingNewlines(files) {
26731
27296
  for (const f of files) if (f.content && !f.content.endsWith("\n")) f.content += "\n";
26732
27297
  return files;
26733
27298
  }
27299
+ /**
27300
+ * Flatten oneOf / allOf+oneOf variant fields onto each base model and pull
27301
+ * in synthetic models / enums for inline variant shapes. Rust emits flat
27302
+ * structs (a synthesised enum-union from `UnionRegistry` exists, but the
27303
+ * field-on-base pattern is what matches `ConnectApplication` today). A
27304
+ * discriminated base whose IR fields the parser stripped gets its original
27305
+ * fields restored to avoid losing variant data.
27306
+ */
27307
+ function enrichModelsForRust(models) {
27308
+ const enriched = enrichModelsFromSpec(models);
27309
+ const originalByName = new Map(models.map((m) => [m.name, m]));
27310
+ return enriched.map((m) => {
27311
+ if (m.discriminator && m.fields.length === 0) {
27312
+ const original = originalByName.get(m.name);
27313
+ if (original && original.fields.length > 0) return {
27314
+ ...m,
27315
+ fields: original.fields
27316
+ };
27317
+ }
27318
+ return m;
27319
+ });
27320
+ }
26734
27321
  const rustEmitter = {
26735
27322
  language: "rust",
26736
27323
  generateModels(models, ctx) {
26737
27324
  unionRegistry.reset();
26738
- return ensureTrailingNewlines(generateModels(models, ctx, unionRegistry));
27325
+ return ensureTrailingNewlines(generateModels(enrichModelsForRust(models), ctx, unionRegistry));
26739
27326
  },
26740
27327
  generateEnums(enums, ctx) {
26741
- return ensureTrailingNewlines(generateEnums(enums, ctx));
27328
+ const syntheticEnums = getSyntheticEnums();
27329
+ return ensureTrailingNewlines(generateEnums([...enums, ...syntheticEnums], ctx));
26742
27330
  },
26743
27331
  generateResources(services, ctx) {
26744
27332
  return ensureTrailingNewlines(generateResources(services, ctx, unionRegistry));
@@ -26814,4 +27402,4 @@ const workosEmittersPlugin = {
26814
27402
  //#endregion
26815
27403
  export { pythonEmitter as _, rustExtractor as a, pythonExtractor as c, rustEmitter as d, rubyEmitter as f, phpEmitter as g, goEmitter as h, kotlinExtractor as i, rubyExtractor as l, dotnetEmitter as m, elixirExtractor as n, goExtractor as o, kotlinEmitter as p, dotnetExtractor as r, phpExtractor as s, workosEmittersPlugin as t, nodeExtractor as u, nodeEmitter as v };
26816
27404
 
26817
- //# sourceMappingURL=plugin-B9F2jmwy.mjs.map
27405
+ //# sourceMappingURL=plugin-BxVeu2v9.mjs.map