@workos/oagen-emitters 0.13.0 → 0.14.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.
@@ -2286,6 +2286,24 @@ dumper.dump;
2286
2286
  //#endregion
2287
2287
  //#region src/shared/model-utils.ts
2288
2288
  /**
2289
+ * Collect model names referenced as the return type of any non-paginated
2290
+ * operation. The list-wrapper skip rule below assumes a wrapper is always
2291
+ * replaced by the SDK's pagination machinery — but a few endpoints
2292
+ * (e.g. `GET /vault/v1/kv/{id}/versions`) have a list-envelope response
2293
+ * shape with no pagination params, so the parser leaves them as a plain
2294
+ * model reference. We must still emit those wrappers as regular models;
2295
+ * otherwise the generated resource code references an undefined name.
2296
+ */
2297
+ function collectNonPaginatedResponseModelNames(services) {
2298
+ const names = /* @__PURE__ */ new Set();
2299
+ for (const service of services) for (const op of service.operations) {
2300
+ if (op.pagination) continue;
2301
+ walkTypeRef(op.response, { model: (r) => names.add(r.name) });
2302
+ for (const sr of op.successResponses ?? []) walkTypeRef(sr.type, { model: (r) => names.add(r.name) });
2303
+ }
2304
+ return names;
2305
+ }
2306
+ /**
2289
2307
  * Detect whether a model is a list wrapper -- the standard paginated
2290
2308
  * list envelope with `data` (array), `list_metadata`, and optionally `object: 'list'`.
2291
2309
  *
@@ -2636,7 +2654,7 @@ function detectDiscriminators(models) {
2636
2654
  * Returns a new array of enriched models (original models are not mutated).
2637
2655
  * Synthetic enums are stored internally; retrieve them via `getSyntheticEnums()`.
2638
2656
  */
