@workos/oagen-emitters 0.18.3 → 0.19.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.
Files changed (67) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/CHANGELOG.md +16 -0
  3. package/dist/index.d.mts.map +1 -1
  4. package/dist/index.mjs +1 -1
  5. package/dist/{plugin-1ckLMpgo.mjs → plugin-BXDPA9pJ.mjs} +581 -172
  6. package/dist/plugin-BXDPA9pJ.mjs.map +1 -0
  7. package/dist/plugin.mjs +1 -1
  8. package/docs/sdk-architecture/rust.md +2 -2
  9. package/package.json +5 -5
  10. package/src/dotnet/enums.ts +11 -5
  11. package/src/dotnet/fixtures.ts +5 -2
  12. package/src/dotnet/index.ts +2 -1
  13. package/src/dotnet/models.ts +41 -10
  14. package/src/dotnet/naming.ts +10 -0
  15. package/src/dotnet/resources.ts +3 -3
  16. package/src/dotnet/tests.ts +8 -4
  17. package/src/go/fixtures.ts +4 -2
  18. package/src/go/index.ts +4 -0
  19. package/src/go/models.ts +4 -2
  20. package/src/go/naming.ts +10 -0
  21. package/src/go/resources.ts +22 -9
  22. package/src/go/tests.ts +3 -3
  23. package/src/kotlin/enums.ts +21 -11
  24. package/src/kotlin/index.ts +2 -1
  25. package/src/kotlin/models.ts +24 -9
  26. package/src/kotlin/naming.ts +11 -0
  27. package/src/kotlin/resources.ts +2 -2
  28. package/src/kotlin/tests.ts +7 -3
  29. package/src/node/enums.ts +8 -5
  30. package/src/node/field-plan.ts +3 -3
  31. package/src/node/index.ts +2 -1
  32. package/src/node/models.ts +69 -22
  33. package/src/node/naming.ts +10 -0
  34. package/src/node/options.ts +45 -1
  35. package/src/node/resources.ts +67 -18
  36. package/src/node/tests.ts +302 -31
  37. package/src/php/enums.ts +18 -5
  38. package/src/php/index.ts +13 -4
  39. package/src/php/models.ts +22 -10
  40. package/src/php/naming.ts +10 -0
  41. package/src/php/resources.ts +6 -4
  42. package/src/php/tests.ts +17 -5
  43. package/src/python/enums.ts +39 -28
  44. package/src/python/fixtures.ts +4 -3
  45. package/src/python/index.ts +2 -1
  46. package/src/python/models.ts +39 -24
  47. package/src/python/naming.ts +10 -0
  48. package/src/python/resources.ts +3 -3
  49. package/src/python/tests.ts +14 -9
  50. package/src/ruby/enums.ts +28 -19
  51. package/src/ruby/index.ts +2 -1
  52. package/src/ruby/models.ts +33 -19
  53. package/src/ruby/naming.ts +10 -0
  54. package/src/ruby/rbi.ts +20 -7
  55. package/src/ruby/resources.ts +2 -2
  56. package/src/ruby/tests.ts +6 -3
  57. package/src/rust/enums.ts +9 -1
  58. package/src/rust/index.ts +2 -1
  59. package/src/rust/models.ts +100 -15
  60. package/src/rust/naming.ts +10 -0
  61. package/src/rust/resources.ts +14 -3
  62. package/src/rust/tests.ts +2 -2
  63. package/src/shared/file-header.ts +13 -0
  64. package/src/shared/resolved-ops.ts +47 -0
  65. package/test/rust/models.test.ts +49 -0
  66. package/test/shared/synthetic-enum-seed.test.ts +79 -0
  67. package/dist/plugin-1ckLMpgo.mjs.map +0 -1
@@ -75,6 +75,49 @@ function groupByMount(ctx) {
75
75
  return groups;
76
76
  }
77
77
  /**
78
+ * Like {@link groupByMount}, but for a scoped (`--services`) run returns ONLY the
79
+ * mount groups the run selected (`ctx.scopedServices`, POST-MOUNT names). When
80
+ * scoping is inactive the full set is returned unchanged.
81
+ *
82
+ * Use this for PER-SERVICE resource/test emission. Do NOT use it for
83
+ * aggregate/barrel files (Rust `mod.rs`, Ruby `client.rbi`, the root client) —
84
+ * those must continue to list every service, so they keep calling
85
+ * {@link groupByMount} over the full set; otherwise a scoped run would drop
86
+ * sibling modules and break the build/type-check.
87
+ */
88
+ function scopedMountGroups(ctx) {
89
+ const groups = groupByMount(ctx);
90
+ const scope = ctx.scopedServices;
91
+ if (!scope || scope.size === 0) return groups;
92
+ return new Map([...groups].filter(([mountName]) => scope.has(mountName)));
93
+ }
94
+ /**
95
+ * True when a POST-MOUNT service name should be emitted in the current run.
96
+ * Inactive scoping (no `ctx.scopedServices`) ⇒ everything is in scope. Use this
97
+ * for inline per-service gates (e.g. manifest loops keyed by `getMountTarget`).
98
+ */
99
+ function isMountInScope(mountName, ctx) {
100
+ const scope = ctx.scopedServices;
101
+ return !scope || scope.size === 0 || scope.has(mountName);
102
+ }
103
+ /**
104
+ * True when a MODEL's per-model FILE should be written in the current run (FR-1.4).
105
+ * A scoped run sets `ctx.scopedModelNames` to the models reachable from the
106
+ * selected services; out-of-scope models are left untouched on disk. Inactive
107
+ * scoping ⇒ everything is in scope. NOTE: gate only the per-model FILE write —
108
+ * the model must still appear in barrels/indexes (built from the full set) so the
109
+ * untouched on-disk file stays importable.
110
+ */
111
+ function isModelInScope(modelName, ctx) {
112
+ const scope = ctx.scopedModelNames;
113
+ return !scope || scope.has(modelName);
114
+ }
115
+ /** Like {@link isModelInScope} but for an ENUM's per-enum file (`ctx.scopedEnumNames`). */
116
+ function isEnumInScope(enumName, ctx) {
117
+ const scope = ctx.scopedEnumNames;
118
+ return !scope || scope.has(enumName);
119
+ }
120
+ /**
78
121
  * Get the mount target for an IR service.
79
122
  * Checks the first resolved operation that belongs to this service.
80
123
  * Falls back to PascalCase of the service name if no resolved ops exist.
@@ -3637,6 +3680,29 @@ function isHandOwnedType(ctx, name) {
3637
3680
  if (!configured || configured.length === 0) return false;
3638
3681
  return configured.includes(name);
3639
3682
  }
3683
+ /**
3684
+ * Resolve the Node operation override for an operation, keyed by "METHOD /path".
3685
+ */
3686
+ function operationOverrideFor$1(ctx, op) {
3687
+ return nodeOptions(ctx).operationOverrides?.[`${op.httpMethod.toUpperCase()} ${op.path}`];
3688
+ }
3689
+ /**
3690
+ * `planOperation` plus the Node `responseModel` override. When an operation
3691
+ * override supplies `responseModel`, the resolved response model name is
3692
+ * replaced so the resource and its generated test reference the desired wire
3693
+ * type and deserializer. Use this everywhere the Node emitter would otherwise
3694
+ * call `planOperation(op)` directly so resource and test stay in lockstep.
3695
+ */
3696
+ function planOperationFor(op, ctx) {
3697
+ const plan = planOperation(op);
3698
+ const responseModel = operationOverrideFor$1(ctx, op)?.responseModel;
3699
+ if (responseModel && responseModel !== plan.responseModelName) return {
3700
+ ...plan,
3701
+ responseModelName: responseModel,
3702
+ isModelResponse: true
3703
+ };
3704
+ return plan;
3705
+ }
3640
3706
  //#endregion
3641
3707
  //#region src/node/live-surface.ts
3642
3708
  const SRC_DIR = "src";
@@ -4632,7 +4698,7 @@ function generateEnums$7(enums, ctx) {
4632
4698
  lines.push(`export type ${enumDef.name} =`);
4633
4699
  lines.push(` (typeof ${enumDef.name})[keyof typeof ${enumDef.name}];`);
4634
4700
  }
