@workos/oagen-emitters 0.18.3 → 0.18.4

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 (51) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/CHANGELOG.md +9 -0
  3. package/dist/index.d.mts.map +1 -1
  4. package/dist/index.mjs +1 -1
  5. package/dist/{plugin-1ckLMpgo.mjs → plugin-Cciic50q.mjs} +443 -99
  6. package/dist/plugin-Cciic50q.mjs.map +1 -0
  7. package/dist/plugin.mjs +1 -1
  8. package/docs/sdk-architecture/rust.md +2 -2
  9. package/package.json +3 -3
  10. package/src/dotnet/fixtures.ts +5 -2
  11. package/src/dotnet/index.ts +2 -1
  12. package/src/dotnet/models.ts +30 -5
  13. package/src/dotnet/naming.ts +10 -0
  14. package/src/dotnet/tests.ts +5 -1
  15. package/src/go/fixtures.ts +4 -2
  16. package/src/go/index.ts +4 -0
  17. package/src/go/models.ts +4 -2
  18. package/src/go/naming.ts +10 -0
  19. package/src/go/resources.ts +19 -6
  20. package/src/kotlin/index.ts +2 -1
  21. package/src/kotlin/models.ts +5 -2
  22. package/src/kotlin/naming.ts +11 -0
  23. package/src/kotlin/tests.ts +5 -1
  24. package/src/node/field-plan.ts +3 -3
  25. package/src/node/index.ts +2 -1
  26. package/src/node/models.ts +40 -1
  27. package/src/node/naming.ts +10 -0
  28. package/src/node/options.ts +45 -1
  29. package/src/node/resources.ts +55 -17
  30. package/src/node/tests.ts +296 -30
  31. package/src/php/index.ts +2 -1
  32. package/src/php/models.ts +11 -5
  33. package/src/php/naming.ts +10 -0
  34. package/src/php/tests.ts +11 -2
  35. package/src/python/fixtures.ts +4 -3
  36. package/src/python/index.ts +2 -1
  37. package/src/python/models.ts +12 -6
  38. package/src/python/naming.ts +10 -0
  39. package/src/python/tests.ts +11 -6
  40. package/src/ruby/index.ts +2 -1
  41. package/src/ruby/models.ts +10 -7
  42. package/src/ruby/naming.ts +10 -0
  43. package/src/ruby/rbi.ts +3 -1
  44. package/src/ruby/tests.ts +4 -1
  45. package/src/rust/index.ts +2 -1
  46. package/src/rust/models.ts +87 -15
  47. package/src/rust/naming.ts +10 -0
  48. package/src/rust/resources.ts +6 -2
  49. package/src/shared/file-header.ts +13 -0
  50. package/test/rust/models.test.ts +49 -0
  51. package/dist/plugin-1ckLMpgo.mjs.map +0 -1
@@ -3637,6 +3637,29 @@ function isHandOwnedType(ctx, name) {
3637
3637
  if (!configured || configured.length === 0) return false;
3638
3638
  return configured.includes(name);
3639
3639
  }
3640
+ /**
3641
+ * Resolve the Node operation override for an operation, keyed by "METHOD /path".
3642
+ */
3643
+ function operationOverrideFor$1(ctx, op) {
3644
+ return nodeOptions(ctx).operationOverrides?.[`${op.httpMethod.toUpperCase()} ${op.path}`];
3645
+ }
3646
+ /**
3647
+ * `planOperation` plus the Node `responseModel` override. When an operation
3648
+ * override supplies `responseModel`, the resolved response model name is
3649
+ * replaced so the resource and its generated test reference the desired wire
3650
+ * type and deserializer. Use this everywhere the Node emitter would otherwise
3651
+ * call `planOperation(op)` directly so resource and test stay in lockstep.
3652
+ */
3653
+ function planOperationFor(op, ctx) {
3654
+ const plan = planOperation(op);
3655
+ const responseModel = operationOverrideFor$1(ctx, op)?.responseModel;
3656
+ if (responseModel && responseModel !== plan.responseModelName) return {
3657
+ ...plan,
3658
+ responseModelName: responseModel,
3659
+ isModelResponse: true
3660
+ };
3661
+ return plan;
3662
+ }
3640
3663
  //#endregion
3641
3664
  //#region src/node/live-surface.ts
3642
3665
  const SRC_DIR = "src";