2639
- function enrichModelsFromSpec(models) {
2657
+ function enrichModelsFromSpec(models, enums = []) {
2640
2658
  if (!loadRawSpec()) {
2641
2659
  _lastSyntheticEnums = [];
2642
2660
  return models;
@@ -2646,6 +2664,10 @@ function enrichModelsFromSpec(models) {
2646
2664
  collector.usedNames.add(m.name);
2647
2665
  collector.usedNames.add(toSnakeCase(m.name));
2648
2666
  }
2667
+ for (const e of enums) {
2668
+ collector.usedNames.add(e.name);
2669
+ collector.usedNames.add(toSnakeCase(e.name));
2670
+ }
2649
2671
  const enriched2 = models.map((model) => {
2650
2672
  const rawSchema = lookupRawSchema(model.name);
2651
2673
  if (!rawSchema) return model;
@@ -2692,6 +2714,7 @@ function enrichModelsFromSpec(models) {
2692
2714
  _lastSyntheticEnums = collector.enums.map((e) => ({
2693
2715
  name: e.name,
2694
2716
  values: e.values.map((v) => ({
2717
+ name: toUpperSnakeCase(String(v.value)),
2695
2718
  value: v.value,
2696
2719
  description: v.description
2697
2720
  }))
@@ -2985,7 +3008,7 @@ function mapTypeRef$7(ref, opts) {
2985
3008
  const genericDefaults = opts?.genericDefaults;
2986
3009
  return mapTypeRef(ref, {
2987
3010
  primitive: mapPrimitive$6,
2988
- array: (_r, items) => `${parenthesizeUnion(items)}[]`,
3011
+ array: (_r, items) => `${parenthesizeUnion$1(items)}[]`,
2989
3012
  model: (r) => resolveDomainName(r.name) + (genericDefaults?.get(r.name) ?? ""),
2990
3013
  enum: (r) => inlineEnumUnions.get(r.name) ?? r.name,
2991
3014
  union: (r, variants) => joinUnionVariants$5(r, variants),
@@ -3002,7 +3025,7 @@ function mapWireTypeRef(ref, opts) {
3002
3025
  const genericDefaults = opts?.genericDefaults;
3003
3026
  return mapTypeRef(ref, {
3004
3027
  primitive: mapWirePrimitive,
3005
- array: (_r, items) => `${parenthesizeUnion(items)}[]`,
3028
+ array: (_r, items) => `${parenthesizeUnion$1(items)}[]`,
3006
3029
  model: (r) => wireInterfaceName(resolveDomainName(r.name)) + (genericDefaults?.get(r.name) ?? ""),
3007
3030
  enum: (r) => inlineEnumUnions.get(r.name) ?? r.name,
3008
3031
  union: (r, variants) => joinUnionVariants$5(r, variants),
@@ -3039,7 +3062,7 @@ function joinUnionVariants$5(ref, variants) {
3039
3062
  if (unique.length === 1) return unique[0];
3040
3063
  return unique.join(" | ");
3041
3064
  }
3042
- function parenthesizeUnion(type) {
3065
+ function parenthesizeUnion$1(type) {
3043
3066
  return type.includes(" | ") || type.includes(" & ") ? `(${type})` : type;
3044
3067
  }
3045
3068
  //#endregion
@@ -4627,7 +4650,7 @@ function splitCamelWords(name) {
4627
4650
  return parts;
4628
4651
  }
4629
4652
  /** Naive singularize: strip trailing 's' unless it ends in 'ss'. */
4630
- function singularize$3(word) {
4653
+ function singularize$4(word) {
4631
4654
  return word.endsWith("s") && !word.endsWith("ss") ? word.slice(0, -1) : word;
4632
4655
  }
4633
4656
  /**
@@ -4644,11 +4667,11 @@ function deduplicateMethodNames(plans, _ctx) {
4644
4667
  if (count <= 1) continue;
4645
4668
  const dupes = plans.filter((p) => p.method === name);
4646
4669
  if (new Set(dupes.map((d) => d.op.path.replace(/\/\{[^}]+\}$/, ""))).size <= 1) continue;
4647
- const nameWords = new Set(splitCamelWords(name).map(singularize$3));
4670
+ const nameWords = new Set(splitCamelWords(name).map(singularize$4));
4648
4671
  const scored = dupes.map((d) => {
4649
4672
  return {
4650
4673
  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
4674
+ score: d.op.path.split("/").filter((s) => s && !s.startsWith("{")).flatMap((s) => s.split("_")).map(singularize$4).filter((w) => nameWords.has(w)).length
4652
4675
  };
4653
4676
  });
4654
4677
  scored.sort((a, b) => b.score - a.score);
@@ -5942,6 +5965,7 @@ function isSupportedFieldType(ref, ownerModelName, shared, ctx) {
5942
5965
  const resolvedName = resolveInterfaceName(ref.name, ctx);
5943
5966
  if (ctx.apiSurface?.interfaces?.[resolvedName] || ctx.apiSurface?.typeAliases?.[resolvedName]) return true;
5944
5967
  if (isAdoptedModelName(ref.name)) return true;
5968
+ if (shared.modelToService.has(ref.name)) return true;
5945
5969
  return liveSurfaceHasManagedFile(`src/${shared.resolveDir(shared.modelToService.get(ref.name))}/interfaces/${fileName$3(ref.name)}.interface.ts`);
5946
5970
  }
5947
5971
  case "enum": {
@@ -5988,12 +6012,15 @@ function generateModels$7(models, ctx, shared) {
5988
6012
  if ((ctx.apiSurface?.interfaces?.[depName])?.sourceFile === parentPath) forceGenerate.add(dep);
5989
6013
  }
5990
6014
  }
6015
+ const discriminatedSkip = ctx._discriminatedModelNames;
6016
+ const nonPaginatedRefs = collectNonPaginatedResponseModelNames(ctx.spec.services);
5991
6017
  for (const originalModel of models) {
5992
6018
  const model = projectedByName.get(originalModel.name) ?? originalModel;
5993
6019
  if (!reachableModels.has(model.name)) continue;
5994
6020
  if (interfaceEligibleModels && !interfaceEligibleModels.has(model.name)) continue;
5995
6021
  if (isListMetadataModel(model)) continue;
5996
- if (isListWrapperModel(model)) continue;
6022
+ if (isListWrapperModel(model) && !nonPaginatedRefs.has(model.name)) continue;
6023
+ if (discriminatedSkip?.has(model.name)) continue;
5997
6024
  const service = modelToService.get(model.name);
5998
6025
  const isOwnedModel = isNodeOwnedService(ctx, service);
5999
6026
  if (!isOwnedModel && !modelHasNewFields(model, ctx) && !forceGenerate.has(model.name)) continue;
@@ -6325,13 +6352,16 @@ function generateSerializers(models, ctx, shared) {
6325
6352
  if ((ctx.apiSurface?.interfaces?.[depName])?.sourceFile === parentPath) forceGenerateSerializer.add(dep);
6326
6353
  }
6327
6354
  }
6355
+ const discriminatedSerializerSkip = ctx._discriminatedModelNames;
6356
+ const serializerNonPaginatedRefs = collectNonPaginatedResponseModelNames(ctx.spec.services);
6328
6357
  const eligibleModels = [];
6329
6358
  for (const originalModel of models) {
6330
6359
  const model = projectedByName.get(originalModel.name) ?? originalModel;
6331
6360
  if (!serializerReachable.has(model.name)) continue;
6332
6361
  if (serializerEligibleModels && !serializerEligibleModels.has(model.name)) continue;
6333
6362
  if (isListMetadataModel(model)) continue;
6334
- if (isListWrapperModel(model)) continue;
6363
+ if (isListWrapperModel(model) && !serializerNonPaginatedRefs.has(model.name)) continue;
6364
+ if (discriminatedSerializerSkip?.has(model.name)) continue;
6335
6365
  if (!isNodeOwnedService(ctx, modelToService.get(model.name)) && !modelHasNewFields(model, ctx) && !forceGenerateSerializer.has(model.name)) continue;
6336
6366
  eligibleModels.push(model);
6337
6367
  }
@@ -7643,6 +7673,445 @@ function generateSerializerTests(spec, ctx) {
7643
7673
  return files;
7644
7674
  }
7645
7675
  //#endregion
7676
+ //#region src/node/discriminated-models.ts
7677
+ function detectDiscriminatedShape(modelName, rawSchemas) {
7678
+ const schema = rawSchemas[modelName];
7679
+ if (!schema?.allOf) return null;
7680
+ let baseObject = null;
7681
+ let oneOfVariants = null;
7682
+ for (const member of schema.allOf) {
7683
+ const resolved = resolveRef(member, rawSchemas);
7684
+ if (resolved.oneOf) {
7685
+ if (oneOfVariants) return null;
7686
+ oneOfVariants = resolved.oneOf;
7687
+ } else if (resolved.properties) baseObject = mergeBase(baseObject, resolved);
7688
+ else if (resolved.allOf) {
7689
+ const nestedBase = flattenObjectAllOf(resolved, rawSchemas);
7690
+ baseObject = mergeBase(baseObject, nestedBase);
7691
+ }
7692
+ }
7693
+ if (!oneOfVariants || oneOfVariants.length < 2) return null;
7694
+ const flattenedVariants = oneOfVariants.map((v) => flattenVariant(v, rawSchemas));
7695
+ const discProp = findSharedDiscriminator(flattenedVariants);
7696
+ if (!discProp) return null;
7697
+ const variants = flattenedVariants.map((fv) => {
7698
+ const discValue = readConstString(fv.alwaysProperties.get(discProp));
7699
+ if (!discValue) return null;
7700
+ return {
7701
+ nameSuffix: variantNameSuffix(discValue),
7702
+ discriminatorValue: discValue,
7703
+ fields: variantFields(fv, discProp, modelName, rawSchemas)
7704
+ };
7705
+ }).filter((v) => v !== null);
7706
+ if (variants.length !== flattenedVariants.length) return null;
7707
+ return {
7708
+ modelName,
7709
+ baseFields: baseObject ? collectObjectFields(baseObject, modelName, rawSchemas) : [],
7710
+ discriminatorProperty: discProp,
7711
+ discriminatorPropertyDomain: toCamelCase(discProp),
7712
+ variants
7713
+ };
7714
+ }
7715
+ function mergeBase(prev, next) {
7716
+ if (!prev) return next;
7717
+ return {
7718
+ type: "object",
7719
+ properties: {
7720
+ ...prev.properties ?? {},
7721
+ ...next.properties ?? {}
7722
+ },
7723
+ required: [...new Set([...prev.required ?? [], ...next.required ?? []])]
7724
+ };
7725
+ }
7726
+ function flattenObjectAllOf(schema, rawSchemas) {
7727
+ let merged = {
7728
+ type: "object",
7729
+ properties: {},
7730
+ required: []
7731
+ };
7732
+ for (const sub of schema.allOf ?? []) {
7733
+ const resolved = resolveRef(sub, rawSchemas);
7734
+ if (resolved.properties) merged = mergeBase(merged, resolved);
7735
+ else if (resolved.allOf) merged = mergeBase(merged, flattenObjectAllOf(resolved, rawSchemas));
7736
+ }
7737
+ return merged;
7738
+ }
7739
+ function flattenVariant(variant, rawSchemas) {
7740
+ const resolved = resolveRef(variant, rawSchemas);
7741
+ if (resolved.properties && !resolved.allOf && !resolved.oneOf) {
7742
+ const props = /* @__PURE__ */ new Map();
7743
+ for (const [k, v] of Object.entries(resolved.properties)) props.set(k, v);
7744
+ return {
7745
+ alwaysProperties: props,
7746
+ optionalProperties: /* @__PURE__ */ new Map(),
7747
+ required: new Set(resolved.required ?? [])
7748
+ };
7749
+ }
7750
+ if (resolved.allOf) {
7751
+ let agg = {
7752
+ alwaysProperties: /* @__PURE__ */ new Map(),
7753
+ optionalProperties: /* @__PURE__ */ new Map(),
7754
+ required: /* @__PURE__ */ new Set()
7755
+ };
7756
+ let initialized = false;
7757
+ for (const member of resolved.allOf) {
7758
+ const memberView = flattenVariant(member, rawSchemas);
7759
+ if (!initialized) {
7760
+ agg = {
7761
+ alwaysProperties: new Map(memberView.alwaysProperties),
7762
+ optionalProperties: new Map(memberView.optionalProperties),
7763
+ required: new Set(memberView.required)
7764
+ };
7765
+ initialized = true;
7766
+ continue;
7767
+ }
7768
+ for (const [k, v] of memberView.alwaysProperties) if (!agg.alwaysProperties.has(k) && !agg.optionalProperties.has(k)) agg.alwaysProperties.set(k, v);
7769
+ for (const [k, v] of memberView.optionalProperties) if (!agg.alwaysProperties.has(k) && !agg.optionalProperties.has(k)) agg.optionalProperties.set(k, v);
7770
+ for (const r of memberView.required) agg.required.add(r);
7771
+ }
7772
+ return agg;
7773
+ }
7774
+ if (resolved.oneOf) {
7775
+ const memberViews = resolved.oneOf.map((m) => flattenVariant(m, rawSchemas));
7776
+ const allKeys = /* @__PURE__ */ new Set();
7777
+ for (const view of memberViews) {
7778
+ for (const k of view.alwaysProperties.keys()) allKeys.add(k);
7779
+ for (const k of view.optionalProperties.keys()) allKeys.add(k);
7780
+ }
7781
+ const always = /* @__PURE__ */ new Map();
7782
+ const optional = /* @__PURE__ */ new Map();
7783
+ const requiredEverywhere = /* @__PURE__ */ new Set();
7784
+ for (const key of allKeys) {
7785
+ const inAll = memberViews.every((v) => v.alwaysProperties.has(key) || v.optionalProperties.has(key));
7786
+ const merged = mergeFieldSchemas(memberViews.map((v) => v.alwaysProperties.get(key) ?? v.optionalProperties.get(key)).filter((s) => Boolean(s)));
7787
+ if (inAll) always.set(key, merged);
7788
+ else optional.set(key, merged);
7789
+ if (inAll && memberViews.every((v) => v.required.has(key))) requiredEverywhere.add(key);
7790
+ }
7791
+ return {
7792
+ alwaysProperties: always,
7793
+ optionalProperties: optional,
7794
+ required: requiredEverywhere
7795
+ };
7796
+ }
7797
+ return {
7798
+ alwaysProperties: /* @__PURE__ */ new Map(),
7799
+ optionalProperties: /* @__PURE__ */ new Map(),
7800
+ required: /* @__PURE__ */ new Set()
7801
+ };
7802
+ }
7803
+ function mergeFieldSchemas(schemas) {
7804
+ if (schemas.length === 0) return {};
7805
+ if (schemas.length === 1) return schemas[0];
7806
+ if (schemas.every((s) => s.type === "boolean" && typeof s.const === "boolean")) return {
7807
+ type: "boolean",
7808
+ description: schemas[0].description
7809
+ };
7810
+ if (schemas.every((s) => s.type === "string" && typeof s.const === "string")) {
7811
+ const values = schemas.map((s) => s.const);
7812
+ if (new Set(values).size === 1) return schemas[0];
7813
+ return {
7814
+ type: "string",
7815
+ description: schemas[0].description
7816
+ };
7817
+ }
7818
+ return schemas[0];
7819
+ }
7820
+ function findSharedDiscriminator(variants) {
7821
+ if (variants.length < 2) return null;
7822
+ const firstAlways = variants[0].alwaysProperties;
7823
+ for (const propName of firstAlways.keys()) {
7824
+ let allConst = true;
7825
+ const values = [];
7826
+ for (const v of variants) {
7827
+ const val = readConstString(v.alwaysProperties.get(propName));
7828
+ if (val === null) {
7829
+ allConst = false;
7830
+ break;
7831
+ }
7832
+ values.push(val);
7833
+ }
7834
+ if (allConst && new Set(values).size === values.length) return propName;
7835
+ }
7836
+ return null;
7837
+ }
7838
+ function readConstString(schema) {
7839
+ if (!schema) return null;
7840
+ if (typeof schema.const === "string") return schema.const;
7841
+ if (Array.isArray(schema.enum) && schema.enum.length === 1 && typeof schema.enum[0] === "string") return schema.enum[0];
7842
+ return null;
7843
+ }
7844
+ function variantNameSuffix(constValue) {
7845
+ return toPascalCase(constValue);
7846
+ }
7847
+ function collectObjectFields(schema, parentName, rawSchemas) {
7848
+ const props = schema.properties ?? {};
7849
+ const required = new Set(schema.required ?? []);
7850
+ const fields = [];
7851
+ for (const [name, propSchema] of Object.entries(props)) fields.push(buildField(name, propSchema, required.has(name), parentName, rawSchemas));
7852
+ return fields;
7853
+ }
7854
+ function variantFields(fv, discriminatorProperty, parentName, rawSchemas) {
7855
+ const fields = [];
7856
+ for (const [name, propSchema] of fv.alwaysProperties) {
7857
+ if (name === discriminatorProperty) continue;
7858
+ fields.push(buildField(name, propSchema, fv.required.has(name), parentName, rawSchemas));
7859
+ }
7860
+ for (const [name, propSchema] of fv.optionalProperties) {
7861
+ if (name === discriminatorProperty) continue;
7862
+ fields.push(buildField(name, propSchema, false, parentName, rawSchemas));
7863
+ }
7864
+ return fields;
7865
+ }
7866
+ function buildField(rawName, schema, required, parentName, rawSchemas) {
7867
+ const modelDeps = /* @__PURE__ */ new Set();
7868
+ const domainType = rawSchemaToTS(schema, parentName, rawName, false, modelDeps, rawSchemas);
7869
+ const wireType = rawSchemaToTS(schema, parentName, rawName, true, modelDeps, rawSchemas);
7870
+ return {
7871
+ name: rawName,
7872
+ description: schema.description,
7873
+ required,
7874
+ domainType,
7875
+ wireType,
7876
+ modelDeps,
7877
+ hasDateTime: schemaHasDateTime(schema)
7878
+ };
7879
+ }
7880
+ function schemaHasDateTime(schema) {
7881
+ if (schema.format === "date-time" && typeOf(schema) === "string") return true;
7882
+ if (schema.items && schemaHasDateTime(schema.items)) return true;
7883
+ return false;
7884
+ }
7885
+ function typeOf(schema) {
7886
+ if (Array.isArray(schema.type)) return schema.type.find((t) => t !== "null");
7887
+ return schema.type;
7888
+ }
7889
+ function isNullable(schema) {
7890
+ return Array.isArray(schema.type) && schema.type.includes("null");
7891
+ }
7892
+ function rawSchemaToTS(schema, parentName, fieldName, isWire, modelDeps, rawSchemas) {
7893
+ if (schema.$ref) {
7894
+ const refName = schema.$ref.split("/").pop();
7895
+ modelDeps.add(refName);
7896
+ const domain = toPascalCase(refName);
7897
+ return isWire ? wireInterfaceName(domain) : domain;
7898
+ }
7899
+ if (typeof schema.const === "string") return `'${schema.const}'`;
7900
+ if (typeof schema.const === "boolean") return String(schema.const);
7901
+ const baseType = typeOf(schema);
7902
+ const nullable = isNullable(schema);
7903
+ let core;
7904
+ if (baseType === "string") core = !isWire && schema.format === "date-time" ? "Date" : "string";
7905
+ else if (baseType === "integer" || baseType === "number") core = "number";
7906
+ else if (baseType === "boolean") core = "boolean";
7907
+ else if (baseType === "array" && schema.items) core = `${parenthesizeUnion(rawSchemaToTS(schema.items, parentName, singularize$3(fieldName), isWire, modelDeps, rawSchemas))}[]`;
7908
+ else if (baseType === "object" && schema.properties) {
7909
+ const synthName = `${parentName}_${singularize$3(fieldName)}`;
7910
+ modelDeps.add(synthName);
7911
+ const domain = toPascalCase(synthName);
7912
+ return isWire ? wireInterfaceName(domain) : domain;
7913
+ } else core = "unknown";
7914
+ return nullable ? `${core} | null` : core;
7915
+ }
7916
+ function parenthesizeUnion(t) {
7917
+ return /\s\|\s/.test(t) ? `(${t})` : t;
7918
+ }
7919
+ function singularize$3(name) {
7920
+ if (name.endsWith("ies") && name.length > 3) return `${name.slice(0, -3)}y`;
7921
+ if (name.endsWith("s") && !name.endsWith("ss")) return name.slice(0, -1);
7922
+ return name;
7923
+ }
7924
+ function resolveRef(schema, rawSchemas) {
7925
+ if (!schema.$ref) return schema;
7926
+ const segments = schema.$ref.split("/");
7927
+ return rawSchemas[segments[segments.length - 1]] ?? schema;
7928
+ }
7929
+ function planDiscriminatedModels(models, ctx) {
7930
+ const plans = /* @__PURE__ */ new Map();
7931
+ const spec = loadRawSpec();
7932
+ if (!spec?.components?.schemas) return plans;
7933
+ const rawSchemas = spec.components.schemas;
7934
+ const { modelToService, resolveDir } = createServiceDirResolver(models, ctx.spec.services, ctx);
7935
+ for (const model of models) {
7936
+ const shape = detectDiscriminatedShape(model.name, rawSchemas);
7937
+ if (!shape) continue;
7938
+ const modelDir = resolveDir(modelToService.get(model.name));
7939
+ plans.set(model.name, {
7940
+ shape,
7941
+ modelDir
7942
+ });
7943
+ }
7944
+ return plans;
7945
+ }
7946
+ function generateDiscriminatedFiles(plans, ctx) {
7947
+ const files = [];
7948
+ for (const plan of plans.values()) {
7949
+ files.push(buildInterfaceFile(plan, ctx));
7950
+ files.push(buildSerializerFile(plan, ctx));
7951
+ }
7952
+ return files;
7953
+ }
7954
+ function buildInterfaceFile(plan, _ctx) {
7955
+ const { shape, modelDir } = plan;
7956
+ const domain = toPascalCase(shape.modelName);
7957
+ const wire = wireInterfaceName(domain);
7958
+ const lines = [];
7959
+ const imports = collectImports$2(plan);
7960
+ if (imports.length > 0) {
7961
+ for (const imp of imports) lines.push(`import type { ${imp.symbols.join(", ")} } from '${imp.path}';`);
7962
+ lines.push("");
7963
+ }
7964
+ for (const variant of shape.variants) {
7965
+ const variantDomain = `${domain}${variant.nameSuffix}`;
7966
+ const variantWire = `${variantDomain}Response`;
7967
+ lines.push(...buildInterfaceBody(variantDomain, shape, variant, false));
7968
+ lines.push("");
7969
+ lines.push(...buildInterfaceBody(variantWire, shape, variant, true));
7970
+ lines.push("");
7971
+ }
7972
+ const variantNames = shape.variants.map((v) => `${domain}${v.nameSuffix}`);
7973
+ lines.push(`export type ${domain} = ${variantNames.join(" | ")};`);
7974
+ lines.push("");
7975
+ const wireVariantNames = shape.variants.map((v) => `${domain}${v.nameSuffix}Response`);
7976
+ lines.push(`export type ${wire} = ${wireVariantNames.join(" | ")};`);
7977
+ return {
7978
+ path: `src/${modelDir}/interfaces/${fileName$3(shape.modelName)}.interface.ts`,
7979
+ content: lines.join("\n") + "\n",
7980
+ overwriteExisting: true
7981
+ };
7982
+ }
7983
+ function buildInterfaceBody(name, shape, variant, isWire) {
7984
+ const lines = [];
7985
+ lines.push(`export interface ${name} {`);
7986
+ for (const field of shape.baseFields) pushFieldLine(lines, field, isWire);
7987
+ const discKey = isWire ? shape.discriminatorProperty : shape.discriminatorPropertyDomain;
7988
+ lines.push(` ${discKey}: '${variant.discriminatorValue}';`);
7989
+ for (const field of variant.fields) pushFieldLine(lines, field, isWire);
7990
+ lines.push("}");
7991
+ return lines;
7992
+ }
7993
+ function pushFieldLine(lines, field, isWire) {
7994
+ const key = isWire ? field.name : toCamelCase(field.name);
7995
+ const opt = field.required ? "" : "?";
7996
+ const type = isWire ? field.wireType : field.domainType;
7997
+ if (field.description) lines.push(` /** ${field.description} */`);
7998
+ lines.push(` ${key}${opt}: ${type};`);
7999
+ }
8000
+ function collectImports$2(plan) {
8001
+ const deps = /* @__PURE__ */ new Set();
8002
+ for (const field of plan.shape.baseFields) for (const d of field.modelDeps) deps.add(d);
8003
+ for (const variant of plan.shape.variants) for (const field of variant.fields) for (const d of field.modelDeps) deps.add(d);
8004
+ const symbols = [];
8005
+ for (const dep of [...deps].sort()) {
8006
+ const domain = toPascalCase(dep);
8007
+ symbols.push(domain);
8008
+ const wire = wireInterfaceName(domain);
8009
+ if (wire !== domain) symbols.push(wire);
8010
+ }
8011
+ if (symbols.length === 0) return [];
8012
+ return symbols.map((sym) => {
8013
+ return {
8014
+ path: `./${fileName$3(toSnakeFromPascal(sym.replace(/Response$/, "")))}.interface`,
8015
+ symbols: [sym]
8016
+ };
8017
+ }).reduce((acc, cur) => {
8018
+ const existing = acc.find((a) => a.path === cur.path);
8019
+ if (existing) existing.symbols.push(...cur.symbols);
8020
+ else acc.push(cur);
8021
+ return acc;
8022
+ }, []);
8023
+ }
8024
+ function toSnakeFromPascal(s) {
8025
+ return s.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").toLowerCase();
8026
+ }
8027
+ function buildSerializerFile(plan, _ctx) {
8028
+ const { shape, modelDir } = plan;
8029
+ const domain = toPascalCase(shape.modelName);
8030
+ const wire = wireInterfaceName(domain);
8031
+ const lines = [];
8032
+ const interfaceImportPath = `../interfaces/${fileName$3(shape.modelName)}.interface`;
8033
+ lines.push(`import type { ${domain}, ${wire} } from '${interfaceImportPath}';`);
8034
+ const allDeps = /* @__PURE__ */ new Set();
8035
+ for (const field of shape.baseFields) for (const d of field.modelDeps) allDeps.add(d);
8036
+ for (const variant of shape.variants) for (const field of variant.fields) for (const d of field.modelDeps) allDeps.add(d);
8037
+ for (const dep of [...allDeps].sort()) {
8038
+ const depDomain = toPascalCase(dep);
8039
+ const depFile = fileName$3(toSnakeFromPascal(depDomain));
8040
+ lines.push(`import { deserialize${depDomain}, serialize${depDomain} } from './${depFile}.serializer';`);
8041
+ }
8042
+ lines.push("");
8043
+ lines.push(`export const deserialize${domain} = (response: ${wire}): ${domain} => {`);
8044
+ lines.push(` switch (response.${shape.discriminatorProperty}) {`);
8045
+ for (const variant of shape.variants) {
8046
+ lines.push(` case '${variant.discriminatorValue}':`);
8047
+ lines.push(` return {`);
8048
+ for (const field of shape.baseFields) lines.push(` ${assignmentLine(field, false, allDeps)},`);
8049
+ lines.push(` ${shape.discriminatorPropertyDomain}: '${variant.discriminatorValue}',`);
8050
+ for (const field of variant.fields) lines.push(` ${assignmentLine(field, false, allDeps)},`);
8051
+ lines.push(` };`);
8052
+ }
8053
+ lines.push(` default:`);
8054
+ lines.push(` throw new Error(\`Unknown ${shape.discriminatorProperty}: \${(response as { ${shape.discriminatorProperty}: string }).${shape.discriminatorProperty}}\`);`);
8055
+ lines.push(` }`);
8056
+ lines.push(`};`);
8057
+ lines.push("");
8058
+ lines.push(`export const serialize${domain} = (model: ${domain}): ${wire} => {`);
8059
+ lines.push(` switch (model.${shape.discriminatorPropertyDomain}) {`);
8060
+ for (const variant of shape.variants) {
8061
+ lines.push(` case '${variant.discriminatorValue}':`);
8062
+ lines.push(` return {`);
8063
+ for (const field of shape.baseFields) lines.push(` ${assignmentLine(field, true, allDeps)},`);
8064
+ lines.push(` ${shape.discriminatorProperty}: '${variant.discriminatorValue}',`);
8065
+ for (const field of variant.fields) lines.push(` ${assignmentLine(field, true, allDeps)},`);
8066
+ lines.push(` };`);
8067
+ }
8068
+ lines.push(` }`);
8069
+ lines.push(`};`);
8070
+ return {
8071
+ path: `src/${modelDir}/serializers/${fileName$3(shape.modelName)}.serializer.ts`,
8072
+ content: lines.join("\n") + "\n",
8073
+ overwriteExisting: true
8074
+ };
8075
+ }
8076
+ function assignmentLine(field, serialize, _allDeps) {
8077
+ const camel = toCamelCase(field.name);
8078
+ const snake = field.name;
8079
+ const lhs = serialize ? snake : camel;
8080
+ const rhsKey = serialize ? camel : snake;
8081
+ const source = serialize ? `model.${rhsKey}` : `response.${rhsKey}`;
8082
+ if (field.hasDateTime) {
8083
+ if (serialize) {
8084
+ if (field.required) return `${lhs}: ${source}.toISOString()`;
8085
+ return `${lhs}: ${source} != null ? ${source}.toISOString() : undefined`;
8086
+ }
8087
+ if (field.required) return `${lhs}: new Date(${source})`;
8088
+ return `${lhs}: ${source} != null ? new Date(${source}) : undefined`;
8089
+ }
8090
+ const arrayDep = arrayItemModelDep(field);
8091
+ if (arrayDep) {
8092
+ const fn = serialize ? `serialize${arrayDep}` : `deserialize${arrayDep}`;
8093
+ if (field.required) return `${lhs}: ${source}.map(${fn})`;
8094
+ return `${lhs}: ${source} != null ? ${source}.map(${fn}) : undefined`;
8095
+ }
8096
+ const scalarDep = scalarModelDepName(field);
8097
+ if (scalarDep) {
8098
+ const fn = serialize ? `serialize${scalarDep}` : `deserialize${scalarDep}`;
8099
+ if (field.required) return `${lhs}: ${fn}(${source})`;
8100
+ return `${lhs}: ${source} != null ? ${fn}(${source}) : undefined`;
8101
+ }
8102
+ return `${lhs}: ${source}`;
8103
+ }
8104
+ function arrayItemModelDep(field) {
8105
+ const m = field.domainType.match(/^([A-Z]\w*)\[\]$/);
8106
+ if (m && field.modelDeps.size > 0) return m[1];
8107
+ return null;
8108
+ }
8109
+ function scalarModelDepName(field) {
8110
+ const stripped = field.domainType.replace(/\s*\|\s*null$/, "");
8111
+ if (/^[A-Z]\w*$/.test(stripped) && field.modelDeps.size === 1) return toPascalCase([...field.modelDeps][0]);
8112
+ return null;
8113
+ }
8114
+ //#endregion
7646
8115
  //#region src/node/node-overrides.ts
7647
8116
  const OPERATION_OVERRIDES = {
7648
8117
  "POST /organizations/{organizationId}/groups": { methodName: "create_group" },
@@ -7662,31 +8131,71 @@ const contextCache = /* @__PURE__ */ new WeakMap();
7662
8131
  function operationKey(resolved) {
7663
8132
  return `${resolved.operation.httpMethod.toUpperCase()} ${resolved.operation.path}`;
7664
8133
  }
8134
+ /**
8135
+ * Apply oneOf / allOf+oneOf enrichment (flattening variant fields onto the
8136
+ * parent model, plus synthetic models/enums for inline shapes) so the rest
8137
+ * of the Node emitter sees a richer `spec.models`.
8138
+ *
8139
+ * Without this, `ConnectApplication` (and any other `allOf [base, oneOf [...]]`
8140
+ * schema whose first variant is itself wrapped in `allOf`) loses every
8141
+ * non-M2M field — the IR parser's discriminator detection silently skips
8142
+ * variants whose properties live behind another `allOf`. Mirrors what the
8143
+ * Go / Kotlin / .NET emitters already do.
8144
+ *
8145
+ * Discriminated bases produced by `enrichModelsFromSpec` get their original
8146
+ * fields restored — Node emits flat interfaces today, not TS sum types, so
8147
+ * an empty base would otherwise drop the common fields.
8148
+ */
8149
+ function enrichSpecModels(models) {
8150
+ const enriched = enrichModelsFromSpec(models);
8151
+ const originalByName = new Map(models.map((m) => [m.name, m]));
8152
+ return enriched.map((m) => {
8153
+ if (m.discriminator && m.fields.length === 0) {
8154
+ const original = originalByName.get(m.name);
8155
+ if (original && original.fields.length > 0) return {
8156
+ ...m,
8157
+ fields: original.fields
8158
+ };
8159
+ }
8160
+ return m;
8161
+ });
8162
+ }
7665
8163
  function withNodeOperationOverrides(ctx) {
7666
8164
  const cached = contextCache.get(ctx);
7667
8165
  if (cached) return cached;
8166
+ const enrichedModels = enrichSpecModels(ctx.spec.models);
8167
+ const specChanged = enrichedModels.length !== ctx.spec.models.length || enrichedModels.some((m, i) => m !== ctx.spec.models[i]);
8168
+ const enrichedSpec = specChanged ? {
8169
+ ...ctx.spec,
8170
+ models: enrichedModels
8171
+ } : ctx.spec;
7668
8172
  const resolvedOperations = ctx.resolvedOperations;
7669
8173
  if (!resolvedOperations?.length) {
7670
- contextCache.set(ctx, ctx);
7671
- return ctx;
8174
+ const next = specChanged ? {
8175
+ ...ctx,
8176
+ spec: enrichedSpec
8177
+ } : ctx;
8178
+ contextCache.set(ctx, next);
8179
+ return next;
7672
8180
  }
7673
- let changed = false;
8181
+ let opsChanged = false;
7674
8182
  const nextResolved = resolvedOperations.map((resolved) => {
7675
8183
  const override = OPERATION_OVERRIDES[operationKey(resolved)];
7676
8184
  if (!override) return resolved;
7677
8185
  const methodName = override.methodName ?? resolved.methodName;
7678
8186
  const mountOn = override.mountOn ?? resolved.mountOn;
7679
8187
  if (methodName === resolved.methodName && mountOn === resolved.mountOn) return resolved;
7680
- changed = true;
8188
+ opsChanged = true;
7681
8189
  return {
7682
8190
  ...resolved,
7683
8191
  methodName,
7684
8192
  mountOn
7685
8193
  };
7686
8194
  });
7687
- const next = changed ? {
8195
+ const next = opsChanged || specChanged ? {
7688
8196
  ...ctx,
7689
- resolvedOperations: nextResolved
8197
+ ...opsChanged ? { resolvedOperations: nextResolved } : {},
8198
+ ...specChanged ? { spec: enrichedSpec } : {}
7690
8199
  } : ctx;
7691
8200
  contextCache.set(ctx, next);
7692
8201
  return next;
@@ -7698,6 +8207,22 @@ function withNodeOperationOverrides(ctx) {
7698
8207
  * one oagen run, so we walk the target SDK once and reuse it.
7699
8208
  */
7700
8209
  const surfaceCache = /* @__PURE__ */ new WeakMap();
8210
+ /**
8211
+ * Paths the node emitter has produced so far in this ctx, accumulated across
8212
+ * `applyLiveSurface` calls. Drives `carryForwardManagedFiles` so files in the
8213
+ * prior manifest that we did not re-emit this run still land in the new
8214
+ * manifest as "still managed" — without that, the orchestrator's prune diff
8215
+ * treats every untouched autogen file as stale.
8216
+ */
8217
+ const emittedPathsCache = /* @__PURE__ */ new WeakMap();
8218
+ function getEmittedPaths(ctx) {
8219
+ let set = emittedPathsCache.get(ctx);
8220
+ if (!set) {
8221
+ set = /* @__PURE__ */ new Set();
8222
+ emittedPathsCache.set(ctx, set);
8223
+ }
8224
+ return set;
8225
+ }
7701
8226
  function getSurface(ctx) {
7702
8227
  let surface = surfaceCache.get(ctx);
7703
8228
  if (surface) return surface;
@@ -7744,8 +8269,10 @@ function getSurface(ctx) {
7744
8269
  * `integrateTarget: false` files (smoke-manifest.json etc.) are also dropped:
7745
8270
  * with no `--target` step they would otherwise land as untracked cruft.
7746
8271
  *
7747
- * Note: pairing this with `--no-prune` is required for stable behavior — see
7748
- * `scripts/sdk-generate.sh` in the spec repo, which enables it for `--lang node`.
8272
+ * Note: the carry-forward step in `generateTests` re-declares prior-manifest
8273
+ * paths we didn't touch this run, so the orchestrator's prune diff stays
8274
+ * accurate without needing `--no-prune` at the call site. See
8275
+ * `carryForwardManagedFiles` below.
7749
8276
  */
7750
8277
  /**
7751
8278
  * `*.spec.ts`, `*.test.ts`, and JSON fixtures under `fixtures/` are owned by
@@ -7895,19 +8422,99 @@ function applyLiveSurface(files, ctx, surface) {
7895
8422
  if (f.content && !f.content.endsWith("\n")) f.content += "\n";
7896
8423
  out.push(f);
7897
8424
  }
8425
+ const emitted = getEmittedPaths(ctx);
8426
+ for (const f of out) emitted.add(f.path);
8427
+ return out;
8428
+ }
8429
+ /**
8430
+ * Re-declare prior-manifest paths that we did not emit this run so manifest
8431
+ * pruning can tell "intentionally removed" from "untouched but still managed."
8432
+ *
8433
+ * The node emitter only outputs files it actually wants to write each run —
8434
+ * untouched-but-up-to-date autogen files don't come back through any
8435
+ * `generateXxx` method. Without this carry-forward, the orchestrator's
8436
+ * `prevManifest.files − currentEmission` diff treats every such file as stale
8437
+ * and prunes the whole tree on a regen. That's why `scripts/sdk-generate.sh`
8438
+ * historically paired the node emitter with `--no-prune` — at the cost of
8439
+ * never pruning legitimately-removed files (e.g. an enum file orphaned by a
8440
+ * `schemaNameTransform` rename like `RadarAction` → `RadarListAction`).
8441
+ *
8442
+ * The carry-forward entry uses `skipIfExists: true`, so writer.ts skips the
8443
+ * write and only ensures the header is present (no-op for files that already
8444
+ * have it). The path still lands in `outputEmittedPaths` and therefore in the
8445
+ * new manifest, which restores correct prune semantics.
8446
+ *
8447
+ * Files dropped from the carry-forward set:
8448
+ * - Not on disk anymore (file was hand-deleted — let prune confirm absence).
8449
+ * - `@oagen-ignore-file` protected (user has explicitly taken ownership).
8450
+ * - `.ts` files that no longer carry the auto-gen header (user has taken
8451
+ * ownership in-place; the next prune cycle will clear the manifest entry).
8452
+ */
8453
+ function carryForwardManagedFiles(ctx, surface) {
8454
+ const priorPaths = ctx.priorTargetManifestPaths;
8455
+ if (!priorPaths || priorPaths.size === 0) return [];
8456
+ const emitted = getEmittedPaths(ctx);
8457
+ const out = [];
8458
+ for (const relPath of priorPaths) {
8459
+ if (emitted.has(relPath)) continue;
8460
+ if (!surface.files.has(relPath)) continue;
8461
+ if (surface.protectedFiles.has(relPath)) continue;
8462
+ if (relPath.endsWith(".ts") && !surface.autogenFiles.has(relPath)) continue;
8463
+ out.push({
8464
+ path: relPath,
8465
+ content: "",
8466
+ skipIfExists: true,
8467
+ headerPlacement: "skip"
8468
+ });
8469
+ emitted.add(relPath);
8470
+ }
7898
8471
  return out;
7899
8472
  }
8473
+ /**
8474
+ * Flatten oneOf / allOf+oneOf variant fields from the raw spec onto each
8475
+ * model. `enrichModelsFromSpec` produces (a) extra optional fields on models
8476
+ * whose schema is `allOf [base, oneOf [...]]`, and (b) synthetic models /
8477
+ * enums for inline objects encountered inside variants (e.g. the inline
8478
+ * `redirect_uris` item shape on `ConnectApplication`).
8479
+ *
8480
+ * Node, like Go / Kotlin / .NET, emits flat interfaces rather than a sum
8481
+ * type, so on `enrichModelsFromSpec`-marked discriminated bases we restore
8482
+ * the original IR fields — otherwise the base interface would be empty.
8483
+ * A future change can emit a real TS discriminated union; for now the goal
8484
+ * is parity with the other flat-emit languages so every variant field is
8485
+ * at least reachable.
8486
+ */
8487
+ function enrichModelsForNode(models) {
8488
+ const enriched = enrichModelsFromSpec(models);
8489
+ const originalByName = new Map(models.map((m) => [m.name, m]));
8490
+ return enriched.map((m) => {
8491
+ if (m.discriminator && m.fields.length === 0) {
8492
+ const original = originalByName.get(m.name);
8493
+ if (original && original.fields.length > 0) return {
8494
+ ...m,
8495
+ fields: original.fields
8496
+ };
8497
+ }
8498
+ return m;
8499
+ });
8500
+ }
7900
8501
  const nodeEmitter = {
7901
8502
  language: "node",
7902
8503
  generateModels(models, ctx) {
7903
8504
  const nodeCtx = withNodeOperationOverrides(ctx);
7904
8505
  const surface = getSurface(nodeCtx);
7905
- return applyLiveSurface(generateModelsAndSerializers(models, nodeCtx), nodeCtx, surface);
8506
+ const enriched = enrichModelsForNode(models);
8507
+ const discPlans = planDiscriminatedModels(enriched, nodeCtx);
8508
+ nodeCtx._discriminatedModelNames = new Set(discPlans.keys());
8509
+ const standardFiles = generateModelsAndSerializers(enriched, nodeCtx);
8510
+ const discFiles = generateDiscriminatedFiles(discPlans, nodeCtx);
8511
+ return applyLiveSurface([...standardFiles, ...discFiles], nodeCtx, surface);
7906
8512
  },
7907
8513
  generateEnums(enums, ctx) {
7908
8514
  const nodeCtx = withNodeOperationOverrides(ctx);
7909
8515
  const surface = getSurface(nodeCtx);
7910
- return applyLiveSurface(generateEnums$7(enums, nodeCtx), nodeCtx, surface);
8516
+ const syntheticEnums = getSyntheticEnums();
8517
+ return applyLiveSurface(generateEnums$7([...enums, ...syntheticEnums], nodeCtx), nodeCtx, surface);
7911
8518
  },
7912
8519
  generateResources(services, ctx) {
7913
8520
  const nodeCtx = withNodeOperationOverrides(ctx);
@@ -7917,7 +8524,7 @@ const nodeEmitter = {
7917
8524
  generateClient(spec, ctx) {
7918
8525
  const nodeCtx = withNodeOperationOverrides(ctx);
7919
8526
  const surface = getSurface(nodeCtx);
7920
- return applyLiveSurface(generateClient$7(spec, nodeCtx), nodeCtx, surface);
8527
+ return applyLiveSurface(generateClient$7(nodeCtx.spec, nodeCtx), nodeCtx, surface);
7921
8528
  },
7922
8529
  generateErrors(_ctx) {
7923
8530
  return [];
@@ -7927,9 +8534,8 @@ const nodeEmitter = {
7927
8534
  },
7928
8535
  generateTests(spec, ctx) {
7929
8536
  const nodeCtx = withNodeOperationOverrides(ctx);
7930
- if (!nodeOptions(nodeCtx).regenerateOwnedTests) return [];
7931
8537
  const surface = getSurface(nodeCtx);
7932
- return applyLiveSurface(generateTests$7(spec, nodeCtx), nodeCtx, surface);
8538
+ return [...nodeOptions(nodeCtx).regenerateOwnedTests ? applyLiveSurface(generateTests$7(spec, nodeCtx), nodeCtx, surface) : [], ...carryForwardManagedFiles(nodeCtx, surface)];
7933
8539
  },
7934
8540
  buildOperationsMap() {
7935
8541
  return {};
@@ -8706,8 +9312,9 @@ function generateModels$6(models, ctx) {
8706
9312
  const symbolToFile = /* @__PURE__ */ new Map();
8707
9313
  const symbolToOriginalService = /* @__PURE__ */ new Map();
8708
9314
  const emittedFilePaths = /* @__PURE__ */ new Set();
9315
+ const nonPaginatedRefs = collectNonPaginatedResponseModelNames(ctx.spec.services);
8709
9316
  for (const model of models) {
8710
- if (isListWrapperModel(model)) continue;
9317
+ if (isListWrapperModel(model) && !nonPaginatedRefs.has(model.name)) continue;
8711
9318
  if (isListMetadataModel(model)) continue;
8712
9319
  const dirName = resolveDir(modelToService.get(model.name));
8713
9320
  const modelClassName = className$5(model.name);
@@ -10121,7 +10728,9 @@ function generateResources$6(services, ctx) {
10121
10728
  }
10122
10729
  for (const op of allOperations) {
10123
10730
  const plan = planOperation(op);
10124
- if (plan.responseModelName && !listWrapperNames.has(plan.responseModelName)) modelImports.add(plan.responseModelName);
10731
+ if (plan.responseModelName) {
10732
+ if (!listWrapperNames.has(plan.responseModelName) || !plan.isPaginated) modelImports.add(plan.responseModelName);
10733
+ }
10125
10734
  if (op.requestBody?.kind === "model") {
10126
10735
  const requestBodyRef = op.requestBody;
10127
10736
  modelImports.add(requestBodyRef.name);
@@ -12250,9 +12859,10 @@ function generateModels$5(models, ctx) {
12250
12859
  ].join("\n"),
12251
12860
  overwriteExisting: true
12252
12861
  });
12862
+ const nonPaginatedRefs = collectNonPaginatedResponseModelNames(ctx.spec.services);
12253
12863
  for (const model of models) {
12254
12864
  if (isListMetadataModel(model)) continue;
12255
- if (isListWrapperModel(model)) continue;
12865
+ if (isListWrapperModel(model) && !nonPaginatedRefs.has(model.name)) continue;
12256
12866
  const name = className$4(model.name);
12257
12867
  const lines = [];
12258
12868
  lines.push(`namespace ${ctx.namespacePascal}\\Resource;`);
@@ -13845,15 +14455,37 @@ function ensureTrailingNewlines$5(files) {
13845
14455
  for (const f of files) if (f.content && !f.content.endsWith("\n")) f.content += "\n";
13846
14456
  return files;
13847
14457
  }
14458
+ /**
14459
+ * Flatten oneOf / allOf+oneOf variant fields onto each base model and pull
14460
+ * in synthetic models / enums for inline variant shapes. PHP emits flat
14461
+ * classes (no sum types), so a discriminated base whose IR fields the
14462
+ * parser stripped (post-allOf-aware detection) gets its original fields
14463
+ * restored to avoid silently dropping variant data.
14464
+ */
14465
+ function enrichModelsForPhp(models) {
14466
+ const enriched = enrichModelsFromSpec(models);
14467
+ const originalByName = new Map(models.map((m) => [m.name, m]));
14468
+ return enriched.map((m) => {
14469
+ if (m.discriminator && m.fields.length === 0) {
14470
+ const original = originalByName.get(m.name);
14471
+ if (original && original.fields.length > 0) return {
14472
+ ...m,
14473
+ fields: original.fields
14474
+ };
14475
+ }
14476
+ return m;
14477
+ });
14478
+ }
13848
14479
  const phpEmitter = {
13849
14480
  language: "php",
13850
14481
  generateModels(models, ctx) {
13851
14482
  ensureNamingInitialized(ctx);
13852
- return ensureTrailingNewlines$5(generateModels$5(models, ctx));
14483
+ return ensureTrailingNewlines$5(generateModels$5(enrichModelsForPhp(models), ctx));
13853
14484
  },
13854
14485
  generateEnums(enums, ctx) {
13855
14486
  ensureNamingInitialized(ctx);
13856
- return ensureTrailingNewlines$5(generateEnums$5(enums, ctx));
14487
+ const syntheticEnums = getSyntheticEnums();
14488
+ return ensureTrailingNewlines$5(generateEnums$5([...enums, ...syntheticEnums], ctx));
13857
14489
  },
13858
14490
  generateResources(services, ctx) {
13859
14491
  ensureNamingInitialized(ctx);
@@ -14130,10 +14762,12 @@ function generateModels$4(models, ctx) {
14130
14762
  lines.push(`package ${ctx.namespace}`);
14131
14763
  lines.push("");
14132
14764
  const requestBodyOnly = collectRequestBodyOnlyModelNames$1(ctx.spec.services, models);
14765
+ const nonPaginatedRefs = collectNonPaginatedResponseModelNames(ctx.spec.services);
14766
+ const skipAsListWrapper = (m) => isListWrapperModel(m) && !nonPaginatedRefs.has(m.name);
14133
14767
  const modelHashMap = /* @__PURE__ */ new Map();
14134
14768
  const hashGroups = /* @__PURE__ */ new Map();
14135
14769
  for (const model of models) {
14136
- if (isListWrapperModel(model) || isListMetadataModel(model)) continue;
14770
+ if (skipAsListWrapper(model) || isListMetadataModel(model)) continue;
14137
14771
  if (requestBodyOnly.has(model.name)) continue;
14138
14772
  const hash = structuralHash$2(model);
14139
14773
  modelHashMap.set(model.name, hash);
@@ -14150,7 +14784,7 @@ function generateModels$4(models, ctx) {
14150
14784
  }
14151
14785
  const batchedAliases = /* @__PURE__ */ new Set();
14152
14786
  for (const model of models) {
14153
- if (isListWrapperModel(model) || isListMetadataModel(model)) continue;
14787
+ if (skipAsListWrapper(model) || isListMetadataModel(model)) continue;
14154
14788
  if (requestBodyOnly.has(model.name)) continue;
14155
14789
  const structName = className$3(model.name);
14156
14790
  const canonicalName = aliasOf.get(model.name);
@@ -16783,7 +17417,15 @@ function isEnumRef(ref) {
16783
17417
  * omission so the API returns a clear `missing required field` error instead
16784
17418
  * of a confusing 422.
16785
17419
  */
16786
- function emitJsonPropertyAttributes(_wireName, options = {}) {
17420
+ function emitJsonPropertyAttributes(wireName, options = {}) {
17421
+ if (options.explicitWireName) {
17422
+ if (options.isRequiredEnum) return [
17423
+ ` [JsonProperty("${wireName}", DefaultValueHandling = DefaultValueHandling.Ignore)]`,
17424
+ ` [STJS.JsonIgnore(Condition = STJS.JsonIgnoreCondition.WhenWritingDefault)]`,
17425
+ ` [STJS.JsonPropertyName("${wireName}")]`
17426
+ ];
17427
+ return [` [JsonProperty("${wireName}")]`, ` [STJS.JsonPropertyName("${wireName}")]`];
17428
+ }
16787
17429
  if (options.isRequiredEnum) return [` [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]`, ` [STJS.JsonIgnore(Condition = STJS.JsonIgnoreCondition.WhenWritingDefault)]`];
16788
17430
  return [];
16789
17431
  }
@@ -16836,16 +17478,23 @@ function generateModels$3(models, ctx, discCtx) {
16836
17478
  const files = [];
16837
17479
  primeModelAliases(models);
16838
17480
  const requestBodyOnlyNames = collectRequestBodyOnlyModelNames(ctx.spec.services, models);
17481
+ const nonPaginatedRefs = collectNonPaginatedResponseModelNames(ctx.spec.services);
17482
+ const skipAsListWrapper = (m) => isListWrapperModel(m) && !nonPaginatedRefs.has(m.name);
16839
17483
  const baseFieldLookup = /* @__PURE__ */ new Map();
16840
17484
  if (discCtx) {
16841
17485
  for (const model of models) if (discCtx.discriminatorBases.has(model.name)) {
17486
+ const baseClassName = modelClassName(model.name);
16842
17487
  const fieldMap = /* @__PURE__ */ new Map();
16843
- for (const field of model.fields) fieldMap.set(fieldName$2(field.name), mapTypeRef$3(field.type));
17488
+ for (const field of model.fields) {
17489
+ let csName = fieldName$2(field.name);
17490
+ if (csName === baseClassName) csName = `${csName}Value`;
17491
+ fieldMap.set(csName, mapTypeRef$3(field.type));
17492
+ }
16844
17493
  baseFieldLookup.set(model.name, fieldMap);
16845
17494
  }
16846
17495
  }
16847
17496
  for (const model of models) {
16848
- if (isListWrapperModel(model) || isListMetadataModel(model)) continue;
17497
+ if (skipAsListWrapper(model) || isListMetadataModel(model)) continue;
16849
17498
  if (requestBodyOnlyNames.has(model.name)) continue;
16850
17499
  const csClassName = modelClassName(model.name);
16851
17500
  if (isModelAlias(model.name)) continue;
@@ -16853,7 +17502,7 @@ function generateModels$3(models, ctx, discCtx) {
16853
17502
  const fieldTypes = model.fields.map((f) => mapTypeRef$3(f.type));
16854
17503
  const needsCollections = fieldTypes.some((t) => t.startsWith("List<") || t.startsWith("Dictionary<"));
16855
17504
  const needsSystem = fieldTypes.some((t) => t.includes("DateTimeOffset"));
16856
- const needsJsonAttrs = model.fields.some((f) => f.required && isEnumRef(f.type));
17505
+ const needsJsonAttrs = model.fields.some((f) => fieldName$2(f.name) === csClassName) || model.fields.some((f) => f.required && isEnumRef(f.type));
16857
17506
  lines.push(`namespace ${ctx.namespacePascal}`);
16858
17507
  lines.push("{");
16859
17508
  if (needsSystem) lines.push(" using System;");
@@ -16879,7 +17528,9 @@ function generateModels$3(models, ctx, discCtx) {
16879
17528
  const dictObjectFields = [];
16880
17529
  const seenFieldNames = /* @__PURE__ */ new Set();
16881
17530
  for (const field of model.fields) {
16882
- const csFieldName = fieldName$2(field.name);
17531
+ let csFieldName = fieldName$2(field.name);
17532
+ const collidesWithClassName = csFieldName === csClassName;
17533
+ if (collidesWithClassName) csFieldName = `${csFieldName}Value`;
16883
17534
  if (seenFieldNames.has(csFieldName)) continue;
16884
17535
  seenFieldNames.add(csFieldName);
16885
17536
  let useNewModifier = false;
@@ -16924,7 +17575,10 @@ function generateModels$3(models, ctx, discCtx) {
16924
17575
  lines.push(` [System.Obsolete("${msg}")]`);
16925
17576
  }
16926
17577
  const isRequiredEnum = field.required && isEnumRef(field.type) && constInit === null;
16927
- lines.push(...emitJsonPropertyAttributes(field.name, { isRequiredEnum }));
17578
+ lines.push(...emitJsonPropertyAttributes(field.name, {
17579
+ isRequiredEnum,
17580
+ explicitWireName: collidesWithClassName
17581
+ }));
16928
17582
  const discriminatedUnionConverter = discriminatedUnionConverterName(field.type);
16929
17583
  if (discriminatedUnionConverter) lines.push(` [Newtonsoft.Json.JsonConverter(typeof(${discriminatedUnionConverter}))]`);
16930
17584
  const newMod = useNewModifier ? "new " : "";
@@ -19509,14 +20163,16 @@ function promoteFieldType$1(f) {
19509
20163
  * List wrappers (`{ data, list_metadata }`) and the shared `ListMetadata`
19510
20164
  * model are skipped — the hand-maintained runtime provides [Page]/[ListMetadata].
19511
20165
  */
19512
- function generateModels$2(models, _ctx) {
20166
+ function generateModels$2(models, ctx) {
19513
20167
  if (models.length === 0) return [];
19514
20168
  for (const model of models) for (const field of model.fields) mapTypeRef$2(field.type);
19515
20169
  const files = [];
20170
+ const nonPaginatedRefs = collectNonPaginatedResponseModelNames(ctx.spec.services);
20171
+ const skipAsListWrapper = (m) => isListWrapperModel(m) && !nonPaginatedRefs.has(m.name);
19516
20172
  modelAliasMap = null;
19517
20173
  const hashGroupsPass1 = /* @__PURE__ */ new Map();
19518
20174
  for (const model of models) {
19519
- if (isListWrapperModel(model) || isListMetadataModel(model)) continue;
20175
+ if (skipAsListWrapper(model) || isListMetadataModel(model)) continue;
19520
20176
  if (model.fields.length === 0 && discriminatedUnions.has(className$1(model.name))) continue;
19521
20177
  const hash = structuralHash(model);
19522
20178
  if (!hashGroupsPass1.has(hash)) hashGroupsPass1.set(hash, []);
@@ -19535,7 +20191,7 @@ function generateModels$2(models, _ctx) {
19535
20191
  modelAliasMap = aliasOf;
19536
20192
  const hashGroupsPass2 = /* @__PURE__ */ new Map();
19537
20193
  for (const model of models) {
19538
- if (isListWrapperModel(model) || isListMetadataModel(model)) continue;
20194
+ if (skipAsListWrapper(model) || isListMetadataModel(model)) continue;
19539
20195
  if (model.fields.length === 0 && discriminatedUnions.has(className$1(model.name))) continue;
19540
20196
  if (aliasOf.has(model.name)) continue;
19541
20197
  const hash = structuralHash(model);
@@ -19553,7 +20209,7 @@ function generateModels$2(models, _ctx) {
19553
20209
  }
19554
20210
  }
19555
20211
  for (const model of models) {
19556
- if (isListWrapperModel(model) || isListMetadataModel(model)) continue;
20212
+ if (skipAsListWrapper(model) || isListMetadataModel(model)) continue;
19557
20213
  const typeName = className$1(model.name);
19558
20214
  if (model.fields.length === 0 && discriminatedUnions.has(typeName)) {
19559
20215
  files.push(emitSealedUnion(typeName, discriminatedUnions.get(typeName)));
@@ -19581,7 +20237,7 @@ function generateModels$2(models, _ctx) {
19581
20237
  }
19582
20238
  const eventMapping = [];
19583
20239
  for (const model of models) {
19584
- if (isListWrapperModel(model) || isListMetadataModel(model)) continue;
20240
+ if (skipAsListWrapper(model) || isListMetadataModel(model)) continue;
19585
20241
  if (aliasOf.has(model.name)) continue;
19586
20242
  if (!isEventEnvelopeModel(model)) continue;
19587
20243
  const eventField = model.fields.find((f) => f.name === "event");
@@ -22298,10 +22954,12 @@ function generateModels$1(models, ctx) {
22298
22954
  return mountDirMap.get(service) ?? classifyUnassignedModel(modelName);
22299
22955
  };
22300
22956
  const files = [];
22957
+ const nonPaginatedRefs = collectNonPaginatedResponseModelNames(ctx.spec.services);
22958
+ const skipAsListWrapper = (m) => isListWrapperModel(m) && !nonPaginatedRefs.has(m.name);
22301
22959
  const recursiveHashes = buildRecursiveHashMap(models, enumNames);
22302
22960
  const hashGroups = /* @__PURE__ */ new Map();
22303
22961
  for (const m of models) {
22304
- if (isListWrapperModel(m) || isListMetadataModel(m)) continue;
22962
+ if (skipAsListWrapper(m) || isListMetadataModel(m)) continue;
22305
22963
  const h = recursiveHashes.get(m.name) ?? "";
22306
22964
  if (!hashGroups.has(h)) hashGroups.set(h, []);
22307
22965
  hashGroups.get(h).push(m.name);
@@ -22314,7 +22972,7 @@ function generateModels$1(models, ctx) {
22314
22972
  for (let i = 1; i < sorted.length; i++) aliasOf.set(sorted[i], canonical);
22315
22973
  }
22316
22974
  for (const model of models) {
22317
- if (isListWrapperModel(model) || isListMetadataModel(model)) continue;
22975
+ if (skipAsListWrapper(model) || isListMetadataModel(model)) continue;
22318
22976
  const cls = className(model.name);
22319
22977
  const file = fileName(model.name);
22320
22978
  const canonical = aliasOf.get(model.name);
@@ -24436,13 +25094,37 @@ function ensureTrailingNewlines$1(files) {
24436
25094
  for (const f of files) if (f.content && !f.content.endsWith("\n")) f.content += "\n";
24437
25095
  return files;
24438
25096
  }
25097
+ /**
25098
+ * Flatten oneOf / allOf+oneOf variant fields onto each base model and pick
25099
+ * up the synthetic models / enums `enrichModelsFromSpec` produces for inline
25100
+ * variant shapes. Ruby emits flat hash-backed models, not sum types, so a
25101
+ * discriminated base whose IR fields were stripped (the new EventSchema-
25102
+ * style behaviour after the parser learned to walk allOf-wrapped variants)
25103
+ * has its original fields restored — otherwise `ConnectApplication`-style
25104
+ * bases would silently lose every variant field they had previously.
25105
+ */
25106
+ function enrichModelsForRuby(models, enums) {
25107
+ const enriched = enrichModelsFromSpec(models, enums);
25108
+ const originalByName = new Map(models.map((m) => [m.name, m]));
25109
+ return enriched.map((m) => {
25110
+ if (m.discriminator && m.fields.length === 0) {
25111
+ const original = originalByName.get(m.name);
25112
+ if (original && original.fields.length > 0) return {
25113
+ ...m,
25114
+ fields: original.fields
25115
+ };
25116
+ }
25117
+ return m;
25118
+ });
25119
+ }
24439
25120
  const rubyEmitter = {
24440
25121
  language: "ruby",
24441
25122
  generateModels(models, ctx) {
24442
- return ensureTrailingNewlines$1(generateModels$1(models, ctx));
25123
+ return ensureTrailingNewlines$1(generateModels$1(enrichModelsForRuby(models, ctx.spec.enums), ctx));
24443
25124
  },
24444
25125
  generateEnums(enums, ctx) {
24445
- return ensureTrailingNewlines$1(generateEnums$1(enums, ctx));
25126
+ const syntheticEnums = getSyntheticEnums();
25127
+ return ensureTrailingNewlines$1(generateEnums$1([...enums, ...syntheticEnums], ctx));
24446
25128
  },
24447
25129
  generateResources(services, ctx) {
24448
25130
  return ensureTrailingNewlines$1(generateResources$1(services, ctx));
@@ -24904,7 +25586,7 @@ function renderField(field, rustField, modelName, registry) {
24904
25586
  function renderModelsBarrel(modules) {
24905
25587
  const sorted = [...new Set(modules)].sort();
24906
25588
  const lines = [];
24907
- for (const m of sorted) lines.push(`pub mod ${m};`);
25589
+ for (const m of sorted) lines.push(`mod ${m};`);
24908
25590
  lines.push("");
24909
25591
  for (const m of sorted) lines.push(`pub use ${m}::*;`);
24910
25592
  return lines.join("\n") + "\n";
@@ -25987,7 +26669,7 @@ function renderResourcesBarrel(exports) {
25987
26669
  }
25988
26670
  unique.sort((a, b) => a.module.localeCompare(b.module));
25989
26671
  const lines = [];
25990
- for (const { module } of unique) lines.push(`pub mod ${module};`);
26672
+ for (const { module } of unique) lines.push(`mod ${module};`);
25991
26673
  lines.push("");
25992
26674
  for (const { module, struct } of unique) lines.push(`pub use ${module}::${struct};`);
25993
26675
  return lines.join("\n") + "\n";
@@ -26731,14 +27413,37 @@ function ensureTrailingNewlines(files) {
26731
27413
  for (const f of files) if (f.content && !f.content.endsWith("\n")) f.content += "\n";
26732
27414
  return files;
26733
27415
  }
27416
+ /**
27417
+ * Flatten oneOf / allOf+oneOf variant fields onto each base model and pull
27418
+ * in synthetic models / enums for inline variant shapes. Rust emits flat
27419
+ * structs (a synthesised enum-union from `UnionRegistry` exists, but the
27420
+ * field-on-base pattern is what matches `ConnectApplication` today). A
27421
+ * discriminated base whose IR fields the parser stripped gets its original
27422
+ * fields restored to avoid losing variant data.
27423
+ */
27424
+ function enrichModelsForRust(models) {
27425
+ const enriched = enrichModelsFromSpec(models);
27426
+ const originalByName = new Map(models.map((m) => [m.name, m]));
27427
+ return enriched.map((m) => {
27428
+ if (m.discriminator && m.fields.length === 0) {
27429
+ const original = originalByName.get(m.name);
27430
+ if (original && original.fields.length > 0) return {
27431
+ ...m,
27432
+ fields: original.fields
27433
+ };
27434
+ }
27435
+ return m;
27436
+ });
27437
+ }
26734
27438
  const rustEmitter = {
26735
27439
  language: "rust",
26736
27440
  generateModels(models, ctx) {
26737
27441
  unionRegistry.reset();
26738
- return ensureTrailingNewlines(generateModels(models, ctx, unionRegistry));
27442
+ return ensureTrailingNewlines(generateModels(enrichModelsForRust(models), ctx, unionRegistry));
26739
27443
  },
26740
27444
  generateEnums(enums, ctx) {
26741
- return ensureTrailingNewlines(generateEnums(enums, ctx));
27445
+ const syntheticEnums = getSyntheticEnums();
27446
+ return ensureTrailingNewlines(generateEnums([...enums, ...syntheticEnums], ctx));
26742
27447
  },
26743
27448
  generateResources(services, ctx) {
26744
27449
  return ensureTrailingNewlines(generateResources(services, ctx, unionRegistry));
@@ -26814,4 +27519,4 @@ const workosEmittersPlugin = {
26814
27519
  //#endregion
26815
27520
  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
27521
 
26817
- //# sourceMappingURL=plugin-B9F2jmwy.mjs.map
27522
+ //# sourceMappingURL=plugin-DRGwxN88.mjs.map