4635
- files.push({
4701
+ if (isEnumInScope(enumDef.name, ctx)) files.push({
4636
4702
  path: `src/${dirName}/interfaces/${fileName$3(enumDef.name)}.interface.ts`,
4637
4703
  content: lines.join("\n"),
4638
4704
  skipIfExists: !hasNewValues
@@ -4913,7 +4979,7 @@ function serializerHasBaselineIncompatibility(model, baselineResponse, baselineD
4913
4979
  const irDomainFields = /* @__PURE__ */ new Set();
4914
4980
  for (const field of model.fields) {
4915
4981
  irWireFields.add(wireFieldName(field.name));
4916
- irDomainFields.add(fieldName$6(field.name));
4982
+ irDomainFields.add(fieldName$6(field.domainName ?? field.name));
4917
4983
  }
4918
4984
  for (const [wireField2, fieldDef] of Object.entries(baselineResponse.fields)) {
4919
4985
  if (fieldDef.optional) continue;
@@ -4957,7 +5023,7 @@ function serializerHasBaselineIncompatibility(model, baselineResponse, baselineD
4957
5023
  return false;
4958
5024
  }
4959
5025
  function planDeserializeField(field, baselineDomain, baselineResponse, skipFormatFields, ctx) {
4960
- const domain = fieldName$6(field.name);
5026
+ const domain = fieldName$6(field.domainName ?? field.name);
4961
5027
  const wireAccess = `response.${wireFieldName(field.name)}`;
4962
5028
  const skip = skipFormatFields.has(field.name);
4963
5029
  const baselineDomainField = baselineDomain?.fields?.[domain];
@@ -4993,7 +5059,7 @@ function planDeserializeGuard(field, expr, wireAccess, effectivelyOptional, isNe
4993
5059
  }
4994
5060
  function planSerializeField(field, baselineDomain, baselineResponse, skipFormatFields, ctx) {
4995
5061
  const wire = wireFieldName(field.name);
4996
- const domain = fieldName$6(field.name);
5062
+ const domain = fieldName$6(field.domainName ?? field.name);
4997
5063
  const domainAccess = `model.${domain}`;
4998
5064
  const skip = skipFormatFields.has(field.name);
4999
5065
  const baselineWireField = baselineResponse?.fields?.[wire];
@@ -5533,9 +5599,6 @@ function existingInterfaceBarrelExports(ctx, serviceDir, stem) {
5533
5599
  const escapedStem = stem.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5534
5600
  return new RegExp(`export\\s+(?:type\\s+)?(?:\\*|\\{[^}]+\\})\\s+from\\s+['"]\\./${escapedStem}['"]`).test(content);
5535
5601
  }
5536
- function operationOverrideFor$1(ctx, op) {
5537
- return nodeOptions(ctx).operationOverrides?.[`${op.httpMethod.toUpperCase()} ${op.path}`];
5538
- }
5539
5602
  function baselineMethodFor$1(service, method, ctx) {
5540
5603
  const serviceClass = resolveResourceClassName$3(service, ctx);
5541
5604
  return ctx.apiSurface?.classes?.[serviceClass]?.methods?.[method]?.[0];
@@ -5561,10 +5624,11 @@ function optionsObjectParam$1(method) {
5561
5624
  const [param] = method.params;
5562
5625
  if (param.name !== "options") return void 0;
5563
5626
  if (param.passingStyle && param.passingStyle !== "options_object") return void 0;
5564
- if (!param.type || /^(Record|object|any|unknown)\b/.test(param.type)) return void 0;
5627
+ const type = param.type?.replace(/(?:\s*\|\s*(?:undefined|null))+\s*$/, "").trim();
5628
+ if (!type || /^(Record|object|any|unknown)\b/.test(type)) return void 0;
5565
5629
  return {
5566
5630
  name: "options",
5567
- type: param.type,
5631
+ type,
5568
5632
  optional: param.optional === true,
5569
5633
  generated: false
5570
5634
  };
@@ -5765,14 +5829,14 @@ function generateOptionsInterfaces(service, ctx, specEnumNames) {
5765
5829
  const resolvedLookup = buildResolvedLookup(ctx);
5766
5830
  const plans = service.operations.map((op) => ({
5767
5831
  op,
5768
- plan: planOperation(op),
5832
+ plan: planOperationFor(op, ctx),
5769
5833
  method: resolveMethodName$6(op, service, ctx)
5770
5834
  }));
5771
5835
  for (const { op, plan, method } of plans) {
5772
5836
  const resolvedOp = lookupResolved(op, resolvedLookup);
5773
5837
  const optionInfo = optionsObjectInfo(service, method, op, plan, ctx, baselineMethodFor$1(service, method, ctx), resolvedOp);
5774
5838
  if (!optionInfo?.generated) continue;
5775
- if (baselineTypeSourceFile(ctx, optionInfo.type)) continue;
5839
+ if (op.pathParams.length === 0 && baselineTypeSourceFile(ctx, optionInfo.type)) continue;
5776
5840
  const optionsName = optionInfo.type;
5777
5841
  const optionFileStem = `${fileName$3(optionsName)}.interface`;
5778
5842
  const filePath = `src/${serviceDir}/interfaces/${optionFileStem}.ts`;
@@ -5815,7 +5879,8 @@ function generateOptionsInterfaces(service, ctx, specEnumNames) {
5815
5879
  }
5816
5880
  headerParts.push(` ${name}${opt}: ${type};`);
5817
5881
  };
5818
- for (const param of op.pathParams) pushField(fieldName$6(param.name), true, mapParamType(param.type, specEnumNames), param.description, param.deprecated);
5882
+ const optionsPathFieldMap = operationOverrideFor$1(ctx, op)?.pathFieldMap;
5883
+ for (const param of op.pathParams) pushField(optionsPathFieldMap?.[fieldName$6(param.name)] ?? fieldName$6(param.name), true, mapParamType(param.type, specEnumNames), param.description, param.deprecated);
5819
5884
  for (const param of visibleQueryParamsForOptions(op, plan, resolvedOp)) pushField(fieldName$6(param.name), param.required, mapParamType(param.type, specEnumNames), param.description, param.deprecated);
5820
5885
  if (bodyInfo?.kind === "model") {
5821
5886
  const bodyModel = ctx.spec.models.find((m) => m.name === bodyInfo.name);
@@ -5843,12 +5908,13 @@ function generateResources$7(services, ctx) {
5843
5908
  if (services.length === 0) return [];
5844
5909
  const files = [];
5845
5910
  const mountGroups = groupByMount(ctx);
5846
- const mergedServices = mountGroups.size > 0 ? [...mountGroups].map(([name, group]) => ({
5911
+ const mergedServices = mountGroups.size > 0 || ctx.scopedServices?.size ? [...mountGroups].map(([name, group]) => ({
5847
5912
  name,
5848
5913
  operations: group.operations
5849
5914
  })) : services;
5850
5915
  const topLevelEnumNames = new Set(ctx.spec.enums.map((e) => e.name));
5851
5916
  for (const service of mergedServices) {
5917
+ if (!isMountInScope(service.name, ctx)) continue;
5852
5918
  const isOwnedService = isNodeOwnedService(ctx, service.name, resolveResourceClassName$3(service, ctx));
5853
5919
  if (!isOwnedService && isServiceCoveredByExisting(service, ctx)) {
5854
5920
  if (!hasMethodsAbsentFromBaseline(service, ctx)) continue;
@@ -5875,6 +5941,7 @@ function generateResources$7(services, ctx) {
5875
5941
  }
5876
5942
  }
5877
5943
  for (const service of mergedServices) {
5944
+ if (!isMountInScope(service.name, ctx)) continue;
5878
5945
  if (!isNodeOwnedService(ctx, service.name, resolveResourceClassName$3(service, ctx)) && isServiceCoveredByExisting(service, ctx) && !hasMethodsAbsentFromBaseline(service, ctx)) continue;
5879
5946
  files.push(...generateOptionsInterfaces(service, ctx, topLevelEnumNames));
5880
5947
  }
@@ -5887,7 +5954,7 @@ function generateResourceClass(service, ctx) {
5887
5954
  const resourcePath = `src/${serviceDir}/${fileName$3(resolvedName)}.ts`;
5888
5955
  let plans = service.operations.map((op) => ({
5889
5956
  op,
5890
- plan: planOperation(op),
5957
+ plan: planOperationFor(op, ctx),
5891
5958
  method: resolveMethodName$6(op, service, ctx)
5892
5959
  }));
5893
5960
  deduplicateMethodNames(plans, ctx);
@@ -5996,6 +6063,7 @@ function generateResourceClass(service, ctx) {
5996
6063
  const hasCustomEncoding = plans.some((p) => p.op.requestBodyEncoding && p.op.requestBodyEncoding !== "json" && p.plan.hasBody);
5997
6064
  if (hasIdempotentPost || hasCustomEncoding) lines.push("import type { PostOptions } from '../common/interfaces/post-options.interface';");
5998
6065
  const importedTypeNames = /* @__PURE__ */ new Set();
6066
+ if (needsPaginationOptionsImport) importedTypeNames.add("PaginationOptions");
5999
6067
  for (const optionType of optionObjectTypes) {
6000
6068
  if (isValidTypeIdentifier(optionType)) {
6001
6069
  if (importedTypeNames.has(optionType)) continue;
@@ -6554,7 +6622,14 @@ function renderOptionsObjectMethod(lines, op, plan, method, service, ctx, modelM
6554
6622
  lines.push(" }");
6555
6623
  return true;
6556
6624
  }
6557
- return false;
6625
+ {
6626
+ lines.push(` async ${method}(${renderOptionsParam(optionParam)}): Promise<void> {`);
6627
+ renderOptionsObjectDestructure(lines, pathBindings);
6628
+ const emptyBodyArg = httpMethodNeedsBody(op.httpMethod) ? ", {}" : "";
6629
+ lines.push(` await this.workos.${op.httpMethod}(${pathStr}${emptyBodyArg}${queryOptionsArg});`);
6630
+ lines.push(" }");
6631
+ return true;
6632
+ }
6558
6633
  }
6559
6634
  function renderOptionsObjectDestructure(lines, pathBindings, restName) {
6560
6635
  if (pathBindings.length > 0 && restName) lines.push(` const { ${pathBindings.join(", ")}, ...${restName} } = options;`);
@@ -6574,7 +6649,8 @@ function bodyArgExprWithParam(bodyExpr, paramName) {
6574
6649
  return paramName === "payload" ? bodyExpr : bodyExpr.replace(/\bpayload\b/g, paramName);
6575
6650
  }
6576
6651
  function buildOptionsObjectPathBindings(op, optionType, ctx) {
6577
- return op.pathParams.map((param) => resolveOptionsObjectField$1(fieldName$6(param.name), optionType, ctx));
6652
+ const pathFieldMap = operationOverrideFor$1(ctx, op)?.pathFieldMap;
6653
+ return op.pathParams.map((param) => resolveOptionsObjectField$1(fieldName$6(param.name), optionType, ctx, pathFieldMap));
6578
6654
  }
6579
6655
  /**
6580
6656
  * Map spec path-param names (e.g. `omId`) to the SDK field name exposed on
@@ -6585,14 +6661,17 @@ function buildOptionsObjectPathBindings(op, optionType, ctx) {
6585
6661
  */
6586
6662
  function buildOptionsObjectPathParamMap(op, optionType, ctx) {
6587
6663
  const map = /* @__PURE__ */ new Map();
6664
+ const pathFieldMap = operationOverrideFor$1(ctx, op)?.pathFieldMap;
6588
6665
  for (const param of op.pathParams) {
6589
6666
  const localName = fieldName$6(param.name);
6590
- const sdkField = resolveOptionsObjectField$1(localName, optionType, ctx);
6667
+ const sdkField = resolveOptionsObjectField$1(localName, optionType, ctx, pathFieldMap);
6591
6668
  if (sdkField !== localName) map.set(param.name, sdkField);
6592
6669
  }
6593
6670
  return map;
6594
6671
  }
6595
- function resolveOptionsObjectField$1(localName, optionType, ctx) {
6672
+ function resolveOptionsObjectField$1(localName, optionType, ctx, pathFieldMap) {
6673
+ const mapped = pathFieldMap?.[localName];
6674
+ if (mapped) return mapped;
6596
6675
  const fields = ctx.apiSurface?.interfaces?.[optionType]?.fields;
6597
6676
  if (!fields) return localName;
6598
6677
  if (fields[localName]) return localName;
@@ -7421,7 +7500,7 @@ function generateModels$7(models, ctx, shared) {
7421
7500
  "",
7422
7501
  ...aliasExports
7423
7502
  ] : [...aliasExports];
7424
- files.push({
7503
+ if (isModelInScope(model.name, ctx)) files.push({
7425
7504
  path: aliasPath,
7426
7505
  content: aliasLines.join("\n"),
7427
7506
  overwriteExisting: true
@@ -7548,7 +7627,7 @@ function generateModels$7(models, ctx, shared) {
7548
7627
  else {
7549
7628
  lines.push(`export interface ${domainName}${typeParams} {`);
7550
7629
  for (const field of model.fields) {
7551
- const domainFieldName = fieldName$6(field.name);
7630
+ const domainFieldName = fieldName$6(field.domainName ?? field.name);
7552
7631
  if (seenDomainFields.has(domainFieldName)) continue;
7553
7632
  seenDomainFields.add(domainFieldName);
7554
7633
  if (field.description || field.deprecated || field.readOnly || field.writeOnly || field.default !== void 0) {
@@ -7565,7 +7644,10 @@ function generateModels$7(models, ctx, shared) {
7565
7644
  const responseBaselineField = baselineResponse?.fields?.[domainWireField];
7566
7645
  const domainResponseOptionalMismatch = baselineField && !baselineField.optional && responseBaselineField && responseBaselineField.optional;
7567
7646
  const readonlyPrefix = field.readOnly ? "readonly " : "";
7568
- if (baselineField && !domainResponseOptionalMismatch && !hasDateTimeConversion(field.type) && baselineTypeResolvable(baselineField.type, importableNames) && baselineFieldCompatible(baselineField, field)) {
7647
+ if (genericDefaults.has(model.name) && baselineField && typeReferencesUnresolvable(baselineField.type, unresolvableNames)) {
7648
+ const opt = baselineField.optional ? "?" : "";
7649
+ lines.push(` ${readonlyPrefix}${domainFieldName}${opt}: ${substituteGenericParam(baselineField.type, unresolvableNames)};`);
7650
+ } else if (baselineField && !domainResponseOptionalMismatch && !hasDateTimeConversion(field.type) && baselineTypeResolvable(baselineField.type, importableNames) && baselineFieldCompatible(baselineField, field)) {
7569
7651
  const opt = baselineField.optional ? "?" : "";
7570
7652
  lines.push(` ${readonlyPrefix}${domainFieldName}${opt}: ${baselineField.type};`);
7571
7653
  } else {
@@ -7588,7 +7670,10 @@ function generateModels$7(models, ctx, shared) {
7588
7670
  if (seenWireFields.has(wireField)) continue;
7589
7671
  seenWireFields.add(wireField);
7590
7672
  const baselineField = baselineResponse?.fields?.[wireField];
7591
- if (baselineField && baselineTypeResolvable(baselineField.type, importableNames) && baselineFieldCompatible(baselineField, field)) {
7673
+ if (genericDefaults.has(model.name) && baselineField && typeReferencesUnresolvable(baselineField.type, unresolvableNames)) {
7674
+ const opt = baselineField.optional ? "?" : "";
7675
+ lines.push(` ${wireField}${opt}: ${substituteGenericParam(baselineField.type, unresolvableNames)};`);
7676
+ } else if (baselineField && baselineTypeResolvable(baselineField.type, importableNames) && baselineFieldCompatible(baselineField, field)) {
7592
7677
  const opt = baselineField.optional ? "?" : "";
7593
7678
  lines.push(` ${wireField}${opt}: ${baselineField.type};`);
7594
7679
  } else {
@@ -7673,7 +7758,7 @@ function generateModels$7(models, ctx, shared) {
7673
7758
  for (const [alias, typeExpr] of typeDecls) if (new RegExp(`\\b${alias}\\b`).test(bodyText)) usedDecls.push(`type ${alias} = ${typeExpr};`);
7674
7759
  if (usedDecls.length > 0) lines.splice(typeDeclInsertIdx, 0, ...usedDecls, "");
7675
7760
  }
7676
- files.push({
7761
+ if (isModelInScope(model.name, ctx)) files.push({
7677
7762
  path: filePath,
7678
7763
  content: pruneUnusedImports(lines).join("\n"),
7679
7764
  overwriteExisting: true
@@ -7786,7 +7871,7 @@ function generateSerializers(models, ctx, shared) {
7786
7871
  if (!canonSkipDeserialize) parts.push(`deserialize${canonDomainName} as deserialize${domainName}`);
7787
7872
  if (!canonSkipSerialize) parts.push(`serialize${canonDomainName} as serialize${domainName}`);
7788
7873
  const reexportContent = `export { ${parts.join(", ")} } from '${rel}';`;
7789
- files.push({
7874
+ if (isModelInScope(model.name, ctx)) files.push({
7790
7875
  path: serializerPath,
7791
7876
  content: reexportContent,
7792
7877
  overwriteExisting: true
@@ -7814,7 +7899,7 @@ function generateSerializers(models, ctx, shared) {
7814
7899
  responseReachableModels,
7815
7900
  ctx
7816
7901
  }), ...emitSerializerBody(model, domainName, responseName, typeParams, baselineDomain, baselineResponse, skipFormatFields, shouldSkipSerialize, shouldSkipDeserialize, ctx)];
7817
- files.push({
7902
+ if (isModelInScope(model.name, ctx)) files.push({
7818
7903
  path: serializerPath,
7819
7904
  content: pruneUnusedImports(lines).join("\n"),
7820
7905
  overwriteExisting: true
@@ -8031,6 +8116,19 @@ function hasSpecificIRType(ref) {
8031
8116
  default: return false;
8032
8117
  }
8033
8118
  }
8119
+ /** True when a baseline field type references one of the model's generic
8120
+ * param identifiers (collected as `unresolvableNames` — bare type names that
8121
+ * resolve to nothing importable, which for a generic model are its params). */
8122
+ function typeReferencesUnresolvable(type, unresolvable) {
8123
+ if (unresolvable.size === 0) return false;
8124
+ const names = type.match(/\b[A-Z][a-zA-Z0-9]*\b/g);
8125
+ return !!names && names.some((n) => unresolvable.has(n));
8126
+ }
8127
+ /** Rewrite a baseline field type so references to the model's (renamed)
8128
+ * generic param resolve to the emitted `GenericType` param. */
8129
+ function substituteGenericParam(type, unresolvable) {
8130
+ return type.replace(/\b[A-Z][a-zA-Z0-9]*\b/g, (name) => unresolvable.has(name) ? "GenericType" : name);
8131
+ }
8034
8132
  function renderTypeParams(model, genericDefaults) {
8035
8133
  if (!model.typeParams?.length) {
8036
8134
  if (genericDefaults?.has(model.name)) return "<GenericType extends Record<string, unknown> = Record<string, unknown>>";
@@ -8557,10 +8655,11 @@ function optionsObjectParam(method) {
8557
8655
  const [param] = method.params;
8558
8656
  if (param.name !== "options") return void 0;
8559
8657
  if (param.passingStyle && param.passingStyle !== "options_object") return void 0;
8560
- if (!param.type || /^(Record|object|any|unknown)\b/.test(param.type)) return void 0;
8658
+ const type = param.type?.replace(/(?:\s*\|\s*(?:undefined|null))+\s*$/, "").trim();
8659
+ if (!type || /^(Record|object|any|unknown)\b/.test(type)) return void 0;
8561
8660
  return {
8562
8661
  name: param.name,
8563
- type: param.type
8662
+ type
8564
8663
  };
8565
8664
  }
8566
8665
  function configuredOptionsMethod(ctx, op) {
@@ -8607,7 +8706,7 @@ function generateTests$7(spec, ctx) {
8607
8706
  const mountGroups = groupByMount(ctx);
8608
8707
  const mountAccessors = /* @__PURE__ */ new Map();
8609
8708
  for (const r of ctx.resolvedOperations ?? []) if (!mountAccessors.has(r.mountOn)) mountAccessors.set(r.mountOn, servicePropertyName$4(r.mountOn));
8610
- const testEntries = mountGroups.size > 0 ? [...mountGroups].map(([name, group]) => ({
8709
+ const testEntries = mountGroups.size > 0 || ctx.scopedServices?.size ? [...mountGroups].map(([name, group]) => ({
8611
8710
  name,
8612
8711
  operations: group.operations
8613
8712
  })) : spec.services.map((s) => ({
@@ -8618,6 +8717,7 @@ function generateTests$7(spec, ctx) {
8618
8717
  if (ctx.apiSurface?.classes?.["WorkOS"]?.methods) for (const name of Object.keys(ctx.apiSurface.classes["WorkOS"].methods)) baselineWorkOSProps.add(name);
8619
8718
  if (ctx.apiSurface?.classes?.["WorkOS"]?.properties) for (const name of Object.keys(ctx.apiSurface.classes["WorkOS"].properties)) baselineWorkOSProps.add(name);
8620
8719
  for (const { name: mountName, operations } of testEntries) {
8720
+ if (!isMountInScope(mountName, ctx)) continue;
8621
8721
  if (operations.length === 0) continue;
8622
8722
  const mergedService = {
8623
8723
  name: mountName,
@@ -8625,6 +8725,7 @@ function generateTests$7(spec, ctx) {
8625
8725
  };
8626
8726
  const ops = isNodeOwnedService(ctx, mountName, resolveResourceClassName$3(mergedService, ctx)) ? operations : uncoveredOperations(mergedService, ctx);
8627
8727
  if (ops.length === 0) continue;
8728
+ const ignoredMethodNames = ignoredResourceMethodNames(ctx, `src/${resolveResourceDir(mergedService, ctx)}/${fileName$3(resolveResourceClassName$3(mergedService, ctx))}.ts`);
8628
8729
  const propName = mountAccessors.get(mountName) ?? servicePropertyName$4(mountName);
8629
8730
  if (ctx.apiSurface && baselineWorkOSProps.size > 0 && !baselineWorkOSProps.has(propName)) continue;
8630
8731
  if (resolveResourceClassName$3(mergedService, ctx) !== mountName) continue;
@@ -8632,23 +8733,31 @@ function generateTests$7(spec, ctx) {
8632
8733
  ...mergedService,
8633
8734
  operations: ops
8634
8735
  } : mergedService;
8635
- files.push(generateServiceTest$3(testService, spec, ctx, modelMap, mountAccessors));
8736
+ files.push(generateServiceTest$3(testService, spec, ctx, modelMap, mountAccessors, ignoredMethodNames));
8636
8737
  }
8637
8738
  const serializerTests = generateSerializerTests(spec, ctx);
8638
8739
  for (const f of serializerTests) files.push(f);
8639
8740
  return files;
8640
8741
  }
8641
- function generateServiceTest$3(service, spec, ctx, modelMap, mountAccessors) {
8742
+ function generateServiceTest$3(service, spec, ctx, modelMap, mountAccessors, ignoredMethodNames = /* @__PURE__ */ new Set()) {
8642
8743
  const resolvedName = resolveResourceClassName$3(service, ctx);
8643
8744
  const serviceDir = resolveResourceDir(service, ctx);
8644
8745
  const serviceClass = resolvedName;
8645
8746
  const serviceProp = mountAccessors?.get(service.name) ?? servicePropertyName$4(resolveServiceName(service, ctx));
8646
8747
  const testPath = `src/${serviceDir}/${fileName$3(resolvedName)}.spec.ts`;
8647
- const plans = service.operations.map((op) => ({
8748
+ const allPlans = service.operations.map((op) => ({
8648
8749
  op,
8649
- plan: planOperation(op),
8750
+ plan: planOperationFor(op, ctx),
8650
8751
  method: resolveMethodName$6(op, service, ctx)
8651
8752
  }));
8753
+ const resolvedLookup = buildResolvedLookup(ctx);
8754
+ const preservedScopes = methodsWithPreservedTestBlocks(ctx, testPath);
8755
+ const plans = allPlans.filter((p) => {
8756
+ if (preservedScopes.has(p.method)) return true;
8757
+ if (ignoredMethodNames.has(p.method)) return false;
8758
+ if (lookupResolved(p.op, resolvedLookup)?.urlBuilder) return false;
8759
+ return true;
8760
+ });
8652
8761
  if (ctx.overlayLookup?.methodByOperation) {
8653
8762
  const methodOrder = /* @__PURE__ */ new Map();
8654
8763
  let pos = 0;
@@ -8695,7 +8804,7 @@ function generateServiceTest$3(service, spec, ctx, modelMap, mountAccessors) {
8695
8804
  lines.push("");
8696
8805
  lines.push("const workos = new WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');");
8697
8806
  lines.push("");
8698
- const { lines: helperLines, helpers: entityHelperNames } = generateEntityHelpers(plans, modelMap, ctx);
8807
+ const { lines: helperLines, helpers: entityHelperNames } = generateEntityHelpers(service, allPlans, plans, existingTestIgnoreText(ctx, testPath), modelMap, ctx);
8699
8808
  for (const line of helperLines) lines.push(line);
8700
8809
  lines.push(`describe('${serviceClass}', () => {`);
8701
8810
  lines.push(" beforeEach(() => fetch.resetMocks());");
@@ -8711,6 +8820,20 @@ function generateServiceTest$3(service, spec, ctx, modelMap, mountAccessors) {
8711
8820
  lines.push(" });");
8712
8821
  }
8713
8822
  lines.push("});");
8823
+ const body = lines.join("\n");
8824
+ const enumImportLines = [];
8825
+ for (const [name, info] of Object.entries(ctx.apiSurface?.enums ?? {})) {
8826
+ if (!new RegExp(`\\b${name}\\.[A-Za-z_$]`).test(body)) continue;
8827
+ if (new RegExp(`import\\b[^;]*\\b${name}\\b[^;]*from`).test(body)) continue;
8828
+ const sourceFile = info.sourceFile;
8829
+ const spec = sourceFile ? relativeImport(testPath, sourceFile).replace(/\.ts$/, "") : "./interfaces";
8830
+ enumImportLines.push(`import { ${name} } from '${spec}';`);
8831
+ }
8832
+ if (enumImportLines.length > 0) {
8833
+ const anchor = lines.indexOf("import { WorkOS } from '../workos';");
8834
+ const at = anchor >= 0 ? anchor + 1 : 0;
8835
+ lines.splice(at, 0, ...enumImportLines);
8836
+ }
8714
8837
  return {
8715
8838
  path: testPath,
8716
8839
  content: lines.join("\n"),
@@ -8729,11 +8852,81 @@ function pathParamTestValue(param, paramName) {
8729
8852
  if (name) return `test_${fieldName$6(name)}`;
8730
8853
  return "test_id";
8731
8854
  }
8732
- function queryParamTestValue(param, modelMap) {
8855
+ /** Render an example value as a valid TS literal expression.
8856
+ * Objects and nested arrays go through JSON.stringify so map-typed params
8857
+ * (e.g. providerQueryParams) don't coerce to the literal text `[object Object]`.
8858
+ */
8859
+ function renderExampleLiteral(value) {
8860
+ if (typeof value === "string") return `'${value}'`;
8861
+ if (Array.isArray(value)) return `[${value.map(renderExampleLiteral).join(", ")}]`;
8862
+ if (value !== null && typeof value === "object") return JSON.stringify(value);
8863
+ return String(value);
8864
+ }
8865
+ /** Resolve the enum members a query-param test value must satisfy. An enum is
8866
+ * expressed several ways: an inline `EnumRef` with `values`, an `enum`/`model`
8867
+ * ref by name, or — when the IR flattened the param to a bare string but the
8868
+ * hand-written options interface still types the field as an enum — the
8869
+ * baseline options field type (e.g. `ConnectionType`). */
8870
+ /** All valid values for an enum named `name`. The extracted SDK surface is
8871
+ * consulted first: when an options field is typed with a hand-written enum
8872
+ * (e.g. `ConnectionType` from `connection-type.enum.ts`), that enum — not a
8873
+ * same-named IR enum that may carry extra members like `Pending` — is what
8874
+ * the generated test must type-check against. Falls back to the IR spec. */
8875
+ function enumValuesByName(name, ctx) {
8876
+ const members = ctx?.apiSurface?.enums?.[name]?.members;
8877
+ if (members && Object.keys(members).length > 0) return Object.values(members);
8878
+ const specValues = ctx?.spec.enums.find((e) => e.name === name)?.values;
8879
+ if (specValues?.length) return specValues.map((v) => v.value);
8880
+ }
8881
+ function resolveParamEnumValues(param, optionFieldType, ctx) {
8882
+ if (optionFieldType) {
8883
+ const bare = optionFieldType.replace(/\[\]/g, "").replace(/\|\s*(undefined|null)/g, "").trim();
8884
+ if (/^[A-Za-z_$][\w$]*$/.test(bare)) {
8885
+ const values = enumValuesByName(bare, ctx);
8886
+ if (values) return values;
8887
+ }
8888
+ }
8889
+ if ((param.type.kind === "enum" || param.type.kind === "model") && param.type.name) {
8890
+ const values = enumValuesByName(param.type.name, ctx);
8891
+ if (values) return values;
8892
+ }
8893
+ if (param.type.kind === "enum" && param.type.values?.length) return param.type.values;
8894
+ }
8895
+ /** When an options field is typed with a real (nominal) TS `enum` from the SDK
8896
+ * surface, a string literal won't type-check — the value must be a member
8897
+ * reference (`ConnectionType.ADFSSAML`). Returns the enum's name + members so
8898
+ * the caller can both render the reference and import the right declaration. */
8899
+ function resolveRealEnum(param, optionFieldType, ctx) {
8900
+ const candidates = [];
8901
+ if (optionFieldType) {
8902
+ const bare = optionFieldType.replace(/\[\]/g, "").replace(/\|\s*(undefined|null)/g, "").trim();
8903
+ if (/^[A-Za-z_$][\w$]*$/.test(bare)) candidates.push(bare);
8904
+ }
8905
+ if ((param.type.kind === "enum" || param.type.kind === "model") && param.type.name) candidates.push(param.type.name);
8906
+ for (const name of candidates) {
8907
+ const members = ctx?.apiSurface?.enums?.[name]?.members;
8908
+ if (members && Object.keys(members).length > 0) return {
8909
+ name,
8910
+ members
8911
+ };
8912
+ }
8913
+ return null;
8914
+ }
8915
+ function queryParamTestValue(param, modelMap, ctx, optionFieldType) {
8916
+ const realEnum = resolveRealEnum(param, optionFieldType, ctx);
8917
+ if (realEnum) {
8918
+ const entries = Object.entries(realEnum.members);
8919
+ const [memberKey] = (typeof param.example === "string" ? entries.find(([, v]) => v === param.example) : void 0) ?? entries[0];
8920
+ return `${realEnum.name}.${memberKey}`;
8921
+ }
8922
+ const enumValues = resolveParamEnumValues(param, optionFieldType, ctx);
8923
+ if (enumValues?.length) {
8924
+ const valid = typeof param.example === "string" && enumValues.includes(param.example) ? param.example : enumValues[0];
8925
+ return typeof valid === "string" ? `'${valid}'` : String(valid);
8926
+ }
8733
8927
  if (param.example !== void 0) {
8734
- if (Array.isArray(param.example)) return `[${param.example.map((v) => typeof v === "string" ? `'${v}'` : String(v)).join(", ")}]`;
8735
8928
  if (param.type.kind === "primitive" && param.type.format === "date-time" && typeof param.example === "string") return `new Date('${param.example}')`;
8736
- return typeof param.example === "string" ? `'${param.example}'` : String(param.example);
8929
+ return renderExampleLiteral(param.example);
8737
8930
  }
8738
8931
  return fixtureValueForType(param.type, param.name, "Options", modelMap) ?? "'test'";
8739
8932
  }
@@ -8899,9 +9092,10 @@ function buildOptionsObjectTestArg(op, plan, baselineMethod, modelMap, ctx) {
8899
9092
  const optionParam = optionsObjectParam(baselineMethod);
8900
9093
  if (!optionParam) return null;
8901
9094
  const entries = [];
9095
+ const pathFieldMap = ctx ? operationOverrideFor(ctx, op)?.pathFieldMap : void 0;
8902
9096
  for (const param of op.pathParams) {
8903
9097
  const localName = fieldName$6(param.name);
8904
- const optionField = resolveOptionsObjectField(localName, optionParam.type, ctx);
9098
+ const optionField = resolveOptionsObjectField(localName, optionParam.type, ctx, pathFieldMap);
8905
9099
  entries.push(`${optionField}: ${JSON.stringify(pathParamTestValue(param, localName))}`);
8906
9100
  }
8907
9101
  if (plan.isPaginated) entries.push("order: 'desc'");
@@ -8913,7 +9107,8 @@ function buildOptionsObjectTestArg(op, plan, baselineMethod, modelMap, ctx) {
8913
9107
  ].includes(param.name)) : op.queryParams).filter((param) => !param.deprecated);
8914
9108
  for (const param of queryParams) {
8915
9109
  const localName = fieldName$6(param.name);
8916
- const value = queryParamTestValue(param, modelMap);
9110
+ const optionFieldType = ctx?.apiSurface?.interfaces?.[optionParam.type]?.fields?.[localName]?.type;
9111
+ const value = queryParamTestValue(param, modelMap, ctx, optionFieldType);
8917
9112
  entries.push(`${localName}: ${value}`);
8918
9113
  }
8919
9114
  if (plan.hasBody) {
@@ -8941,7 +9136,9 @@ function objectLiteralEntries(literal) {
8941
9136
  const body = trimmed.slice(1, -1).trim();
8942
9137
  return body ? body.split(",").map((entry) => entry.trim()) : [];
8943
9138
  }
8944
- function resolveOptionsObjectField(localName, optionType, ctx) {
9139
+ function resolveOptionsObjectField(localName, optionType, ctx, pathFieldMap) {
9140
+ const mapped = pathFieldMap?.[localName];
9141
+ if (mapped) return mapped;
8945
9142
  const fields = ctx?.apiSurface?.interfaces?.[optionType]?.fields;
8946
9143
  if (!fields) return localName;
8947
9144
  if (fields[localName]) return localName;
@@ -8956,20 +9153,85 @@ function resolveOptionsObjectField(localName, optionType, ctx) {
8956
9153
  * Generate per-entity assertion helper functions for models used in 2+ tests.
8957
9154
  * Returns { lines, helpers } where helpers is a Set of helper function names.
8958
9155
  */
8959
- function generateEntityHelpers(plans, modelMap, ctx) {
8960
- const modelUsage = /* @__PURE__ */ new Map();
8961
- for (const { op, plan } of plans) {
8962
- let modelName = null;
9156
+ /** Describe-scope names (i.e. method names) that have an `@oagen-ignore` block
9157
+ * nested inside them in the existing test file. Such a `describe` must keep
9158
+ * being emitted even when the method is hand-owned — otherwise the engine has
9159
+ * no `describe` to re-nest the preserved block under and orphans it to the top
9160
+ * of the file (out of `beforeEach` scope), which hangs at runtime. Mirrors the
9161
+ * engine's `findContainingDescribeScope`. */
9162
+ function methodsWithPreservedTestBlocks(ctx, relPath) {
9163
+ const scopes = /* @__PURE__ */ new Set();
9164
+ const root = ctx.outputDir ?? ctx.targetDir;
9165
+ if (!root) return scopes;
9166
+ let content;
9167
+ try {
9168
+ content = fs.readFileSync(path.join(root, relPath), "utf8");
9169
+ } catch {
9170
+ return scopes;
9171
+ }
9172
+ const lines = content.split("\n");
9173
+ const indentOf = (l) => l.length - l.trimStart().length;
9174
+ for (let i = 0; i < lines.length; i++) {
9175
+ if (!lines[i].includes("@oagen-ignore-start")) continue;
9176
+ if (/^\S/.test(lines[i])) continue;
9177
+ const markerIndent = indentOf(lines[i]);
9178
+ for (let j = i - 1; j >= 0; j--) {
9179
+ if (indentOf(lines[j]) >= markerIndent) continue;
9180
+ const m = lines[j].match(/^\s*describe\((['"`])(.+?)\1\s*,/);
9181
+ if (m) {
9182
+ scopes.add(m[2]);
9183
+ break;
9184
+ }
9185
+ if (/^\s*(?:export\s+)?class\s+\w+/.test(lines[j])) break;
9186
+ }
9187
+ }
9188
+ return scopes;
9189
+ }
9190
+ /** Concatenated text of the existing test file's `@oagen-ignore` regions, so
9191
+ * helper generation can see which `expect<Model>()` helpers preserved
9192
+ * hand-written test blocks still reference. */
9193
+ function existingTestIgnoreText(ctx, relPath) {
9194
+ const root = ctx.outputDir ?? ctx.targetDir;
9195
+ if (!root) return "";
9196
+ let content;
9197
+ try {
9198
+ content = fs.readFileSync(path.join(root, relPath), "utf8");
9199
+ } catch {
9200
+ return "";
9201
+ }
9202
+ return [...content.matchAll(/@oagen-ignore-start[\s\S]*?@oagen-ignore-end/g)].map((m) => m[0]).join("\n");
9203
+ }
9204
+ function generateEntityHelpers(service, allPlans, renderedPlans, ignoreRegionText, modelMap, ctx) {
9205
+ const responseModelOf = (entry) => {
9206
+ const { op, plan } = entry;
8963
9207
  if (plan.isPaginated && op.pagination?.itemType.kind === "model") {
8964
- modelName = op.pagination.itemType.name;
9208
+ let modelName = op.pagination.itemType.name;
8965
9209
  const rawModel = modelMap.get(modelName);
8966
9210
  if (rawModel) {
8967
9211
  const unwrapped = unwrapListModel$4(rawModel, modelMap);
8968
9212
  if (unwrapped) modelName = unwrapped.name;
8969
9213
  }
8970
- } else if (plan.responseModelName) modelName = plan.responseModelName;
9214
+ return modelName;
9215
+ }
9216
+ return plan.responseModelName ?? null;
9217
+ };
9218
+ const modelUsage = /* @__PURE__ */ new Map();
9219
+ for (const entry of allPlans) {
9220
+ const modelName = responseModelOf(entry);
8971
9221
  if (modelName) modelUsage.set(modelName, (modelUsage.get(modelName) ?? 0) + 1);
8972
9222
  }
9223
+ const renderedModels = /* @__PURE__ */ new Set();
9224
+ for (const entry of renderedPlans) {
9225
+ const modelName = responseModelOf(entry);
9226
+ if (!modelName) continue;
9227
+ if (entry.plan.isPaginated && entry.op.pagination?.itemType.kind === "model") {
9228
+ const baselineMethod = optionsMethodFor(service, entry.method, entry.op, entry.plan, ctx);
9229
+ const baselineItemType = autoPaginatableItemType(baselineMethod?.returnType);
9230
+ const generatedItemType = resolveInterfaceName(modelName, ctx);
9231
+ if (Boolean(baselineMethod?.returnType && !baselineItemType) || Boolean(baselineItemType && generatedItemType && baselineItemType !== generatedItemType)) continue;
9232
+ }
9233
+ renderedModels.add(modelName);
9234
+ }
8973
9235
  const lines = [];
8974
9236
  const helpers = /* @__PURE__ */ new Set();
8975
9237
  for (const [modelName, count] of modelUsage) {
@@ -8980,6 +9242,7 @@ function generateEntityHelpers(plans, modelMap, ctx) {
8980
9242
  if (assertions.length === 0) continue;
8981
9243
  const helperName = `expect${resolveInterfaceName(modelName, ctx)}`;
8982
9244
  if (helpers.has(helperName)) continue;
9245
+ if (!(renderedModels.has(modelName) || ignoreRegionText.includes(`${helperName}(`))) continue;
8983
9246
  helpers.add(helperName);
8984
9247
  lines.push(`function ${helperName}(result: any) {`);
8985
9248
  for (const assertion of assertions) lines.push(` ${assertion}`);
@@ -9009,7 +9272,7 @@ function buildFieldAssertions(model, accessor, modelMap) {
9009
9272
  const assertions = [];
9010
9273
  for (const field of model.fields) {
9011
9274
  if (!field.required) continue;
9012
- const domainField = fieldName$6(field.name);
9275
+ const domainField = fieldName$6(field.domainName ?? field.name);
9013
9276
  const isDateTime = isDateTimeFieldType(field.type);
9014
9277
  const fieldAccessor = isDateTime ? `${accessor}.${domainField}.toISOString()` : `${accessor}.${domainField}`;
9015
9278
  if (field.example !== void 0) {
@@ -10075,6 +10338,21 @@ function withNodeOperationOverrides(ctx) {
10075
10338
  return next;
10076
10339
  }
10077
10340
  //#endregion
10341
+ //#region src/shared/file-header.ts
10342
+ /**
10343
+ * Canonical "do not edit" banner text shared by every emitter's
10344
+ * `fileHeader()`. The text is comment-syntax agnostic — each emitter prefixes
10345
+ * it with the appropriate comment marker for its language (`//`, `#`, etc.).
10346
+ *
10347
+ * Keeping this in one place prevents the wording from drifting between
10348
+ * languages (Rust previously emitted a different banner).
10349
+ *
10350
+ * Go intentionally does NOT use this constant: its header must match the
10351
+ * standard `^// Code generated .* DO NOT EDIT\.$` marker that Go tooling
10352
+ * relies on to recognize generated files. See `src/go/index.ts`.
10353
+ */
10354
+ const AUTOGEN_NOTICE = "This file is auto-generated by oagen. Do not edit.";
10355
+ //#endregion
10078
10356
  //#region src/node/index.ts
10079
10357
  /**
10080
10358
  * Cache live-surface per ctx — every emitter method receives the same ctx in
@@ -10620,7 +10898,7 @@ const nodeEmitter = {
10620
10898
  return {};
10621
10899
  },
10622
10900
  fileHeader() {
10623
- return "// This file is auto-generated by oagen. Do not edit.";
10901
+ return `// ${AUTOGEN_NOTICE}`;
10624
10902
  },
10625
10903
  formatCommand(targetDir) {
10626
10904
  const hasPrettier = fs$1.existsSync(path$1.join(targetDir, ".prettierrc"));
@@ -10688,6 +10966,15 @@ function fieldName$5(name) {
10688
10966
  return toSnakeCase(name);
10689
10967
  }
10690
10968
  /**
10969
+ * snake_case domain field name for a model field, honoring a `domainName`
10970
+ * override (set via the `fieldHints` config) so a wire field can surface under
10971
+ * a friendlier name. The wire name (still derived from `field.name`) is
10972
+ * unaffected, so the API contract is preserved.
10973
+ */
10974
+ function domainFieldName$5(field) {
10975
+ return toSnakeCase(field.domainName ?? field.name);
10976
+ }
10977
+ /**
10691
10978
  * Python builtins that should not be shadowed by parameter names.
10692
10979
  * When a path/query param name collides, suffix with underscore.
10693
10980
  */
@@ -11230,6 +11517,7 @@ function generateEnums$6(enums, ctx) {
11230
11517
  const aliasOf = placement.enumAliases;
11231
11518
  for (const enumDef of enums) {
11232
11519
  const dirName = resolveDir(enumToService.get(enumDef.name));
11520
+ const enumInScope = isEnumInScope(enumDef.name, ctx);
11233
11521
  const canonicalName = aliasOf.get(enumDef.name);
11234
11522
  if (canonicalName) {
11235
11523
  if (fileName$2(enumDef.name) === fileName$2(canonicalName)) continue;
@@ -11256,7 +11544,7 @@ function generateEnums$6(enums, ctx) {
11256
11544
  lines.push(" raise AttributeError(f\"module {__name__!r} has no attribute {name!r}\")");
11257
11545
  }
11258
11546
  lines.push(`__all__ = ["${aliasCls}"]`);
11259
- files.push({
11547
+ if (enumInScope) files.push({
11260
11548
  path: `src/${ctx.namespace}/${dirName}/models/${fileName$2(enumDef.name)}.py`,
11261
11549
  content: lines.join("\n"),
11262
11550
  integrateTarget: true,
@@ -11287,7 +11575,7 @@ function generateEnums$6(enums, ctx) {
11287
11575
  `__all__ = ["${aliasName}"]`
11288
11576
  ].join("\n");
11289
11577
  }
11290
- files.push({
11578
+ if (enumInScope) files.push({
11291
11579
  path: `src/${ctx.namespace}/${dirName}/models/${fileName$2(aliasName)}.py`,
11292
11580
  content: compatContent,
11293
11581
  integrateTarget: true,
@@ -11386,24 +11674,26 @@ function generateEnums$6(enums, ctx) {
11386
11674
  lines.push("");
11387
11675
  lines.push(`${cls}Literal: TypeAlias = Literal[${uniqueValues.map((v) => typeof v.value === "string" ? `"${v.value}"` : typeof v.value === "boolean" ? v.value ? "True" : "False" : String(v.value)).join(", ")}]`);
11388
11676
  }
11389
- files.push({
11390
- path: `src/${ctx.namespace}/${dirName}/models/${fileName$2(enumDef.name)}.py`,
11391
- content: lines.join("\n"),
11392
- integrateTarget: true,
11393
- overwriteExisting: true
11394
- });
11395
- for (const aliasName of compatAliases.get(enumDef.name) ?? []) files.push({
11396
- path: `src/${ctx.namespace}/${dirName}/models/${fileName$2(aliasName)}.py`,
11397
- content: [
11398
- "from typing import TypeAlias",
11399
- `from .${fileName$2(enumDef.name)} import ${cls}`,
11400
- "",
11401
- `${aliasName}: TypeAlias = ${cls}`,
11402
- `__all__ = ["${aliasName}"]`
11403
- ].join("\n"),
11404
- integrateTarget: true,
11405
- overwriteExisting: true
11406
- });
11677
+ if (enumInScope) {
11678
+ files.push({
11679
+ path: `src/${ctx.namespace}/${dirName}/models/${fileName$2(enumDef.name)}.py`,
11680
+ content: lines.join("\n"),
11681
+ integrateTarget: true,
11682
+ overwriteExisting: true
11683
+ });
11684
+ for (const aliasName of compatAliases.get(enumDef.name) ?? []) files.push({
11685
+ path: `src/${ctx.namespace}/${dirName}/models/${fileName$2(aliasName)}.py`,
11686
+ content: [
11687
+ "from typing import TypeAlias",
11688
+ `from .${fileName$2(enumDef.name)} import ${cls}`,
11689
+ "",
11690
+ `${aliasName}: TypeAlias = ${cls}`,
11691
+ `__all__ = ["${aliasName}"]`
11692
+ ].join("\n"),
11693
+ integrateTarget: true,
11694
+ overwriteExisting: true
11695
+ });
11696
+ }
11407
11697
  }
11408
11698
  return files;
11409
11699
  }
@@ -11540,7 +11830,7 @@ function generateModels$6(models, ctx) {
11540
11830
  dispLines.push(" if dispatch_cls is not None:");
11541
11831
  dispLines.push(` return cast("${variantTypeName}", dispatch_cls.from_dict(data))`);
11542
11832
  dispLines.push(` return ${unknownClassName}.from_dict(data)`);
11543
- files.push({
11833
+ if (isModelInScope(model.name, ctx)) files.push({
11544
11834
  path: `src/${ctx.namespace}/${dirName}/models/${fileName$2(model.name)}.py`,
11545
11835
  content: dispLines.join("\n"),
11546
11836
  integrateTarget: true,
@@ -11571,7 +11861,7 @@ function generateModels$6(models, ctx) {
11571
11861
  else lines.push(`from ${ctx.namespace}.${dirToModule(canonicalDir)}.models.${fileName$2(canonicalName)} import ${canonicalClassName}`);
11572
11862
  lines.push("");
11573
11863
  lines.push(`${modelClassName}: TypeAlias = ${canonicalClassName}`);
11574
- files.push({
11864
+ if (isModelInScope(model.name, ctx)) files.push({
11575
11865
  path: `src/${ctx.namespace}/${dirName}/models/${fileName$2(model.name)}.py`,
11576
11866
  content: lines.join("\n"),
11577
11867
  integrateTarget: true,
@@ -11585,7 +11875,7 @@ function generateModels$6(models, ctx) {
11585
11875
  }
11586
11876
  const seenFieldNames = /* @__PURE__ */ new Set();
11587
11877
  const deduplicatedFields = model.fields.filter((f) => {
11588
- const pyName = fieldName$5(f.name);
11878
+ const pyName = domainFieldName$5(f);
11589
11879
  if (seenFieldNames.has(pyName)) return false;
11590
11880
  seenFieldNames.add(pyName);
11591
11881
  return true;
@@ -11647,7 +11937,7 @@ function generateModels$6(models, ctx) {
11647
11937
  return typeStr;
11648
11938
  };
11649
11939
  for (const field of requiredFields) {
11650
- const pyFieldName = fieldName$5(field.name);
11940
+ const pyFieldName = domainFieldName$5(field);
11651
11941
  const pyType = rewriteDiscriminatorType(resolveModelFieldType(field.type));
11652
11942
  if (field.description || field.deprecated) {
11653
11943
  const parts = [];
@@ -11658,7 +11948,7 @@ function generateModels$6(models, ctx) {
11658
11948
  } else lines.push(` ${pyFieldName}: ${pyType}`);
11659
11949
  }
11660
11950
  for (const field of optionalFields) {
11661
- const pyFieldName = fieldName$5(field.name);
11951
+ const pyFieldName = domainFieldName$5(field);
11662
11952
  const pyType = `Optional[${rewriteDiscriminatorType(field.type.kind === "nullable" ? resolveModelFieldType(field.type.inner) : resolveModelFieldType(field.type))}]`;
11663
11953
  if (field.description || field.deprecated) {
11664
11954
  const parts = [];
@@ -11676,7 +11966,7 @@ function generateModels$6(models, ctx) {
11676
11966
  const preludeLines = [];
11677
11967
  const fieldAssignmentLines = [];
11678
11968
  for (const field of [...requiredFields, ...optionalFields]) {
11679
- const pyFieldName = fieldName$5(field.name);
11969
+ const pyFieldName = domainFieldName$5(field);
11680
11970
  const wireKey = field.name;
11681
11971
  const isRequired = !isOptionalField(model.name, field, ctx);
11682
11972
  const discPrelude = renderDiscriminatedUnionPrelude(field, pyFieldName, wireKey, modelClassName, isRequired);
@@ -11704,7 +11994,7 @@ function generateModels$6(models, ctx) {
11704
11994
  lines.push(" \"\"\"Serialize to a dictionary.\"\"\"");
11705
11995
  lines.push(" result: Dict[str, Any] = {}");
11706
11996
  for (const field of [...requiredFields, ...optionalFields]) {
11707
- const pyFieldName = fieldName$5(field.name);
11997
+ const pyFieldName = domainFieldName$5(field);
11708
11998
  const wireKey = field.name;
11709
11999
  const isRequired = !isOptionalField(model.name, field, ctx);
11710
12000
  const isNullable = field.type.kind === "nullable";
@@ -11725,7 +12015,7 @@ function generateModels$6(models, ctx) {
11725
12015
  }
11726
12016
  }
11727
12017
  lines.push(" return result");
11728
- files.push({
12018
+ if (isModelInScope(model.name, ctx)) files.push({
11729
12019
  path: `src/${ctx.namespace}/${dirName}/models/${fileName$2(model.name)}.py`,
11730
12020
  content: lines.join("\n"),
11731
12021
  integrateTarget: true,
@@ -12893,8 +13183,8 @@ function generateResources$6(services, ctx) {
12893
13183
  const resolvedLookup = buildResolvedLookup(ctx);
12894
13184
  const files = [];
12895
13185
  const mountDirMap = buildMountDirMap$1(ctx);
12896
- const mountGroups = groupByMount(ctx);
12897
- const entries = mountGroups.size > 0 ? [...mountGroups].map(([name, group]) => ({
13186
+ const mountGroups = scopedMountGroups(ctx);
13187
+ const entries = mountGroups.size > 0 || ctx.scopedServices?.size ? [...mountGroups].map(([name, group]) => ({
12898
13188
  name,
12899
13189
  operations: group.operations
12900
13190
  })) : services.map((s) => ({
@@ -13580,7 +13870,7 @@ function generateModelFixture$4(model, modelMap, enumMap) {
13580
13870
  const fixture = {};
13581
13871
  const seenFieldNames = /* @__PURE__ */ new Set();
13582
13872
  const deduplicatedFields = model.fields.filter((f) => {
13583
- const pyName = fieldName$5(f.name);
13873
+ const pyName = domainFieldName$5(f);
13584
13874
  if (seenFieldNames.has(pyName)) return false;
13585
13875
  seenFieldNames.add(pyName);
13586
13876
  return true;
@@ -13703,8 +13993,8 @@ function generateTests$6(spec, ctx) {
13703
13993
  overwriteExisting: true
13704
13994
  });
13705
13995
  const accessPaths = buildServiceAccessPaths$3(spec.services, ctx);
13706
- const mountGroups = groupByMount(ctx);
13707
- const testEntries = mountGroups.size > 0 ? [...mountGroups].map(([name, group]) => ({
13996
+ const mountGroups = scopedMountGroups(ctx);
13997
+ const testEntries = mountGroups.size > 0 || ctx.scopedServices?.size ? [...mountGroups].map(([name, group]) => ({
13708
13998
  name,
13709
13999
  operations: group.operations,
13710
14000
  resolvedOps: group.resolvedOps
@@ -14376,16 +14666,16 @@ function pickAssertableFields(modelName, spec, maxFields = 2) {
14376
14666
  if (typeof val === "string") {
14377
14667
  if (val.includes("\"") || val.includes("'") || val.includes("{") || val.includes("\\") || val.includes("\n")) continue;
14378
14668
  results.push({
14379
- field: fieldName$5(f.name),
14669
+ field: domainFieldName$5(f),
14380
14670
  value: `"${val}"`
14381
14671
  });
14382
14672
  } else if (typeof val === "boolean") results.push({
14383
- field: fieldName$5(f.name),
14673
+ field: domainFieldName$5(f),
14384
14674
  value: val ? "True" : "False",
14385
14675
  isBool: true
14386
14676
  });
14387
14677
  else if (typeof val === "number") results.push({
14388
- field: fieldName$5(f.name),
14678
+ field: domainFieldName$5(f),
14389
14679
  value: String(val)
14390
14680
  });
14391
14681
  }
@@ -14445,7 +14735,7 @@ function buildTestArgs$1(op, spec, hiddenParams) {
14445
14735
  ].includes(param.name)) continue;
14446
14736
  if (plan.hasBody && op.requestBody?.kind === "model") {
14447
14737
  const rbName = op.requestBody.name;
14448
- if (spec.models.find((m) => m.name === rbName)?.fields.some((f) => fieldName$5(f.name) === fieldName$5(param.name))) continue;
14738
+ if (spec.models.find((m) => m.name === rbName)?.fields.some((f) => domainFieldName$5(f) === fieldName$5(param.name))) continue;
14449
14739
  }
14450
14740
  if (param.required && !pathParamNames.has(fieldName$5(param.name))) args.push(`${fieldName$5(param.name)}=${generateTestValue$1(param.type, param.name)}`);
14451
14741
  }
@@ -14704,7 +14994,7 @@ function generateModelRoundTripTests(spec, ctx) {
14704
14994
  if (model.discriminator) continue;
14705
14995
  const seenFieldNames = /* @__PURE__ */ new Set();
14706
14996
  const dedupFields = model.fields.filter((f) => {
14707
- const pyName = fieldName$5(f.name);
14997
+ const pyName = domainFieldName$5(f);
14708
14998
  if (seenFieldNames.has(pyName)) return false;
14709
14999
  seenFieldNames.add(pyName);
14710
15000
  return true;
@@ -14896,7 +15186,7 @@ const pythonEmitter = {
14896
15186
  return buildOperationsMap$6(spec, ctx);
14897
15187
  },
14898
15188
  fileHeader() {
14899
- return "# This file is auto-generated by oagen. Do not edit.";
15189
+ return `# ${AUTOGEN_NOTICE}`;
14900
15190
  },
14901
15191
  formatCommand(_targetDir) {
14902
15192
  return {
@@ -14997,6 +15287,15 @@ function resolveMethodName$4(op, _service, ctx) {
14997
15287
  function fieldName$4(name) {
14998
15288
  return toCamelCase(name);
14999
15289
  }
15290
+ /**
15291
+ * camelCase DOMAIN property name for a model field, honoring a `domainName`
15292
+ * override (set via the `fieldHints` config) so a wire field can surface under
15293
+ * a friendlier PHP property name. The wire key (see {@link wireName}) still
15294
+ * derives from `field.name`. No-op when `domainName` is unset.
15295
+ */
15296
+ function domainFieldName$4(field) {
15297
+ return fieldName$4(field.domainName ?? field.name);
15298
+ }
15000
15299
  /** snake_case name for fixtures and other snake_case contexts. */
15001
15300
  function snakeName(name) {
15002
15301
  return toSnakeCase(stripUrnPrefix(name));
@@ -15131,14 +15430,14 @@ function generateModels$5(models, ctx) {
15131
15430
  const optionalFields = model.fields.filter((f) => !f.required);
15132
15431
  const seenNames = /* @__PURE__ */ new Set();
15133
15432
  const allFields = [...requiredFields, ...optionalFields].filter((f) => {
15134
- const phpName = fieldName$4(f.name);
15433
+ const phpName = domainFieldName$4(f);
15135
15434
  if (seenNames.has(phpName)) return false;
15136
15435
  seenNames.add(phpName);
15137
15436
  return true;
15138
15437
  });
15139
15438
  for (let i = 0; i < allFields.length; i++) {
15140
15439
  const field = allFields[i];
15141
- const phpName = fieldName$4(field.name);
15440
+ const phpName = domainFieldName$4(field);
15142
15441
  const phpType = mapTypeRef$5(field.type);
15143
15442
  const isOptional = !field.required;
15144
15443
  const comma = i < allFields.length - 1 ? "," : ",";
@@ -15165,7 +15464,7 @@ function generateModels$5(models, ctx) {
15165
15464
  lines.push(` return new self(`);
15166
15465
  for (let i = 0; i < allFields.length; i++) {
15167
15466
  const field = allFields[i];
15168
- const phpName = fieldName$4(field.name);
15467
+ const phpName = domainFieldName$4(field);
15169
15468
  const wireName = field.name;
15170
15469
  const comma = i < allFields.length - 1 ? "," : ",";
15171
15470
  const accessor = generateFromArrayAccessor(field.type, wireName, field.required);
@@ -15178,7 +15477,7 @@ function generateModels$5(models, ctx) {
15178
15477
  lines.push(" {");
15179
15478
  lines.push(" return [");
15180
15479
  for (const field of allFields) {
15181
- const phpName = fieldName$4(field.name);
15480
+ const phpName = domainFieldName$4(field);
15182
15481
  const wireName = field.name;
15183
15482
  const serialized = generateToArrayValue(field.type, `$this->${phpName}`, !field.required);
15184
15483
  lines.push(` '${wireName}' => ${serialized},`);
@@ -15186,7 +15485,7 @@ function generateModels$5(models, ctx) {
15186
15485
  lines.push(" ];");
15187
15486
  lines.push(" }");
15188
15487
  lines.push("}");
15189
- files.push({
15488
+ if (isModelInScope(model.name, ctx)) files.push({
15190
15489
  path: `lib/Resource/${name}.php`,
15191
15490
  content: lines.join("\n"),
15192
15491
  overwriteExisting: true
@@ -15344,6 +15643,7 @@ function generateEnums$5(enums, ctx) {
15344
15643
  const canonical = resolveEnumName$1(e.name);
15345
15644
  if (emittedCanonical.has(canonical)) continue;
15346
15645
  emittedCanonical.add(canonical);
15646
+ const enumInScope = enums.some((other) => resolveEnumName$1(other.name) === canonical && isEnumInScope(other.name, ctx));
15347
15647
  const name = className$4(canonical);
15348
15648
  e.values.every((v) => typeof v.value === "string");
15349
15649
  const backingType = e.values.every((v) => typeof v.value === "number" && Number.isInteger(v.value)) ? "int" : "string";
@@ -15369,7 +15669,7 @@ function generateEnums$5(enums, ctx) {
15369
15669
  else lines.push(` case ${caseName} = ${val.value};`);
15370
15670
  }
15371
15671
  lines.push("}");
15372
- files.push({
15672
+ if (enumInScope) files.push({
15373
15673
  path: `lib/Resource/${name}.php`,
15374
15674
  content: lines.join("\n"),
15375
15675
  overwriteExisting: true
@@ -15510,8 +15810,8 @@ function generateResources$5(services, ctx) {
15510
15810
  if (services.length === 0) return [];
15511
15811
  const files = [];
15512
15812
  const modelMap = new Map(ctx.spec.models.map((m) => [m.name, m]));
15513
- const mountGroups = groupByMount(ctx);
15514
- const entries = mountGroups.size > 0 ? [...mountGroups].map(([name, group]) => ({
15813
+ const mountGroups = scopedMountGroups(ctx);
15814
+ const entries = mountGroups.size > 0 || ctx.scopedServices?.size ? [...mountGroups].map(([name, group]) => ({
15515
15815
  name,
15516
15816
  operations: group.operations
15517
15817
  })) : services.map((s) => ({
@@ -16281,9 +16581,9 @@ function generatePrimitiveValue$2(type, format, name, modelName) {
16281
16581
  */
16282
16582
  function generateTests$5(spec, ctx) {
16283
16583
  const files = [];
16284
- const mountGroupsFromResolved = groupByMount(ctx);
16584
+ const mountGroupsFromResolved = scopedMountGroups(ctx);
16285
16585
  const mountGroups = /* @__PURE__ */ new Map();
16286
- if (mountGroupsFromResolved.size > 0) for (const [target, group] of mountGroupsFromResolved) mountGroups.set(target, group.resolvedOps.map((r) => ({
16586
+ if (mountGroupsFromResolved.size > 0 || ctx.scopedServices?.size) for (const [target, group] of mountGroupsFromResolved) mountGroups.set(target, group.resolvedOps.map((r) => ({
16287
16587
  op: r.operation,
16288
16588
  service: r.service,
16289
16589
  resolvedOp: r
@@ -16602,7 +16902,7 @@ function emitFieldHydrationAssertions(lines, modelName, resultVar, fixtureVar, c
16602
16902
  const assertFields = [candidates.find((f) => f.name === "id"), candidates.filter((f) => f.name !== "id")[0]].filter(Boolean);
16603
16903
  for (const f of assertFields) {
16604
16904
  if (!f) continue;
16605
- const phpProp = toCamelCase(f.name);
16905
+ const phpProp = domainFieldName$4(f);
16606
16906
  lines.push(` $this->assertSame(${fixtureVar}['${f.name}'], ${resultVar}->${phpProp});`);
16607
16907
  }
16608
16908
  }
@@ -16733,9 +17033,17 @@ function ensureTrailingNewlines$5(files) {
16733
17033
  * classes (no sum types), so a discriminated base whose IR fields the
16734
17034
  * parser stripped (post-allOf-aware detection) gets its original fields
16735
17035
  * restored to avoid silently dropping variant data.
16736
- */
16737
- function enrichModelsForPhp(models) {
16738
- const enriched = enrichModelsFromSpec(models);
17036
+ *
17037
+ * `enums` is forwarded to seed `enrichModelsFromSpec`'s collision set: an
17038
+ * inline oneOf enum whose synthetic name (`Parent_field`) snake-collapses
17039
+ * onto an existing IR enum (e.g. `DataIntegrationAccessTokenResponse_error`
17040
+ * vs `DataIntegrationAccessTokenResponseError`) must NOT spawn a duplicate
17041
+ * synthetic. Otherwise both collapse to the same `lib/Resource/X.php` path
17042
+ * and the later writer wins by array order — which differs between a full
17043
+ * and a scoped (`--services`) run, producing a non-deterministic case order.
17044
+ */
17045
+ function enrichModelsForPhp(models, enums) {
17046
+ const enriched = enrichModelsFromSpec(models, enums);
16739
17047
  const originalByName = new Map(models.map((m) => [m.name, m]));
16740
17048
  return enriched.map((m) => {
16741
17049
  if (m.discriminator && m.fields.length === 0) {
@@ -16752,7 +17060,7 @@ const phpEmitter = {
16752
17060
  language: "php",
16753
17061
  generateModels(models, ctx) {
16754
17062
  ensureNamingInitialized(ctx);
16755
- return ensureTrailingNewlines$5(generateModels$5(enrichModelsForPhp(models), ctx));
17063
+ return ensureTrailingNewlines$5(generateModels$5(enrichModelsForPhp(models, ctx.spec.enums), ctx));
16756
17064
  },
16757
17065
  generateEnums(enums, ctx) {
16758
17066
  ensureNamingInitialized(ctx);
@@ -16782,7 +17090,7 @@ const phpEmitter = {
16782
17090
  return buildOperationsMap$5(spec, ctx);
16783
17091
  },
16784
17092
  fileHeader() {
16785
- return "<?php\n\ndeclare(strict_types=1);\n\n// This file is auto-generated by oagen. Do not edit.";
17093
+ return `<?php\n\ndeclare(strict_types=1);\n\n// ${AUTOGEN_NOTICE}`;
16786
17094
  },
16787
17095
  formatCommand(targetDir) {
16788
17096
  if (fs$1.existsSync(path$1.join(targetDir, ".php-cs-fixer.dist.php")) || fs$1.existsSync(path$1.join(targetDir, ".php-cs-fixer.php"))) return {
@@ -16845,6 +17153,15 @@ function methodName$3(name) {
16845
17153
  function fieldName$3(name) {
16846
17154
  return applyAcronyms(toPascalCase(name));
16847
17155
  }
17156
+ /**
17157
+ * PascalCase domain field name for a model field, honoring a `domainName`
17158
+ * override (set via the `fieldHints` config) so a wire field can surface under
17159
+ * a friendlier name. The wire name (the `json:"..."` struct tag) still derives
17160
+ * from `field.name`.
17161
+ */
17162
+ function domainFieldName$3(field) {
17163
+ return applyAcronyms(toPascalCase(field.domainName ?? field.name));
17164
+ }
16848
17165
  /** Lower-camel identifier with Go acronym conventions preserved. */
16849
17166
  function unexportedName(name) {
16850
17167
  const exported = className$3(name);
@@ -17093,7 +17410,7 @@ function generateModels$4(models, ctx) {
17093
17410
  lines.push(`type ${structName} struct {`);
17094
17411
  const seenFieldNames = /* @__PURE__ */ new Set();
17095
17412
  for (const field of model.fields) {
17096
- const goFieldName = fieldName$3(field.name);
17413
+ const goFieldName = domainFieldName$3(field);
17097
17414
  if (seenFieldNames.has(goFieldName)) continue;
17098
17415
  seenFieldNames.add(goFieldName);
17099
17416
  const goType = !field.required ? makeOptional$2(mapTypeRef$4(field.type)) : mapTypeRef$4(field.type);
@@ -17680,8 +17997,8 @@ function resolveResourceClassName$1(service, ctx) {
17680
17997
  function generateResources$4(services, ctx) {
17681
17998
  if (services.length === 0) return [];
17682
17999
  const files = [];
17683
- const mountGroups = groupByMount(ctx);
17684
- const entries = mountGroups.size > 0 ? [...mountGroups].map(([name, group]) => ({
18000
+ const mountGroups = scopedMountGroups(ctx);
18001
+ const entries = mountGroups.size > 0 || ctx.scopedServices?.size ? [...mountGroups].map(([name, group]) => ({
17685
18002
  name,
17686
18003
  operations: group.operations
17687
18004
  })) : services.map((s) => ({
@@ -17907,7 +18224,7 @@ function generateParamsStruct(mountName, method, op, plan, ctx, resolvedOp) {
17907
18224
  if (bodyModel) for (const field of bodyModel.fields) {
17908
18225
  if (hidden.has(field.name)) continue;
17909
18226
  if (groupedParams.has(field.name)) continue;
17910
- const goField = fieldName$3(field.name);
18227
+ const goField = domainFieldName$3(field);
17911
18228
  if (emittedFields.has(goField)) continue;
17912
18229
  emittedFields.add(goField);
17913
18230
  const goType = !field.required ? makeOptional$1(mapTypeRef$4(field.type)) : mapTypeRef$4(field.type);
@@ -18245,7 +18562,7 @@ function emitHiddenParamsBodyStruct(lines, method, op, ctx, resolvedOp) {
18245
18562
  if (hidden.has(field.name)) continue;
18246
18563
  if (groupedParamNames.has(field.name)) continue;
18247
18564
  if (!field.required) continue;
18248
- const goField = fieldName$3(field.name);
18565
+ const goField = domainFieldName$3(field);
18249
18566
  const goType = mapTypeRef$4(field.type);
18250
18567
  lines.push(`\t${goField} ${goType} \`json:"${field.name}"\``);
18251
18568
  }
@@ -18257,7 +18574,7 @@ function emitHiddenParamsBodyStruct(lines, method, op, ctx, resolvedOp) {
18257
18574
  if (hidden.has(field.name)) continue;
18258
18575
  if (groupedParamNames.has(field.name)) continue;
18259
18576
  if (field.required) continue;
18260
- const goField = fieldName$3(field.name);
18577
+ const goField = domainFieldName$3(field);
18261
18578
  const goType = makeOptional$1(mapTypeRef$4(field.type));
18262
18579
  lines.push(`\t${goField} ${goType} \`json:"${field.name},omitempty"\``);
18263
18580
  }
@@ -18278,7 +18595,7 @@ function emitBodyWithHiddenParams(lines, op, pathExpr, plan, ctx, resolvedOp, pa
18278
18595
  if (paramsType && bodyModel) for (const field of bodyModel.fields) {
18279
18596
  if (hidden.has(field.name)) continue;
18280
18597
  if (!field.required) continue;
18281
- const goField = fieldName$3(field.name);
18598
+ const goField = domainFieldName$3(field);
18282
18599
  lines.push(`\t\t${goField}: params.${goField},`);
18283
18600
  }
18284
18601
  lines.push(" }");
@@ -18289,7 +18606,7 @@ function emitBodyWithHiddenParams(lines, op, pathExpr, plan, ctx, resolvedOp, pa
18289
18606
  if (paramsType && bodyModel) for (const field of bodyModel.fields) {
18290
18607
  if (hidden.has(field.name)) continue;
18291
18608
  if (field.required) continue;
18292
- const goField = fieldName$3(field.name);
18609
+ const goField = domainFieldName$3(field);
18293
18610
  lines.push(`\tbody.${goField} = params.${goField}`);
18294
18611
  }
18295
18612
  const queryArg = op.queryParams.filter((qp) => !hidden.has(qp.name)).length > 0 ? "params" : "nil";
@@ -18716,7 +19033,7 @@ function generateModelFixture$2(model, modelMap, enumMap) {
18716
19033
  const fixture = {};
18717
19034
  const seenFieldNames = /* @__PURE__ */ new Set();
18718
19035
  const deduplicatedFields = model.fields.filter((f) => {
18719
- const goName = fieldName$3(f.name);
19036
+ const goName = domainFieldName$3(f);
18720
19037
  if (seenFieldNames.has(goName)) return false;
18721
19038
  seenFieldNames.add(goName);
18722
19039
  return true;
@@ -18841,8 +19158,8 @@ function generateTests$4(spec, ctx) {
18841
19158
  headerPlacement: "skip"
18842
19159
  });
18843
19160
  const accessPaths = buildServiceAccessPaths$1(spec.services, ctx);
18844
- const mountGroups = groupByMount(ctx);
18845
- const testEntries = mountGroups.size > 0 ? [...mountGroups].map(([name, group]) => ({
19161
+ const mountGroups = scopedMountGroups(ctx);
19162
+ const testEntries = mountGroups.size > 0 || ctx.scopedServices?.size ? [...mountGroups].map(([name, group]) => ({
18846
19163
  name,
18847
19164
  operations: group.operations
18848
19165
  })) : spec.services.map((s) => ({
@@ -19456,6 +19773,15 @@ function methodName$2(name) {
19456
19773
  function fieldName$2(name) {
19457
19774
  return toPascalCase(name);
19458
19775
  }
19776
+ /**
19777
+ * PascalCase domain property name for a model field, honoring a `domainName`
19778
+ * override (set via the `fieldHints` config) so a wire field can surface under
19779
+ * a friendlier C# property name. The wire/serialization key (the
19780
+ * `[JsonPropertyName("...")]` value) still derives from `field.name`.
19781
+ */
19782
+ function domainFieldName$2(field) {
19783
+ return toPascalCase(field.domainName ?? field.name);
19784
+ }
19459
19785
  function trimAsyncSuffix(name) {
19460
19786
  return name.endsWith("Async") ? name.slice(0, -5) : name;
19461
19787
  }
@@ -19822,7 +20148,7 @@ function generateModels$3(models, ctx, discCtx) {
19822
20148
  const baseClassName = modelClassName(model.name);
19823
20149
  const fieldMap = /* @__PURE__ */ new Map();
19824
20150
  for (const field of model.fields) {
19825
- let csName = fieldName$2(field.name);
20151
+ let csName = domainFieldName$2(field);
19826
20152
  if (csName === baseClassName) csName = `${csName}Value`;
19827
20153
  fieldMap.set(csName, mapTypeRef$3(field.type));
19828
20154
  }
@@ -19838,7 +20164,9 @@ function generateModels$3(models, ctx, discCtx) {
19838
20164
  const fieldTypes = model.fields.map((f) => mapTypeRef$3(f.type));
19839
20165
  const needsCollections = fieldTypes.some((t) => t.startsWith("List<") || t.startsWith("Dictionary<"));
19840
20166
  const needsSystem = fieldTypes.some((t) => t.includes("DateTimeOffset"));
19841
- const needsJsonAttrs = model.fields.some((f) => fieldName$2(f.name) === csClassName) || model.fields.some((f) => f.required && isEnumRef(f.type));
20167
+ const hasClassNameCollision = model.fields.some((f) => domainFieldName$2(f) === csClassName);
20168
+ const hasDomainRename = model.fields.some((f) => domainFieldName$2(f) !== fieldName$2(f.name));
20169
+ const needsJsonAttrs = hasClassNameCollision || hasDomainRename || model.fields.some((f) => f.required && isEnumRef(f.type));
19842
20170
  lines.push(`namespace ${ctx.namespacePascal}`);
19843
20171
  lines.push("{");
19844
20172
  if (needsSystem) lines.push(" using System;");
@@ -19864,9 +20192,10 @@ function generateModels$3(models, ctx, discCtx) {
19864
20192
  const dictObjectFields = [];
19865
20193
  const seenFieldNames = /* @__PURE__ */ new Set();
19866
20194
  for (const field of model.fields) {
19867
- let csFieldName = fieldName$2(field.name);
20195
+ let csFieldName = domainFieldName$2(field);
19868
20196
  const collidesWithClassName = csFieldName === csClassName;
19869
20197
  if (collidesWithClassName) csFieldName = `${csFieldName}Value`;
20198
+ const hasDomainOverride = domainFieldName$2(field) !== fieldName$2(field.name);
19870
20199
  if (seenFieldNames.has(csFieldName)) continue;
19871
20200
  seenFieldNames.add(csFieldName);
19872
20201
  let useNewModifier = false;
@@ -19913,7 +20242,7 @@ function generateModels$3(models, ctx, discCtx) {
19913
20242
  const isRequiredEnum = field.required && isEnumRef(field.type) && constInit === null;
19914
20243
  lines.push(...emitJsonPropertyAttributes(field.name, {
19915
20244
  isRequiredEnum,
19916
- explicitWireName: collidesWithClassName
20245
+ explicitWireName: collidesWithClassName || hasDomainOverride
19917
20246
  }));
19918
20247
  const discriminatedUnionConverter = discriminatedUnionConverterName(field.type);
19919
20248
  if (discriminatedUnionConverter) lines.push(` [Newtonsoft.Json.JsonConverter(typeof(${discriminatedUnionConverter}))]`);
@@ -19973,7 +20302,7 @@ function generateModels$3(models, ctx, discCtx) {
19973
20302
  }
19974
20303
  lines.push(" }");
19975
20304
  lines.push("}");
19976
- files.push({
20305
+ if (isModelInScope(model.name, ctx)) files.push({
19977
20306
  path: `Entities/${csClassName}.cs`,
19978
20307
  content: lines.join("\n"),
19979
20308
  overwriteExisting: true
@@ -20217,7 +20546,7 @@ function generateEnums$3(enums, ctx) {
20217
20546
  }
20218
20547
  lines.push(" }");
20219
20548
  lines.push("}");
20220
- files.push({
20549
+ if (isEnumInScope(enumDef.name, ctx)) files.push({
20221
20550
  path: `Enums/${typeName}.cs`,
20222
20551
  content: lines.join("\n"),
20223
20552
  overwriteExisting: true
@@ -20474,8 +20803,8 @@ function resolveResourceClassName(service, ctx) {
20474
20803
  function generateResources$3(services, ctx) {
20475
20804
  if (services.length === 0) return [];
20476
20805
  const files = [];
20477
- const mountGroups = groupByMount(ctx);
20478
- const entries = mountGroups.size > 0 ? [...mountGroups].map(([name, group]) => ({
20806
+ const mountGroups = scopedMountGroups(ctx);
20807
+ const entries = mountGroups.size > 0 || ctx.scopedServices?.size ? [...mountGroups].map(([name, group]) => ({
20479
20808
  name,
20480
20809
  operations: group.operations
20481
20810
  })) : services.map((s) => ({
@@ -21219,7 +21548,7 @@ function generateModelFixture$1(model, modelMap, enumMap) {
21219
21548
  const fixture = {};
21220
21549
  const seenFieldNames = /* @__PURE__ */ new Set();
21221
21550
  const deduplicatedFields = model.fields.filter((f) => {
21222
- const csName = fieldName$2(f.name);
21551
+ const csName = domainFieldName$2(f);
21223
21552
  if (seenFieldNames.has(csName)) return false;
21224
21553
  seenFieldNames.add(csName);
21225
21554
  return true;
@@ -21286,8 +21615,8 @@ function generateTests$3(spec, ctx) {
21286
21615
  content: fixture.content,
21287
21616
  headerPlacement: "skip"
21288
21617
  });
21289
- const mountGroups = groupByMount(ctx);
21290
- const testEntries = mountGroups.size > 0 ? [...mountGroups].map(([name, group]) => ({
21618
+ const mountGroups = scopedMountGroups(ctx);
21619
+ const testEntries = mountGroups.size > 0 || ctx.scopedServices?.size ? [...mountGroups].map(([name, group]) => ({
21291
21620
  name,
21292
21621
  operations: group.operations
21293
21622
  })) : spec.services.map((s) => ({
@@ -21740,7 +22069,7 @@ function buildFixtureAssertions(model, spec) {
21740
22069
  if (field.type.kind !== "primitive" || field.type.type !== "string") continue;
21741
22070
  if (field.type.format === "date-time" || field.type.format === "date") continue;
21742
22071
  if (field.type.format === "binary") continue;
21743
- const csField = fieldName$2(field.name);
22072
+ const csField = domainFieldName$2(field);
21744
22073
  const val = fixture[field.name];
21745
22074
  if (typeof val === "string" && val.length > 0) assertions.push(`Assert.Equal(${csStringLiteral(val)}, result.${csField});`);
21746
22075
  else assertions.push(`Assert.NotEmpty(result.${csField});`);
@@ -21999,7 +22328,7 @@ const dotnetEmitter = {
21999
22328
  return buildOperationsMap$3(spec, fixNamespace(ctx));
22000
22329
  },
22001
22330
  fileHeader() {
22002
- return "// This file is auto-generated by oagen. Do not edit.";
22331
+ return `// ${AUTOGEN_NOTICE}`;
22003
22332
  },
22004
22333
  formatCommand(targetDir) {
22005
22334
  const workspace = findDotnetWorkspace(targetDir);
@@ -22062,6 +22391,16 @@ function propertyName(name) {
22062
22391
  if (camel === "object") return "objectType";
22063
22392
  return escapeReserved(camel);
22064
22393
  }
22394
+ /**
22395
+ * camelCase domain property name for a MODEL field, honoring a `domainName`
22396
+ * override (set via the `fieldHints` config) so a wire field can surface under
22397
+ * a friendlier name. The wire key (the `@JsonProperty("...")` argument) still
22398
+ * derives from `field.name`. No-op when `domainName` is unset, so it is also
22399
+ * safe on params. Only apply to model fields.
22400
+ */
22401
+ function domainPropertyName(field) {
22402
+ return propertyName(field.domainName ?? field.name);
22403
+ }
22065
22404
  /** Lower-case Kotlin package segment for a service / mount group. */
22066
22405
  function packageSegment(name) {
22067
22406
  return toPascalCase(name).toLowerCase();
@@ -22255,7 +22594,7 @@ const enumCanonicalMap = /* @__PURE__ */ new Map();
22255
22594
  * shortest PascalCase name becomes canonical and the rest emit `typealias`
22256
22595
  * files pointing at the canonical class.
22257
22596
  */
22258
- function generateEnums$2(enums, _ctx) {
22597
+ function generateEnums$2(enums, ctx) {
22259
22598
  if (enums.length === 0) return [];
22260
22599
  enumCanonicalMap.clear();
22261
22600
  const hashGroups = /* @__PURE__ */ new Map();
@@ -22292,6 +22631,7 @@ function generateEnums$2(enums, _ctx) {
22292
22631
  for (const enumDef of enums) {
22293
22632
  if (enumDef.values.length === 0) continue;
22294
22633
  const typeName = canonicalEnumTypeName(enumDef);
22634
+ const enumInScope = isEnumInScope(enumDef.name, ctx);
22295
22635
  const canonicalName = sharedSortEmitters.has(enumDef.name) ? void 0 : aliasOf.get(enumDef.name) ?? enumCanonicalMap.get(enumDef.name);
22296
22636
  if (canonicalName) {
22297
22637
  const canonicalType = className$1(canonicalName);
@@ -22304,7 +22644,7 @@ function generateEnums$2(enums, _ctx) {
22304
22644
  aliasLine,
22305
22645
  ""
22306
22646
  ].join("\n");
22307
- files.push({
22647
+ if (enumInScope) files.push({
22308
22648
  path: `${KOTLIN_SRC_PREFIX$3}${ENUMS_DIR}/${typeName}.kt`,
22309
22649
  content: aliasContent,
22310
22650
  overwriteExisting: true
@@ -22360,7 +22700,7 @@ function generateEnums$2(enums, _ctx) {
22360
22700
  }
22361
22701
  lines.push("}");
22362
22702
  lines.push("");
22363
- files.push({
22703
+ if (enumInScope) files.push({
22364
22704
  path: `${KOTLIN_SRC_PREFIX$3}${ENUMS_DIR}/${typeName}.kt`,
22365
22705
  content: lines.join("\n"),
22366
22706
  overwriteExisting: true
@@ -22557,8 +22897,9 @@ function generateModels$2(models, ctx) {
22557
22897
  for (const model of models) {
22558
22898
  if (skipAsListWrapper(model) || skipAsListMetadata(model)) continue;
22559
22899
  const typeName = className$1(model.name);
22900
+ const modelInScope = isModelInScope(model.name, ctx);
22560
22901
  if (model.fields.length === 0 && discriminatedUnions.has(typeName)) {
22561
- files.push(emitSealedUnion(typeName, discriminatedUnions.get(typeName)));
22902
+ if (modelInScope) files.push(emitSealedUnion(typeName, discriminatedUnions.get(typeName)));
22562
22903
  continue;
22563
22904
  }
22564
22905
  const canonical = aliasOf.get(model.name);
@@ -22572,14 +22913,14 @@ function generateModels$2(models, ctx) {
22572
22913
  `typealias ${typeName} = ${canonicalType}`,
22573
22914
  ""
22574
22915
  ].join("\n");
22575
- files.push({
22916
+ if (modelInScope) files.push({
22576
22917
  path: `${KOTLIN_SRC_PREFIX$2}${MODELS_DIR}/${typeName}.kt`,
22577
22918
  content: aliasContent,
22578
22919
  overwriteExisting: true
22579
22920
  });
22580
22921
  continue;
22581
22922
  }
22582
- files.push(emitDataClass(model));
22923
+ if (modelInScope) files.push(emitDataClass(model));
22583
22924
  }
22584
22925
  const eventMapping = [];
22585
22926
  for (const model of models) {
@@ -22764,7 +23105,7 @@ function renderFields(fields, overrideFields = /* @__PURE__ */ new Set()) {
22764
23105
  const lines = [];
22765
23106
  for (const rawField of fields) {
22766
23107
  const field = promoteFieldType$1(rawField);
22767
- const kotlinName = propertyName(field.name);
23108
+ const kotlinName = domainPropertyName(field);
22768
23109
  if (seen.has(kotlinName)) continue;
22769
23110
  seen.add(kotlinName);
22770
23111
  const baseType = mapTypeRef$2(field.type);
@@ -23273,7 +23614,7 @@ function promoteFieldType(f) {
23273
23614
  */
23274
23615
  function generateResources$2(services, ctx) {
23275
23616
  if (services.length === 0) return [];
23276
- const mountGroups = groupByMount(ctx);
23617
+ const mountGroups = scopedMountGroups(ctx);
23277
23618
  if (mountGroups.size === 0) return [];
23278
23619
  const files = [];
23279
23620
  const resolvedLookup = buildResolvedLookup(ctx);
@@ -24181,7 +24522,7 @@ function promoteIso8601TypeRef(type, description) {
24181
24522
  */
24182
24523
  function generateTests$2(spec, ctx) {
24183
24524
  const files = [];
24184
- const mountGroups = groupByMount(ctx);
24525
+ const mountGroups = scopedMountGroups(ctx);
24185
24526
  const resolvedLookup = buildResolvedLookup(ctx);
24186
24527
  const exportedClasses = buildExportedClassNameSet$1(ctx);
24187
24528
  for (const [mountName, group] of mountGroups) {
@@ -24618,7 +24959,7 @@ function buildResponseAssertions(responseModelName, ctx) {
24618
24959
  for (const field of model.fields) {
24619
24960
  if (!field.required) continue;
24620
24961
  if (assertions.length >= MAX_RESPONSE_ASSERTIONS) break;
24621
- const ktProp = propertyName(field.name);
24962
+ const ktProp = domainPropertyName(field);
24622
24963
  const type = field.type;
24623
24964
  if (type.kind === "primitive") {
24624
24965
  if (type.format === "date-time") continue;
@@ -25062,7 +25403,7 @@ const kotlinEmitter = {
25062
25403
  return buildOperationsMap$2(spec, ctx);
25063
25404
  },
25064
25405
  fileHeader() {
25065
- return "// This file is auto-generated by oagen. Do not edit.";
25406
+ return `// ${AUTOGEN_NOTICE}`;
25066
25407
  },
25067
25408
  formatCommand(targetDir) {
25068
25409
  if (!fs.existsSync(path.join(targetDir, "gradlew"))) return null;
@@ -25114,6 +25455,15 @@ function fieldName$1(name) {
25114
25455
  return toSnakeCase(name);
25115
25456
  }
25116
25457
  /**
25458
+ * snake_case domain field name for a model field, honoring a `domainName`
25459
+ * override (set via the `fieldHints` config) so a wire field can surface under
25460
+ * a friendlier name. The wire key (still derived from `field.name`) is what
25461
+ * gets sent/received over the wire — only the domain attr/accessor name changes.
25462
+ */
25463
+ function domainFieldName$1(field) {
25464
+ return toSnakeCase(field.domainName ?? field.name);
25465
+ }
25466
+ /**
25117
25467
  * Ruby reserved words that cannot be used as parameter names.
25118
25468
  * When a path/query param name collides, suffix with underscore.
25119
25469
  */
@@ -25334,7 +25684,7 @@ function generateModels$1(models, ctx) {
25334
25684
  lines.push("module WorkOS");
25335
25685
  lines.push(` ${cls} = ${canonCls}`);
25336
25686
  lines.push("end");
25337
- files.push({
25687
+ if (isModelInScope(model.name, ctx)) files.push({
25338
25688
  path: `lib/workos/${dirFor(model.name)}/${file}.rb`,
25339
25689
  content: lines.join("\n"),
25340
25690
  integrateTarget: true,
@@ -25344,7 +25694,7 @@ function generateModels$1(models, ctx) {
25344
25694
  }
25345
25695
  const seenFieldNames = /* @__PURE__ */ new Set();
25346
25696
  const fields = model.fields.filter((f) => {
25347
- const n = fieldName$1(f.name);
25697
+ const n = domainFieldName$1(f);
25348
25698
  if (seenFieldNames.has(n)) return false;
25349
25699
  seenFieldNames.add(n);
25350
25700
  return true;
@@ -25358,7 +25708,7 @@ function generateModels$1(models, ctx) {
25358
25708
  lines.push(" HASH_ATTRS = {");
25359
25709
  for (let i = 0; i < fields.length; i++) {
25360
25710
  const field = fields[i];
25361
- const fname = fieldName$1(field.name);
25711
+ const fname = domainFieldName$1(field);
25362
25712
  const sep = i === fields.length - 1 ? "" : ",";
25363
25713
  lines.push(` ${rubyHashLiteralKey(field.name)} :${fname}${sep}`);
25364
25714
  }
@@ -25367,13 +25717,13 @@ function generateModels$1(models, ctx) {
25367
25717
  if (deprecatedFields.length > 0) {
25368
25718
  for (const f of deprecatedFields) {
25369
25719
  const desc = f.description ? ` ${f.description.split("\n")[0].trim()}` : "";
25370
- lines.push(` # @!attribute ${fieldName$1(f.name)}`);
25720
+ lines.push(` # @!attribute ${domainFieldName$1(f)}`);
25371
25721
  lines.push(` # @deprecated${desc}`);
25372
25722
  }
25373
25723
  lines.push("");
25374
25724
  }
25375
25725
  if (accessorFields.length > 0) {
25376
- const attrs = accessorFields.map((f) => `:${fieldName$1(f.name)}`);
25726
+ const attrs = accessorFields.map((f) => `:${domainFieldName$1(f)}`);
25377
25727
  if (attrs.length === 1) lines.push(` attr_accessor ${attrs[0]}`);
25378
25728
  else {
25379
25729
  lines.push(` attr_accessor \\`);
@@ -25385,7 +25735,7 @@ function generateModels$1(models, ctx) {
25385
25735
  lines.push("");
25386
25736
  }
25387
25737
  for (const f of deprecatedFields) {
25388
- const fname = fieldName$1(f.name);
25738
+ const fname = domainFieldName$1(f);
25389
25739
  lines.push(` def ${fname}`);
25390
25740
  lines.push(` warn "[DEPRECATION] \\\`${fname}\\\` is deprecated and will be removed in a future version.", uplevel: 1`);
25391
25741
  lines.push(` @${fname}`);
@@ -25399,14 +25749,14 @@ function generateModels$1(models, ctx) {
25399
25749
  lines.push(" def initialize(json)");
25400
25750
  lines.push(" hash = self.class.normalize(json)");
25401
25751
  for (const field of fields) {
25402
- const fname = fieldName$1(field.name);
25752
+ const fname = domainFieldName$1(field);
25403
25753
  const rawKey = field.name;
25404
25754
  lines.push(` ${deserializeAssignment(fname, rawKey, field.type, field.required, enumNames, modelNames)}`);
25405
25755
  }
25406
25756
  lines.push(" end");
25407
25757
  lines.push(" end");
25408
25758
  lines.push("end");
25409
- files.push({
25759
+ if (isModelInScope(model.name, ctx)) files.push({
25410
25760
  path: `lib/workos/${dirFor(model.name)}/${file}.rb`,
25411
25761
  content: lines.join("\n"),
25412
25762
  integrateTarget: true,
@@ -25515,6 +25865,7 @@ function generateEnums$1(enums, ctx) {
25515
25865
  const aliasOf = collectEnumAliasOf(enums);
25516
25866
  for (const enumDef of enums) {
25517
25867
  const cls = className(enumDef.name);
25868
+ const enumInScope = isEnumInScope(enumDef.name, ctx);
25518
25869
  const canonicalName = aliasOf.get(enumDef.name);
25519
25870
  if (canonicalName) {
25520
25871
  const canonicalCls = className(canonicalName);
@@ -25524,7 +25875,7 @@ function generateEnums$1(enums, ctx) {
25524
25875
  lines.push(` ${cls} = ${canonicalCls}`);
25525
25876
  lines.push(" end");
25526
25877
  lines.push("end");
25527
- files.push({
25878
+ if (enumInScope) files.push({
25528
25879
  path: `lib/workos/types/${fileName(enumDef.name)}.rb`,
25529
25880
  content: lines.join("\n"),
25530
25881
  integrateTarget: true,
@@ -25553,7 +25904,7 @@ function generateEnums$1(enums, ctx) {
25553
25904
  lines.push(" end");
25554
25905
  lines.push(" end");
25555
25906
  lines.push("end");
25556
- files.push({
25907
+ if (enumInScope) files.push({
25557
25908
  path: `lib/workos/types/${fileName(enumDef.name)}.rb`,
25558
25909
  content: lines.join("\n"),
25559
25910
  integrateTarget: true,
@@ -25590,7 +25941,7 @@ function generateEnums$1(enums, ctx) {
25590
25941
  lines.push(" end");
25591
25942
  lines.push(" end");
25592
25943
  lines.push("end");
25593
- files.push({
25944
+ if (enumInScope) files.push({
25594
25945
  path: `lib/workos/types/${fileName(enumDef.name)}.rb`,
25595
25946
  content: lines.join("\n"),
25596
25947
  integrateTarget: true,
@@ -25968,7 +26319,7 @@ function emitInlineVariantRbi(v) {
25968
26319
  */
25969
26320
  function generateResources$1(services, ctx) {
25970
26321
  const files = [];
25971
- const groups = groupByMount(ctx);
26322
+ const groups = scopedMountGroups(ctx);
25972
26323
  const lookup = buildResolvedLookup(ctx);
25973
26324
  const modelNames = new Set(ctx.spec.models.map((m) => m.name));
25974
26325
  const enumNames = new Set(ctx.spec.enums.map((e) => e.name));
@@ -26746,7 +27097,7 @@ function generateClientClass(spec, ctx) {
26746
27097
  */
26747
27098
  function generateTests$1(spec, ctx) {
26748
27099
  const files = [];
26749
- const groups = groupByMount(ctx);
27100
+ const groups = scopedMountGroups(ctx);
26750
27101
  const models = spec.models;
26751
27102
  const modelByName = /* @__PURE__ */ new Map();
26752
27103
  for (const m of models) modelByName.set(m.name, m);
@@ -26895,7 +27246,7 @@ function generateModelRoundTripTest(spec) {
26895
27246
  const dedupFields = /* @__PURE__ */ new Set();
26896
27247
  for (const f of model.fields) {
26897
27248
  const wireName = f.name;
26898
- const rubyFieldName = fieldName$1(f.name);
27249
+ const rubyFieldName = domainFieldName$1(f);
26899
27250
  if (dedupFields.has(rubyFieldName)) continue;
26900
27251
  dedupFields.add(rubyFieldName);
26901
27252
  const stub = roundTripStub(f.type, enumNames);
@@ -27224,7 +27575,7 @@ function generateRbiFiles(spec, ctx) {
27224
27575
  lines.push("");
27225
27576
  const seenFieldNames = /* @__PURE__ */ new Set();
27226
27577
  for (const f of model.fields) {
27227
- const fname = fieldName$1(f.name);
27578
+ const fname = domainFieldName$1(f);
27228
27579
  if (seenFieldNames.has(fname)) continue;
27229
27580
  seenFieldNames.add(fname);
27230
27581
  const sorbetType = f.required ? mapSorbetType(f.type) : wrapNilable(mapSorbetType(f.type));
@@ -27242,7 +27593,7 @@ function generateRbiFiles(spec, ctx) {
27242
27593
  lines.push(" def to_json(*args); end");
27243
27594
  lines.push(" end");
27244
27595
  lines.push("end");
27245
- files.push({
27596
+ if (isModelInScope(model.name, ctx)) files.push({
27246
27597
  path: `rbi/workos/${fileName(model.name)}.rbi`,
27247
27598
  content: lines.join("\n"),
27248
27599
  integrateTarget: true,
@@ -27258,6 +27609,7 @@ function generateRbiFiles(spec, ctx) {
27258
27609
  const groupOwners = buildGroupOwnerMap(ctx);
27259
27610
  const exportedClasses = buildExportedClassNameSet(ctx);
27260
27611
  for (const [mountTarget, group] of groups) {
27612
+ if (!isMountInScope(mountTarget, ctx)) continue;
27261
27613
  const resolvedTarget = resolveServiceTarget(mountTarget, exportedClasses);
27262
27614
  const cls = className(resolvedTarget);
27263
27615
  const lines = [];
@@ -27501,7 +27853,7 @@ const rubyEmitter = {
27501
27853
  return buildOperationsMap$1(spec, ctx);
27502
27854
  },
27503
27855
  fileHeader() {
27504
- return `# frozen_string_literal: true\n\n# This file is auto-generated by oagen. Do not edit.`;
27856
+ return `# frozen_string_literal: true\n\n# ${AUTOGEN_NOTICE}`;
27505
27857
  },
27506
27858
  formatCommand(targetDir) {
27507
27859
  return {
@@ -27587,6 +27939,15 @@ function methodName(name) {
27587
27939
  function fieldName(name) {
27588
27940
  return escapeKeyword(toSnakeCase(name));
27589
27941
  }
27942
+ /**
27943
+ * snake_case domain field name for a model field, honoring a `domainName`
27944
+ * override (set via the `fieldHints` config) so a wire field can surface under
27945
+ * a friendlier identifier. The wire name (and thus the `#[serde(rename = ...)]`
27946
+ * key) still derives from `field.name`.
27947
+ */
27948
+ function domainFieldName(field) {
27949
+ return escapeKeyword(toSnakeCase(field.domainName ?? field.name));
27950
+ }
27590
27951
  /** PascalCase enum variant. */
27591
27952
  function variantName(value) {
27592
27953
  const pascal = toPascalCase(String(value));
@@ -27858,15 +28219,17 @@ function generateModels(models, ctx, registry) {
27858
28219
  const files = [];
27859
28220
  const moduleNames = [];
27860
28221
  const seen = /* @__PURE__ */ new Set();
28222
+ const taggedVariantFields = collectTaggedVariantFields(models);
27861
28223
  for (const model of models) {
27862
28224
  const mod = moduleName(model.name);
27863
28225
  if (seen.has(mod)) continue;
27864
28226
  seen.add(mod);
27865
28227
  moduleNames.push(mod);
27866
28228
  const path = ctx.overlayLookup?.fileBySymbol?.get(model.name) ?? `src/models/${mod}.rs`;
27867
- files.push({
28229
+ const content = renderModel(model, registry, taggedVariantFields.get(model.name));
28230
+ if (isModelInScope(model.name, ctx)) files.push({
27868
28231
  path,
27869
- content: renderModel(model, registry),
28232
+ content,
27870
28233
  overwriteExisting: true
27871
28234
  });
27872
28235
  }
@@ -27878,7 +28241,43 @@ function generateModels(models, ctx, registry) {
27878
28241
  });
27879
28242
  return files;
27880
28243
  }
27881
- function renderModel(model, registry) {
28244
+ /**
28245
+ * Walk every model field and record which models are arms of an
28246
+ * internally-tagged union, mapped to that union's discriminator property.
28247
+ */
28248
+ function collectTaggedVariantFields(models) {
28249
+ const out = /* @__PURE__ */ new Map();
28250
+ const visit = (ref) => {
28251
+ switch (ref.kind) {
28252
+ case "union":
28253
+ if (ref.discriminator?.property) for (const variant of ref.variants) {
28254
+ const name = variantModelName(variant);
28255
+ if (name) out.set(name, ref.discriminator.property);
28256
+ }
28257
+ ref.variants.forEach(visit);
28258
+ break;
28259
+ case "array":
28260
+ visit(ref.items);
28261
+ break;
28262
+ case "nullable":
28263
+ visit(ref.inner);
28264
+ break;
28265
+ case "map":
28266
+ visit(ref.valueType);
28267
+ break;
28268
+ default: break;
28269
+ }
28270
+ };
28271
+ for (const model of models) for (const field of model.fields) visit(field.type);
28272
+ return out;
28273
+ }
28274
+ /** Resolve the underlying model name of a union arm, unwrapping a nullable. */
28275
+ function variantModelName(ref) {
28276
+ if (ref.kind === "model") return ref.name;
28277
+ if (ref.kind === "nullable") return variantModelName(ref.inner);
28278
+ return null;
28279
+ }
28280
+ function renderModel(model, registry, tagField) {
27882
28281
  const lines = [];
27883
28282
  lines.push(HEADER_PLACEHOLDER);
27884
28283
  lines.push("#[allow(unused_imports)]");
@@ -27890,7 +28289,7 @@ function renderModel(model, registry) {
27890
28289
  if (model.description) lines.push(...docComment$1(model.description));
27891
28290
  lines.push("#[derive(Debug, Clone, Serialize, Deserialize)]");
27892
28291
  const resolvedNames = resolveFieldNames(model.fields);
27893
- const fieldLines = model.fields.map((f, i) => renderField(f, resolvedNames[i], model.name, registry));
28292
+ const fieldLines = model.fields.map((f, i) => renderField(f, resolvedNames[i], model.name, registry, tagField));
27894
28293
  if (fieldLines.length === 0) lines.push(`pub struct ${typeName(model.name)} {}`);
27895
28294
  else {
27896
28295
  lines.push(`pub struct ${typeName(model.name)} {`);
@@ -27900,17 +28299,20 @@ function renderModel(model, registry) {
27900
28299
  return lines.filter((l) => l !== HEADER_PLACEHOLDER).join("\n") + "\n";
27901
28300
  }
27902
28301
  /**
27903
- * Resolve unique Rust identifiers for struct fields. Multiple wire names can
27904
- * collide after `fieldName()` snake-cases them (e.g. `integration_type` and
27905
- * `integrationType` both become `integration_type`). Subsequent collisions get
27906
- * a numeric suffix so the struct compiles; serde `rename` preserves the
27907
- * original wire name in every case.
28302
+ * Resolve unique Rust identifiers for struct fields. The domain identifier
28303
+ * honors a `fieldHints` override (`domainName`, e.g. wire `connection_type`
28304
+ * domain `type`); the wire name (and the `#[serde(rename = ...)]` key emitted
28305
+ * in `renderField`) still derives from `f.name`. Multiple names can collide
28306
+ * after snake-casing (e.g. `integration_type` and `integrationType` both
28307
+ * become `integration_type`). Subsequent collisions get a numeric suffix so
28308
+ * the struct compiles; serde `rename` preserves the original wire name in every
28309
+ * case.
27908
28310
  */
27909
28311
  function resolveFieldNames(fields) {
27910
28312
  const used = /* @__PURE__ */ new Set();
27911
28313
  const out = [];
27912
28314
  for (const f of fields) {
27913
- const base = fieldName(f.name);
28315
+ const base = domainFieldName(f);
27914
28316
  let candidate = base;
27915
28317
  let suffix = 2;
27916
28318
  while (used.has(candidate)) {
@@ -27922,7 +28324,7 @@ function resolveFieldNames(fields) {
27922
28324
  }
27923
28325
  return out;
27924
28326
  }
27925
- function renderField(field, rustField, modelName, registry) {
28327
+ function renderField(field, rustField, modelName, registry, tagField) {
27926
28328
  const lines = [];
27927
28329
  const hasDescription = !!field.description;
27928
28330
  if (hasDescription) for (const c of docComment$1(field.description)) lines.push(` ${c}`);
@@ -27937,8 +28339,13 @@ function renderField(field, rustField, modelName, registry) {
27937
28339
  });
27938
28340
  if ((!field.required || field.type.kind === "nullable") && !baseType.startsWith("Option<")) baseType = makeOptional(baseType);
27939
28341
  baseType = applySecretRedaction(baseType, field.name);
27940
- if (rename) lines.push(` #[serde(rename = "${rename}")]`);
27941
- if (baseType.startsWith("Option<")) lines.push(" #[serde(skip_serializing_if = \"Option::is_none\", default)]");
28342
+ if (tagField === field.name) {
28343
+ const args = rename ? `rename = "${rename}", default, skip_serializing` : "default, skip_serializing";
28344
+ lines.push(` #[serde(${args})]`);
28345
+ } else {
28346
+ if (rename) lines.push(` #[serde(rename = "${rename}")]`);
28347
+ if (baseType.startsWith("Option<")) lines.push(" #[serde(skip_serializing_if = \"Option::is_none\", default)]");
28348
+ }
27942
28349
  if (field.deprecated) lines.push(" #[deprecated]");
27943
28350
  lines.push(` pub ${rustField}: ${baseType},`);
27944
28351
  return lines.join("\n");
@@ -27981,7 +28388,7 @@ function formatDefault$1(value) {
27981
28388
  * variant and re-serialize as the canonical wire string.
27982
28389
  * - `Display`, `FromStr`, and `AsRef<str>` are implemented for ergonomics.
27983
28390
  */
27984
- function generateEnums(enums, _ctx) {
28391
+ function generateEnums(enums, ctx) {
27985
28392
  const files = [];
27986
28393
  const seen = /* @__PURE__ */ new Set();
27987
28394
  const moduleNames = [];
@@ -27991,6 +28398,7 @@ function generateEnums(enums, _ctx) {
27991
28398
  if (seen.has(mod)) continue;
27992
28399
  seen.add(mod);
27993
28400
  moduleNames.push(mod);
28401
+ if (!isEnumInScope(e.name, ctx)) continue;
27994
28402
  files.push({
27995
28403
  path: `src/enums/${mod}.rs`,
27996
28404
  content: renderEnum(e),
@@ -28150,6 +28558,7 @@ function generateResources(_services, ctx, registry) {
28150
28558
  module: basename,
28151
28559
  struct
28152
28560
  });
28561
+ if (!isMountInScope(mountName, ctx)) continue;
28153
28562
  files.push({
28154
28563
  path: `src/resources/${basename}.rs`,
28155
28564
  content: renderMountGroup(mountName, group.resolvedOps, ctx, registry, lookup),
@@ -28520,7 +28929,7 @@ function registerSyntheticBody(op, paramsName, bodyGroupParamNames, bodyGroupFie
28520
28929
  if (!f.required && !rust.startsWith("Option<")) rust = makeOptional(rust);
28521
28930
  rust = applySecretRedaction(rust, f.name);
28522
28931
  return {
28523
- rustName: fieldName(f.name),
28932
+ rustName: domainFieldName(f),
28524
28933
  wireName: f.name,
28525
28934
  rustType: rust,
28526
28935
  required: !!f.required && !rust.startsWith("Option<"),
@@ -29298,7 +29707,7 @@ function generateTests(spec, ctx) {
29298
29707
  content: renderCommon(ctx),
29299
29708
  overwriteExisting: true
29300
29709
  });
29301
- const groups = groupByMount(ctx);
29710
+ const groups = scopedMountGroups(ctx);
29302
29711
  const modelMap = new Map(spec.models.map((m) => [m.name, m]));
29303
29712
  const enumMap = new Map(spec.enums.map((e) => [e.name, e]));
29304
29713
  for (const [mountName, group] of groups) {
@@ -29875,7 +30284,7 @@ const rustEmitter = {
29875
30284
  return buildOperationsMap(spec, ctx);
29876
30285
  },
29877
30286
  fileHeader() {
29878
- return "// Code generated by oagen. DO NOT EDIT.";
30287
+ return `// ${AUTOGEN_NOTICE}`;
29879
30288
  },
29880
30289
  formatCommand(_targetDir) {
29881
30290
  return {
@@ -29930,4 +30339,4 @@ const workosEmittersPlugin = {
29930
30339
  //#endregion
29931
30340
  export { fieldName$2 as A, servicePropertyName$2 as B, apiClassName as C, dotnetEmitter as D, propertyName as E, fieldName$3 as F, fieldName$5 as H, methodName$3 as I, trimMountedResourceFromMethod$2 as L, trimMountedResourceFromMethod$1 as M, goEmitter as N, appendAsyncSuffix as O, className$3 as P, phpEmitter as R, kotlinEmitter as S, packageSegment as T, safeParamName$1 as U, pythonEmitter as V, nodeEmitter as W, rubyEmitter as _, rustExtractor as a, resolveServiceTarget as b, pythonExtractor as c, rustEmitter as d, fieldName as f, typeName as g, resourceAccessorName as h, kotlinExtractor as i, methodName$2 as j, className$2 as k, rubyExtractor as l, moduleName as m, elixirExtractor as n, goExtractor as o, methodName as p, dotnetExtractor as r, phpExtractor as s, workosEmittersPlugin as t, nodeExtractor as u, buildExportedClassNameSet as v, methodName$1 as w, safeParamName as x, fieldName$1 as y, fieldName$4 as z };
29932
30341
 
29933
- //# sourceMappingURL=plugin-1ckLMpgo.mjs.map
30342
+ //# sourceMappingURL=plugin-BXDPA9pJ.mjs.map