@@ -4913,7 +4936,7 @@ function serializerHasBaselineIncompatibility(model, baselineResponse, baselineD
4913
4936
  const irDomainFields = /* @__PURE__ */ new Set();
4914
4937
  for (const field of model.fields) {
4915
4938
  irWireFields.add(wireFieldName(field.name));
4916
- irDomainFields.add(fieldName$6(field.name));
4939
+ irDomainFields.add(fieldName$6(field.domainName ?? field.name));
4917
4940
  }
4918
4941
  for (const [wireField2, fieldDef] of Object.entries(baselineResponse.fields)) {
4919
4942
  if (fieldDef.optional) continue;
@@ -4957,7 +4980,7 @@ function serializerHasBaselineIncompatibility(model, baselineResponse, baselineD
4957
4980
  return false;
4958
4981
  }
4959
4982
  function planDeserializeField(field, baselineDomain, baselineResponse, skipFormatFields, ctx) {
4960
- const domain = fieldName$6(field.name);
4983
+ const domain = fieldName$6(field.domainName ?? field.name);
4961
4984
  const wireAccess = `response.${wireFieldName(field.name)}`;
4962
4985
  const skip = skipFormatFields.has(field.name);
4963
4986
  const baselineDomainField = baselineDomain?.fields?.[domain];
@@ -4993,7 +5016,7 @@ function planDeserializeGuard(field, expr, wireAccess, effectivelyOptional, isNe
4993
5016
  }
4994
5017
  function planSerializeField(field, baselineDomain, baselineResponse, skipFormatFields, ctx) {
4995
5018
  const wire = wireFieldName(field.name);
4996
- const domain = fieldName$6(field.name);
5019
+ const domain = fieldName$6(field.domainName ?? field.name);
4997
5020
  const domainAccess = `model.${domain}`;
4998
5021
  const skip = skipFormatFields.has(field.name);
4999
5022
  const baselineWireField = baselineResponse?.fields?.[wire];
@@ -5533,9 +5556,6 @@ function existingInterfaceBarrelExports(ctx, serviceDir, stem) {
5533
5556
  const escapedStem = stem.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5534
5557
  return new RegExp(`export\\s+(?:type\\s+)?(?:\\*|\\{[^}]+\\})\\s+from\\s+['"]\\./${escapedStem}['"]`).test(content);
5535
5558
  }
5536
- function operationOverrideFor$1(ctx, op) {
5537
- return nodeOptions(ctx).operationOverrides?.[`${op.httpMethod.toUpperCase()} ${op.path}`];
5538
- }
5539
5559
  function baselineMethodFor$1(service, method, ctx) {
5540
5560
  const serviceClass = resolveResourceClassName$3(service, ctx);
5541
5561
  return ctx.apiSurface?.classes?.[serviceClass]?.methods?.[method]?.[0];
@@ -5561,10 +5581,11 @@ function optionsObjectParam$1(method) {
5561
5581
  const [param] = method.params;
5562
5582
  if (param.name !== "options") return void 0;
5563
5583
  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;
5584
+ const type = param.type?.replace(/(?:\s*\|\s*(?:undefined|null))+\s*$/, "").trim();
5585
+ if (!type || /^(Record|object|any|unknown)\b/.test(type)) return void 0;
5565
5586
  return {
5566
5587
  name: "options",
5567
- type: param.type,
5588
+ type,
5568
5589
  optional: param.optional === true,
5569
5590
  generated: false
5570
5591
  };
@@ -5765,14 +5786,14 @@ function generateOptionsInterfaces(service, ctx, specEnumNames) {
5765
5786
  const resolvedLookup = buildResolvedLookup(ctx);
5766
5787
  const plans = service.operations.map((op) => ({
5767
5788
  op,
5768
- plan: planOperation(op),
5789
+ plan: planOperationFor(op, ctx),
5769
5790
  method: resolveMethodName$6(op, service, ctx)
5770
5791
  }));
5771
5792
  for (const { op, plan, method } of plans) {
5772
5793
  const resolvedOp = lookupResolved(op, resolvedLookup);
5773
5794
  const optionInfo = optionsObjectInfo(service, method, op, plan, ctx, baselineMethodFor$1(service, method, ctx), resolvedOp);
5774
5795
  if (!optionInfo?.generated) continue;
5775
- if (baselineTypeSourceFile(ctx, optionInfo.type)) continue;
5796
+ if (op.pathParams.length === 0 && baselineTypeSourceFile(ctx, optionInfo.type)) continue;
5776
5797
  const optionsName = optionInfo.type;
5777
5798
  const optionFileStem = `${fileName$3(optionsName)}.interface`;
5778
5799
  const filePath = `src/${serviceDir}/interfaces/${optionFileStem}.ts`;
@@ -5815,7 +5836,8 @@ function generateOptionsInterfaces(service, ctx, specEnumNames) {
5815
5836
  }
5816
5837
  headerParts.push(` ${name}${opt}: ${type};`);
5817
5838
  };
5818
- for (const param of op.pathParams) pushField(fieldName$6(param.name), true, mapParamType(param.type, specEnumNames), param.description, param.deprecated);
5839
+ const optionsPathFieldMap = operationOverrideFor$1(ctx, op)?.pathFieldMap;
5840
+ 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
5841
  for (const param of visibleQueryParamsForOptions(op, plan, resolvedOp)) pushField(fieldName$6(param.name), param.required, mapParamType(param.type, specEnumNames), param.description, param.deprecated);
5820
5842
  if (bodyInfo?.kind === "model") {
5821
5843
  const bodyModel = ctx.spec.models.find((m) => m.name === bodyInfo.name);
@@ -5887,7 +5909,7 @@ function generateResourceClass(service, ctx) {
5887
5909
  const resourcePath = `src/${serviceDir}/${fileName$3(resolvedName)}.ts`;
5888
5910
  let plans = service.operations.map((op) => ({
5889
5911
  op,
5890
- plan: planOperation(op),
5912
+ plan: planOperationFor(op, ctx),
5891
5913
  method: resolveMethodName$6(op, service, ctx)
5892
5914
  }));
5893
5915
  deduplicateMethodNames(plans, ctx);
@@ -5996,6 +6018,7 @@ function generateResourceClass(service, ctx) {
5996
6018
  const hasCustomEncoding = plans.some((p) => p.op.requestBodyEncoding && p.op.requestBodyEncoding !== "json" && p.plan.hasBody);
5997
6019
  if (hasIdempotentPost || hasCustomEncoding) lines.push("import type { PostOptions } from '../common/interfaces/post-options.interface';");
5998
6020
  const importedTypeNames = /* @__PURE__ */ new Set();
6021
+ if (needsPaginationOptionsImport) importedTypeNames.add("PaginationOptions");
5999
6022
  for (const optionType of optionObjectTypes) {
6000
6023
  if (isValidTypeIdentifier(optionType)) {
6001
6024
  if (importedTypeNames.has(optionType)) continue;
@@ -6554,7 +6577,14 @@ function renderOptionsObjectMethod(lines, op, plan, method, service, ctx, modelM
6554
6577
  lines.push(" }");
6555
6578
  return true;
6556
6579
  }
6557
- return false;
6580
+ {
6581
+ lines.push(` async ${method}(${renderOptionsParam(optionParam)}): Promise<void> {`);
6582
+ renderOptionsObjectDestructure(lines, pathBindings);
6583
+ const emptyBodyArg = httpMethodNeedsBody(op.httpMethod) ? ", {}" : "";
6584
+ lines.push(` await this.workos.${op.httpMethod}(${pathStr}${emptyBodyArg}${queryOptionsArg});`);
6585
+ lines.push(" }");
6586
+ return true;
6587
+ }
6558
6588
  }
6559
6589
  function renderOptionsObjectDestructure(lines, pathBindings, restName) {
6560
6590
  if (pathBindings.length > 0 && restName) lines.push(` const { ${pathBindings.join(", ")}, ...${restName} } = options;`);
@@ -6574,7 +6604,8 @@ function bodyArgExprWithParam(bodyExpr, paramName) {
6574
6604
  return paramName === "payload" ? bodyExpr : bodyExpr.replace(/\bpayload\b/g, paramName);
6575
6605
  }
6576
6606
  function buildOptionsObjectPathBindings(op, optionType, ctx) {
6577
- return op.pathParams.map((param) => resolveOptionsObjectField$1(fieldName$6(param.name), optionType, ctx));
6607
+ const pathFieldMap = operationOverrideFor$1(ctx, op)?.pathFieldMap;
6608
+ return op.pathParams.map((param) => resolveOptionsObjectField$1(fieldName$6(param.name), optionType, ctx, pathFieldMap));
6578
6609
  }
6579
6610
  /**
6580
6611
  * Map spec path-param names (e.g. `omId`) to the SDK field name exposed on
@@ -6585,14 +6616,17 @@ function buildOptionsObjectPathBindings(op, optionType, ctx) {
6585
6616
  */
6586
6617
  function buildOptionsObjectPathParamMap(op, optionType, ctx) {
6587
6618
  const map = /* @__PURE__ */ new Map();
6619
+ const pathFieldMap = operationOverrideFor$1(ctx, op)?.pathFieldMap;
6588
6620
  for (const param of op.pathParams) {
6589
6621
  const localName = fieldName$6(param.name);
6590
- const sdkField = resolveOptionsObjectField$1(localName, optionType, ctx);
6622
+ const sdkField = resolveOptionsObjectField$1(localName, optionType, ctx, pathFieldMap);
6591
6623
  if (sdkField !== localName) map.set(param.name, sdkField);
6592
6624
  }
6593
6625
  return map;
6594
6626
  }
6595
- function resolveOptionsObjectField$1(localName, optionType, ctx) {
6627
+ function resolveOptionsObjectField$1(localName, optionType, ctx, pathFieldMap) {
6628
+ const mapped = pathFieldMap?.[localName];
6629
+ if (mapped) return mapped;
6596
6630
  const fields = ctx.apiSurface?.interfaces?.[optionType]?.fields;
6597
6631
  if (!fields) return localName;
6598
6632
  if (fields[localName]) return localName;
@@ -7548,7 +7582,7 @@ function generateModels$7(models, ctx, shared) {
7548
7582
  else {
7549
7583
  lines.push(`export interface ${domainName}${typeParams} {`);
7550
7584
  for (const field of model.fields) {
7551
- const domainFieldName = fieldName$6(field.name);
7585
+ const domainFieldName = fieldName$6(field.domainName ?? field.name);
7552
7586
  if (seenDomainFields.has(domainFieldName)) continue;
7553
7587
  seenDomainFields.add(domainFieldName);
7554
7588
  if (field.description || field.deprecated || field.readOnly || field.writeOnly || field.default !== void 0) {
@@ -7565,7 +7599,10 @@ function generateModels$7(models, ctx, shared) {
7565
7599
  const responseBaselineField = baselineResponse?.fields?.[domainWireField];
7566
7600
  const domainResponseOptionalMismatch = baselineField && !baselineField.optional && responseBaselineField && responseBaselineField.optional;
7567
7601
  const readonlyPrefix = field.readOnly ? "readonly " : "";
7568
- if (baselineField && !domainResponseOptionalMismatch && !hasDateTimeConversion(field.type) && baselineTypeResolvable(baselineField.type, importableNames) && baselineFieldCompatible(baselineField, field)) {
7602
+ if (genericDefaults.has(model.name) && baselineField && typeReferencesUnresolvable(baselineField.type, unresolvableNames)) {
7603
+ const opt = baselineField.optional ? "?" : "";
7604
+ lines.push(` ${readonlyPrefix}${domainFieldName}${opt}: ${substituteGenericParam(baselineField.type, unresolvableNames)};`);
7605
+ } else if (baselineField && !domainResponseOptionalMismatch && !hasDateTimeConversion(field.type) && baselineTypeResolvable(baselineField.type, importableNames) && baselineFieldCompatible(baselineField, field)) {
7569
7606
  const opt = baselineField.optional ? "?" : "";
7570
7607
  lines.push(` ${readonlyPrefix}${domainFieldName}${opt}: ${baselineField.type};`);
7571
7608
  } else {
@@ -7588,7 +7625,10 @@ function generateModels$7(models, ctx, shared) {
7588
7625
  if (seenWireFields.has(wireField)) continue;
7589
7626
  seenWireFields.add(wireField);
7590
7627
  const baselineField = baselineResponse?.fields?.[wireField];
7591
- if (baselineField && baselineTypeResolvable(baselineField.type, importableNames) && baselineFieldCompatible(baselineField, field)) {
7628
+ if (genericDefaults.has(model.name) && baselineField && typeReferencesUnresolvable(baselineField.type, unresolvableNames)) {
7629
+ const opt = baselineField.optional ? "?" : "";
7630
+ lines.push(` ${wireField}${opt}: ${substituteGenericParam(baselineField.type, unresolvableNames)};`);
7631
+ } else if (baselineField && baselineTypeResolvable(baselineField.type, importableNames) && baselineFieldCompatible(baselineField, field)) {
7592
7632
  const opt = baselineField.optional ? "?" : "";
7593
7633
  lines.push(` ${wireField}${opt}: ${baselineField.type};`);
7594
7634
  } else {
@@ -8031,6 +8071,19 @@ function hasSpecificIRType(ref) {
8031
8071
  default: return false;
8032
8072
  }
8033
8073
  }
8074
+ /** True when a baseline field type references one of the model's generic
8075
+ * param identifiers (collected as `unresolvableNames` — bare type names that
8076
+ * resolve to nothing importable, which for a generic model are its params). */
8077
+ function typeReferencesUnresolvable(type, unresolvable) {
8078
+ if (unresolvable.size === 0) return false;
8079
+ const names = type.match(/\b[A-Z][a-zA-Z0-9]*\b/g);
8080
+ return !!names && names.some((n) => unresolvable.has(n));
8081
+ }
8082
+ /** Rewrite a baseline field type so references to the model's (renamed)
8083
+ * generic param resolve to the emitted `GenericType` param. */
8084
+ function substituteGenericParam(type, unresolvable) {
8085
+ return type.replace(/\b[A-Z][a-zA-Z0-9]*\b/g, (name) => unresolvable.has(name) ? "GenericType" : name);
8086
+ }
8034
8087
  function renderTypeParams(model, genericDefaults) {
8035
8088
  if (!model.typeParams?.length) {
8036
8089
  if (genericDefaults?.has(model.name)) return "<GenericType extends Record<string, unknown> = Record<string, unknown>>";
@@ -8557,10 +8610,11 @@ function optionsObjectParam(method) {
8557
8610
  const [param] = method.params;
8558
8611
  if (param.name !== "options") return void 0;
8559
8612
  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;
8613
+ const type = param.type?.replace(/(?:\s*\|\s*(?:undefined|null))+\s*$/, "").trim();
8614
+ if (!type || /^(Record|object|any|unknown)\b/.test(type)) return void 0;
8561
8615
  return {
8562
8616
  name: param.name,
8563
- type: param.type
8617
+ type
8564
8618
  };
8565
8619
  }
8566
8620
  function configuredOptionsMethod(ctx, op) {
@@ -8625,6 +8679,7 @@ function generateTests$7(spec, ctx) {
8625
8679
  };
8626
8680
  const ops = isNodeOwnedService(ctx, mountName, resolveResourceClassName$3(mergedService, ctx)) ? operations : uncoveredOperations(mergedService, ctx);
8627
8681
  if (ops.length === 0) continue;
8682
+ const ignoredMethodNames = ignoredResourceMethodNames(ctx, `src/${resolveResourceDir(mergedService, ctx)}/${fileName$3(resolveResourceClassName$3(mergedService, ctx))}.ts`);
8628
8683
  const propName = mountAccessors.get(mountName) ?? servicePropertyName$4(mountName);
8629
8684
  if (ctx.apiSurface && baselineWorkOSProps.size > 0 && !baselineWorkOSProps.has(propName)) continue;
8630
8685
  if (resolveResourceClassName$3(mergedService, ctx) !== mountName) continue;
@@ -8632,23 +8687,31 @@ function generateTests$7(spec, ctx) {
8632
8687
  ...mergedService,
8633
8688
  operations: ops
8634
8689
  } : mergedService;
8635
- files.push(generateServiceTest$3(testService, spec, ctx, modelMap, mountAccessors));
8690
+ files.push(generateServiceTest$3(testService, spec, ctx, modelMap, mountAccessors, ignoredMethodNames));
8636
8691
  }
8637
8692
  const serializerTests = generateSerializerTests(spec, ctx);
8638
8693
  for (const f of serializerTests) files.push(f);
8639
8694
  return files;
8640
8695
  }
8641
- function generateServiceTest$3(service, spec, ctx, modelMap, mountAccessors) {
8696
+ function generateServiceTest$3(service, spec, ctx, modelMap, mountAccessors, ignoredMethodNames = /* @__PURE__ */ new Set()) {
8642
8697
  const resolvedName = resolveResourceClassName$3(service, ctx);
8643
8698
  const serviceDir = resolveResourceDir(service, ctx);
8644
8699
  const serviceClass = resolvedName;
8645
8700
  const serviceProp = mountAccessors?.get(service.name) ?? servicePropertyName$4(resolveServiceName(service, ctx));
8646
8701
  const testPath = `src/${serviceDir}/${fileName$3(resolvedName)}.spec.ts`;
8647
- const plans = service.operations.map((op) => ({
8702
+ const allPlans = service.operations.map((op) => ({
8648
8703
  op,
8649
- plan: planOperation(op),
8704
+ plan: planOperationFor(op, ctx),
8650
8705
  method: resolveMethodName$6(op, service, ctx)
8651
8706
  }));
8707
+ const resolvedLookup = buildResolvedLookup(ctx);
8708
+ const preservedScopes = methodsWithPreservedTestBlocks(ctx, testPath);
8709
+ const plans = allPlans.filter((p) => {
8710
+ if (preservedScopes.has(p.method)) return true;
8711
+ if (ignoredMethodNames.has(p.method)) return false;
8712
+ if (lookupResolved(p.op, resolvedLookup)?.urlBuilder) return false;
8713
+ return true;
8714
+ });
8652
8715
  if (ctx.overlayLookup?.methodByOperation) {
8653
8716
  const methodOrder = /* @__PURE__ */ new Map();
8654
8717
  let pos = 0;
@@ -8695,7 +8758,7 @@ function generateServiceTest$3(service, spec, ctx, modelMap, mountAccessors) {
8695
8758
  lines.push("");
8696
8759
  lines.push("const workos = new WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');");
8697
8760
  lines.push("");
8698
- const { lines: helperLines, helpers: entityHelperNames } = generateEntityHelpers(plans, modelMap, ctx);
8761
+ const { lines: helperLines, helpers: entityHelperNames } = generateEntityHelpers(service, allPlans, plans, existingTestIgnoreText(ctx, testPath), modelMap, ctx);
8699
8762
  for (const line of helperLines) lines.push(line);
8700
8763
  lines.push(`describe('${serviceClass}', () => {`);
8701
8764
  lines.push(" beforeEach(() => fetch.resetMocks());");
@@ -8711,6 +8774,20 @@ function generateServiceTest$3(service, spec, ctx, modelMap, mountAccessors) {
8711
8774
  lines.push(" });");
8712
8775
  }
8713
8776
  lines.push("});");
8777
+ const body = lines.join("\n");
8778
+ const enumImportLines = [];
8779
+ for (const [name, info] of Object.entries(ctx.apiSurface?.enums ?? {})) {
8780
+ if (!new RegExp(`\\b${name}\\.[A-Za-z_$]`).test(body)) continue;
8781
+ if (new RegExp(`import\\b[^;]*\\b${name}\\b[^;]*from`).test(body)) continue;
8782
+ const sourceFile = info.sourceFile;
8783
+ const spec = sourceFile ? relativeImport(testPath, sourceFile).replace(/\.ts$/, "") : "./interfaces";
8784
+ enumImportLines.push(`import { ${name} } from '${spec}';`);
8785
+ }
8786
+ if (enumImportLines.length > 0) {
8787
+ const anchor = lines.indexOf("import { WorkOS } from '../workos';");
8788
+ const at = anchor >= 0 ? anchor + 1 : 0;
8789
+ lines.splice(at, 0, ...enumImportLines);
8790
+ }
8714
8791
  return {
8715
8792
  path: testPath,
8716
8793
  content: lines.join("\n"),
@@ -8729,11 +8806,81 @@ function pathParamTestValue(param, paramName) {
8729
8806
  if (name) return `test_${fieldName$6(name)}`;
8730
8807
  return "test_id";
8731
8808
  }
8732
- function queryParamTestValue(param, modelMap) {
8809
+ /** Render an example value as a valid TS literal expression.
8810
+ * Objects and nested arrays go through JSON.stringify so map-typed params
8811
+ * (e.g. providerQueryParams) don't coerce to the literal text `[object Object]`.
8812
+ */
8813
+ function renderExampleLiteral(value) {
8814
+ if (typeof value === "string") return `'${value}'`;
8815
+ if (Array.isArray(value)) return `[${value.map(renderExampleLiteral).join(", ")}]`;
8816
+ if (value !== null && typeof value === "object") return JSON.stringify(value);
8817
+ return String(value);
8818
+ }
8819
+ /** Resolve the enum members a query-param test value must satisfy. An enum is
8820
+ * expressed several ways: an inline `EnumRef` with `values`, an `enum`/`model`
8821
+ * ref by name, or — when the IR flattened the param to a bare string but the
8822
+ * hand-written options interface still types the field as an enum — the
8823
+ * baseline options field type (e.g. `ConnectionType`). */
8824
+ /** All valid values for an enum named `name`. The extracted SDK surface is
8825
+ * consulted first: when an options field is typed with a hand-written enum
8826
+ * (e.g. `ConnectionType` from `connection-type.enum.ts`), that enum — not a
8827
+ * same-named IR enum that may carry extra members like `Pending` — is what
8828
+ * the generated test must type-check against. Falls back to the IR spec. */
8829
+ function enumValuesByName(name, ctx) {
8830
+ const members = ctx?.apiSurface?.enums?.[name]?.members;
8831
+ if (members && Object.keys(members).length > 0) return Object.values(members);
8832
+ const specValues = ctx?.spec.enums.find((e) => e.name === name)?.values;
8833
+ if (specValues?.length) return specValues.map((v) => v.value);
8834
+ }
8835
+ function resolveParamEnumValues(param, optionFieldType, ctx) {
8836
+ if (optionFieldType) {
8837
+ const bare = optionFieldType.replace(/\[\]/g, "").replace(/\|\s*(undefined|null)/g, "").trim();
8838
+ if (/^[A-Za-z_$][\w$]*$/.test(bare)) {
8839
+ const values = enumValuesByName(bare, ctx);
8840
+ if (values) return values;
8841
+ }
8842
+ }
8843
+ if ((param.type.kind === "enum" || param.type.kind === "model") && param.type.name) {
8844
+ const values = enumValuesByName(param.type.name, ctx);
8845
+ if (values) return values;
8846
+ }
8847
+ if (param.type.kind === "enum" && param.type.values?.length) return param.type.values;
8848
+ }
8849
+ /** When an options field is typed with a real (nominal) TS `enum` from the SDK
8850
+ * surface, a string literal won't type-check — the value must be a member
8851
+ * reference (`ConnectionType.ADFSSAML`). Returns the enum's name + members so
8852
+ * the caller can both render the reference and import the right declaration. */
8853
+ function resolveRealEnum(param, optionFieldType, ctx) {
8854
+ const candidates = [];
8855
+ if (optionFieldType) {
8856
+ const bare = optionFieldType.replace(/\[\]/g, "").replace(/\|\s*(undefined|null)/g, "").trim();
8857
+ if (/^[A-Za-z_$][\w$]*$/.test(bare)) candidates.push(bare);
8858
+ }
8859
+ if ((param.type.kind === "enum" || param.type.kind === "model") && param.type.name) candidates.push(param.type.name);
8860
+ for (const name of candidates) {
8861
+ const members = ctx?.apiSurface?.enums?.[name]?.members;
8862
+ if (members && Object.keys(members).length > 0) return {
8863
+ name,
8864
+ members
8865
+ };
8866
+ }
8867
+ return null;
8868
+ }
8869
+ function queryParamTestValue(param, modelMap, ctx, optionFieldType) {
8870
+ const realEnum = resolveRealEnum(param, optionFieldType, ctx);
8871
+ if (realEnum) {
8872
+ const entries = Object.entries(realEnum.members);
8873
+ const [memberKey] = (typeof param.example === "string" ? entries.find(([, v]) => v === param.example) : void 0) ?? entries[0];
8874
+ return `${realEnum.name}.${memberKey}`;
8875
+ }
8876
+ const enumValues = resolveParamEnumValues(param, optionFieldType, ctx);
8877
+ if (enumValues?.length) {
8878
+ const valid = typeof param.example === "string" && enumValues.includes(param.example) ? param.example : enumValues[0];
8879
+ return typeof valid === "string" ? `'${valid}'` : String(valid);
8880
+ }
8733
8881
  if (param.example !== void 0) {
8734
- if (Array.isArray(param.example)) return `[${param.example.map((v) => typeof v === "string" ? `'${v}'` : String(v)).join(", ")}]`;
8735
8882
  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);
8883
+ return renderExampleLiteral(param.example);
8737
8884
  }
8738
8885
  return fixtureValueForType(param.type, param.name, "Options", modelMap) ?? "'test'";
8739
8886
  }
@@ -8899,9 +9046,10 @@ function buildOptionsObjectTestArg(op, plan, baselineMethod, modelMap, ctx) {
8899
9046
  const optionParam = optionsObjectParam(baselineMethod);
8900
9047
  if (!optionParam) return null;
8901
9048
  const entries = [];
9049
+ const pathFieldMap = ctx ? operationOverrideFor(ctx, op)?.pathFieldMap : void 0;
8902
9050
  for (const param of op.pathParams) {
8903
9051
  const localName = fieldName$6(param.name);
8904
- const optionField = resolveOptionsObjectField(localName, optionParam.type, ctx);
9052
+ const optionField = resolveOptionsObjectField(localName, optionParam.type, ctx, pathFieldMap);
8905
9053
  entries.push(`${optionField}: ${JSON.stringify(pathParamTestValue(param, localName))}`);
8906
9054
  }
8907
9055
  if (plan.isPaginated) entries.push("order: 'desc'");
@@ -8913,7 +9061,8 @@ function buildOptionsObjectTestArg(op, plan, baselineMethod, modelMap, ctx) {
8913
9061
  ].includes(param.name)) : op.queryParams).filter((param) => !param.deprecated);
8914
9062
  for (const param of queryParams) {
8915
9063
  const localName = fieldName$6(param.name);
8916
- const value = queryParamTestValue(param, modelMap);
9064
+ const optionFieldType = ctx?.apiSurface?.interfaces?.[optionParam.type]?.fields?.[localName]?.type;
9065
+ const value = queryParamTestValue(param, modelMap, ctx, optionFieldType);
8917
9066
  entries.push(`${localName}: ${value}`);
8918
9067
  }
8919
9068
  if (plan.hasBody) {
@@ -8941,7 +9090,9 @@ function objectLiteralEntries(literal) {
8941
9090
  const body = trimmed.slice(1, -1).trim();
8942
9091
  return body ? body.split(",").map((entry) => entry.trim()) : [];
8943
9092
  }
8944
- function resolveOptionsObjectField(localName, optionType, ctx) {
9093
+ function resolveOptionsObjectField(localName, optionType, ctx, pathFieldMap) {
9094
+ const mapped = pathFieldMap?.[localName];
9095
+ if (mapped) return mapped;
8945
9096
  const fields = ctx?.apiSurface?.interfaces?.[optionType]?.fields;
8946
9097
  if (!fields) return localName;
8947
9098
  if (fields[localName]) return localName;
@@ -8956,20 +9107,85 @@ function resolveOptionsObjectField(localName, optionType, ctx) {
8956
9107
  * Generate per-entity assertion helper functions for models used in 2+ tests.
8957
9108
  * Returns { lines, helpers } where helpers is a Set of helper function names.
8958
9109
  */
8959
- function generateEntityHelpers(plans, modelMap, ctx) {
8960
- const modelUsage = /* @__PURE__ */ new Map();
8961
- for (const { op, plan } of plans) {
8962
- let modelName = null;
9110
+ /** Describe-scope names (i.e. method names) that have an `@oagen-ignore` block
9111
+ * nested inside them in the existing test file. Such a `describe` must keep
9112
+ * being emitted even when the method is hand-owned — otherwise the engine has
9113
+ * no `describe` to re-nest the preserved block under and orphans it to the top
9114
+ * of the file (out of `beforeEach` scope), which hangs at runtime. Mirrors the
9115
+ * engine's `findContainingDescribeScope`. */
9116
+ function methodsWithPreservedTestBlocks(ctx, relPath) {
9117
+ const scopes = /* @__PURE__ */ new Set();
9118
+ const root = ctx.outputDir ?? ctx.targetDir;
9119
+ if (!root) return scopes;
9120
+ let content;
9121
+ try {
9122
+ content = fs.readFileSync(path.join(root, relPath), "utf8");
9123
+ } catch {
9124
+ return scopes;
9125
+ }
9126
+ const lines = content.split("\n");
9127
+ const indentOf = (l) => l.length - l.trimStart().length;
9128
+ for (let i = 0; i < lines.length; i++) {
9129
+ if (!lines[i].includes("@oagen-ignore-start")) continue;
9130
+ if (/^\S/.test(lines[i])) continue;
9131
+ const markerIndent = indentOf(lines[i]);
9132
+ for (let j = i - 1; j >= 0; j--) {
9133
+ if (indentOf(lines[j]) >= markerIndent) continue;
9134
+ const m = lines[j].match(/^\s*describe\((['"`])(.+?)\1\s*,/);
9135
+ if (m) {
9136
+ scopes.add(m[2]);
9137
+ break;
9138
+ }
9139
+ if (/^\s*(?:export\s+)?class\s+\w+/.test(lines[j])) break;
9140
+ }
9141
+ }
9142
+ return scopes;
9143
+ }
9144
+ /** Concatenated text of the existing test file's `@oagen-ignore` regions, so
9145
+ * helper generation can see which `expect<Model>()` helpers preserved
9146
+ * hand-written test blocks still reference. */
9147
+ function existingTestIgnoreText(ctx, relPath) {
9148
+ const root = ctx.outputDir ?? ctx.targetDir;
9149
+ if (!root) return "";
9150
+ let content;
9151
+ try {
9152
+ content = fs.readFileSync(path.join(root, relPath), "utf8");
9153
+ } catch {
9154
+ return "";
9155
+ }
9156
+ return [...content.matchAll(/@oagen-ignore-start[\s\S]*?@oagen-ignore-end/g)].map((m) => m[0]).join("\n");
9157
+ }
9158
+ function generateEntityHelpers(service, allPlans, renderedPlans, ignoreRegionText, modelMap, ctx) {
9159
+ const responseModelOf = (entry) => {
9160
+ const { op, plan } = entry;
8963
9161
  if (plan.isPaginated && op.pagination?.itemType.kind === "model") {
8964
- modelName = op.pagination.itemType.name;
9162
+ let modelName = op.pagination.itemType.name;
8965
9163
  const rawModel = modelMap.get(modelName);
8966
9164
  if (rawModel) {
8967
9165
  const unwrapped = unwrapListModel$4(rawModel, modelMap);
8968
9166
  if (unwrapped) modelName = unwrapped.name;
8969
9167
  }
8970
- } else if (plan.responseModelName) modelName = plan.responseModelName;
9168
+ return modelName;
9169
+ }
9170
+ return plan.responseModelName ?? null;
9171
+ };
9172
+ const modelUsage = /* @__PURE__ */ new Map();
9173
+ for (const entry of allPlans) {
9174
+ const modelName = responseModelOf(entry);
8971
9175
  if (modelName) modelUsage.set(modelName, (modelUsage.get(modelName) ?? 0) + 1);
8972
9176
  }
9177
+ const renderedModels = /* @__PURE__ */ new Set();
9178
+ for (const entry of renderedPlans) {
9179
+ const modelName = responseModelOf(entry);
9180
+ if (!modelName) continue;
9181
+ if (entry.plan.isPaginated && entry.op.pagination?.itemType.kind === "model") {
9182
+ const baselineMethod = optionsMethodFor(service, entry.method, entry.op, entry.plan, ctx);
9183
+ const baselineItemType = autoPaginatableItemType(baselineMethod?.returnType);
9184
+ const generatedItemType = resolveInterfaceName(modelName, ctx);
9185
+ if (Boolean(baselineMethod?.returnType && !baselineItemType) || Boolean(baselineItemType && generatedItemType && baselineItemType !== generatedItemType)) continue;
9186
+ }
9187
+ renderedModels.add(modelName);
9188
+ }
8973
9189
  const lines = [];
8974
9190
  const helpers = /* @__PURE__ */ new Set();
8975
9191
  for (const [modelName, count] of modelUsage) {
@@ -8980,6 +9196,7 @@ function generateEntityHelpers(plans, modelMap, ctx) {
8980
9196
  if (assertions.length === 0) continue;
8981
9197
  const helperName = `expect${resolveInterfaceName(modelName, ctx)}`;
8982
9198
  if (helpers.has(helperName)) continue;
9199
+ if (!(renderedModels.has(modelName) || ignoreRegionText.includes(`${helperName}(`))) continue;
8983
9200
  helpers.add(helperName);
8984
9201
  lines.push(`function ${helperName}(result: any) {`);
8985
9202
  for (const assertion of assertions) lines.push(` ${assertion}`);
@@ -9009,7 +9226,7 @@ function buildFieldAssertions(model, accessor, modelMap) {
9009
9226
  const assertions = [];
9010
9227
  for (const field of model.fields) {
9011
9228
  if (!field.required) continue;
9012
- const domainField = fieldName$6(field.name);
9229
+ const domainField = fieldName$6(field.domainName ?? field.name);
9013
9230
  const isDateTime = isDateTimeFieldType(field.type);
9014
9231
  const fieldAccessor = isDateTime ? `${accessor}.${domainField}.toISOString()` : `${accessor}.${domainField}`;
9015
9232
  if (field.example !== void 0) {
@@ -10075,6 +10292,21 @@ function withNodeOperationOverrides(ctx) {
10075
10292
  return next;
10076
10293
  }
10077
10294
  //#endregion
10295
+ //#region src/shared/file-header.ts
10296
+ /**
10297
+ * Canonical "do not edit" banner text shared by every emitter's
10298
+ * `fileHeader()`. The text is comment-syntax agnostic — each emitter prefixes
10299
+ * it with the appropriate comment marker for its language (`//`, `#`, etc.).
10300
+ *
10301
+ * Keeping this in one place prevents the wording from drifting between
10302
+ * languages (Rust previously emitted a different banner).
10303
+ *
10304
+ * Go intentionally does NOT use this constant: its header must match the
10305
+ * standard `^// Code generated .* DO NOT EDIT\.$` marker that Go tooling
10306
+ * relies on to recognize generated files. See `src/go/index.ts`.
10307
+ */
10308
+ const AUTOGEN_NOTICE = "This file is auto-generated by oagen. Do not edit.";
10309
+ //#endregion
10078
10310
  //#region src/node/index.ts
10079
10311
  /**
10080
10312
  * Cache live-surface per ctx — every emitter method receives the same ctx in
@@ -10620,7 +10852,7 @@ const nodeEmitter = {
10620
10852
  return {};
10621
10853
  },
10622
10854
  fileHeader() {
10623
- return "// This file is auto-generated by oagen. Do not edit.";
10855
+ return `// ${AUTOGEN_NOTICE}`;
10624
10856
  },
10625
10857
  formatCommand(targetDir) {
10626
10858
  const hasPrettier = fs$1.existsSync(path$1.join(targetDir, ".prettierrc"));
@@ -10688,6 +10920,15 @@ function fieldName$5(name) {
10688
10920
  return toSnakeCase(name);
10689
10921
  }
10690
10922
  /**
10923
+ * snake_case domain field name for a model field, honoring a `domainName`
10924
+ * override (set via the `fieldHints` config) so a wire field can surface under
10925
+ * a friendlier name. The wire name (still derived from `field.name`) is
10926
+ * unaffected, so the API contract is preserved.
10927
+ */
10928
+ function domainFieldName$5(field) {
10929
+ return toSnakeCase(field.domainName ?? field.name);
10930
+ }
10931
+ /**
10691
10932
  * Python builtins that should not be shadowed by parameter names.
10692
10933
  * When a path/query param name collides, suffix with underscore.
10693
10934
  */
@@ -11585,7 +11826,7 @@ function generateModels$6(models, ctx) {
11585
11826
  }
11586
11827
  const seenFieldNames = /* @__PURE__ */ new Set();
11587
11828
  const deduplicatedFields = model.fields.filter((f) => {
11588
- const pyName = fieldName$5(f.name);
11829
+ const pyName = domainFieldName$5(f);
11589
11830
  if (seenFieldNames.has(pyName)) return false;
11590
11831
  seenFieldNames.add(pyName);
11591
11832
  return true;
@@ -11647,7 +11888,7 @@ function generateModels$6(models, ctx) {
11647
11888
  return typeStr;
11648
11889
  };
11649
11890
  for (const field of requiredFields) {
11650
- const pyFieldName = fieldName$5(field.name);
11891
+ const pyFieldName = domainFieldName$5(field);
11651
11892
  const pyType = rewriteDiscriminatorType(resolveModelFieldType(field.type));
11652
11893
  if (field.description || field.deprecated) {
11653
11894
  const parts = [];
@@ -11658,7 +11899,7 @@ function generateModels$6(models, ctx) {
11658
11899
  } else lines.push(` ${pyFieldName}: ${pyType}`);
11659
11900
  }
11660
11901
  for (const field of optionalFields) {
11661
- const pyFieldName = fieldName$5(field.name);
11902
+ const pyFieldName = domainFieldName$5(field);
11662
11903
  const pyType = `Optional[${rewriteDiscriminatorType(field.type.kind === "nullable" ? resolveModelFieldType(field.type.inner) : resolveModelFieldType(field.type))}]`;
11663
11904
  if (field.description || field.deprecated) {
11664
11905
  const parts = [];
@@ -11676,7 +11917,7 @@ function generateModels$6(models, ctx) {
11676
11917
  const preludeLines = [];
11677
11918
  const fieldAssignmentLines = [];
11678
11919
  for (const field of [...requiredFields, ...optionalFields]) {
11679
- const pyFieldName = fieldName$5(field.name);
11920
+ const pyFieldName = domainFieldName$5(field);
11680
11921
  const wireKey = field.name;
11681
11922
  const isRequired = !isOptionalField(model.name, field, ctx);
11682
11923
  const discPrelude = renderDiscriminatedUnionPrelude(field, pyFieldName, wireKey, modelClassName, isRequired);
@@ -11704,7 +11945,7 @@ function generateModels$6(models, ctx) {
11704
11945
  lines.push(" \"\"\"Serialize to a dictionary.\"\"\"");
11705
11946
  lines.push(" result: Dict[str, Any] = {}");
11706
11947
  for (const field of [...requiredFields, ...optionalFields]) {
11707
- const pyFieldName = fieldName$5(field.name);
11948
+ const pyFieldName = domainFieldName$5(field);
11708
11949
  const wireKey = field.name;
11709
11950
  const isRequired = !isOptionalField(model.name, field, ctx);
11710
11951
  const isNullable = field.type.kind === "nullable";
@@ -13580,7 +13821,7 @@ function generateModelFixture$4(model, modelMap, enumMap) {
13580
13821
  const fixture = {};
13581
13822
  const seenFieldNames = /* @__PURE__ */ new Set();
13582
13823
  const deduplicatedFields = model.fields.filter((f) => {
13583
- const pyName = fieldName$5(f.name);
13824
+ const pyName = domainFieldName$5(f);
13584
13825
  if (seenFieldNames.has(pyName)) return false;
13585
13826
  seenFieldNames.add(pyName);
13586
13827
  return true;
@@ -14376,16 +14617,16 @@ function pickAssertableFields(modelName, spec, maxFields = 2) {
14376
14617
  if (typeof val === "string") {
14377
14618
  if (val.includes("\"") || val.includes("'") || val.includes("{") || val.includes("\\") || val.includes("\n")) continue;
14378
14619
  results.push({
14379
- field: fieldName$5(f.name),
14620
+ field: domainFieldName$5(f),
14380
14621
  value: `"${val}"`
14381
14622
  });
14382
14623
  } else if (typeof val === "boolean") results.push({
14383
- field: fieldName$5(f.name),
14624
+ field: domainFieldName$5(f),
14384
14625
  value: val ? "True" : "False",
14385
14626
  isBool: true
14386
14627
  });
14387
14628
  else if (typeof val === "number") results.push({
14388
- field: fieldName$5(f.name),
14629
+ field: domainFieldName$5(f),
14389
14630
  value: String(val)
14390
14631
  });
14391
14632
  }
@@ -14445,7 +14686,7 @@ function buildTestArgs$1(op, spec, hiddenParams) {
14445
14686
  ].includes(param.name)) continue;
14446
14687
  if (plan.hasBody && op.requestBody?.kind === "model") {
14447
14688
  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;
14689
+ if (spec.models.find((m) => m.name === rbName)?.fields.some((f) => domainFieldName$5(f) === fieldName$5(param.name))) continue;
14449
14690
  }
14450
14691
  if (param.required && !pathParamNames.has(fieldName$5(param.name))) args.push(`${fieldName$5(param.name)}=${generateTestValue$1(param.type, param.name)}`);
14451
14692
  }
@@ -14704,7 +14945,7 @@ function generateModelRoundTripTests(spec, ctx) {
14704
14945
  if (model.discriminator) continue;
14705
14946
  const seenFieldNames = /* @__PURE__ */ new Set();
14706
14947
  const dedupFields = model.fields.filter((f) => {
14707
- const pyName = fieldName$5(f.name);
14948
+ const pyName = domainFieldName$5(f);
14708
14949
  if (seenFieldNames.has(pyName)) return false;
14709
14950
  seenFieldNames.add(pyName);
14710
14951
  return true;
@@ -14896,7 +15137,7 @@ const pythonEmitter = {
14896
15137
  return buildOperationsMap$6(spec, ctx);
14897
15138
  },
14898
15139
  fileHeader() {
14899
- return "# This file is auto-generated by oagen. Do not edit.";
15140
+ return `# ${AUTOGEN_NOTICE}`;
14900
15141
  },
14901
15142
  formatCommand(_targetDir) {
14902
15143
  return {
@@ -14997,6 +15238,15 @@ function resolveMethodName$4(op, _service, ctx) {
14997
15238
  function fieldName$4(name) {
14998
15239
  return toCamelCase(name);
14999
15240
  }
15241
+ /**
15242
+ * camelCase DOMAIN property name for a model field, honoring a `domainName`
15243
+ * override (set via the `fieldHints` config) so a wire field can surface under
15244
+ * a friendlier PHP property name. The wire key (see {@link wireName}) still
15245
+ * derives from `field.name`. No-op when `domainName` is unset.
15246
+ */
15247
+ function domainFieldName$4(field) {
15248
+ return fieldName$4(field.domainName ?? field.name);
15249
+ }
15000
15250
  /** snake_case name for fixtures and other snake_case contexts. */
15001
15251
  function snakeName(name) {
15002
15252
  return toSnakeCase(stripUrnPrefix(name));
@@ -15131,14 +15381,14 @@ function generateModels$5(models, ctx) {
15131
15381
  const optionalFields = model.fields.filter((f) => !f.required);
15132
15382
  const seenNames = /* @__PURE__ */ new Set();
15133
15383
  const allFields = [...requiredFields, ...optionalFields].filter((f) => {
15134
- const phpName = fieldName$4(f.name);
15384
+ const phpName = domainFieldName$4(f);
15135
15385
  if (seenNames.has(phpName)) return false;
15136
15386
  seenNames.add(phpName);
15137
15387
  return true;
15138
15388
  });
15139
15389
  for (let i = 0; i < allFields.length; i++) {
15140
15390
  const field = allFields[i];
15141
- const phpName = fieldName$4(field.name);
15391
+ const phpName = domainFieldName$4(field);
15142
15392
  const phpType = mapTypeRef$5(field.type);
15143
15393
  const isOptional = !field.required;
15144
15394
  const comma = i < allFields.length - 1 ? "," : ",";
@@ -15165,7 +15415,7 @@ function generateModels$5(models, ctx) {
15165
15415
  lines.push(` return new self(`);
15166
15416
  for (let i = 0; i < allFields.length; i++) {
15167
15417
  const field = allFields[i];
15168
- const phpName = fieldName$4(field.name);
15418
+ const phpName = domainFieldName$4(field);
15169
15419
  const wireName = field.name;
15170
15420
  const comma = i < allFields.length - 1 ? "," : ",";
15171
15421
  const accessor = generateFromArrayAccessor(field.type, wireName, field.required);
@@ -15178,7 +15428,7 @@ function generateModels$5(models, ctx) {
15178
15428
  lines.push(" {");
15179
15429
  lines.push(" return [");
15180
15430
  for (const field of allFields) {
15181
- const phpName = fieldName$4(field.name);
15431
+ const phpName = domainFieldName$4(field);
15182
15432
  const wireName = field.name;
15183
15433
  const serialized = generateToArrayValue(field.type, `$this->${phpName}`, !field.required);
15184
15434
  lines.push(` '${wireName}' => ${serialized},`);
@@ -16602,7 +16852,7 @@ function emitFieldHydrationAssertions(lines, modelName, resultVar, fixtureVar, c
16602
16852
  const assertFields = [candidates.find((f) => f.name === "id"), candidates.filter((f) => f.name !== "id")[0]].filter(Boolean);
16603
16853
  for (const f of assertFields) {
16604
16854
  if (!f) continue;
16605
- const phpProp = toCamelCase(f.name);
16855
+ const phpProp = domainFieldName$4(f);
16606
16856
  lines.push(` $this->assertSame(${fixtureVar}['${f.name}'], ${resultVar}->${phpProp});`);
16607
16857
  }
16608
16858
  }
@@ -16782,7 +17032,7 @@ const phpEmitter = {
16782
17032
  return buildOperationsMap$5(spec, ctx);
16783
17033
  },
16784
17034
  fileHeader() {
16785
- return "<?php\n\ndeclare(strict_types=1);\n\n// This file is auto-generated by oagen. Do not edit.";
17035
+ return `<?php\n\ndeclare(strict_types=1);\n\n// ${AUTOGEN_NOTICE}`;
16786
17036
  },
16787
17037
  formatCommand(targetDir) {
16788
17038
  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 +17095,15 @@ function methodName$3(name) {
16845
17095
  function fieldName$3(name) {
16846
17096
  return applyAcronyms(toPascalCase(name));
16847
17097
  }
17098
+ /**
17099
+ * PascalCase domain field name for a model field, honoring a `domainName`
17100
+ * override (set via the `fieldHints` config) so a wire field can surface under
17101
+ * a friendlier name. The wire name (the `json:"..."` struct tag) still derives
17102
+ * from `field.name`.
17103
+ */
17104
+ function domainFieldName$3(field) {
17105
+ return applyAcronyms(toPascalCase(field.domainName ?? field.name));
17106
+ }
16848
17107
  /** Lower-camel identifier with Go acronym conventions preserved. */
16849
17108
  function unexportedName(name) {
16850
17109
  const exported = className$3(name);
@@ -17093,7 +17352,7 @@ function generateModels$4(models, ctx) {
17093
17352
  lines.push(`type ${structName} struct {`);
17094
17353
  const seenFieldNames = /* @__PURE__ */ new Set();
17095
17354
  for (const field of model.fields) {
17096
- const goFieldName = fieldName$3(field.name);
17355
+ const goFieldName = domainFieldName$3(field);
17097
17356
  if (seenFieldNames.has(goFieldName)) continue;
17098
17357
  seenFieldNames.add(goFieldName);
17099
17358
  const goType = !field.required ? makeOptional$2(mapTypeRef$4(field.type)) : mapTypeRef$4(field.type);
@@ -17907,7 +18166,7 @@ function generateParamsStruct(mountName, method, op, plan, ctx, resolvedOp) {
17907
18166
  if (bodyModel) for (const field of bodyModel.fields) {
17908
18167
  if (hidden.has(field.name)) continue;
17909
18168
  if (groupedParams.has(field.name)) continue;
17910
- const goField = fieldName$3(field.name);
18169
+ const goField = domainFieldName$3(field);
17911
18170
  if (emittedFields.has(goField)) continue;
17912
18171
  emittedFields.add(goField);
17913
18172
  const goType = !field.required ? makeOptional$1(mapTypeRef$4(field.type)) : mapTypeRef$4(field.type);
@@ -18245,7 +18504,7 @@ function emitHiddenParamsBodyStruct(lines, method, op, ctx, resolvedOp) {
18245
18504
  if (hidden.has(field.name)) continue;
18246
18505
  if (groupedParamNames.has(field.name)) continue;
18247
18506
  if (!field.required) continue;
18248
- const goField = fieldName$3(field.name);
18507
+ const goField = domainFieldName$3(field);
18249
18508
  const goType = mapTypeRef$4(field.type);
18250
18509
  lines.push(`\t${goField} ${goType} \`json:"${field.name}"\``);
18251
18510
  }
@@ -18257,7 +18516,7 @@ function emitHiddenParamsBodyStruct(lines, method, op, ctx, resolvedOp) {
18257
18516
  if (hidden.has(field.name)) continue;
18258
18517
  if (groupedParamNames.has(field.name)) continue;
18259
18518
  if (field.required) continue;
18260
- const goField = fieldName$3(field.name);
18519
+ const goField = domainFieldName$3(field);
18261
18520
  const goType = makeOptional$1(mapTypeRef$4(field.type));
18262
18521
  lines.push(`\t${goField} ${goType} \`json:"${field.name},omitempty"\``);
18263
18522
  }
@@ -18278,7 +18537,7 @@ function emitBodyWithHiddenParams(lines, op, pathExpr, plan, ctx, resolvedOp, pa
18278
18537
  if (paramsType && bodyModel) for (const field of bodyModel.fields) {
18279
18538
  if (hidden.has(field.name)) continue;
18280
18539
  if (!field.required) continue;
18281
- const goField = fieldName$3(field.name);
18540
+ const goField = domainFieldName$3(field);
18282
18541
  lines.push(`\t\t${goField}: params.${goField},`);
18283
18542
  }
18284
18543
  lines.push(" }");
@@ -18289,7 +18548,7 @@ function emitBodyWithHiddenParams(lines, op, pathExpr, plan, ctx, resolvedOp, pa
18289
18548
  if (paramsType && bodyModel) for (const field of bodyModel.fields) {
18290
18549
  if (hidden.has(field.name)) continue;
18291
18550
  if (field.required) continue;
18292
- const goField = fieldName$3(field.name);
18551
+ const goField = domainFieldName$3(field);
18293
18552
  lines.push(`\tbody.${goField} = params.${goField}`);
18294
18553
  }
18295
18554
  const queryArg = op.queryParams.filter((qp) => !hidden.has(qp.name)).length > 0 ? "params" : "nil";
@@ -18716,7 +18975,7 @@ function generateModelFixture$2(model, modelMap, enumMap) {
18716
18975
  const fixture = {};
18717
18976
  const seenFieldNames = /* @__PURE__ */ new Set();
18718
18977
  const deduplicatedFields = model.fields.filter((f) => {
18719
- const goName = fieldName$3(f.name);
18978
+ const goName = domainFieldName$3(f);
18720
18979
  if (seenFieldNames.has(goName)) return false;
18721
18980
  seenFieldNames.add(goName);
18722
18981
  return true;
@@ -19456,6 +19715,15 @@ function methodName$2(name) {
19456
19715
  function fieldName$2(name) {
19457
19716
  return toPascalCase(name);
19458
19717
  }
19718
+ /**
19719
+ * PascalCase domain property name for a model field, honoring a `domainName`
19720
+ * override (set via the `fieldHints` config) so a wire field can surface under
19721
+ * a friendlier C# property name. The wire/serialization key (the
19722
+ * `[JsonPropertyName("...")]` value) still derives from `field.name`.
19723
+ */
19724
+ function domainFieldName$2(field) {
19725
+ return toPascalCase(field.domainName ?? field.name);
19726
+ }
19459
19727
  function trimAsyncSuffix(name) {
19460
19728
  return name.endsWith("Async") ? name.slice(0, -5) : name;
19461
19729
  }
@@ -19822,7 +20090,7 @@ function generateModels$3(models, ctx, discCtx) {
19822
20090
  const baseClassName = modelClassName(model.name);
19823
20091
  const fieldMap = /* @__PURE__ */ new Map();
19824
20092
  for (const field of model.fields) {
19825
- let csName = fieldName$2(field.name);
20093
+ let csName = domainFieldName$2(field);
19826
20094
  if (csName === baseClassName) csName = `${csName}Value`;
19827
20095
  fieldMap.set(csName, mapTypeRef$3(field.type));
19828
20096
  }
@@ -19838,7 +20106,9 @@ function generateModels$3(models, ctx, discCtx) {
19838
20106
  const fieldTypes = model.fields.map((f) => mapTypeRef$3(f.type));
19839
20107
  const needsCollections = fieldTypes.some((t) => t.startsWith("List<") || t.startsWith("Dictionary<"));
19840
20108
  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));
20109
+ const hasClassNameCollision = model.fields.some((f) => domainFieldName$2(f) === csClassName);
20110
+ const hasDomainRename = model.fields.some((f) => domainFieldName$2(f) !== fieldName$2(f.name));
20111
+ const needsJsonAttrs = hasClassNameCollision || hasDomainRename || model.fields.some((f) => f.required && isEnumRef(f.type));
19842
20112
  lines.push(`namespace ${ctx.namespacePascal}`);
19843
20113
  lines.push("{");
19844
20114
  if (needsSystem) lines.push(" using System;");
@@ -19864,9 +20134,10 @@ function generateModels$3(models, ctx, discCtx) {
19864
20134
  const dictObjectFields = [];
19865
20135
  const seenFieldNames = /* @__PURE__ */ new Set();
19866
20136
  for (const field of model.fields) {
19867
- let csFieldName = fieldName$2(field.name);
20137
+ let csFieldName = domainFieldName$2(field);
19868
20138
  const collidesWithClassName = csFieldName === csClassName;
19869
20139
  if (collidesWithClassName) csFieldName = `${csFieldName}Value`;
20140
+ const hasDomainOverride = domainFieldName$2(field) !== fieldName$2(field.name);
19870
20141
  if (seenFieldNames.has(csFieldName)) continue;
19871
20142
  seenFieldNames.add(csFieldName);
19872
20143
  let useNewModifier = false;
@@ -19913,7 +20184,7 @@ function generateModels$3(models, ctx, discCtx) {
19913
20184
  const isRequiredEnum = field.required && isEnumRef(field.type) && constInit === null;
19914
20185
  lines.push(...emitJsonPropertyAttributes(field.name, {
19915
20186
  isRequiredEnum,
19916
- explicitWireName: collidesWithClassName
20187
+ explicitWireName: collidesWithClassName || hasDomainOverride
19917
20188
  }));
19918
20189
  const discriminatedUnionConverter = discriminatedUnionConverterName(field.type);
19919
20190
  if (discriminatedUnionConverter) lines.push(` [Newtonsoft.Json.JsonConverter(typeof(${discriminatedUnionConverter}))]`);
@@ -21219,7 +21490,7 @@ function generateModelFixture$1(model, modelMap, enumMap) {
21219
21490
  const fixture = {};
21220
21491
  const seenFieldNames = /* @__PURE__ */ new Set();
21221
21492
  const deduplicatedFields = model.fields.filter((f) => {
21222
- const csName = fieldName$2(f.name);
21493
+ const csName = domainFieldName$2(f);
21223
21494
  if (seenFieldNames.has(csName)) return false;
21224
21495
  seenFieldNames.add(csName);
21225
21496
  return true;
@@ -21740,7 +22011,7 @@ function buildFixtureAssertions(model, spec) {
21740
22011
  if (field.type.kind !== "primitive" || field.type.type !== "string") continue;
21741
22012
  if (field.type.format === "date-time" || field.type.format === "date") continue;
21742
22013
  if (field.type.format === "binary") continue;
21743
- const csField = fieldName$2(field.name);
22014
+ const csField = domainFieldName$2(field);
21744
22015
  const val = fixture[field.name];
21745
22016
  if (typeof val === "string" && val.length > 0) assertions.push(`Assert.Equal(${csStringLiteral(val)}, result.${csField});`);
21746
22017
  else assertions.push(`Assert.NotEmpty(result.${csField});`);
@@ -21999,7 +22270,7 @@ const dotnetEmitter = {
21999
22270
  return buildOperationsMap$3(spec, fixNamespace(ctx));
22000
22271
  },
22001
22272
  fileHeader() {
22002
- return "// This file is auto-generated by oagen. Do not edit.";
22273
+ return `// ${AUTOGEN_NOTICE}`;
22003
22274
  },
22004
22275
  formatCommand(targetDir) {
22005
22276
  const workspace = findDotnetWorkspace(targetDir);
@@ -22062,6 +22333,16 @@ function propertyName(name) {
22062
22333
  if (camel === "object") return "objectType";
22063
22334
  return escapeReserved(camel);
22064
22335
  }
22336
+ /**
22337
+ * camelCase domain property name for a MODEL field, honoring a `domainName`
22338
+ * override (set via the `fieldHints` config) so a wire field can surface under
22339
+ * a friendlier name. The wire key (the `@JsonProperty("...")` argument) still
22340
+ * derives from `field.name`. No-op when `domainName` is unset, so it is also
22341
+ * safe on params. Only apply to model fields.
22342
+ */
22343
+ function domainPropertyName(field) {
22344
+ return propertyName(field.domainName ?? field.name);
22345
+ }
22065
22346
  /** Lower-case Kotlin package segment for a service / mount group. */
22066
22347
  function packageSegment(name) {
22067
22348
  return toPascalCase(name).toLowerCase();
@@ -22764,7 +23045,7 @@ function renderFields(fields, overrideFields = /* @__PURE__ */ new Set()) {
22764
23045
  const lines = [];
22765
23046
  for (const rawField of fields) {
22766
23047
  const field = promoteFieldType$1(rawField);
22767
- const kotlinName = propertyName(field.name);
23048
+ const kotlinName = domainPropertyName(field);
22768
23049
  if (seen.has(kotlinName)) continue;
22769
23050
  seen.add(kotlinName);
22770
23051
  const baseType = mapTypeRef$2(field.type);
@@ -24618,7 +24899,7 @@ function buildResponseAssertions(responseModelName, ctx) {
24618
24899
  for (const field of model.fields) {
24619
24900
  if (!field.required) continue;
24620
24901
  if (assertions.length >= MAX_RESPONSE_ASSERTIONS) break;
24621
- const ktProp = propertyName(field.name);
24902
+ const ktProp = domainPropertyName(field);
24622
24903
  const type = field.type;
24623
24904
  if (type.kind === "primitive") {
24624
24905
  if (type.format === "date-time") continue;
@@ -25062,7 +25343,7 @@ const kotlinEmitter = {
25062
25343
  return buildOperationsMap$2(spec, ctx);
25063
25344
  },
25064
25345
  fileHeader() {
25065
- return "// This file is auto-generated by oagen. Do not edit.";
25346
+ return `// ${AUTOGEN_NOTICE}`;
25066
25347
  },
25067
25348
  formatCommand(targetDir) {
25068
25349
  if (!fs.existsSync(path.join(targetDir, "gradlew"))) return null;
@@ -25114,6 +25395,15 @@ function fieldName$1(name) {
25114
25395
  return toSnakeCase(name);
25115
25396
  }
25116
25397
  /**
25398
+ * snake_case domain field name for a model field, honoring a `domainName`
25399
+ * override (set via the `fieldHints` config) so a wire field can surface under
25400
+ * a friendlier name. The wire key (still derived from `field.name`) is what
25401
+ * gets sent/received over the wire — only the domain attr/accessor name changes.
25402
+ */
25403
+ function domainFieldName$1(field) {
25404
+ return toSnakeCase(field.domainName ?? field.name);
25405
+ }
25406
+ /**
25117
25407
  * Ruby reserved words that cannot be used as parameter names.
25118
25408
  * When a path/query param name collides, suffix with underscore.
25119
25409
  */
@@ -25344,7 +25634,7 @@ function generateModels$1(models, ctx) {
25344
25634
  }
25345
25635
  const seenFieldNames = /* @__PURE__ */ new Set();
25346
25636
  const fields = model.fields.filter((f) => {
25347
- const n = fieldName$1(f.name);
25637
+ const n = domainFieldName$1(f);
25348
25638
  if (seenFieldNames.has(n)) return false;
25349
25639
  seenFieldNames.add(n);
25350
25640
  return true;
@@ -25358,7 +25648,7 @@ function generateModels$1(models, ctx) {
25358
25648
  lines.push(" HASH_ATTRS = {");
25359
25649
  for (let i = 0; i < fields.length; i++) {
25360
25650
  const field = fields[i];
25361
- const fname = fieldName$1(field.name);
25651
+ const fname = domainFieldName$1(field);
25362
25652
  const sep = i === fields.length - 1 ? "" : ",";
25363
25653
  lines.push(` ${rubyHashLiteralKey(field.name)} :${fname}${sep}`);
25364
25654
  }
@@ -25367,13 +25657,13 @@ function generateModels$1(models, ctx) {
25367
25657
  if (deprecatedFields.length > 0) {
25368
25658
  for (const f of deprecatedFields) {
25369
25659
  const desc = f.description ? ` ${f.description.split("\n")[0].trim()}` : "";
25370
- lines.push(` # @!attribute ${fieldName$1(f.name)}`);
25660
+ lines.push(` # @!attribute ${domainFieldName$1(f)}`);
25371
25661
  lines.push(` # @deprecated${desc}`);
25372
25662
  }
25373
25663
  lines.push("");
25374
25664
  }
25375
25665
  if (accessorFields.length > 0) {
25376
- const attrs = accessorFields.map((f) => `:${fieldName$1(f.name)}`);
25666
+ const attrs = accessorFields.map((f) => `:${domainFieldName$1(f)}`);
25377
25667
  if (attrs.length === 1) lines.push(` attr_accessor ${attrs[0]}`);
25378
25668
  else {
25379
25669
  lines.push(` attr_accessor \\`);
@@ -25385,7 +25675,7 @@ function generateModels$1(models, ctx) {
25385
25675
  lines.push("");
25386
25676
  }
25387
25677
  for (const f of deprecatedFields) {
25388
- const fname = fieldName$1(f.name);
25678
+ const fname = domainFieldName$1(f);
25389
25679
  lines.push(` def ${fname}`);
25390
25680
  lines.push(` warn "[DEPRECATION] \\\`${fname}\\\` is deprecated and will be removed in a future version.", uplevel: 1`);
25391
25681
  lines.push(` @${fname}`);
@@ -25399,7 +25689,7 @@ function generateModels$1(models, ctx) {
25399
25689
  lines.push(" def initialize(json)");
25400
25690
  lines.push(" hash = self.class.normalize(json)");
25401
25691
  for (const field of fields) {
25402
- const fname = fieldName$1(field.name);
25692
+ const fname = domainFieldName$1(field);
25403
25693
  const rawKey = field.name;
25404
25694
  lines.push(` ${deserializeAssignment(fname, rawKey, field.type, field.required, enumNames, modelNames)}`);
25405
25695
  }
@@ -26895,7 +27185,7 @@ function generateModelRoundTripTest(spec) {
26895
27185
  const dedupFields = /* @__PURE__ */ new Set();
26896
27186
  for (const f of model.fields) {
26897
27187
  const wireName = f.name;
26898
- const rubyFieldName = fieldName$1(f.name);
27188
+ const rubyFieldName = domainFieldName$1(f);
26899
27189
  if (dedupFields.has(rubyFieldName)) continue;
26900
27190
  dedupFields.add(rubyFieldName);
26901
27191
  const stub = roundTripStub(f.type, enumNames);
@@ -27224,7 +27514,7 @@ function generateRbiFiles(spec, ctx) {
27224
27514
  lines.push("");
27225
27515
  const seenFieldNames = /* @__PURE__ */ new Set();
27226
27516
  for (const f of model.fields) {
27227
- const fname = fieldName$1(f.name);
27517
+ const fname = domainFieldName$1(f);
27228
27518
  if (seenFieldNames.has(fname)) continue;
27229
27519
  seenFieldNames.add(fname);
27230
27520
  const sorbetType = f.required ? mapSorbetType(f.type) : wrapNilable(mapSorbetType(f.type));
@@ -27501,7 +27791,7 @@ const rubyEmitter = {
27501
27791
  return buildOperationsMap$1(spec, ctx);
27502
27792
  },
27503
27793
  fileHeader() {
27504
- return `# frozen_string_literal: true\n\n# This file is auto-generated by oagen. Do not edit.`;
27794
+ return `# frozen_string_literal: true\n\n# ${AUTOGEN_NOTICE}`;
27505
27795
  },
27506
27796
  formatCommand(targetDir) {
27507
27797
  return {
@@ -27587,6 +27877,15 @@ function methodName(name) {
27587
27877
  function fieldName(name) {
27588
27878
  return escapeKeyword(toSnakeCase(name));
27589
27879
  }
27880
+ /**
27881
+ * snake_case domain field name for a model field, honoring a `domainName`
27882
+ * override (set via the `fieldHints` config) so a wire field can surface under
27883
+ * a friendlier identifier. The wire name (and thus the `#[serde(rename = ...)]`
27884
+ * key) still derives from `field.name`.
27885
+ */
27886
+ function domainFieldName(field) {
27887
+ return escapeKeyword(toSnakeCase(field.domainName ?? field.name));
27888
+ }
27590
27889
  /** PascalCase enum variant. */
27591
27890
  function variantName(value) {
27592
27891
  const pascal = toPascalCase(String(value));
@@ -27858,6 +28157,7 @@ function generateModels(models, ctx, registry) {
27858
28157
  const files = [];
27859
28158
  const moduleNames = [];
27860
28159
  const seen = /* @__PURE__ */ new Set();
28160
+ const taggedVariantFields = collectTaggedVariantFields(models);
27861
28161
  for (const model of models) {
27862
28162
  const mod = moduleName(model.name);
27863
28163
  if (seen.has(mod)) continue;
@@ -27866,7 +28166,7 @@ function generateModels(models, ctx, registry) {
27866
28166
  const path = ctx.overlayLookup?.fileBySymbol?.get(model.name) ?? `src/models/${mod}.rs`;
27867
28167
  files.push({
27868
28168
  path,
27869
- content: renderModel(model, registry),
28169
+ content: renderModel(model, registry, taggedVariantFields.get(model.name)),
27870
28170
  overwriteExisting: true
27871
28171
  });
27872
28172
  }
@@ -27878,7 +28178,43 @@ function generateModels(models, ctx, registry) {
27878
28178
  });
27879
28179
  return files;
27880
28180
  }
27881
- function renderModel(model, registry) {
28181
+ /**
28182
+ * Walk every model field and record which models are arms of an
28183
+ * internally-tagged union, mapped to that union's discriminator property.
28184
+ */
28185
+ function collectTaggedVariantFields(models) {
28186
+ const out = /* @__PURE__ */ new Map();
28187
+ const visit = (ref) => {
28188
+ switch (ref.kind) {
28189
+ case "union":
28190
+ if (ref.discriminator?.property) for (const variant of ref.variants) {
28191
+ const name = variantModelName(variant);
28192
+ if (name) out.set(name, ref.discriminator.property);
28193
+ }
28194
+ ref.variants.forEach(visit);
28195
+ break;
28196
+ case "array":
28197
+ visit(ref.items);
28198
+ break;
28199
+ case "nullable":
28200
+ visit(ref.inner);
28201
+ break;
28202
+ case "map":
28203
+ visit(ref.valueType);
28204
+ break;
28205
+ default: break;
28206
+ }
28207
+ };
28208
+ for (const model of models) for (const field of model.fields) visit(field.type);
28209
+ return out;
28210
+ }
28211
+ /** Resolve the underlying model name of a union arm, unwrapping a nullable. */
28212
+ function variantModelName(ref) {
28213
+ if (ref.kind === "model") return ref.name;
28214
+ if (ref.kind === "nullable") return variantModelName(ref.inner);
28215
+ return null;
28216
+ }
28217
+ function renderModel(model, registry, tagField) {
27882
28218
  const lines = [];
27883
28219
  lines.push(HEADER_PLACEHOLDER);
27884
28220
  lines.push("#[allow(unused_imports)]");
@@ -27890,7 +28226,7 @@ function renderModel(model, registry) {
27890
28226
  if (model.description) lines.push(...docComment$1(model.description));
27891
28227
  lines.push("#[derive(Debug, Clone, Serialize, Deserialize)]");
27892
28228
  const resolvedNames = resolveFieldNames(model.fields);
27893
- const fieldLines = model.fields.map((f, i) => renderField(f, resolvedNames[i], model.name, registry));
28229
+ const fieldLines = model.fields.map((f, i) => renderField(f, resolvedNames[i], model.name, registry, tagField));
27894
28230
  if (fieldLines.length === 0) lines.push(`pub struct ${typeName(model.name)} {}`);
27895
28231
  else {
27896
28232
  lines.push(`pub struct ${typeName(model.name)} {`);
@@ -27900,17 +28236,20 @@ function renderModel(model, registry) {
27900
28236
  return lines.filter((l) => l !== HEADER_PLACEHOLDER).join("\n") + "\n";
27901
28237
  }
27902
28238
  /**
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.
28239
+ * Resolve unique Rust identifiers for struct fields. The domain identifier
28240
+ * honors a `fieldHints` override (`domainName`, e.g. wire `connection_type`
28241
+ * domain `type`); the wire name (and the `#[serde(rename = ...)]` key emitted
28242
+ * in `renderField`) still derives from `f.name`. Multiple names can collide
28243
+ * after snake-casing (e.g. `integration_type` and `integrationType` both
28244
+ * become `integration_type`). Subsequent collisions get a numeric suffix so
28245
+ * the struct compiles; serde `rename` preserves the original wire name in every
28246
+ * case.
27908
28247
  */
27909
28248
  function resolveFieldNames(fields) {
27910
28249
  const used = /* @__PURE__ */ new Set();
27911
28250
  const out = [];
27912
28251
  for (const f of fields) {
27913
- const base = fieldName(f.name);
28252
+ const base = domainFieldName(f);
27914
28253
  let candidate = base;
27915
28254
  let suffix = 2;
27916
28255
  while (used.has(candidate)) {
@@ -27922,7 +28261,7 @@ function resolveFieldNames(fields) {
27922
28261
  }
27923
28262
  return out;
27924
28263
  }
27925
- function renderField(field, rustField, modelName, registry) {
28264
+ function renderField(field, rustField, modelName, registry, tagField) {
27926
28265
  const lines = [];
27927
28266
  const hasDescription = !!field.description;
27928
28267
  if (hasDescription) for (const c of docComment$1(field.description)) lines.push(` ${c}`);
@@ -27937,8 +28276,13 @@ function renderField(field, rustField, modelName, registry) {
27937
28276
  });
27938
28277
  if ((!field.required || field.type.kind === "nullable") && !baseType.startsWith("Option<")) baseType = makeOptional(baseType);
27939
28278
  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)]");
28279
+ if (tagField === field.name) {
28280
+ const args = rename ? `rename = "${rename}", default, skip_serializing` : "default, skip_serializing";
28281
+ lines.push(` #[serde(${args})]`);
28282
+ } else {
28283
+ if (rename) lines.push(` #[serde(rename = "${rename}")]`);
28284
+ if (baseType.startsWith("Option<")) lines.push(" #[serde(skip_serializing_if = \"Option::is_none\", default)]");
28285
+ }
27942
28286
  if (field.deprecated) lines.push(" #[deprecated]");
27943
28287
  lines.push(` pub ${rustField}: ${baseType},`);
27944
28288
  return lines.join("\n");
@@ -28520,7 +28864,7 @@ function registerSyntheticBody(op, paramsName, bodyGroupParamNames, bodyGroupFie
28520
28864
  if (!f.required && !rust.startsWith("Option<")) rust = makeOptional(rust);
28521
28865
  rust = applySecretRedaction(rust, f.name);
28522
28866
  return {
28523
- rustName: fieldName(f.name),
28867
+ rustName: domainFieldName(f),
28524
28868
  wireName: f.name,
28525
28869
  rustType: rust,
28526
28870
  required: !!f.required && !rust.startsWith("Option<"),
@@ -29875,7 +30219,7 @@ const rustEmitter = {
29875
30219
  return buildOperationsMap(spec, ctx);
29876
30220
  },
29877
30221
  fileHeader() {
29878
- return "// Code generated by oagen. DO NOT EDIT.";
30222
+ return `// ${AUTOGEN_NOTICE}`;
29879
30223
  },
29880
30224
  formatCommand(_targetDir) {
29881
30225
  return {
@@ -29930,4 +30274,4 @@ const workosEmittersPlugin = {
29930
30274
  //#endregion
29931
30275
  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
30276
 
29933
- //# sourceMappingURL=plugin-1ckLMpgo.mjs.map
30277
+ //# sourceMappingURL=plugin-Cciic50q.mjs.map