@workos/oagen-emitters 0.14.4 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/CHANGELOG.md +19 -0
  3. package/dist/index.d.mts.map +1 -1
  4. package/dist/index.mjs +1 -1
  5. package/dist/{plugin-BGVaMGqe.mjs → plugin-C2Hp2Vs2.mjs} +1039 -274
  6. package/dist/plugin-C2Hp2Vs2.mjs.map +1 -0
  7. package/dist/plugin.mjs +1 -1
  8. package/package.json +7 -7
  9. package/renovate.json +1 -61
  10. package/src/go/client.ts +1 -1
  11. package/src/go/enums.ts +77 -0
  12. package/src/kotlin/enums.ts +11 -4
  13. package/src/node/client.ts +158 -2
  14. package/src/node/discriminated-models.ts +68 -24
  15. package/src/node/field-plan.ts +64 -8
  16. package/src/node/index.ts +59 -3
  17. package/src/node/models.ts +73 -30
  18. package/src/node/naming.ts +14 -1
  19. package/src/node/node-overrides.ts +4 -37
  20. package/src/node/options.ts +29 -1
  21. package/src/node/resources.ts +553 -89
  22. package/src/node/tests.ts +108 -7
  23. package/src/php/fixtures.ts +4 -1
  24. package/src/php/models.ts +3 -1
  25. package/src/php/resources.ts +40 -11
  26. package/src/php/tests.ts +22 -12
  27. package/src/python/client.ts +0 -8
  28. package/src/python/enums.ts +41 -15
  29. package/src/python/fixtures.ts +23 -7
  30. package/src/python/models.ts +26 -5
  31. package/src/python/resources.ts +71 -3
  32. package/src/python/tests.ts +70 -12
  33. package/src/python/wrappers.ts +25 -4
  34. package/src/ruby/client.ts +0 -1
  35. package/src/rust/resources.ts +10 -7
  36. package/src/shared/non-spec-services.ts +0 -5
  37. package/test/go/enums.test.ts +24 -0
  38. package/test/node/resources.test.ts +11 -1
  39. package/test/node/tests.test.ts +3 -3
  40. package/test/php/client.test.ts +0 -1
  41. package/test/php/resources.test.ts +50 -0
  42. package/test/rust/resources.test.ts +9 -0
  43. package/dist/plugin-BGVaMGqe.mjs.map +0 -1
@@ -2855,6 +2855,15 @@ function isAdoptedModelName(name) {
2855
2855
  return adoptedModelNames.has(name);
2856
2856
  }
2857
2857
  /**
2858
+ * IR model names handled by the discriminated-models module. These must not
2859
+ * be remapped by the structural matcher because that module emits files and
2860
+ * helpers under the original IR names.
2861
+ */
2862
+ let discriminatedModelNames = /* @__PURE__ */ new Set();
2863
+ function setDiscriminatedModelNames(names) {
2864
+ discriminatedModelNames = names;
2865
+ }
2866
+ /**
2858
2867
  * Domain names that `resolveInterfaceName` reached via a structural rename
2859
2868
  * — the resolved name differs from the IR model's own name. `wireInterfaceName`
2860
2869
  * consults this set to decide whether to fire the "single-form wire" case:
@@ -2980,7 +2989,7 @@ function resolveClassName$5(service, ctx) {
2980
2989
  function resolveInterfaceName(name, ctx, opts) {
2981
2990
  const existing = ctx.overlayLookup?.interfaceByName?.get(name);
2982
2991
  if (existing) return existing;
2983
- let inferred = adoptedModelNames.has(name) ? void 0 : ctx.overlayLookup?.modelNameByIR?.get(name);
2992
+ let inferred = adoptedModelNames.has(name) || discriminatedModelNames.has(name) ? void 0 : ctx.overlayLookup?.modelNameByIR?.get(name);
2984
2993
  if (inferred) {
2985
2994
  if (inferred.startsWith("Serialized")) {
2986
2995
  const stripped = inferred.slice(10);
@@ -3825,6 +3834,25 @@ function assignEnumsToServices(enums, services, models = [], ctx) {
3825
3834
  return enumToService;
3826
3835
  }
3827
3836
  //#endregion
3837
+ //#region src/node/options.ts
3838
+ function nodeOptions(ctx) {
3839
+ return ctx.emitterOptions ?? {};
3840
+ }
3841
+ function normalizeServiceName(name) {
3842
+ return name.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
3843
+ }
3844
+ function ownedLookupNames(name) {
3845
+ const names = [name];
3846
+ if (name.startsWith("__baseline_dir__:")) names.push(name.slice(17));
3847
+ return names;
3848
+ }
3849
+ function isNodeOwnedService(ctx, ...names) {
3850
+ const configured = nodeOptions(ctx).ownedServices ?? [];
3851
+ if (configured.length === 0) return false;
3852
+ const owned = new Set(configured.map(normalizeServiceName));
3853
+ return names.some((name) => name !== void 0 ? ownedLookupNames(name).some((candidate) => owned.has(normalizeServiceName(candidate))) : false);
3854
+ }
3855
+ //#endregion
3828
3856
  //#region src/node/field-plan.ts
3829
3857
  /**
3830
3858
  * Decide whether a `deserialize${X}` / `serialize${X}` helper will be
@@ -3843,6 +3871,15 @@ function helperExists(helperName, depModelName, ctx) {
3843
3871
  if (liveSurfaceHasFunction(helperName)) return true;
3844
3872
  const depModel = ctx.spec.models.find((m) => m.name === depModelName);
3845
3873
  if (!depModel) return false;
3874
+ const depService = assignModelsToServices$1(ctx.spec.models, ctx.spec.services, ctx.modelHints).get(depModelName);
3875
+ const resolvedName = resolveInterfaceName(depModelName, ctx);
3876
+ const siblingPath = liveSurfaceFunctionPath(`${helperName.startsWith("serialize") ? "deserialize" : "serialize"}${resolvedName}`);
3877
+ if (siblingPath && liveSurfaceHasFile(siblingPath) && !liveSurfaceHasAutogenFile(siblingPath)) return false;
3878
+ const sourceFile = (ctx.apiSurface?.interfaces?.[resolvedName])?.sourceFile;
3879
+ const { resolveDir } = createServiceDirResolver(ctx.spec.models, ctx.spec.services, ctx);
3880
+ const candidate = sourceFile ? sourceFile.replace("/interfaces/", "/serializers/").replace(".interface.ts", ".serializer.ts") : `src/${resolveDir(depService)}/serializers/${fileName$3(depModelName)}.serializer.ts`;
3881
+ if (liveSurfaceHasFile(candidate) && !liveSurfaceHasAutogenFile(candidate)) return false;
3882
+ if (isNodeOwnedService(ctx, depService)) return true;
3846
3883
  return modelHasNewFields(depModel, ctx);
3847
3884
  }
3848
3885
  function deserializeExpression$1(ref, wireExpr, ctx, nullFallback = "null") {
@@ -4201,11 +4238,12 @@ function buildSerializerImports(model, serializerPath, dirName, domainName, resp
4201
4238
  const depService = sctx.modelToService.get(dep);
4202
4239
  const depDir = sctx.resolveDir(depService);
4203
4240
  const depName = resolveInterfaceName(dep, sctx.ctx);
4204
- const baselineSrc = (sctx.ctx.apiSurface?.interfaces?.[depName])?.sourceFile;
4241
+ const depIsOwned = isNodeOwnedService(sctx.ctx, depService);
4242
+ const baselineSrc = depIsOwned ? void 0 : (sctx.ctx.apiSurface?.interfaces?.[depName])?.sourceFile;
4205
4243
  const baselineSerializerPath = baselineSrc ? baselineSrc.replace("/interfaces/", "/serializers/").replace(".interface.ts", ".serializer.ts") : null;
4206
4244
  const irNameSerializerPath = `src/${depDir}/serializers/${fileName$3(dep)}.serializer.ts`;
4207
- const liveDeserPath = liveSurfaceFunctionPath(`deserialize${depName}`);
4208
- const liveSerPath = liveSurfaceFunctionPath(`serialize${depName}`);
4245
+ const liveDeserPath = depIsOwned ? void 0 : liveSurfaceFunctionPath(`deserialize${depName}`);
4246
+ const liveSerPath = depIsOwned ? void 0 : liveSurfaceFunctionPath(`serialize${depName}`);
4209
4247
  const depSerializerPath = liveDeserPath ?? liveSerPath ?? (baselineSerializerPath && liveSurfaceHasFile(baselineSerializerPath) ? baselineSerializerPath : irNameSerializerPath);
4210
4248
  const rel = relativeImport(serializerPath, depSerializerPath);
4211
4249
  const canon = sctx.dedup.get(dep);
@@ -4213,11 +4251,11 @@ function buildSerializerImports(model, serializerPath, dirName, domainName, resp
4213
4251
  const depSkipDeserialize = sctx.responseReachableModels !== void 0 && !sctx.responseReachableModels.has(dep) && (canon == null || !sctx.responseReachableModels.has(canon));
4214
4252
  const hasDeser = liveSurfaceHasFunction(`deserialize${depName}`);
4215
4253
  const hasSer = liveSurfaceHasFunction(`serialize${depName}`);
4216
- const fileExists = liveSurfaceHasFile(depSerializerPath);
4254
+ const fileExists = !depIsOwned && liveSurfaceHasFile(depSerializerPath);
4217
4255
  if (fileExists && !hasDeser && !hasSer) continue;
4218
4256
  if (!fileExists) {
4219
4257
  const depModel = sctx.ctx.spec.models.find((m) => m.name === dep);
4220
- if (!(depModel ? modelHasNewFields(depModel, sctx.ctx) : true)) continue;
4258
+ if (!(depModel ? depIsOwned || modelHasNewFields(depModel, sctx.ctx) : true)) continue;
4221
4259
  }
4222
4260
  if (fileExists && depSkipSerialize) {
4223
4261
  if (hasDeser) lines.push(`import { deserialize${depName} } from '${rel}';`);
@@ -4252,7 +4290,7 @@ function buildSkipFormatFields(model, baselineDomain) {
4252
4290
  return skipFormatFields;
4253
4291
  }
4254
4292
  function shouldSkipSerializeForModel(model, baselineResponse, baselineDomain, dedup, skippedSerializeModels, ctx) {
4255
- let shouldSkip = serializerHasBaselineIncompatibility(model, baselineResponse, baselineDomain, ctx);
4293
+ let shouldSkip = serializerHasBaselineIncompatibility(model, baselineResponse, baselineDomain, ctx) || hasUnsafeSerializePassthrough(model, baselineDomain, baselineResponse, ctx);
4256
4294
  if (!shouldSkip) for (const field of model.fields) {
4257
4295
  for (const ref of collectSerializedModelRefs(field.type)) {
4258
4296
  if (skippedSerializeModels.has(ref)) {
@@ -4264,6 +4302,11 @@ function shouldSkipSerializeForModel(model, baselineResponse, baselineDomain, de
4264
4302
  shouldSkip = true;
4265
4303
  break;
4266
4304
  }
4305
+ const resolved = resolveInterfaceName(ref, ctx);
4306
+ if (wireInterfaceName(resolved) !== resolved && !helperExists(`serialize${resolved}`, ref, ctx)) {
4307
+ shouldSkip = true;
4308
+ break;
4309
+ }
4267
4310
  }
4268
4311
  if (shouldSkip) break;
4269
4312
  }
@@ -4271,6 +4314,7 @@ function shouldSkipSerializeForModel(model, baselineResponse, baselineDomain, de
4271
4314
  }
4272
4315
  function emitSerializerBody(model, domainName, responseName, typeParams, baselineDomain, baselineResponse, skipFormatFields, shouldSkipSerialize, shouldSkipDeserialize, ctx) {
4273
4316
  const lines = [];
4317
+ const effectiveShouldSkipSerialize = shouldSkipSerialize || hasUnsafeSerializePassthrough(model, baselineDomain, baselineResponse, ctx);
4274
4318
  if (!shouldSkipDeserialize) {
4275
4319
  const seenDeserFields = /* @__PURE__ */ new Set();
4276
4320
  const deserParamPrefix = model.fields.length === 0 ? "_" : "";
@@ -4286,7 +4330,7 @@ function emitSerializerBody(model, domainName, responseName, typeParams, baselin
4286
4330
  }
4287
4331
  lines.push("});");
4288
4332
  }
4289
- if (!shouldSkipSerialize) {
4333
+ if (!effectiveShouldSkipSerialize) {
4290
4334
  if (!shouldSkipDeserialize) lines.push("");
4291
4335
  const serParamPrefix = model.fields.length === 0 ? "_" : "";
4292
4336
  lines.push(`export const serialize${domainName} = ${typeParams.decl}(`);
@@ -4304,19 +4348,18 @@ function emitSerializerBody(model, domainName, responseName, typeParams, baselin
4304
4348
  }
4305
4349
  return lines;
4306
4350
  }
4307
- //#endregion
4308
- //#region src/node/options.ts
4309
- function nodeOptions(ctx) {
4310
- return ctx.emitterOptions ?? {};
4311
- }
4312
- function normalizeServiceName(name) {
4313
- return name.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
4314
- }
4315
- function isNodeOwnedService(ctx, ...names) {
4316
- const configured = nodeOptions(ctx).ownedServices ?? [];
4317
- if (configured.length === 0) return false;
4318
- const owned = new Set(configured.map(normalizeServiceName));
4319
- return names.some((name) => name !== void 0 && owned.has(normalizeServiceName(name)));
4351
+ function hasUnsafeSerializePassthrough(model, baselineDomain, baselineResponse, ctx) {
4352
+ if (!baselineDomain?.fields || !baselineResponse?.fields) return false;
4353
+ for (const field of model.fields) {
4354
+ const domain = fieldName$6(field.name);
4355
+ const wire = wireFieldName(field.name);
4356
+ const domainField = baselineDomain.fields[domain];
4357
+ const wireField = baselineResponse.fields[wire];
4358
+ if (!domainField || !wireField || domainField.type === wireField.type) continue;
4359
+ const domainAccess = `model.${domain}`;
4360
+ if (serializeExpression(field.type, domainAccess, ctx) === domainAccess) return true;
4361
+ }
4362
+ return false;
4320
4363
  }
4321
4364
  //#endregion
4322
4365
  //#region src/node/sdk-errors.ts
@@ -4634,10 +4677,52 @@ function paginatedOptionsName(method, resolvedServiceName) {
4634
4677
  if (method === "list") return `${toPascalCase(resolvedServiceName)}ListOptions`;
4635
4678
  return toPascalCase(method) + "Options";
4636
4679
  }
4680
+ function recordGeneratedOptionInterface(ctx, serviceDir, stem, typeName) {
4681
+ const key = "_nodeGeneratedOptionInterfaceExports";
4682
+ const registry = ctx[key] ??= /* @__PURE__ */ new Map();
4683
+ const exports = registry.get(serviceDir) ?? [];
4684
+ if (!exports.some((entry) => entry.stem === stem && entry.typeName === typeName)) exports.push({
4685
+ stem,
4686
+ typeName
4687
+ });
4688
+ registry.set(serviceDir, exports);
4689
+ }
4690
+ function existingInterfaceBarrelExports(ctx, serviceDir, stem) {
4691
+ const root = ctx.outputDir ?? ctx.targetDir;
4692
+ if (!root) return false;
4693
+ const barrelPath = path.join(root, "src", serviceDir, "interfaces", "index.ts");
4694
+ let content;
4695
+ try {
4696
+ content = fs.readFileSync(barrelPath, "utf8");
4697
+ } catch {
4698
+ return false;
4699
+ }
4700
+ const escapedStem = stem.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4701
+ return new RegExp(`export\\s+(?:type\\s+)?(?:\\*|\\{[^}]+\\})\\s+from\\s+['"]\\./${escapedStem}['"]`).test(content);
4702
+ }
4703
+ function operationOverrideFor$1(ctx, op) {
4704
+ return nodeOptions(ctx).operationOverrides?.[`${op.httpMethod.toUpperCase()} ${op.path}`];
4705
+ }
4637
4706
  function baselineMethodFor$1(service, method, ctx) {
4638
4707
  const serviceClass = resolveResourceClassName$3(service, ctx);
4639
4708
  return ctx.apiSurface?.classes?.[serviceClass]?.methods?.[method]?.[0];
4640
4709
  }
4710
+ function ignoredResourceMethodNames(ctx, resourcePath) {
4711
+ const root = ctx.outputDir ?? ctx.targetDir;
4712
+ if (!root) return /* @__PURE__ */ new Set();
4713
+ let content;
4714
+ try {
4715
+ content = fs.readFileSync(path.join(root, resourcePath), "utf8");
4716
+ } catch {
4717
+ return /* @__PURE__ */ new Set();
4718
+ }
4719
+ const methods = /* @__PURE__ */ new Set();
4720
+ for (const block of content.matchAll(/@oagen-ignore-start[\s\S]*?@oagen-ignore-end/g)) for (const line of block[0].split("\n")) {
4721
+ const match = line.match(/^\s{2}(?:(?:public|private|protected)\s+)?(?:async\s+)?([A-Za-z_$][\w$]*)\s*\(/);
4722
+ if (match) methods.add(match[1]);
4723
+ }
4724
+ return methods;
4725
+ }
4641
4726
  function optionsObjectParam$1(method) {
4642
4727
  if (!method || method.params.length !== 1) return void 0;
4643
4728
  const [param] = method.params;
@@ -4645,12 +4730,58 @@ function optionsObjectParam$1(method) {
4645
4730
  if (param.passingStyle && param.passingStyle !== "options_object") return void 0;
4646
4731
  if (!param.type || /^(Record|object|any|unknown)\b/.test(param.type)) return void 0;
4647
4732
  return {
4648
- name: param.name,
4649
- type: param.type
4733
+ name: "options",
4734
+ type: param.type,
4735
+ optional: param.optional === true,
4736
+ generated: false
4650
4737
  };
4651
4738
  }
4739
+ function methodOptionsName$1(method, resolvedServiceName) {
4740
+ if (method === "list") return `${toPascalCase(resolvedServiceName)}ListOptions`;
4741
+ return `${toPascalCase(method)}Options`;
4742
+ }
4743
+ function hiddenParamsFor(resolvedOp) {
4744
+ return new Set([...Object.keys(getOpDefaults(resolvedOp)), ...getOpInferFromClient(resolvedOp)]);
4745
+ }
4746
+ function visibleQueryParamsForOptions(op, plan, resolvedOp) {
4747
+ const hidden = hiddenParamsFor(resolvedOp);
4748
+ return op.queryParams.filter((param) => {
4749
+ if (hidden.has(param.name)) return false;
4750
+ if (plan.isPaginated && PAGINATION_PARAM_NAMES.has(param.name)) return false;
4751
+ return true;
4752
+ });
4753
+ }
4754
+ function optionsObjectShouldBeOptional(op, plan, resolvedOp) {
4755
+ if (plan.hasBody) return false;
4756
+ if (op.pathParams.length > 0) return false;
4757
+ return visibleQueryParamsForOptions(op, plan, resolvedOp).every((param) => !param.required);
4758
+ }
4759
+ function operationHasOptionsInput(op, plan, resolvedOp) {
4760
+ return op.pathParams.length > 0 || plan.hasBody || plan.isPaginated || visibleQueryParamsForOptions(op, plan, resolvedOp).length > 0;
4761
+ }
4762
+ function optionsObjectInfo(service, method, op, plan, ctx, baselineMethod, resolvedOp) {
4763
+ const baseline = optionsObjectParam$1(baselineMethod);
4764
+ if (baseline) return baseline;
4765
+ const overrideType = operationOverrideFor$1(ctx, op)?.optionsType;
4766
+ if (overrideType) return {
4767
+ name: "options",
4768
+ type: overrideType,
4769
+ optional: optionsObjectShouldBeOptional(op, plan, resolvedOp),
4770
+ generated: baselineTypeSourceFile(ctx, overrideType) === void 0
4771
+ };
4772
+ if (!operationHasOptionsInput(op, plan, resolvedOp)) return void 0;
4773
+ return {
4774
+ name: "options",
4775
+ type: methodOptionsName$1(method, resolveResourceClassName$3(service, ctx)),
4776
+ optional: optionsObjectShouldBeOptional(op, plan, resolvedOp),
4777
+ generated: true
4778
+ };
4779
+ }
4780
+ function renderOptionsParam(param) {
4781
+ return `options${param.optional ? "?" : ""}: ${param.type}`;
4782
+ }
4652
4783
  function autoPaginatableItemType$1(returnType) {
4653
- return returnType?.match(/\bAutoPaginatable<\s*([A-Za-z_$][\w$]*)/)?.[1];
4784
+ return returnType?.match(/\b(?:AutoPaginatable|List)<\s*([A-Za-z_$][\w$]*)/)?.[1];
4654
4785
  }
4655
4786
  function baselineTypeSourceFile(ctx, typeName) {
4656
4787
  const surface = ctx.apiSurface;
@@ -4668,9 +4799,12 @@ function preferredBaselineTypeName(ctx, typeName) {
4668
4799
  return typeName;
4669
4800
  }
4670
4801
  function preferredBaselineReturnType(ctx, returnType) {
4802
+ if (!returnType) return void 0;
4803
+ if (!/\bAutoPaginatable\b/.test(returnType)) return void 0;
4671
4804
  const itemType = autoPaginatableItemType$1(returnType);
4805
+ if (!itemType) return void 0;
4672
4806
  const preferred = preferredBaselineTypeName(ctx, itemType);
4673
- if (!returnType || !itemType || !preferred || preferred === itemType) return returnType;
4807
+ if (!preferred || preferred === itemType) return returnType;
4674
4808
  return returnType.replace(new RegExp(`\\b${itemType}\\b`, "g"), preferred);
4675
4809
  }
4676
4810
  function requestEntityType(bodyExpr, requestType) {
@@ -4738,43 +4872,83 @@ function deduplicateMethodNames(plans, _ctx) {
4738
4872
  }
4739
4873
  }
4740
4874
  /**
4741
- * Emit one interface file per paginated list operation that has extension
4742
- * query params. Placing the options interface under `interfaces/` lets the
4743
- * per-service barrel pick it up via `export * from './interfaces'`, which
4744
- * is what the root `src/index.ts` re-exports. When the interface was
4745
- * declared inline in the resource file, it was unreachable from the barrel
4746
- * and callers couldn't import the type by name from the package root.
4875
+ * Emit one interface/type file per generated options-object operation.
4876
+ * Placing the options type under `interfaces/` lets the per-service barrel
4877
+ * pick it up via `export * from './interfaces'`.
4747
4878
  */
4748
- function generatePaginatedOptionsInterfaces(service, ctx, specEnumNames) {
4879
+ function generateOptionsInterfaces(service, ctx, specEnumNames) {
4749
4880
  const files = [];
4750
- const resolvedName = resolveResourceClassName$3(service, ctx);
4751
4881
  const serviceDir = resolveResourceDir(service, ctx);
4882
+ const resolvedLookup = buildResolvedLookup(ctx);
4752
4883
  const plans = service.operations.map((op) => ({
4753
4884
  op,
4754
4885
  plan: planOperation(op),
4755
4886
  method: resolveMethodName$6(op, service, ctx)
4756
4887
  }));
4757
4888
  for (const { op, plan, method } of plans) {
4758
- if (!plan.isPaginated) continue;
4759
- const extraParams = op.queryParams.filter((p) => !PAGINATION_PARAM_NAMES.has(p.name));
4760
- if (extraParams.length === 0) continue;
4761
- const optionsName = paginatedOptionsName(method, resolvedName);
4762
- const filePath = `src/${serviceDir}/interfaces/${fileName$3(optionsName)}.interface.ts`;
4889
+ const resolvedOp = lookupResolved(op, resolvedLookup);
4890
+ const optionInfo = optionsObjectInfo(service, method, op, plan, ctx, baselineMethodFor$1(service, method, ctx), resolvedOp);
4891
+ if (!optionInfo?.generated) continue;
4892
+ if (baselineTypeSourceFile(ctx, optionInfo.type)) continue;
4893
+ const optionsName = optionInfo.type;
4894
+ const optionFileStem = `${fileName$3(optionsName)}.interface`;
4895
+ const filePath = `src/${serviceDir}/interfaces/${optionFileStem}.ts`;
4896
+ if (!liveSurfaceHasFile(filePath) || existingInterfaceBarrelExports(ctx, serviceDir, optionFileStem)) recordGeneratedOptionInterface(ctx, serviceDir, optionFileStem, optionsName);
4897
+ const optEnums = /* @__PURE__ */ new Set();
4898
+ const optModels = /* @__PURE__ */ new Set();
4899
+ for (const param of [...op.pathParams, ...visibleQueryParamsForOptions(op, plan, resolvedOp)]) collectParamTypeRefs(param.type, optEnums, optModels);
4900
+ const bodyInfo = extractRequestBodyType(op, ctx);
4901
+ if (bodyInfo?.kind === "model") {
4902
+ const bodyModel = ctx.spec.models.find((m) => m.name === bodyInfo.name);
4903
+ if (bodyModel) for (const field of bodyModel.fields) collectParamTypeRefs(field.type, optEnums, optModels);
4904
+ } else if (bodyInfo?.kind === "union") for (const name of bodyInfo.modelNames) optModels.add(name);
4763
4905
  const lines = [];
4764
- lines.push("import type { PaginationOptions } from '../../common/interfaces/pagination-options.interface';");
4906
+ if (plan.isPaginated) lines.push("import type { PaginationOptions } from '../../common/interfaces/pagination-options.interface';");
4907
+ const { modelToService, resolveDir } = createServiceDirResolver(ctx.spec.models, ctx.spec.services, ctx);
4908
+ if (optEnums.size > 0) {
4909
+ const enumToService = assignEnumsToServices(ctx.spec.enums, ctx.spec.services, ctx.spec.models, ctx);
4910
+ for (const name of optEnums) {
4911
+ if (!specEnumNames.has(name)) continue;
4912
+ if (isInlineEnum(name)) continue;
4913
+ const enumDir = resolveDir(enumToService.get(name));
4914
+ const relPath = enumDir === serviceDir ? `./${fileName$3(name)}.interface` : `../../${enumDir}/interfaces/${fileName$3(name)}.interface`;
4915
+ lines.push(`import type { ${name} } from '${relPath}';`);
4916
+ }
4917
+ }
4918
+ for (const name of optModels) {
4919
+ const modelDir = resolveDir(modelToService.get(name));
4920
+ const relPath = modelDir === serviceDir ? `./${fileName$3(name)}.interface` : `../../${modelDir}/interfaces/${fileName$3(name)}.interface`;
4921
+ lines.push(`import type { ${resolveInterfaceName(name, ctx)} } from '${relPath}';`);
4922
+ }
4765
4923
  lines.push("");
4766
- lines.push(`export interface ${optionsName} extends PaginationOptions {`);
4767
- for (const param of extraParams) {
4768
- const opt = !param.required ? "?" : "";
4769
- if (param.description || param.deprecated) {
4924
+ const headerParts = [];
4925
+ const pushField = (name, required, type, description, deprecated) => {
4926
+ const opt = !required ? "?" : "";
4927
+ if (description || deprecated) {
4770
4928
  const parts = [];
4771
- if (param.description) parts.push(param.description);
4772
- if (param.deprecated) parts.push("@deprecated");
4773
- lines.push(...docComment$2(parts.join("\n"), 2));
4929
+ if (description) parts.push(description);
4930
+ if (deprecated) parts.push("@deprecated");
4931
+ headerParts.push(...docComment$2(parts.join("\n"), 2));
4774
4932
  }
4775
- lines.push(` ${fieldName$6(param.name)}${opt}: ${mapParamType(param.type, specEnumNames)};`);
4933
+ headerParts.push(` ${name}${opt}: ${type};`);
4934
+ };
4935
+ for (const param of op.pathParams) pushField(fieldName$6(param.name), true, mapParamType(param.type, specEnumNames), param.description, param.deprecated);
4936
+ for (const param of visibleQueryParamsForOptions(op, plan, resolvedOp)) pushField(fieldName$6(param.name), param.required, mapParamType(param.type, specEnumNames), param.description, param.deprecated);
4937
+ if (bodyInfo?.kind === "model") {
4938
+ const bodyModel = ctx.spec.models.find((m) => m.name === bodyInfo.name);
4939
+ if (bodyModel) for (const field of bodyModel.fields) pushField(fieldName$6(field.name), field.required, mapParamType(field.type, specEnumNames), field.description, field.deprecated);
4940
+ lines.push(`export interface ${optionsName}${plan.isPaginated ? " extends PaginationOptions" : ""} {`);
4941
+ lines.push(...headerParts);
4942
+ lines.push("}");
4943
+ } else if (bodyInfo?.kind === "union") {
4944
+ const baseType = headerParts.length > 0 ? `{\n${headerParts.join("\n")}\n}` : "{}";
4945
+ lines.push(`export type ${optionsName} = ${baseType} & (${bodyInfo.typeStr});`);
4946
+ } else if (plan.isPaginated && headerParts.length === 0) lines.push(`export type ${optionsName} = PaginationOptions;`);
4947
+ else {
4948
+ lines.push(`export interface ${optionsName}${plan.isPaginated ? " extends PaginationOptions" : ""} {`);
4949
+ lines.push(...headerParts);
4950
+ lines.push("}");
4776
4951
  }
4777
- lines.push("}");
4778
4952
  files.push({
4779
4953
  path: filePath,
4780
4954
  content: lines.join("\n")
@@ -4819,7 +4993,7 @@ function generateResources$7(services, ctx) {
4819
4993
  }
4820
4994
  for (const service of mergedServices) {
4821
4995
  if (!isNodeOwnedService(ctx, service.name, resolveResourceClassName$3(service, ctx)) && isServiceCoveredByExisting(service, ctx) && !hasMethodsAbsentFromBaseline(service, ctx)) continue;
4822
- files.push(...generatePaginatedOptionsInterfaces(service, ctx, topLevelEnumNames));
4996
+ files.push(...generateOptionsInterfaces(service, ctx, topLevelEnumNames));
4823
4997
  }
4824
4998
  return files;
4825
4999
  }
@@ -4844,27 +5018,40 @@ function generateResourceClass(service, ctx) {
4844
5018
  return (httpKeyOrder.get(aKey) ?? Number.MAX_SAFE_INTEGER) - (httpKeyOrder.get(bKey) ?? Number.MAX_SAFE_INTEGER);
4845
5019
  });
4846
5020
  }
5021
+ const ignoredMethodNames = ignoredResourceMethodNames(ctx, resourcePath);
4847
5022
  const baselineMethodNames = new Set(Object.keys(ctx.apiSurface?.classes?.[serviceClass]?.methods ?? {}));
4848
5023
  const planCountBeforeFilter = plans.length;
5024
+ if (ignoredMethodNames.size > 0) plans = plans.filter((p) => !ignoredMethodNames.has(p.method));
4849
5025
  if (!isNodeOwnedService(ctx, service.name, serviceClass) && baselineMethodNames.size > 0) plans = plans.filter((p) => !baselineMethodNames.has(p.method));
4850
5026
  const filteredOut = planCountBeforeFilter - plans.length;
4851
5027
  if (plans.length === 0 && filteredOut > 0) return null;
4852
5028
  const hasPaginated = plans.some((p) => p.plan.isPaginated);
4853
- const needsPaginationOptionsImport = plans.some((p) => p.plan.isPaginated && (!optionsObjectParam$1(baselineMethodFor$1(service, p.method, ctx)) || /\bPaginationOptions\b/.test(preferredBaselineReturnType(ctx, baselineMethodFor$1(service, p.method, ctx)?.returnType) ?? "")));
5029
+ const resolvedLookup = buildResolvedLookup(ctx);
5030
+ const needsPaginationOptionsImport = plans.some((p) => {
5031
+ const baseline = baselineMethodFor$1(service, p.method, ctx);
5032
+ const optionInfo = optionsObjectInfo(service, p.method, p.op, p.plan, ctx, baseline, lookupResolved(p.op, resolvedLookup));
5033
+ const needsWireSerializer = p.op.queryParams.filter((param) => !PAGINATION_PARAM_NAMES.has(param.name)).some((param) => fieldName$6(param.name) !== wireFieldName(param.name));
5034
+ return p.plan.isPaginated && (needsWireSerializer || !optionInfo || /\bPaginationOptions\b/.test(preferredBaselineReturnType(ctx, baselineMethodFor$1(service, p.method, ctx)?.returnType) ?? ""));
5035
+ });
4854
5036
  const modelMap = new Map(ctx.spec.models.map((m) => [m.name, m]));
4855
5037
  const responseModels = /* @__PURE__ */ new Set();
5038
+ const responseModelsForSignature = /* @__PURE__ */ new Set();
4856
5039
  const requestModels = /* @__PURE__ */ new Set();
4857
5040
  const requestModelsForSignature = /* @__PURE__ */ new Set();
4858
5041
  const paramEnums = /* @__PURE__ */ new Set();
4859
5042
  const paramModels = /* @__PURE__ */ new Set();
4860
5043
  const optionObjectTypes = /* @__PURE__ */ new Set();
5044
+ const returnTypeImports = /* @__PURE__ */ new Set();
4861
5045
  const baselineResponseTypes = /* @__PURE__ */ new Set();
4862
- for (const { op, plan } of plans) {
4863
- const baselineMethod = baselineMethodFor$1(service, resolveMethodName$6(op, service, ctx), ctx);
4864
- const existingOptions = optionsObjectParam$1(baselineMethod);
5046
+ for (const { op, plan, method } of plans) {
5047
+ const baselineMethod = baselineMethodFor$1(service, method, ctx);
5048
+ const existingOptions = optionsObjectInfo(service, method, op, plan, ctx, baselineMethod, lookupResolved(op, resolvedLookup));
4865
5049
  if (existingOptions) optionObjectTypes.add(existingOptions.type);
4866
- const queryParams = plan.isPaginated ? op.queryParams.filter((p) => !PAGINATION_PARAM_NAMES.has(p.name)) : op.queryParams;
4867
- for (const param of [...queryParams, ...op.pathParams]) collectParamTypeRefs(param.type, paramEnums, paramModels);
5050
+ for (const typeName of operationOverrideFor$1(ctx, op)?.returnTypeImports ?? []) returnTypeImports.add(typeName);
5051
+ if (!existingOptions) {
5052
+ const queryParams = plan.isPaginated ? [] : op.queryParams;
5053
+ for (const param of [...queryParams, ...op.pathParams]) collectParamTypeRefs(param.type, paramEnums, paramModels);
5054
+ }
4868
5055
  const baselinePaginatedItemType = existingOptions ? preferredBaselineTypeName(ctx, autoPaginatableItemType$1(baselineMethod?.returnType)) : void 0;
4869
5056
  if (plan.isPaginated && baselinePaginatedItemType) baselineResponseTypes.add(baselinePaginatedItemType);
4870
5057
  else if (plan.isPaginated && op.pagination?.itemType.kind === "model") {
@@ -4875,7 +5062,13 @@ function generateResourceClass(service, ctx) {
4875
5062
  if (unwrapped) itemName = unwrapped.name;
4876
5063
  }
4877
5064
  responseModels.add(itemName);
4878
- } else if (plan.responseModelName) responseModels.add(plan.responseModelName);
5065
+ responseModelsForSignature.add(itemName);
5066
+ } else if (plan.responseModelName) {
5067
+ responseModels.add(plan.responseModelName);
5068
+ const override = operationOverrideFor$1(ctx, op);
5069
+ const resolvedResponseName = resolveInterfaceName(plan.responseModelName, ctx);
5070
+ if (!override?.returnType || override.returnType.includes(resolvedResponseName)) responseModelsForSignature.add(plan.responseModelName);
5071
+ }
4879
5072
  const bodyInfo = extractRequestBodyType(op, ctx);
4880
5073
  if (bodyInfo?.kind === "model") {
4881
5074
  requestModels.add(bodyInfo.name);
@@ -4889,7 +5082,6 @@ function generateResourceClass(service, ctx) {
4889
5082
  if (!existingOptions) requestModelsForSignature.add(name);
4890
5083
  }
4891
5084
  }
4892
- const resolvedLookup = buildResolvedLookup(ctx);
4893
5085
  for (const { op } of plans) {
4894
5086
  const resolved = lookupResolved(op, resolvedLookup);
4895
5087
  if (resolved) {
@@ -4898,7 +5090,7 @@ function generateResourceClass(service, ctx) {
4898
5090
  }
4899
5091
  }
4900
5092
  const allModels = new Set([
4901
- ...responseModels,
5093
+ ...responseModelsForSignature,
4902
5094
  ...requestModelsForSignature,
4903
5095
  ...paramModels
4904
5096
  ]);
@@ -4909,12 +5101,8 @@ function generateResourceClass(service, ctx) {
4909
5101
  lines.push("import { AutoPaginatable } from '../common/utils/pagination';");
4910
5102
  lines.push("import { fetchAndDeserialize } from '../common/utils/fetch-and-deserialize';");
4911
5103
  }
4912
- for (const { op, plan, method } of plans) {
4913
- if (!plan.isPaginated) continue;
4914
- if (op.queryParams.filter((p) => !PAGINATION_PARAM_NAMES.has(p.name)).length === 0) continue;
4915
- const optionsName = paginatedOptionsName(method, resolvedName);
4916
- lines.push(`import type { ${optionsName} } from './interfaces/${fileName$3(optionsName)}.interface';`);
4917
- }
5104
+ const shouldEmitVaultCryptoHelpers = serviceClass === "Vault" && !ignoredMethodNames.has("encrypt") && !ignoredMethodNames.has("decrypt");
5105
+ if (shouldEmitVaultCryptoHelpers) lines.push("import { base64ToUint8Array, uint8ArrayToBase64 } from '../common/utils/base64';");
4918
5106
  const hasIdempotentPost = plans.some((p) => p.plan.isIdempotentPost);
4919
5107
  const hasCustomEncoding = plans.some((p) => p.op.requestBodyEncoding && p.op.requestBodyEncoding !== "json" && p.plan.hasBody);
4920
5108
  if (hasIdempotentPost || hasCustomEncoding) lines.push("import type { PostOptions } from '../common/interfaces/post-options.interface';");
@@ -4922,7 +5110,16 @@ function generateResourceClass(service, ctx) {
4922
5110
  for (const optionType of optionObjectTypes) {
4923
5111
  if (importedTypeNames.has(optionType)) continue;
4924
5112
  importedTypeNames.add(optionType);
4925
- lines.push(`import type { ${optionType} } from './interfaces/${fileName$3(optionType)}.interface';`);
5113
+ const sourceFile = baselineTypeSourceFile(ctx, optionType);
5114
+ const relPath = sourceFile ? relativeImport(resourcePath, sourceFile) : `./interfaces/${fileName$3(optionType)}.interface`;
5115
+ lines.push(`import type { ${optionType} } from '${relPath}';`);
5116
+ }
5117
+ for (const typeName of returnTypeImports) {
5118
+ if (importedTypeNames.has(typeName)) continue;
5119
+ const sourceFile = baselineTypeSourceFile(ctx, typeName) ?? liveSurfaceInterfacePath(typeName);
5120
+ if (!sourceFile) continue;
5121
+ importedTypeNames.add(typeName);
5122
+ lines.push(`import type { ${typeName} } from '${relativeImport(resourcePath, sourceFile)}';`);
4926
5123
  }
4927
5124
  const { modelToService, resolveDir } = createServiceDirResolver(ctx.spec.models, ctx.spec.services, ctx);
4928
5125
  const usedWireTypes = /* @__PURE__ */ new Set();
@@ -4954,6 +5151,17 @@ function generateResourceClass(service, ctx) {
4954
5151
  lines.push(`import type { ${wireName} } from '${relPath}';`);
4955
5152
  importedTypeNames.add(wireName);
4956
5153
  }
5154
+ for (const name of responseModels) {
5155
+ if (allModels.has(name)) continue;
5156
+ const resolved = resolveInterfaceName(name, ctx);
5157
+ if (!usedWireTypes.has(resolved)) continue;
5158
+ const wireName = wireInterfaceName(resolved);
5159
+ if (importedTypeNames.has(wireName)) continue;
5160
+ const modelServiceDir = resolveDir(modelToService.get(name));
5161
+ const relPath = modelServiceDir === serviceDir ? `./interfaces/${fileName$3(name)}.interface` : `../${modelServiceDir}/interfaces/${fileName$3(name)}.interface`;
5162
+ lines.push(`import type { ${wireName} } from '${relPath}';`);
5163
+ importedTypeNames.add(wireName);
5164
+ }
4957
5165
  for (const name of baselineResponseTypes) {
4958
5166
  if (importedTypeNames.has(name)) continue;
4959
5167
  const wireName = wireInterfaceName(name);
@@ -5017,47 +5225,64 @@ function generateResourceClass(service, ctx) {
5017
5225
  }
5018
5226
  }
5019
5227
  lines.push("");
5020
- for (const { op, plan, method } of plans) if (plan.isPaginated) {
5021
- const extraParams = op.queryParams.filter((p) => !PAGINATION_PARAM_NAMES.has(p.name));
5022
- if (extraParams.length > 0) {
5023
- const optionsName = paginatedOptionsName(method, resolvedName);
5024
- if (extraParams.some((p) => fieldName$6(p.name) !== wireFieldName(p.name))) {
5025
- const serializerName = `serialize${optionsName}`;
5026
- lines.push(`const ${serializerName} = (options: ${optionsName}): PaginationOptions => {`);
5027
- lines.push(" const wire: Record<string, unknown> = {");
5028
- for (const p of PAGINATION_PARAM_NAMES) lines.push(` ${p}: options.${p},`);
5029
- lines.push(" };");
5030
- for (const param of extraParams) {
5031
- const camel = fieldName$6(param.name);
5032
- const snake = wireFieldName(param.name);
5033
- lines.push(` if (options.${camel} !== undefined) wire.${snake} = options.${camel};`);
5228
+ for (const { op, plan, method } of plans) {
5229
+ const resolved = lookupResolved(op, resolvedLookup);
5230
+ const optionInfo = optionsObjectInfo(service, method, op, plan, ctx, baselineMethodFor$1(service, method, ctx), resolved);
5231
+ if (plan.isPaginated) {
5232
+ const extraParams = op.queryParams.filter((p) => !PAGINATION_PARAM_NAMES.has(p.name));
5233
+ if (extraParams.length > 0) {
5234
+ const optionsName = optionInfo?.type ?? paginatedOptionsName(method, resolvedName);
5235
+ if (extraParams.some((p) => fieldName$6(p.name) !== wireFieldName(p.name))) {
5236
+ const serializerName = `serialize${optionsName}`;
5237
+ lines.push(`const ${serializerName} = (options: ${optionsName}): PaginationOptions => {`);
5238
+ lines.push(" const wire: Record<string, unknown> = {");
5239
+ const baselineFields = (ctx.apiSurface?.interfaces)?.[optionsName]?.fields;
5240
+ for (const p of PAGINATION_PARAM_NAMES) {
5241
+ if (baselineFields && !(p in baselineFields) && !baselineFields[toCamelCase(p)]) continue;
5242
+ lines.push(` ${p}: options.${p},`);
5243
+ }
5244
+ lines.push(" };");
5245
+ for (const param of extraParams) {
5246
+ const camel = fieldName$6(param.name);
5247
+ const snake = wireFieldName(param.name);
5248
+ lines.push(` if (options.${camel} !== undefined) wire.${snake} = options.${camel};`);
5249
+ }
5250
+ lines.push(" return wire as PaginationOptions;");
5251
+ lines.push("};");
5252
+ lines.push("");
5034
5253
  }
5035
- lines.push(" return wire as PaginationOptions;");
5036
- lines.push("};");
5037
- lines.push("");
5038
5254
  }
5039
- }
5040
- } else if (!plan.isPaginated && !plan.hasBody && !plan.isDelete && op.queryParams.length > 0) {
5041
- const resolved = lookupResolved(op, resolvedLookup);
5042
- const opHiddenParams = new Set([...Object.keys(getOpDefaults(resolved)), ...getOpInferFromClient(resolved)]);
5043
- const visibleParams = op.queryParams.filter((p) => !opHiddenParams.has(p.name));
5044
- if (visibleParams.length > 0) {
5045
- const optionsName = toPascalCase(method) + "Options";
5046
- lines.push(`export interface ${optionsName} {`);
5047
- for (const param of visibleParams) {
5048
- const opt = !param.required ? "?" : "";
5049
- if (param.description || param.deprecated) {
5050
- const parts = [];
5051
- if (param.description) parts.push(param.description);
5052
- if (param.deprecated) parts.push("@deprecated");
5053
- lines.push(...docComment$2(parts.join("\n"), 2));
5255
+ } else if (!optionInfo && !plan.isPaginated && !plan.hasBody && !plan.isDelete && op.queryParams.length > 0) {
5256
+ const resolved = lookupResolved(op, resolvedLookup);
5257
+ const opHiddenParams = new Set([...Object.keys(getOpDefaults(resolved)), ...getOpInferFromClient(resolved)]);
5258
+ const visibleParams = op.queryParams.filter((p) => !opHiddenParams.has(p.name));
5259
+ if (visibleParams.length > 0) {
5260
+ const optionsName = toPascalCase(method) + "Options";
5261
+ lines.push(`export interface ${optionsName} {`);
5262
+ for (const param of visibleParams) {
5263
+ const opt = !param.required ? "?" : "";
5264
+ if (param.description || param.deprecated) {
5265
+ const parts = [];
5266
+ if (param.description) parts.push(param.description);
5267
+ if (param.deprecated) parts.push("@deprecated");
5268
+ lines.push(...docComment$2(parts.join("\n"), 2));
5269
+ }
5270
+ lines.push(` ${fieldName$6(param.name)}${opt}: ${mapParamType(param.type, specEnumNames)};`);
5054
5271
  }
5055
- lines.push(` ${fieldName$6(param.name)}${opt}: ${mapParamType(param.type, specEnumNames)};`);
5272
+ lines.push("}");
5273
+ lines.push("");
5056
5274
  }
5057
- lines.push("}");
5058
- lines.push("");
5059
5275
  }
5060
5276
  }
5277
+ if (shouldEmitVaultCryptoHelpers) {
5278
+ lines.push("export interface VaultEncryptedData {");
5279
+ lines.push(" ciphertext: string;");
5280
+ lines.push(" encryptedKeys: string;");
5281
+ lines.push(" iv: string;");
5282
+ lines.push(" tag: string;");
5283
+ lines.push("}");
5284
+ lines.push("");
5285
+ }
5061
5286
  if (service.description) lines.push(...docComment$2(service.description));
5062
5287
  lines.push(`export class ${serviceClass} {`);
5063
5288
  lines.push(" constructor(private readonly workos: WorkOS) {}");
@@ -5067,6 +5292,10 @@ function generateResourceClass(service, ctx) {
5067
5292
  lines.push(...renderMethod$2(op, plan, method, service, ctx, modelMap, specEnumNames, resolved));
5068
5293
  if (resolved?.wrappers && resolved.wrappers.length > 0) lines.push(...generateWrapperMethods$5(resolved, ctx));
5069
5294
  }
5295
+ if (shouldEmitVaultCryptoHelpers) {
5296
+ lines.push("");
5297
+ lines.push(...renderVaultCryptoMethods());
5298
+ }
5070
5299
  lines.push("}");
5071
5300
  return {
5072
5301
  path: resourcePath,
@@ -5074,6 +5303,51 @@ function generateResourceClass(service, ctx) {
5074
5303
  skipIfExists: true
5075
5304
  };
5076
5305
  }
5306
+ function renderVaultCryptoMethods() {
5307
+ return [
5308
+ " async encrypt(",
5309
+ " plaintext: string,",
5310
+ " context: Record<string, string>,",
5311
+ " aad?: string,",
5312
+ " ): Promise<VaultEncryptedData> {",
5313
+ " const dataKeyPair = await this.createDataKey({ context });",
5314
+ " const encodedPlaintext = new TextEncoder().encode(plaintext);",
5315
+ " const encodedAad = aad ? new TextEncoder().encode(aad) : undefined;",
5316
+ " const encrypted = await this.workos.getCryptoProvider().encrypt(",
5317
+ " encodedPlaintext,",
5318
+ " base64ToUint8Array(dataKeyPair.dataKey.key),",
5319
+ " undefined,",
5320
+ " encodedAad,",
5321
+ " );",
5322
+ "",
5323
+ " return {",
5324
+ " ciphertext: uint8ArrayToBase64(encrypted.ciphertext),",
5325
+ " encryptedKeys: dataKeyPair.encryptedKeys,",
5326
+ " iv: uint8ArrayToBase64(encrypted.iv),",
5327
+ " tag: uint8ArrayToBase64(encrypted.tag),",
5328
+ " };",
5329
+ " }",
5330
+ "",
5331
+ " async decrypt(",
5332
+ " encryptedData: VaultEncryptedData,",
5333
+ " aad?: string,",
5334
+ " ): Promise<string> {",
5335
+ " const dataKey = await this.decryptDataKey({",
5336
+ " keys: encryptedData.encryptedKeys,",
5337
+ " });",
5338
+ " const encodedAad = aad ? new TextEncoder().encode(aad) : undefined;",
5339
+ " const decrypted = await this.workos.getCryptoProvider().decrypt(",
5340
+ " base64ToUint8Array(encryptedData.ciphertext),",
5341
+ " base64ToUint8Array(dataKey.key),",
5342
+ " base64ToUint8Array(encryptedData.iv),",
5343
+ " base64ToUint8Array(encryptedData.tag),",
5344
+ " encodedAad,",
5345
+ " );",
5346
+ "",
5347
+ " return new TextDecoder().decode(decrypted);",
5348
+ " }"
5349
+ ];
5350
+ }
5077
5351
  function renderMethod$2(op, plan, method, service, ctx, modelMap, specEnumNames, resolvedOp) {
5078
5352
  const lines = [];
5079
5353
  const responseModel = plan.responseModelName ? resolveInterfaceName(plan.responseModelName, ctx) : null;
@@ -5081,9 +5355,11 @@ function renderMethod$2(op, plan, method, service, ctx, modelMap, specEnumNames,
5081
5355
  const hiddenParams = new Set([...Object.keys(getOpDefaults(resolvedOp)), ...getOpInferFromClient(resolvedOp)]);
5082
5356
  const httpKey = `${op.httpMethod.toUpperCase()} ${op.path}`;
5083
5357
  const baselineClassMethod = baselineMethodFor$1(service, method, ctx);
5358
+ const optionInfo = optionsObjectInfo(service, method, op, plan, ctx, baselineClassMethod, resolvedOp);
5084
5359
  const overlayMethod = ctx.overlayLookup?.methodByOperation?.get(httpKey) ?? baselineClassMethod;
5085
5360
  let validParamNames = null;
5086
- if (overlayMethod) validParamNames = new Set(overlayMethod.params.map((p) => p.name));
5361
+ if (optionInfo) validParamNames = new Set(["options"]);
5362
+ else if (overlayMethod) validParamNames = new Set(overlayMethod.params.map((p) => p.name));
5087
5363
  else {
5088
5364
  const actualParams = /* @__PURE__ */ new Set();
5089
5365
  for (const p of op.pathParams) actualParams.add(fieldName$6(p.name));
@@ -5197,7 +5473,7 @@ function renderMethod$2(op, plan, method, service, ctx, modelMap, specEnumNames,
5197
5473
  const unwrapped = unwrapListModel$4(pModel, modelMap);
5198
5474
  if (unwrapped) itemRawName = unwrapped.name;
5199
5475
  }
5200
- const itemTypeName = resolveInterfaceName(itemRawName, ctx);
5476
+ const itemTypeName = preferredBaselineTypeName(ctx, autoPaginatableItemType$1(baselineClassMethod?.returnType) ?? autoPaginatableItemType$1(overlayMethod?.returnType)) ?? resolveInterfaceName(itemRawName, ctx);
5201
5477
  docParts.push(`@returns {Promise<AutoPaginatable<${itemTypeName}>>}`);
5202
5478
  } else if (responseModel) {
5203
5479
  const returnTypeDoc = plan.isArrayResponse ? `${responseModel}[]` : responseModel;
@@ -5219,7 +5495,7 @@ function renderMethod$2(op, plan, method, service, ctx, modelMap, specEnumNames,
5219
5495
  }
5220
5496
  }
5221
5497
  }
5222
- if (renderOptionsObjectMethod(lines, op, plan, method, service, ctx, modelMap, specEnumNames, baselineClassMethod)) return lines;
5498
+ if (renderOptionsObjectMethod(lines, op, plan, method, service, ctx, modelMap, specEnumNames, baselineClassMethod, resolvedOp)) return lines;
5223
5499
  const preDecisionCount = lines.length;
5224
5500
  if (plan.isPaginated && op.pagination && op.httpMethod === "get") {
5225
5501
  let paginatedItemRawName = op.pagination.itemType.kind === "model" ? op.pagination.itemType.name : null;
@@ -5246,12 +5522,17 @@ function renderMethod$2(op, plan, method, service, ctx, modelMap, specEnumNames,
5246
5522
  }
5247
5523
  return lines;
5248
5524
  }
5249
- function renderOptionsObjectMethod(lines, op, plan, method, service, ctx, modelMap, specEnumNames, baselineMethod) {
5250
- const optionParam = optionsObjectParam$1(baselineMethod);
5525
+ function renderOptionsObjectMethod(lines, op, plan, method, service, ctx, modelMap, specEnumNames, baselineMethod, resolvedOp) {
5526
+ const optionParam = optionsObjectInfo(service, method, op, plan, ctx, baselineMethod, resolvedOp);
5251
5527
  if (!optionParam) return false;
5252
5528
  const responseModel = plan.responseModelName ? resolveInterfaceName(plan.responseModelName, ctx) : null;
5253
5529
  const pathBindings = buildOptionsObjectPathBindings(op, optionParam.type, ctx);
5254
5530
  const pathStr = buildPathStr(op, buildOptionsObjectPathParamMap(op, optionParam.type, ctx));
5531
+ const override = operationOverrideFor$1(ctx, op);
5532
+ const bodyFieldMap = override?.bodyFieldMap;
5533
+ const visibleQueryParams = visibleQueryParamsForOptions(op, plan, resolvedOp);
5534
+ const queryBindings = visibleQueryParams.map((param) => fieldName$6(param.name));
5535
+ const queryOptionsArg = visibleQueryParams.length > 0 || Object.keys(getOpDefaults(resolvedOp)).length > 0 || getOpInferFromClient(resolvedOp).length > 0 ? `, { query: ${renderQueryExprWithOptions(visibleQueryParams, optionParam.optional, resolvedOp)} }` : "";
5255
5536
  if (plan.isPaginated && op.pagination && op.httpMethod === "get") {
5256
5537
  let itemRawName = op.pagination.itemType.kind === "model" ? op.pagination.itemType.name : null;
5257
5538
  if (itemRawName) {
@@ -5265,14 +5546,16 @@ function renderOptionsObjectMethod(lines, op, plan, method, service, ctx, modelM
5265
5546
  if (!itemType) return false;
5266
5547
  const wireType = wireInterfaceName(itemType);
5267
5548
  const returnType = preferredBaselineReturnType(ctx, baselineMethod?.returnType) ?? `Promise<AutoPaginatable<${itemType}>>`;
5268
- lines.push(` async ${method}(options: ${optionParam.type}): ${returnType} {`);
5269
- renderOptionsObjectDestructure(lines, pathBindings, "paginationOptions");
5549
+ const needsWireSerializer = op.queryParams.filter((p) => !PAGINATION_PARAM_NAMES.has(p.name)).some((p) => fieldName$6(p.name) !== wireFieldName(p.name));
5550
+ const listOptionsExpr = needsWireSerializer ? `options ? serialize${optionParam.type}(options) : undefined` : "paginationOptions";
5551
+ lines.push(` async ${method}(${renderOptionsParam(optionParam)}): ${returnType} {`);
5552
+ renderOptionsObjectDestructure(lines, pathBindings, needsWireSerializer ? void 0 : "paginationOptions");
5270
5553
  lines.push(` return new AutoPaginatable(`);
5271
5554
  lines.push(` await fetchAndDeserialize<${wireType}, ${itemType}>(`);
5272
5555
  lines.push(` this.workos,`);
5273
5556
  lines.push(` ${pathStr},`);
5274
5557
  lines.push(` deserialize${itemType},`);
5275
- lines.push(` paginationOptions,`);
5558
+ lines.push(` ${listOptionsExpr},`);
5276
5559
  lines.push(` ),`);
5277
5560
  lines.push(` (params) =>`);
5278
5561
  lines.push(` fetchAndDeserialize<${wireType}, ${itemType}>(`);
@@ -5281,15 +5564,15 @@ function renderOptionsObjectMethod(lines, op, plan, method, service, ctx, modelM
5281
5564
  lines.push(` deserialize${itemType},`);
5282
5565
  lines.push(` params,`);
5283
5566
  lines.push(` ),`);
5284
- lines.push(` paginationOptions,`);
5567
+ lines.push(` ${listOptionsExpr},`);
5285
5568
  lines.push(` );`);
5286
5569
  lines.push(" }");
5287
5570
  return true;
5288
5571
  }
5289
5572
  if (plan.isDelete && !plan.hasBody) {
5290
- lines.push(` async ${method}(options: ${optionParam.type}): Promise<void> {`);
5573
+ lines.push(` async ${method}(${renderOptionsParam(optionParam)}): Promise<void> {`);
5291
5574
  renderOptionsObjectDestructure(lines, pathBindings);
5292
- lines.push(` await this.workos.delete(${pathStr});`);
5575
+ lines.push(` await this.workos.delete(${pathStr}${queryOptionsArg});`);
5293
5576
  lines.push(" }");
5294
5577
  return true;
5295
5578
  }
@@ -5312,40 +5595,56 @@ function renderOptionsObjectMethod(lines, op, plan, method, service, ctx, modelM
5312
5595
  entityType = requestType;
5313
5596
  }
5314
5597
  if (plan.isDelete) {
5315
- lines.push(` async ${method}(options: ${optionParam.type}): Promise<void> {`);
5316
- renderOptionsObjectDestructure(lines, pathBindings, "payload");
5317
- lines.push(` await this.workos.deleteWithBody<${entityType}>(${pathStr}, ${bodyExpr});`);
5598
+ lines.push(` async ${method}(${renderOptionsParam(optionParam)}): Promise<void> {`);
5599
+ renderOptionsObjectDestructure(lines, [...pathBindings, ...queryBindings], "payload");
5600
+ const bodyParam = renderOptionsObjectBodyFieldMap(lines, bodyFieldMap);
5601
+ bodyExpr = bodyArgExprWithParam(bodyExpr, bodyParam);
5602
+ lines.push(` await this.workos.deleteWithBody<${entityType}>(${pathStr}, ${bodyExpr}${queryOptionsArg});`);
5318
5603
  lines.push(" }");
5319
5604
  return true;
5320
5605
  }
5321
5606
  if (!responseModel) {
5322
- lines.push(` async ${method}(options: ${optionParam.type}): Promise<void> {`);
5323
- renderOptionsObjectDestructure(lines, pathBindings, "payload");
5324
- lines.push(` await this.workos.${op.httpMethod}<void, ${entityType}>(${pathStr}, ${bodyExpr});`);
5607
+ lines.push(` async ${method}(${renderOptionsParam(optionParam)}): Promise<void> {`);
5608
+ renderOptionsObjectDestructure(lines, [...pathBindings, ...queryBindings], "payload");
5609
+ const bodyParam = renderOptionsObjectBodyFieldMap(lines, bodyFieldMap);
5610
+ bodyExpr = bodyArgExprWithParam(bodyExpr, bodyParam);
5611
+ lines.push(` await this.workos.${op.httpMethod}<void, ${entityType}>(${pathStr}, ${bodyExpr}${queryOptionsArg});`);
5325
5612
  lines.push(" }");
5326
5613
  return true;
5327
5614
  }
5328
5615
  const returnType = plan.isArrayResponse ? `${responseModel}[]` : responseModel;
5616
+ const methodReturnType = override?.returnType ?? `Promise<${returnType}>`;
5329
5617
  const wireType = plan.isArrayResponse ? `${wireInterfaceName(responseModel)}[]` : wireInterfaceName(responseModel);
5330
5618
  const returnExpr = plan.isArrayResponse ? `data.map(deserialize${responseModel})` : `deserialize${responseModel}(data)`;
5331
- lines.push(` async ${method}(options: ${optionParam.type}): Promise<${returnType}> {`);
5332
- renderOptionsObjectDestructure(lines, pathBindings, "payload");
5619
+ const finalReturnExpr = override?.returnDataProperty ? `${returnExpr}.${override.returnDataProperty}` : returnExpr;
5620
+ lines.push(` async ${method}(${renderOptionsParam(optionParam)}): ${methodReturnType} {`);
5621
+ renderOptionsObjectDestructure(lines, [...pathBindings, ...queryBindings], "payload");
5622
+ const bodyParam = renderOptionsObjectBodyFieldMap(lines, bodyFieldMap);
5623
+ bodyExpr = bodyArgExprWithParam(bodyExpr, bodyParam);
5333
5624
  lines.push(` const { data } = await this.workos.${op.httpMethod}<${wireType}, ${entityType}>(`);
5334
5625
  lines.push(` ${pathStr},`);
5335
- lines.push(` ${bodyExpr},`);
5626
+ lines.push(` ${bodyExpr}${queryOptionsArg},`);
5336
5627
  lines.push(" );");
5337
- lines.push(` return ${returnExpr};`);
5628
+ if (override?.returnExpression) {
5629
+ lines.push(` const result = ${returnExpr};`);
5630
+ lines.push(` return ${override.returnExpression};`);
5631
+ } else lines.push(` return ${finalReturnExpr};`);
5338
5632
  lines.push(" }");
5339
5633
  return true;
5340
5634
  }
5341
5635
  if (responseModel) {
5342
5636
  const returnType = plan.isArrayResponse ? `${responseModel}[]` : responseModel;
5637
+ const methodReturnType = override?.returnType ?? `Promise<${returnType}>`;
5343
5638
  const wireType = plan.isArrayResponse ? `${wireInterfaceName(responseModel)}[]` : wireInterfaceName(responseModel);
5344
5639
  const returnExpr = plan.isArrayResponse ? `data.map(deserialize${responseModel})` : `deserialize${responseModel}(data)`;
5345
- lines.push(` async ${method}(options: ${optionParam.type}): Promise<${returnType}> {`);
5640
+ const finalReturnExpr = override?.returnDataProperty ? `${returnExpr}.${override.returnDataProperty}` : returnExpr;
5641
+ lines.push(` async ${method}(${renderOptionsParam(optionParam)}): ${methodReturnType} {`);
5346
5642
  renderOptionsObjectDestructure(lines, pathBindings);
5347
- lines.push(` const { data } = await this.workos.${op.httpMethod}<${wireType}>(${pathStr});`);
5348
- lines.push(` return ${returnExpr};`);
5643
+ lines.push(` const { data } = await this.workos.${op.httpMethod}<${wireType}>(${pathStr}${queryOptionsArg});`);
5644
+ if (override?.returnExpression) {
5645
+ lines.push(` const result = ${returnExpr};`);
5646
+ lines.push(` return ${override.returnExpression};`);
5647
+ } else lines.push(` return ${finalReturnExpr};`);
5349
5648
  lines.push(" }");
5350
5649
  return true;
5351
5650
  }
@@ -5356,6 +5655,18 @@ function renderOptionsObjectDestructure(lines, pathBindings, restName) {
5356
5655
  else if (pathBindings.length > 0) lines.push(` const { ${pathBindings.join(", ")} } = options;`);
5357
5656
  else if (restName) lines.push(` const ${restName} = options;`);
5358
5657
  }
5658
+ function renderOptionsObjectBodyFieldMap(lines, bodyFieldMap) {
5659
+ const entries = Object.entries(bodyFieldMap ?? {});
5660
+ if (entries.length === 0) return "payload";
5661
+ lines.push(" const requestPayload = {");
5662
+ lines.push(" ...payload,");
5663
+ for (const [source, target] of entries) lines.push(` ${target}: payload.${source},`);
5664
+ lines.push(" };");
5665
+ return "requestPayload";
5666
+ }
5667
+ function bodyArgExprWithParam(bodyExpr, paramName) {
5668
+ return paramName === "payload" ? bodyExpr : bodyExpr.replace(/\bpayload\b/g, paramName);
5669
+ }
5359
5670
  function buildOptionsObjectPathBindings(op, optionType, ctx) {
5360
5671
  return op.pathParams.map((param) => resolveOptionsObjectField$1(fieldName$6(param.name), optionType, ctx));
5361
5672
  }
@@ -5405,7 +5716,7 @@ function renderPaginatedMethod(lines, op, plan, method, itemType, pathStr, resol
5405
5716
  lines.push(` deserialize${itemType},`);
5406
5717
  lines.push(` params,`);
5407
5718
  lines.push(` ),`);
5408
- lines.push(` options,`);
5719
+ lines.push(` ${serializeCall},`);
5409
5720
  lines.push(` );`);
5410
5721
  lines.push(" }");
5411
5722
  }
@@ -5634,6 +5945,20 @@ function renderQueryExpr(queryParams) {
5634
5945
  }
5635
5946
  return `options ? { ${parts.join(", ")} } : undefined`;
5636
5947
  }
5948
+ function renderQueryExprWithOptions(queryParams, optional, resolvedOp) {
5949
+ const parts = [];
5950
+ for (const param of queryParams) {
5951
+ const camel = fieldName$6(param.name);
5952
+ const snake = wireFieldName(param.name);
5953
+ const access = optional ? `options?.${camel}` : `options.${camel}`;
5954
+ if (param.required) parts.push(`${snake}: ${access}`);
5955
+ else parts.push(`...(${access} !== undefined && { ${snake}: ${access} })`);
5956
+ }
5957
+ for (const [key, value] of Object.entries(getOpDefaults(resolvedOp))) parts.push(`${key}: ${tsLiteral(value)}`);
5958
+ for (const field of getOpInferFromClient(resolvedOp)) parts.push(`${field}: ${clientFieldExpression$8(field)}`);
5959
+ if (parts.length === 0) return optional ? "options" : "{}";
5960
+ return `{ ${parts.join(", ")} }`;
5961
+ }
5637
5962
  function buildPathStr(op, paramNameMap) {
5638
5963
  return buildNodePathExpression(op.path, paramNameMap);
5639
5964
  }
@@ -6074,6 +6399,17 @@ function generateModels$7(models, ctx, shared) {
6074
6399
  const discriminatedSkip = ctx._discriminatedModelNames;
6075
6400
  const nonPaginatedRefs = collectNonPaginatedResponseModelNames(ctx.spec.services);
6076
6401
  const listMetadataNeeded = collectReferencedListMetadataModels(models, nonPaginatedRefs);
6402
+ for (const originalModel of models) {
6403
+ const model = projectedByName.get(originalModel.name) ?? originalModel;
6404
+ if (!reachableModels.has(model.name)) continue;
6405
+ if (interfaceEligibleModels && !interfaceEligibleModels.has(model.name)) continue;
6406
+ if (!isNodeOwnedService(ctx, modelToService.get(model.name)) && !modelHasNewFields(model, ctx) && !forceGenerate.has(model.name)) continue;
6407
+ const canonicalName = dedup.get(model.name);
6408
+ if (canonicalName) {
6409
+ forceGenerate.add(canonicalName);
6410
+ if (interfaceEligibleModels) interfaceEligibleModels.add(canonicalName);
6411
+ }
6412
+ }
6077
6413
  for (const originalModel of models) {
6078
6414
  const model = projectedByName.get(originalModel.name) ?? originalModel;
6079
6415
  if (!reachableModels.has(model.name)) continue;
@@ -6200,9 +6536,11 @@ function generateModels$7(models, ctx, shared) {
6200
6536
  }
6201
6537
  for (const dep of deps.models) {
6202
6538
  const depName = resolveInterfaceName(dep, ctx);
6203
- const depDir = resolveDir(modelToService.get(dep));
6539
+ const depService = modelToService.get(dep);
6540
+ const depDir = resolveDir(depService);
6541
+ const depIsOwned = isNodeOwnedService(ctx, depService);
6204
6542
  const currentFilePath = `src/${dirName}/interfaces/${fileName$3(model.name)}.interface.ts`;
6205
- const baselineSrc = (ctx.apiSurface?.interfaces?.[depName])?.sourceFile;
6543
+ const baselineSrc = depIsOwned ? void 0 : (ctx.apiSurface?.interfaces?.[depName])?.sourceFile;
6206
6544
  if (baselineSrc === currentFilePath) continue;
6207
6545
  let relPath;
6208
6546
  if (baselineSrc) relPath = relativeImport(currentFilePath, baselineSrc).replace(/\.ts$/, "");
@@ -6385,6 +6723,7 @@ function generateSerializers(models, ctx, shared) {
6385
6723
  const resourceUsage = buildGeneratedResourceModelUsage(models, ctx);
6386
6724
  const serializerEligibleModels = resourceUsage ? expandModelRoots(resourceUsage.serializerRoots, projectedByName) : void 0;
6387
6725
  const responseReachableModels = resourceUsage ? expandModelRoots(resourceUsage.responseRoots, projectedByName) : void 0;
6726
+ const requestReachableModels = resourceUsage ? expandModelRoots(resourceUsage.requestRoots, projectedByName) : void 0;
6388
6727
  const serializerReachable = computeNonEventReachable(ctx.spec.services, models);
6389
6728
  const liveRoot = ctx.targetDir ?? ctx.outputDir;
6390
6729
  if (liveRoot) for (const originalModel of models) {
@@ -6417,6 +6756,17 @@ function generateSerializers(models, ctx, shared) {
6417
6756
  const discriminatedSerializerSkip = ctx._discriminatedModelNames;
6418
6757
  const serializerNonPaginatedRefs = collectNonPaginatedResponseModelNames(ctx.spec.services);
6419
6758
  const serializerListMetadataNeeded = collectReferencedListMetadataModels(models, serializerNonPaginatedRefs);
6759
+ for (const originalModel of models) {
6760
+ const model = projectedByName.get(originalModel.name) ?? originalModel;
6761
+ if (!serializerReachable.has(model.name)) continue;
6762
+ if (serializerEligibleModels && !serializerEligibleModels.has(model.name)) continue;
6763
+ if (!isNodeOwnedService(ctx, modelToService.get(model.name)) && !modelHasNewFields(model, ctx) && !forceGenerateSerializer.has(model.name)) continue;
6764
+ const canonicalName = dedup.get(model.name);
6765
+ if (canonicalName) {
6766
+ forceGenerateSerializer.add(canonicalName);
6767
+ if (serializerEligibleModels) serializerEligibleModels.add(canonicalName);
6768
+ }
6769
+ }
6420
6770
  const eligibleModels = [];
6421
6771
  for (const originalModel of models) {
6422
6772
  const model = projectedByName.get(originalModel.name) ?? originalModel;
@@ -6435,7 +6785,7 @@ function generateSerializers(models, ctx, shared) {
6435
6785
  const responseName = wireInterfaceName(domainName);
6436
6786
  const baselineResponse = ctx.apiSurface?.interfaces?.[responseName];
6437
6787
  const baselineDomain = ctx.apiSurface?.interfaces?.[domainName];
6438
- if (shouldSkipSerializeForModel(model, baselineResponse, baselineDomain, dedup, skippedSerializeModels, ctx)) skippedSerializeModels.add(model.name);
6788
+ if (requestReachableModels !== void 0 && !requestReachableModels.has(model.name) || shouldSkipSerializeForModel(model, baselineResponse, baselineDomain, dedup, skippedSerializeModels, ctx)) skippedSerializeModels.add(model.name);
6439
6789
  }
6440
6790
  for (const model of eligibleModels) {
6441
6791
  const service = modelToService.get(model.name);
@@ -6451,20 +6801,24 @@ function generateSerializers(models, ctx, shared) {
6451
6801
  const canonSerializerPath = `src/${canonDir}/serializers/${fileName$3(canonicalName)}.serializer.ts`;
6452
6802
  if (serializerPath === canonSerializerPath) continue;
6453
6803
  if (domainName === canonDomainName) continue;
6454
- const rel = relativeImport(serializerPath, canonSerializerPath);
6455
- const canonSkipSerialize = skippedSerializeModels.has(canonicalName) || skippedSerializeModels.has(model.name);
6456
- const canonSkipDeserialize = responseReachableModels !== void 0 && !responseReachableModels.has(canonicalName) && !responseReachableModels.has(model.name);
6457
- if (canonSkipSerialize && canonSkipDeserialize) continue;
6458
- const parts = [];
6459
- if (!canonSkipDeserialize) parts.push(`deserialize${canonDomainName} as deserialize${domainName}`);
6460
- if (!canonSkipSerialize) parts.push(`serialize${canonDomainName} as serialize${domainName}`);
6461
- const reexportContent = `export { ${parts.join(", ")} } from '${rel}';`;
6462
- files.push({
6463
- path: serializerPath,
6464
- content: reexportContent,
6465
- overwriteExisting: true
6466
- });
6467
- continue;
6804
+ const aliasNeedsDeserialize = responseReachableModels === void 0 || responseReachableModels.has(model.name);
6805
+ const canonicalHasDeserialize = responseReachableModels === void 0 || responseReachableModels.has(canonicalName);
6806
+ if (!aliasNeedsDeserialize || canonicalHasDeserialize) {
6807
+ const rel = relativeImport(serializerPath, canonSerializerPath);
6808
+ const canonSkipSerialize = skippedSerializeModels.has(canonicalName) || skippedSerializeModels.has(model.name);
6809
+ const canonSkipDeserialize = responseReachableModels !== void 0 && !responseReachableModels.has(canonicalName) && !responseReachableModels.has(model.name);
6810
+ if (canonSkipSerialize && canonSkipDeserialize) continue;
6811
+ const parts = [];
6812
+ if (!canonSkipDeserialize) parts.push(`deserialize${canonDomainName} as deserialize${domainName}`);
6813
+ if (!canonSkipSerialize) parts.push(`serialize${canonDomainName} as serialize${domainName}`);
6814
+ const reexportContent = `export { ${parts.join(", ")} } from '${rel}';`;
6815
+ files.push({
6816
+ path: serializerPath,
6817
+ content: reexportContent,
6818
+ overwriteExisting: true
6819
+ });
6820
+ continue;
6821
+ }
6468
6822
  }
6469
6823
  const dirName = resolveDir(service);
6470
6824
  const isDedupCanonical = [...dedup.values()].includes(model.name);
@@ -6504,7 +6858,7 @@ function generateSerializers(models, ctx, shared) {
6504
6858
  }
6505
6859
  const liveRootForBarrel = ctx.outputDir ?? ctx.targetDir;
6506
6860
  for (const [dir, stems] of serializersByDir) {
6507
- if (liveRootForBarrel) {
6861
+ if (liveRootForBarrel && !isNodeOwnedService(ctx, dir)) {
6508
6862
  const serializersDir = path.join(liveRootForBarrel, "src", dir, "serializers");
6509
6863
  try {
6510
6864
  for (const entry of fs.readdirSync(serializersDir)) {
@@ -6750,6 +7104,25 @@ function generateWorkOSClient$1(spec, ctx) {
6750
7104
  skipIfExists: true
6751
7105
  };
6752
7106
  }
7107
+ function sourceFileForExportedType(ctx, typeName) {
7108
+ return (ctx.apiSurface?.interfaces?.[typeName])?.sourceFile ?? (ctx.apiSurface?.typeAliases?.[typeName])?.sourceFile ?? (ctx.apiSurface?.enums?.[typeName])?.sourceFile ?? liveSurfaceInterfacePath(typeName);
7109
+ }
7110
+ function exportedNamesForSource(ctx, sourceFile) {
7111
+ const names = /* @__PURE__ */ new Set();
7112
+ const addNamesFromSurface = (items) => {
7113
+ if (!items) return;
7114
+ for (const [name, item] of Object.entries(items)) if (item.sourceFile === sourceFile) names.add(name);
7115
+ };
7116
+ addNamesFromSurface(ctx.apiSurface?.interfaces);
7117
+ addNamesFromSurface(ctx.apiSurface?.typeAliases);
7118
+ addNamesFromSurface(ctx.apiSurface?.enums);
7119
+ const rootDir = ctx.targetDir ?? ctx.outputDir;
7120
+ if (rootDir) try {
7121
+ const content = fs.readFileSync(path.join(rootDir, sourceFile), "utf-8");
7122
+ for (const match of content.matchAll(/export\s+(?:interface|type|enum|class|const|function)\s+(\w+)/g)) names.add(match[1]);
7123
+ } catch {}
7124
+ return [...names];
7125
+ }
6753
7126
  /**
6754
7127
  * Generate per-service barrel files (interfaces/index.ts) that re-export
6755
7128
  * all interface and enum files for each service directory. This reduces
@@ -6762,11 +7135,21 @@ function generateServiceBarrels(spec, ctx) {
6762
7135
  const enumToService = assignEnumsToServices(spec.enums, spec.services, spec.models, ctx);
6763
7136
  const dirExports = /* @__PURE__ */ new Map();
6764
7137
  const dirSymbols = /* @__PURE__ */ new Map();
7138
+ const ownedDirNames = /* @__PURE__ */ new Set();
7139
+ for (const service of spec.services) if (isNodeOwnedService(ctx, service.name)) {
7140
+ const dir = resolveDir(service.name);
7141
+ ownedDirNames.add(dir);
7142
+ if (!dirExports.has(dir)) {
7143
+ dirExports.set(dir, []);
7144
+ if (!dirSymbols.has(dir)) dirSymbols.set(dir, /* @__PURE__ */ new Set());
7145
+ }
7146
+ }
6765
7147
  const dirSymbolsFromBaseline = /* @__PURE__ */ new Map();
6766
7148
  const seedFromBaseline = (sourceFile, name) => {
6767
7149
  const match = sourceFile.match(/^src\/([^/]+)\/interfaces\/(.+)\.ts$/);
6768
7150
  if (!match) return;
6769
7151
  const dirName = match[1];
7152
+ if (ownedDirNames.has(dirName)) return;
6770
7153
  const fileStem = match[2];
6771
7154
  if (!dirSymbols.has(dirName)) dirSymbols.set(dirName, /* @__PURE__ */ new Set());
6772
7155
  dirSymbols.get(dirName).add(name);
@@ -6819,9 +7202,63 @@ function generateServiceBarrels(spec, ctx) {
6819
7202
  globalExistingSymbols.add(enumDef.name);
6820
7203
  dirExports.get(dirName).push(`export * from './${fileName$3(enumDef.name)}.interface';`);
6821
7204
  }
7205
+ const overrideWildcardSources = /* @__PURE__ */ new Set();
7206
+ const overrideNamedExports = /* @__PURE__ */ new Set();
7207
+ const addOverrideTypeExport = (typeName) => {
7208
+ if (!typeName) return;
7209
+ const sourceFile = sourceFileForExportedType(ctx, typeName);
7210
+ if (!sourceFile) return;
7211
+ const match = sourceFile?.match(/^src\/([^/]+)\/interfaces\/(.+)\.ts$/);
7212
+ if (!match) return;
7213
+ const dirName = match[1];
7214
+ const stem = match[2].replace(/\.ts$/, "");
7215
+ if (!dirExports.has(dirName)) {
7216
+ dirExports.set(dirName, []);
7217
+ if (!dirSymbols.has(dirName)) dirSymbols.set(dirName, /* @__PURE__ */ new Set());
7218
+ }
7219
+ const symbols = dirSymbols.get(dirName);
7220
+ const sourceKey = `${dirName}/${stem}`;
7221
+ if (overrideWildcardSources.has(sourceKey)) return;
7222
+ const sourceNames = exportedNamesForSource(ctx, sourceFile);
7223
+ if (sourceNames.some((name) => name !== typeName && globalExistingSymbols.has(name))) {
7224
+ const exportKey = `${sourceKey}:${typeName}`;
7225
+ if (overrideNamedExports.has(exportKey)) return;
7226
+ dirExports.get(dirName).push(`export type { ${typeName} } from './${stem}';`);
7227
+ overrideNamedExports.add(exportKey);
7228
+ symbols.add(typeName);
7229
+ globalExistingSymbols.add(typeName);
7230
+ return;
7231
+ }
7232
+ dirExports.get(dirName).push(`export * from './${stem}';`);
7233
+ overrideWildcardSources.add(sourceKey);
7234
+ const namesToRegister = sourceNames.length > 0 ? sourceNames : [typeName];
7235
+ for (const name of namesToRegister) {
7236
+ symbols.add(name);
7237
+ globalExistingSymbols.add(name);
7238
+ }
7239
+ };
7240
+ for (const override of Object.values(nodeOptions(ctx).operationOverrides ?? {})) {
7241
+ addOverrideTypeExport(override.optionsType);
7242
+ for (const typeName of override.returnTypeImports ?? []) addOverrideTypeExport(typeName);
7243
+ }
7244
+ const generatedOptionExports = ctx._nodeGeneratedOptionInterfaceExports;
7245
+ for (const [dirName, options] of generatedOptionExports ?? []) {
7246
+ if (!dirExports.has(dirName)) {
7247
+ dirExports.set(dirName, []);
7248
+ if (!dirSymbols.has(dirName)) dirSymbols.set(dirName, /* @__PURE__ */ new Set());
7249
+ }
7250
+ const symbols = dirSymbols.get(dirName);
7251
+ for (const { stem, typeName } of options) {
7252
+ if (globalExistingSymbols.has(typeName)) continue;
7253
+ symbols.add(typeName);
7254
+ globalExistingSymbols.add(typeName);
7255
+ dirExports.get(dirName).push(`export * from './${stem}';`);
7256
+ }
7257
+ }
6822
7258
  for (const [dirName, exports] of dirExports) {
6823
7259
  const exportSet = new Set(exports);
6824
- if (ctx.apiSurface) {
7260
+ const isDirOwned = ownedDirNames.has(dirName);
7261
+ if (ctx.apiSurface && !isDirOwned) {
6825
7262
  const addBaselineExports = (items) => {
6826
7263
  if (!items) return;
6827
7264
  for (const item of Object.values(items)) {
@@ -6834,7 +7271,7 @@ function generateServiceBarrels(spec, ctx) {
6834
7271
  addBaselineExports(ctx.apiSurface.interfaces);
6835
7272
  addBaselineExports(ctx.apiSurface.typeAliases);
6836
7273
  addBaselineExports(ctx.apiSurface.enums);
6837
- if (ctx.targetDir) {
7274
+ if (ctx.targetDir && !isDirOwned) {
6838
7275
  const interfacesDir = path.join(ctx.targetDir, "src", dirName, "interfaces");
6839
7276
  try {
6840
7277
  const barrelPath = path.join(interfacesDir, "index.ts");
@@ -6872,6 +7309,28 @@ function generateServiceBarrels(spec, ctx) {
6872
7309
  } catch {}
6873
7310
  }
6874
7311
  }
7312
+ const ownedScanRoot = ctx.targetDir ?? ctx.outputDir;
7313
+ if (ownedScanRoot && isDirOwned) {
7314
+ const interfacesDir = path.join(ownedScanRoot, "src", dirName, "interfaces");
7315
+ const symbols = dirSymbols.get(dirName) ?? /* @__PURE__ */ new Set();
7316
+ try {
7317
+ for (const entry of fs.readdirSync(interfacesDir)) {
7318
+ if (entry === "index.ts") continue;
7319
+ if (!entry.endsWith(".ts")) continue;
7320
+ const exportLine = `export * from './${entry.replace(/\.ts$/, "")}';`;
7321
+ if (exportSet.has(exportLine)) continue;
7322
+ const content = fs.readFileSync(path.join(interfacesDir, entry), "utf-8");
7323
+ const exportedNames = [];
7324
+ for (const m of content.matchAll(/export\s+(?:interface|type|enum|class|const|function)\s+(\w+)/g)) exportedNames.push(m[1]);
7325
+ if (exportedNames.some((name) => globalExistingSymbols.has(name))) continue;
7326
+ for (const name of exportedNames) {
7327
+ symbols.add(name);
7328
+ globalExistingSymbols.add(name);
7329
+ }
7330
+ exportSet.add(exportLine);
7331
+ }
7332
+ } catch {}
7333
+ }
6875
7334
  const uniqueExports = [...exportSet];
6876
7335
  uniqueExports.sort();
6877
7336
  if (ctx.apiSurface) files.push({
@@ -7088,6 +7547,9 @@ function serverConstName(description) {
7088
7547
  }
7089
7548
  //#endregion
7090
7549
  //#region src/node/tests.ts
7550
+ function operationOverrideFor(ctx, op) {
7551
+ return nodeOptions(ctx).operationOverrides?.[`${op.httpMethod.toUpperCase()} ${op.path}`];
7552
+ }
7091
7553
  function baselineMethodFor(service, method, ctx) {
7092
7554
  const serviceClass = resolveResourceClassName$3(service, ctx);
7093
7555
  return ctx.apiSurface?.classes?.[serviceClass]?.methods?.[method]?.[0];
@@ -7103,6 +7565,34 @@ function optionsObjectParam(method) {
7103
7565
  type: param.type
7104
7566
  };
7105
7567
  }
7568
+ function configuredOptionsMethod(ctx, op) {
7569
+ const optionsType = operationOverrideFor(ctx, op)?.optionsType;
7570
+ if (!optionsType) return void 0;
7571
+ return { params: [{
7572
+ name: "options",
7573
+ type: optionsType,
7574
+ passingStyle: "options_object"
7575
+ }] };
7576
+ }
7577
+ function methodOptionsName(method, serviceClass) {
7578
+ if (method === "list") return `${toPascalCase(serviceClass)}ListOptions`;
7579
+ return `${toPascalCase(method)}Options`;
7580
+ }
7581
+ function hasOptionsInput(op, plan) {
7582
+ return op.pathParams.length > 0 || plan.hasBody || plan.isPaginated || op.queryParams.length > 0;
7583
+ }
7584
+ function optionsMethodFor(service, method, op, plan, ctx) {
7585
+ const baseline = baselineMethodFor(service, method, ctx);
7586
+ if (optionsObjectParam(baseline)) return baseline;
7587
+ const configured = configuredOptionsMethod(ctx, op);
7588
+ if (configured) return configured;
7589
+ if (!hasOptionsInput(op, plan)) return baseline;
7590
+ return { params: [{
7591
+ name: "options",
7592
+ type: methodOptionsName(method, resolveResourceClassName$3(service, ctx)),
7593
+ passingStyle: "options_object"
7594
+ }] };
7595
+ }
7106
7596
  function autoPaginatableItemType(returnType) {
7107
7597
  return returnType?.match(/\bAutoPaginatable<\s*([A-Za-z_$][\w$]*)/)?.[1];
7108
7598
  }
@@ -7212,7 +7702,7 @@ function generateServiceTest$3(service, spec, ctx, modelMap, mountAccessors) {
7212
7702
  lines.push(`describe('${serviceClass}', () => {`);
7213
7703
  lines.push(" beforeEach(() => fetch.resetMocks());");
7214
7704
  for (const { op, plan, method } of plans) {
7215
- const existingMethod = baselineMethodFor(service, method, ctx);
7705
+ const existingMethod = optionsMethodFor(service, method, op, plan, ctx);
7216
7706
  lines.push("");
7217
7707
  lines.push(` describe('${method}', () => {`);
7218
7708
  if (plan.isPaginated) renderPaginatedTest(lines, op, plan, method, serviceProp, modelMap, ctx, entityHelperNames, existingMethod);
@@ -7329,6 +7819,12 @@ function renderBodyTest(lines, op, plan, method, serviceProp, modelMap, ctx, ent
7329
7819
  if (payload) lines.push(` expect(fetchBody()).toEqual(expect.objectContaining(${payload.snakeCaseObj}));`);
7330
7820
  else lines.push(" expect(fetchBody()).toBeDefined();");
7331
7821
  if (isArrayResponse) lines.push(" expect(Array.isArray(result)).toBe(true);");
7822
+ const override = ctx ? operationOverrideFor(ctx, op) : void 0;
7823
+ if (override?.returnExpression || override?.returnDataProperty) {
7824
+ lines.push(" expect(result).toBeDefined();");
7825
+ lines.push(" });");
7826
+ return;
7827
+ }
7332
7828
  const bodyHelperName = ctx ? `expect${resolveInterfaceName(responseModelName, ctx)}` : null;
7333
7829
  if (bodyHelperName && entityHelpers?.has(bodyHelperName)) lines.push(` ${bodyHelperName}(${accessor});`);
7334
7830
  else {
@@ -7358,6 +7854,13 @@ function renderGetTest(lines, op, plan, method, serviceProp, modelMap, ctx, enti
7358
7854
  const expectedPathGet = buildExpectedPath$4(op);
7359
7855
  lines.push(` expect(new URL(String(fetchURL())).pathname).toBe('${expectedPathGet}');`);
7360
7856
  if (isArrayResponse) lines.push(" expect(Array.isArray(result)).toBe(true);");
7857
+ const returnDataProperty = ctx ? operationOverrideFor(ctx, op)?.returnDataProperty : void 0;
7858
+ if (returnDataProperty) {
7859
+ if ((modelMap.get(responseModelName)?.fields.find((field) => fieldName$6(field.name) === returnDataProperty))?.type.kind === "array") lines.push(" expect(Array.isArray(result)).toBe(true);");
7860
+ else lines.push(" expect(result).toBeDefined();");
7861
+ lines.push(" });");
7862
+ return;
7863
+ }
7361
7864
  const helperName = ctx ? `expect${resolveInterfaceName(responseModelName, ctx)}` : null;
7362
7865
  if (helperName && entityHelpers?.has(helperName)) lines.push(` ${helperName}(${accessor});`);
7363
7866
  else {
@@ -7395,12 +7898,37 @@ function buildOptionsObjectTestArg(op, plan, baselineMethod, modelMap, ctx) {
7395
7898
  const optionField = resolveOptionsObjectField(localName, optionParam.type, ctx);
7396
7899
  entries.push(`${optionField}: ${JSON.stringify(pathParamTestValue(param, localName))}`);
7397
7900
  }
7901
+ if (plan.isPaginated) entries.push("order: 'desc'");
7902
+ const queryParams = plan.isPaginated ? op.queryParams.filter((param) => ![
7903
+ "limit",
7904
+ "before",
7905
+ "after",
7906
+ "order"
7907
+ ].includes(param.name)) : op.queryParams;
7908
+ for (const param of queryParams) {
7909
+ const localName = fieldName$6(param.name);
7910
+ const value = fixtureValueForType(param.type, param.name, "Options", modelMap) ?? "'test'";
7911
+ entries.push(`${localName}: ${value}`);
7912
+ }
7398
7913
  if (plan.hasBody) {
7399
7914
  const payload = buildTestPayload(op, modelMap);
7400
- if (payload) entries.push(...objectLiteralEntries(payload.camelCaseObj));
7915
+ if (payload) {
7916
+ const bodyFieldMap = ctx ? operationOverrideFor(ctx, op)?.bodyFieldMap : void 0;
7917
+ entries.push(...mapBodyOptionEntries(objectLiteralEntries(payload.camelCaseObj), bodyFieldMap));
7918
+ } else if (entries.length === 0) return "({} as any)";
7401
7919
  }
7402
7920
  return `{ ${entries.join(", ")} }`;
7403
7921
  }
7922
+ function mapBodyOptionEntries(entries, bodyFieldMap) {
7923
+ const reverseMap = new Map(Object.entries(bodyFieldMap ?? {}).map(([source, target]) => [target, source]));
7924
+ if (reverseMap.size === 0) return entries;
7925
+ return entries.map((entry) => {
7926
+ const match = entry.match(/^([A-Za-z_$][\w$]*)\s*:/);
7927
+ if (!match) return entry;
7928
+ const source = reverseMap.get(match[1]);
7929
+ return source ? entry.replace(match[1], source) : entry;
7930
+ });
7931
+ }
7404
7932
  function objectLiteralEntries(literal) {
7405
7933
  const trimmed = literal.trim();
7406
7934
  if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) return [];
@@ -7476,7 +8004,8 @@ function buildFieldAssertions(model, accessor, modelMap) {
7476
8004
  for (const field of model.fields) {
7477
8005
  if (!field.required) continue;
7478
8006
  const domainField = fieldName$6(field.name);
7479
- const fieldAccessor = isDateTimeFieldType(field.type) ? `${accessor}.${domainField}.toISOString()` : `${accessor}.${domainField}`;
8007
+ const isDateTime = isDateTimeFieldType(field.type);
8008
+ const fieldAccessor = isDateTime ? `${accessor}.${domainField}.toISOString()` : `${accessor}.${domainField}`;
7480
8009
  if (field.example !== void 0) {
7481
8010
  if (field.example === null) {
7482
8011
  assertions.push(`expect(${accessor}.${domainField}).toBeNull();`);
@@ -7484,7 +8013,7 @@ function buildFieldAssertions(model, accessor, modelMap) {
7484
8013
  }
7485
8014
  if (typeof field.example === "object") assertions.push(`expect(${accessor}.${domainField}).toEqual(${JSON.stringify(field.example)});`);
7486
8015
  else {
7487
- const exampleLiteral = typeof field.example === "string" ? `'${field.example}'` : String(field.example);
8016
+ const exampleLiteral = isDateTime && typeof field.example === "string" ? `'${new Date(field.example).toISOString()}'` : typeof field.example === "string" ? `'${field.example}'` : String(field.example);
7488
8017
  assertions.push(`expect(${fieldAccessor}).toBe(${exampleLiteral});`);
7489
8018
  }
7490
8019
  continue;
@@ -7513,7 +8042,7 @@ function buildFieldAssertions(model, accessor, modelMap) {
7513
8042
  */
7514
8043
  function fixtureValueForType(ref, name, modelName, modelMap, wire) {
7515
8044
  switch (ref.kind) {
7516
- case "primitive": return fixtureValueForPrimitive(ref.type, ref.format, name, modelName);
8045
+ case "primitive": return fixtureValueForPrimitive(ref.type, ref.format, name, modelName, wire);
7517
8046
  case "literal": return typeof ref.value === "string" ? `'${ref.value}'` : String(ref.value);
7518
8047
  case "enum":
7519
8048
  if (ref.values?.length) {
@@ -7543,10 +8072,10 @@ function fixtureValueForType(ref, name, modelName, modelMap, wire) {
7543
8072
  default: return null;
7544
8073
  }
7545
8074
  }
7546
- function fixtureValueForPrimitive(type, format, name, modelName) {
8075
+ function fixtureValueForPrimitive(type, format, name, modelName, wire) {
7547
8076
  switch (type) {
7548
8077
  case "string":
7549
- if (format === "date-time") return "'2023-01-01T00:00:00.000Z'";
8078
+ if (format === "date-time") return wire ? "'2023-01-01T00:00:00.000Z'" : "new Date('2023-01-01T00:00:00.000Z')";
7550
8079
  if (format === "date") return "'2023-01-01'";
7551
8080
  if (format === "uuid") return "'00000000-0000-0000-0000-000000000000'";
7552
8081
  if (name === "id") return `'${ID_PREFIXES$4[modelName] ?? ""}01234'`;
@@ -7771,11 +8300,14 @@ function detectDiscriminatedShape(modelName, rawSchemas) {
7771
8300
  };
7772
8301
  }).filter((v) => v !== null);
7773
8302
  if (variants.length !== flattenedVariants.length) return null;
8303
+ const baseFields = baseObject ? collectObjectFields(baseObject, modelName) : [];
8304
+ const discriminatorDescription = flattenedVariants[0].alwaysProperties.get(discProp)?.description;
7774
8305
  return {
7775
8306
  modelName,
7776
- baseFields: baseObject ? collectObjectFields(baseObject, modelName) : [],
8307
+ baseFields,
7777
8308
  discriminatorProperty: discProp,
7778
8309
  discriminatorPropertyDomain: toCamelCase(discProp),
8310
+ discriminatorDescription,
7779
8311
  variants
7780
8312
  };
7781
8313
  }
@@ -7999,13 +8531,29 @@ function planDiscriminatedModels(models, ctx) {
7999
8531
  if (!spec?.components?.schemas) return plans;
8000
8532
  const rawSchemas = spec.components.schemas;
8001
8533
  const { modelToService, resolveDir } = createServiceDirResolver(models, ctx.spec.services, ctx);
8534
+ const irModelDir = /* @__PURE__ */ new Map();
8535
+ for (const model of models) irModelDir.set(model.name, resolveDir(modelToService.get(model.name)));
8536
+ const depDirMap = /* @__PURE__ */ new Map();
8537
+ for (const rawName of Object.keys(rawSchemas)) {
8538
+ if (irModelDir.has(rawName)) {
8539
+ depDirMap.set(rawName, irModelDir.get(rawName));
8540
+ continue;
8541
+ }
8542
+ const stripped = rawName.replace(/Dto/g, "").replace(/DTO/g, "").replace(/Json$/, "");
8543
+ if (stripped !== rawName && irModelDir.has(stripped)) depDirMap.set(rawName, irModelDir.get(stripped));
8544
+ }
8002
8545
  for (const model of models) {
8003
8546
  const shape = detectDiscriminatedShape(model.name, rawSchemas);
8004
8547
  if (!shape) continue;
8548
+ const allDeps = /* @__PURE__ */ new Set();
8549
+ for (const field of shape.baseFields) for (const d of field.modelDeps) allDeps.add(d);
8550
+ for (const variant of shape.variants) for (const field of variant.fields) for (const d of field.modelDeps) allDeps.add(d);
8551
+ if ([...allDeps].some((dep) => !depDirMap.has(dep) && !irModelDir.has(dep))) continue;
8005
8552
  const modelDir = resolveDir(modelToService.get(model.name));
8006
8553
  plans.set(model.name, {
8007
8554
  shape,
8008
- modelDir
8555
+ modelDir,
8556
+ depDirMap
8009
8557
  });
8010
8558
  }
8011
8559
  return plans;
@@ -8050,8 +8598,14 @@ function buildInterfaceFile(plan, _ctx) {
8050
8598
  function buildInterfaceBody(name, shape, variant, isWire) {
8051
8599
  const lines = [];
8052
8600
  lines.push(`export interface ${name} {`);
8053
- for (const field of shape.baseFields) pushFieldLine(lines, field, isWire);
8601
+ const variantFieldNames = new Set(variant.fields.map((f) => f.name));
8602
+ for (const field of shape.baseFields) {
8603
+ if (variantFieldNames.has(field.name)) continue;
8604
+ if (field.name === shape.discriminatorProperty) continue;
8605
+ pushFieldLine(lines, field, isWire);
8606
+ }
8054
8607
  const discKey = isWire ? shape.discriminatorProperty : shape.discriminatorPropertyDomain;
8608
+ if (shape.discriminatorDescription) lines.push(` /** ${shape.discriminatorDescription} */`);
8055
8609
  lines.push(` ${discKey}: '${variant.discriminatorValue}';`);
8056
8610
  for (const field of variant.fields) pushFieldLine(lines, field, isWire);
8057
8611
  lines.push("}");
@@ -8068,25 +8622,24 @@ function collectImports$2(plan) {
8068
8622
  const deps = /* @__PURE__ */ new Set();
8069
8623
  for (const field of plan.shape.baseFields) for (const d of field.modelDeps) deps.add(d);
8070
8624
  for (const variant of plan.shape.variants) for (const field of variant.fields) for (const d of field.modelDeps) deps.add(d);
8071
- const symbols = [];
8625
+ const result = [];
8072
8626
  for (const dep of [...deps].sort()) {
8073
8627
  const domain = toPascalCase(dep);
8074
- symbols.push(domain);
8075
8628
  const wire = wireInterfaceName(domain);
8076
- if (wire !== domain) symbols.push(wire);
8629
+ const symbols = wire !== domain ? [domain, wire] : [domain];
8630
+ const depDir = plan.depDirMap.get(dep);
8631
+ const baseName = fileName$3(toSnakeFromPascal(domain));
8632
+ let importPath;
8633
+ if (!depDir || depDir === plan.modelDir) importPath = `./${baseName}.interface`;
8634
+ else importPath = `../../${depDir}/interfaces/${baseName}.interface`;
8635
+ const existing = result.find((a) => a.path === importPath);
8636
+ if (existing) existing.symbols.push(...symbols);
8637
+ else result.push({
8638
+ path: importPath,
8639
+ symbols
8640
+ });
8077
8641
  }
8078
- if (symbols.length === 0) return [];
8079
- return symbols.map((sym) => {
8080
- return {
8081
- path: `./${fileName$3(toSnakeFromPascal(sym.replace(/Response$/, "")))}.interface`,
8082
- symbols: [sym]
8083
- };
8084
- }).reduce((acc, cur) => {
8085
- const existing = acc.find((a) => a.path === cur.path);
8086
- if (existing) existing.symbols.push(...cur.symbols);
8087
- else acc.push(cur);
8088
- return acc;
8089
- }, []);
8642
+ return result;
8090
8643
  }
8091
8644
  function toSnakeFromPascal(s) {
8092
8645
  return s.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").toLowerCase();
@@ -8180,20 +8733,6 @@ function scalarModelDepName(field) {
8180
8733
  }
8181
8734
  //#endregion
8182
8735
  //#region src/node/node-overrides.ts
8183
- const OPERATION_OVERRIDES = {
8184
- "POST /organizations/{organizationId}/groups": { methodName: "create_group" },
8185
- "GET /organizations/{organizationId}/groups": { methodName: "list_groups" },
8186
- "GET /organizations/{organizationId}/groups/{groupId}": { methodName: "get_group" },
8187
- "PATCH /organizations/{organizationId}/groups/{groupId}": { methodName: "update_group" },
8188
- "DELETE /organizations/{organizationId}/groups/{groupId}": { methodName: "delete_group" },
8189
- "POST /organizations/{organizationId}/groups/{groupId}/organization-memberships": { methodName: "add_organization_membership" },
8190
- "GET /organizations/{organizationId}/groups/{groupId}/organization-memberships": { methodName: "list_organization_memberships" },
8191
- "DELETE /organizations/{organizationId}/groups/{groupId}/organization-memberships/{omId}": { methodName: "remove_organization_membership" },
8192
- "GET /user_management/organization_memberships/{omId}/groups": {
8193
- methodName: "list_groups_for_organization_membership",
8194
- mountOn: "UserManagement"
8195
- }
8196
- };
8197
8736
  const contextCache = /* @__PURE__ */ new WeakMap();
8198
8737
  function operationKey(resolved) {
8199
8738
  return `${resolved.operation.httpMethod.toUpperCase()} ${resolved.operation.path}`;
@@ -8245,9 +8784,10 @@ function withNodeOperationOverrides(ctx) {
8245
8784
  contextCache.set(ctx, next);
8246
8785
  return next;
8247
8786
  }
8787
+ const configOverrides = nodeOptions(ctx).operationOverrides ?? {};
8248
8788
  let opsChanged = false;
8249
8789
  const nextResolved = resolvedOperations.map((resolved) => {
8250
- const override = OPERATION_OVERRIDES[operationKey(resolved)];
8790
+ const override = configOverrides[operationKey(resolved)];
8251
8791
  if (!override) return resolved;
8252
8792
  const methodName = override.methodName ?? resolved.methodName;
8253
8793
  const mountOn = override.mountOn ?? resolved.mountOn;
@@ -8475,20 +9015,48 @@ function isOwnedPath(relPath, policy) {
8475
9015
  const dir = topLevelDir(relPath);
8476
9016
  return dir !== void 0 && policy.ownedServiceDirs.has(dir);
8477
9017
  }
9018
+ function extractRelativeImportPaths(content, fromPath) {
9019
+ const dir = path$1.dirname(fromPath);
9020
+ const paths = [];
9021
+ const re = /from\s+['"](\.[^'"]+)['"]/g;
9022
+ let match;
9023
+ while ((match = re.exec(content)) !== null) paths.push(path$1.normalize(path$1.join(dir, match[1])) + ".ts");
9024
+ return paths;
9025
+ }
8478
9026
  function applyLiveSurface(files, ctx, surface) {
8479
9027
  const out = [];
8480
9028
  const policy = buildLiveSurfacePolicy(ctx, surface);
9029
+ const filesByPath = new Map(files.map((f) => [f.path, f]));
9030
+ const dependencyAllowedPaths = /* @__PURE__ */ new Set();
9031
+ const queue = [];
9032
+ for (const f of files) {
9033
+ if (f.integrateTarget === false) continue;
9034
+ if (!canCreateNewPath(f.path, policy)) continue;
9035
+ for (const importPath of extractRelativeImportPaths(f.content, f.path)) if (filesByPath.has(importPath) && !canCreateNewPath(importPath, policy) && !dependencyAllowedPaths.has(importPath)) {
9036
+ dependencyAllowedPaths.add(importPath);
9037
+ queue.push(importPath);
9038
+ }
9039
+ }
9040
+ while (queue.length > 0) {
9041
+ const relPath = queue.pop();
9042
+ const file = filesByPath.get(relPath);
9043
+ if (!file) continue;
9044
+ for (const importPath of extractRelativeImportPaths(file.content, relPath)) if (filesByPath.has(importPath) && !canCreateNewPath(importPath, policy) && !dependencyAllowedPaths.has(importPath)) {
9045
+ dependencyAllowedPaths.add(importPath);
9046
+ queue.push(importPath);
9047
+ }
9048
+ }
8481
9049
  for (const f of files) {
8482
9050
  const ownedPath = isOwnedPath(f.path, policy);
8483
9051
  if (f.integrateTarget === false) continue;
8484
9052
  if (surface.protectedFiles.has(f.path)) continue;
8485
- if (policy.hasExistingSdk && !policy.managedPaths.has(f.path) && !canCreateNewPath(f.path, policy)) continue;
9053
+ if (policy.hasExistingSdk && !policy.managedPaths.has(f.path) && !canCreateNewPath(f.path, policy) && !dependencyAllowedPaths.has(f.path)) continue;
8486
9054
  if (surface.files.has(f.path) && !surface.autogenFiles.has(f.path) && !ownedPath) continue;
8487
9055
  if (isUserOwnedAfterFirstEmit(f.path)) {
8488
9056
  const dir = topLevelDir(f.path);
8489
9057
  const isAdoptedDir = dir !== void 0 && policy.adoptedServiceDirs.has(dir);
8490
9058
  const isManagedDir = ownedPath || isAdoptedDir;
8491
- if (surface.files.has(f.path) && !surface.autogenFiles.has(f.path)) continue;
9059
+ if (surface.files.has(f.path) && !surface.autogenFiles.has(f.path) && !(ownedPath && policy.regenerateOwnedTests)) continue;
8492
9060
  if (!isManagedDir && !surface.autogenFiles.has(f.path)) continue;
8493
9061
  if (isManagedDir && !policy.regenerateOwnedTests) continue;
8494
9062
  }
@@ -8582,7 +9150,9 @@ const nodeEmitter = {
8582
9150
  const surface = getSurface(nodeCtx);
8583
9151
  const enriched = enrichModelsForNode(models);
8584
9152
  const discPlans = planDiscriminatedModels(enriched, nodeCtx);
8585
- nodeCtx._discriminatedModelNames = new Set(discPlans.keys());
9153
+ const discriminatedNames = new Set(discPlans.keys());
9154
+ nodeCtx._discriminatedModelNames = discriminatedNames;
9155
+ setDiscriminatedModelNames(discriminatedNames);
8586
9156
  const standardFiles = generateModelsAndSerializers(enriched, nodeCtx);
8587
9157
  const discFiles = generateDiscriminatedFiles(discPlans, nodeCtx);
8588
9158
  return applyLiveSurface([...standardFiles, ...discFiles], nodeCtx, surface);
@@ -9195,11 +9765,24 @@ function generateEnums$6(enums, ctx) {
9195
9765
  const canonicalCls = className$5(canonicalName);
9196
9766
  const aliasCls = className$5(enumDef.name);
9197
9767
  const lines = [];
9198
- lines.push("from typing import TypeAlias");
9199
- if (canonicalDir === dirName) lines.push(`from .${fileName$2(canonicalName)} import ${canonicalCls}`);
9200
- else lines.push(`from ${ctx.namespace}.${dirToModule(canonicalDir)}.models.${fileName$2(canonicalName)} import ${canonicalCls}`);
9201
- lines.push("");
9202
- lines.push(`${aliasCls}: TypeAlias = ${canonicalCls}`);
9768
+ if (canonicalDir === dirName) {
9769
+ lines.push("from typing import TypeAlias");
9770
+ lines.push(`from .${fileName$2(canonicalName)} import ${canonicalCls}`);
9771
+ lines.push("");
9772
+ lines.push(`${aliasCls}: TypeAlias = ${canonicalCls}`);
9773
+ } else {
9774
+ const modPath = `${ctx.namespace}.${dirToModule(canonicalDir)}.models.${fileName$2(canonicalName)}`;
9775
+ lines.push("from typing import TYPE_CHECKING");
9776
+ lines.push("");
9777
+ lines.push("if TYPE_CHECKING:");
9778
+ lines.push(` from ${modPath} import ${canonicalCls} as ${aliasCls}`);
9779
+ lines.push("else:");
9780
+ lines.push(" def __getattr__(name: str):");
9781
+ lines.push(` if name == "${aliasCls}":`);
9782
+ lines.push(` from ${modPath} import ${canonicalCls}`);
9783
+ lines.push(` return ${canonicalCls}`);
9784
+ lines.push(" raise AttributeError(f\"module {__name__!r} has no attribute {name!r}\")");
9785
+ }
9203
9786
  lines.push(`__all__ = ["${aliasCls}"]`);
9204
9787
  files.push({
9205
9788
  path: `src/${ctx.namespace}/${dirName}/models/${fileName$2(enumDef.name)}.py`,
@@ -9208,16 +9791,33 @@ function generateEnums$6(enums, ctx) {
9208
9791
  overwriteExisting: true
9209
9792
  });
9210
9793
  for (const aliasName of compatAliases.get(enumDef.name) ?? []) {
9211
- const importLine = canonicalDir === dirName ? `from .${fileName$2(canonicalName)} import ${canonicalCls}` : `from ${ctx.namespace}.${dirToModule(canonicalDir)}.models.${fileName$2(canonicalName)} import ${canonicalCls}`;
9212
- files.push({
9213
- path: `src/${ctx.namespace}/${dirName}/models/${fileName$2(aliasName)}.py`,
9214
- content: [
9215
- "from typing import TypeAlias",
9216
- importLine,
9794
+ let compatContent;
9795
+ if (canonicalDir === dirName) compatContent = [
9796
+ "from typing import TypeAlias",
9797
+ `from .${fileName$2(canonicalName)} import ${canonicalCls}`,
9798
+ "",
9799
+ `${aliasName}: TypeAlias = ${canonicalCls}`,
9800
+ `__all__ = ["${aliasName}"]`
9801
+ ].join("\n");
9802
+ else {
9803
+ const modPath = `${ctx.namespace}.${dirToModule(canonicalDir)}.models.${fileName$2(canonicalName)}`;
9804
+ compatContent = [
9805
+ "from typing import TYPE_CHECKING",
9217
9806
  "",
9218
- `${aliasName}: TypeAlias = ${canonicalCls}`,
9807
+ "if TYPE_CHECKING:",
9808
+ ` from ${modPath} import ${canonicalCls} as ${aliasName}`,
9809
+ "else:",
9810
+ " def __getattr__(name: str):",
9811
+ ` if name == "${aliasName}":`,
9812
+ ` from ${modPath} import ${canonicalCls}`,
9813
+ ` return ${canonicalCls}`,
9814
+ " raise AttributeError(f\"module {__name__!r} has no attribute {name!r}\")",
9219
9815
  `__all__ = ["${aliasName}"]`
9220
- ].join("\n"),
9816
+ ].join("\n");
9817
+ }
9818
+ files.push({
9819
+ path: `src/${ctx.namespace}/${dirName}/models/${fileName$2(aliasName)}.py`,
9820
+ content: compatContent,
9221
9821
  integrateTarget: true,
9222
9822
  overwriteExisting: true
9223
9823
  });
@@ -9391,6 +9991,8 @@ function generateModels$6(models, ctx) {
9391
9991
  const emittedFilePaths = /* @__PURE__ */ new Set();
9392
9992
  const nonPaginatedRefs = collectNonPaginatedResponseModelNames(ctx.spec.services);
9393
9993
  const listMetadataNeeded = collectReferencedListMetadataModels(models, nonPaginatedRefs);
9994
+ const discriminatorNames = /* @__PURE__ */ new Set();
9995
+ for (const m of models) if (m.discriminator) discriminatorNames.add(m.name);
9394
9996
  for (const model of models) {
9395
9997
  if (isListWrapperModel(model) && !nonPaginatedRefs.has(model.name)) continue;
9396
9998
  if (isListMetadataModel(model) && !listMetadataNeeded.has(model.name)) continue;
@@ -9526,7 +10128,7 @@ function generateModels$6(models, ctx) {
9526
10128
  typingImports.add("Dict");
9527
10129
  for (const field of deduplicatedFields) collectTypingImports(field.type, typingImports);
9528
10130
  if (deduplicatedFields.some((f) => isOptionalField(model.name, f, ctx))) typingImports.add("Optional");
9529
- const usesDateTime = deduplicatedFields.some((f) => isDateTimeType(f.type));
10131
+ const usesDateTime = deduplicatedFields.some((f) => isDateTimeType$1(f.type));
9530
10132
  const usesEnum = deps.enums.size > 0;
9531
10133
  lines.push("from __future__ import annotations");
9532
10134
  lines.push("");
@@ -9542,8 +10144,9 @@ function generateModels$6(models, ctx) {
9542
10144
  for (const modelName of [...deps.models].sort()) {
9543
10145
  if (modelName === model.name) continue;
9544
10146
  const modelDir = resolveDir(modelToService.get(modelName));
9545
- if (modelDir === dirName) lines.push(`from .${fileName$2(modelName)} import ${className$5(modelName)}`);
9546
- else lines.push(`from ${ctx.namespace}.${dirToModule(modelDir)}.models.${fileName$2(modelName)} import ${className$5(modelName)}`);
10147
+ const importNames = discriminatorNames.has(modelName) ? `${className$5(modelName)}, ${className$5(modelName)}Variant` : className$5(modelName);
10148
+ if (modelDir === dirName) lines.push(`from .${fileName$2(modelName)} import ${importNames}`);
10149
+ else lines.push(`from ${ctx.namespace}.${dirToModule(modelDir)}.models.${fileName$2(modelName)} import ${importNames}`);
9547
10150
  }
9548
10151
  }
9549
10152
  if (deps.enums.size > 0) for (const enumName of [...deps.enums].sort()) {
@@ -9564,9 +10167,16 @@ function generateModels$6(models, ctx) {
9564
10167
  lines.push("");
9565
10168
  const requiredFields = deduplicatedFields.filter((f) => !isOptionalField(model.name, f, ctx));
9566
10169
  const optionalFields = deduplicatedFields.filter((f) => isOptionalField(model.name, f, ctx));
10170
+ const rewriteDiscriminatorType = (typeStr) => {
10171
+ for (const discName of discriminatorNames) {
10172
+ const quoted = `"${className$5(discName)}"`;
10173
+ if (typeStr.includes(quoted)) typeStr = typeStr.replace(quoted, `"${className$5(discName)}Variant"`);
10174
+ }
10175
+ return typeStr;
10176
+ };
9567
10177
  for (const field of requiredFields) {
9568
10178
  const pyFieldName = fieldName$5(field.name);
9569
- const pyType = resolveModelFieldType(field.type);
10179
+ const pyType = rewriteDiscriminatorType(resolveModelFieldType(field.type));
9570
10180
  if (field.description || field.deprecated) {
9571
10181
  const parts = [];
9572
10182
  if (field.description) parts.push(field.description);
@@ -9577,7 +10187,7 @@ function generateModels$6(models, ctx) {
9577
10187
  }
9578
10188
  for (const field of optionalFields) {
9579
10189
  const pyFieldName = fieldName$5(field.name);
9580
- const pyType = `Optional[${field.type.kind === "nullable" ? resolveModelFieldType(field.type.inner) : resolveModelFieldType(field.type)}]`;
10190
+ const pyType = `Optional[${rewriteDiscriminatorType(field.type.kind === "nullable" ? resolveModelFieldType(field.type.inner) : resolveModelFieldType(field.type))}]`;
9581
10191
  if (field.description || field.deprecated) {
9582
10192
  const parts = [];
9583
10193
  if (field.description) parts.push(field.description);
@@ -9821,12 +10431,12 @@ function pythonLiteralDefault(value) {
9821
10431
  return String(value);
9822
10432
  }
9823
10433
  function resolveModelFieldType(ref) {
9824
- if (ref.kind === "nullable" && isDateTimeType(ref.inner)) return "Optional[datetime]";
9825
- if (isDateTimeType(ref)) return "datetime";
10434
+ if (ref.kind === "nullable" && isDateTimeType$1(ref.inner)) return "Optional[datetime]";
10435
+ if (isDateTimeType$1(ref)) return "datetime";
9826
10436
  return mapTypeRef$6(ref);
9827
10437
  }
9828
- function isDateTimeType(ref) {
9829
- if (ref.kind === "nullable") return isDateTimeType(ref.inner);
10438
+ function isDateTimeType$1(ref) {
10439
+ if (ref.kind === "nullable") return isDateTimeType$1(ref.inner);
9830
10440
  return ref.kind === "primitive" && ref.type === "string" && ref.format === "date-time";
9831
10441
  }
9832
10442
  /**
@@ -9887,7 +10497,7 @@ function renderDiscriminatedUnionPrelude(field, pyFieldName, wireKey, parentClas
9887
10497
  prelude.push(`${indent}if ${rawVar} is None:`);
9888
10498
  prelude.push(`${indent} ${valueVar} = None`);
9889
10499
  prelude.push(`${indent}else:`);
9890
- prelude.push(...dispatchBlock(indent + " "));
10500
+ prelude.push(...dispatchBlock(" "));
9891
10501
  prelude.push(`${indent} ${valueVar} = ${clsVar}.from_dict(${dataVar})`);
9892
10502
  return {
9893
10503
  prelude,
@@ -9895,7 +10505,7 @@ function renderDiscriminatedUnionPrelude(field, pyFieldName, wireKey, parentClas
9895
10505
  };
9896
10506
  }
9897
10507
  function deserializeField(ref, accessor, isRequired, walrusVar = "_v") {
9898
- if (isDateTimeType(ref)) {
10508
+ if (isDateTimeType$1(ref)) {
9899
10509
  if (isRequired) return `_parse_datetime(${accessor})`;
9900
10510
  return `_parse_datetime(${walrusVar}) if (${walrusVar} := ${accessor}) is not None else None`;
9901
10511
  }
@@ -9934,7 +10544,7 @@ function deserializeField(ref, accessor, isRequired, walrusVar = "_v") {
9934
10544
  }
9935
10545
  }
9936
10546
  function serializeField(ref, accessor) {
9937
- if (isDateTimeType(ref)) return `_format_datetime(${accessor})`;
10547
+ if (isDateTimeType$1(ref)) return `_format_datetime(${accessor})`;
9938
10548
  switch (ref.kind) {
9939
10549
  case "model": return `${accessor}.to_dict()`;
9940
10550
  case "enum": return `${accessor}.value if isinstance(${accessor}, Enum) else ${accessor}`;
@@ -10078,7 +10688,8 @@ function emitWrapperMethod$5(lines, resolvedOp, wrapper, ctx, isAsync) {
10078
10688
  else lines.push(` ${pyName}: ${pyType},`);
10079
10689
  }
10080
10690
  lines.push(" request_options: Optional[RequestOptions] = None,");
10081
- const responseType = wrapper.responseModelName ? className$5(wrapper.responseModelName) : "None";
10691
+ const isDiscriminatorResponse = wrapper.responseModelName ? !!ctx.spec.models.find((m) => m.name === wrapper.responseModelName)?.discriminator : false;
10692
+ const responseType = wrapper.responseModelName ? isDiscriminatorResponse ? className$5(wrapper.responseModelName) + "Variant" : className$5(wrapper.responseModelName) : "None";
10082
10693
  lines.push(` ) -> ${responseType}:`);
10083
10694
  lines.push(` """${formatWrapperDescription(wrapper.name)}."""`);
10084
10695
  lines.push(" body: Dict[str, Any] = {");
@@ -10103,7 +10714,19 @@ function emitWrapperMethod$5(lines, resolvedOp, wrapper, ctx, isAsync) {
10103
10714
  const pathExpr = buildPythonPathExpression(op.path);
10104
10715
  const awaitPrefix = isAsync ? "await " : "";
10105
10716
  lines.push("");
10106
- if (wrapper.responseModelName) {
10717
+ if (wrapper.responseModelName && isDiscriminatorResponse) {
10718
+ const variantType = className$5(wrapper.responseModelName) + "Variant";
10719
+ lines.push(` return cast(`);
10720
+ lines.push(` ${variantType},`);
10721
+ lines.push(` ${awaitPrefix}self._client.request(`);
10722
+ lines.push(` method="${op.httpMethod.toUpperCase()}",`);
10723
+ lines.push(` path=${pathExpr},`);
10724
+ lines.push(" body=body,");
10725
+ lines.push(` model=${className$5(wrapper.responseModelName)}, # type: ignore[arg-type] # dispatcher; request only calls from_dict`);
10726
+ lines.push(" request_options=request_options,");
10727
+ lines.push(" )");
10728
+ lines.push(" )");
10729
+ } else if (wrapper.responseModelName) {
10107
10730
  lines.push(` return ${awaitPrefix}self._client.request(`);
10108
10731
  lines.push(` method="${op.httpMethod.toUpperCase()}",`);
10109
10732
  lines.push(` path=${pathExpr},`);
@@ -10335,8 +10958,8 @@ function emitMethodSignature(lines, op, plan, method, isAsync, specEnumNames, mo
10335
10958
  else if (isPaginated) {
10336
10959
  const resolvedItem = resolvePageItemName(op.pagination.itemType, listWrapperNames, ctx);
10337
10960
  returnType = `${pageType}[${ctx.spec.models.find((m) => m.name === resolvedItem)?.discriminator ? className$5(resolvedItem) + "Variant" : className$5(resolvedItem)}]`;
10338
- } else if (isArrayResponse) returnType = `List[${className$5(plan.responseModelName)}]`;
10339
- else if (plan.responseModelName) returnType = className$5(plan.responseModelName);
10961
+ } else if (isArrayResponse) returnType = `List[${ctx.spec.models.find((m) => m.name === plan.responseModelName)?.discriminator ? className$5(plan.responseModelName) + "Variant" : className$5(plan.responseModelName)}]`;
10962
+ else if (plan.responseModelName) returnType = ctx.spec.models.find((m) => m.name === plan.responseModelName)?.discriminator ? className$5(plan.responseModelName) + "Variant" : className$5(plan.responseModelName);
10340
10963
  else returnType = "None";
10341
10964
  lines.push(` ) -> ${returnType}:`);
10342
10965
  return {
@@ -10627,6 +11250,7 @@ function emitMethodBody(lines, op, plan, meta, isAsync, modelImports, listWrappe
10627
11250
  lines.push(" )");
10628
11251
  } else if (plan.hasBody && op.requestBody) {
10629
11252
  const responseModel = plan.responseModelName ? className$5(plan.responseModelName) : "None";
11253
+ const isBodyResponseDiscriminator = !!(plan.responseModelName ? ctx.spec.models.find((m) => m.name === plan.responseModelName) : null)?.discriminator;
10630
11254
  const bodyGroupedParams = collectGroupedParamNames(op);
10631
11255
  const bodyModel = ctx.spec.models.find((m) => op.requestBody?.kind === "model" && m.name === op.requestBody.name);
10632
11256
  const bodyFieldNamesSet = /* @__PURE__ */ new Set();
@@ -10664,6 +11288,20 @@ function emitMethodBody(lines, op, plan, meta, isAsync, modelImports, listWrappe
10664
11288
  lines.push(" request_options=request_options,");
10665
11289
  lines.push(" )");
10666
11290
  lines.push(` return [${itemModel}.from_dict(cast(Dict[str, Any], item)) for item in raw]`);
11291
+ } else if (isBodyResponseDiscriminator && responseModel !== "None") {
11292
+ const variantType = responseModel + "Variant";
11293
+ lines.push(` return cast(`);
11294
+ lines.push(` ${variantType},`);
11295
+ lines.push(` ${awaitPrefix}self._client.request(`);
11296
+ lines.push(` method="${httpMethod}",`);
11297
+ lines.push(` path=${pathStr},`);
11298
+ lines.push(` body=${bodyVarName},`);
11299
+ if (bodyHasParams) lines.push(" params=params,");
11300
+ lines.push(` model=${responseModel}, # type: ignore[arg-type] # dispatcher; request only calls from_dict`);
11301
+ if (plan.isIdempotentPost) lines.push(" idempotency_key=idempotency_key,");
11302
+ lines.push(" request_options=request_options,");
11303
+ lines.push(" )");
11304
+ lines.push(" )");
10667
11305
  } else {
10668
11306
  const bodyReturnPrefix = responseModel !== "None" ? "return " : "";
10669
11307
  lines.push(` ${bodyReturnPrefix}${awaitPrefix}self._client.request(`);
@@ -10678,6 +11316,7 @@ function emitMethodBody(lines, op, plan, meta, isAsync, modelImports, listWrappe
10678
11316
  }
10679
11317
  } else {
10680
11318
  const responseModel = plan.responseModelName ? className$5(plan.responseModelName) : "None";
11319
+ const isGetResponseDiscriminator = !!(plan.responseModelName ? ctx.spec.models.find((m) => m.name === plan.responseModelName) : null)?.discriminator;
10681
11320
  const getGroupedParams = collectGroupedParamNames(op);
10682
11321
  const hasGroups = (op.parameterGroups?.length ?? 0) > 0;
10683
11322
  const visibleQueryParams = op.queryParams.filter((p) => !hiddenParams.has(p.name) && !getGroupedParams.has(p.name));
@@ -10720,6 +11359,18 @@ function emitMethodBody(lines, op, plan, meta, isAsync, modelImports, listWrappe
10720
11359
  lines.push(" request_options=request_options,");
10721
11360
  lines.push(" )");
10722
11361
  lines.push(` return [${itemModel}.from_dict(cast(Dict[str, Any], item)) for item in raw]`);
11362
+ } else if (isGetResponseDiscriminator && responseModel !== "None") {
11363
+ const variantType = responseModel + "Variant";
11364
+ lines.push(` return cast(`);
11365
+ lines.push(` ${variantType},`);
11366
+ lines.push(` ${awaitPrefix}self._client.request(`);
11367
+ lines.push(` method="${httpMethod}",`);
11368
+ lines.push(` path=${pathStr},`);
11369
+ if (emittedParams) lines.push(" params=params,");
11370
+ lines.push(` model=${responseModel}, # type: ignore[arg-type] # dispatcher; request only calls from_dict`);
11371
+ lines.push(" request_options=request_options,");
11372
+ lines.push(" )");
11373
+ lines.push(" )");
10723
11374
  } else {
10724
11375
  const returnPrefix = responseModel !== "None" ? "return " : "";
10725
11376
  lines.push(` ${returnPrefix}${awaitPrefix}self._client.request(`);
@@ -10808,6 +11459,9 @@ function generateResources$6(services, ctx) {
10808
11459
  const plan = planOperation(op);
10809
11460
  if (plan.responseModelName) {
10810
11461
  if (!listWrapperNames.has(plan.responseModelName) || !plan.isPaginated) modelImports.add(plan.responseModelName);
11462
+ if (!plan.isPaginated) {
11463
+ if (ctx.spec.models.find((m) => m.name === plan.responseModelName)?.discriminator) modelImports.add(plan.responseModelName + "Variant");
11464
+ }
10811
11465
  }
10812
11466
  if (op.requestBody?.kind === "model") {
10813
11467
  const requestBodyRef = op.requestBody;
@@ -10867,10 +11521,15 @@ function generateResources$6(services, ctx) {
10867
11521
  }
10868
11522
  }
10869
11523
  const localSet = new Set(localModels);
11524
+ const variantToFile = /* @__PURE__ */ new Map();
11525
+ for (const model of ctx.spec.models) if (model.discriminator) variantToFile.set(model.name + "Variant", fileName$2(model.name));
10870
11526
  if (localModels.length > 0) lines.push(`from .models import ${localModels.map((n) => className$5(n)).join(", ")}`);
10871
11527
  for (const [csDir, names] of [...crossServiceModels].sort()) {
10872
11528
  const unique = names.filter((n) => !localSet.has(n));
10873
- for (const n of unique) lines.push(`from ${ctx.namespace}.${dirToModule(csDir)}.models.${fileName$2(n)} import ${className$5(n)}`);
11529
+ for (const n of unique) {
11530
+ const filePath = variantToFile.get(n) ?? fileName$2(n);
11531
+ lines.push(`from ${ctx.namespace}.${dirToModule(csDir)}.models.${filePath} import ${className$5(n)}`);
11532
+ }
10874
11533
  }
10875
11534
  const enumToServiceMap = placement.enumToService;
10876
11535
  const localEnums = [];
@@ -11101,11 +11760,6 @@ const NON_SPEC_SERVICES = [
11101
11760
  description: "Passwordless (magic-link) session endpoints, not yet in the OpenAPI spec.",
11102
11761
  hasClientAccessor: true
11103
11762
  },
11104
- {
11105
- id: "vault",
11106
- description: "Vault KV storage, key operations, and client-side AES-GCM encrypt/decrypt.",
11107
- hasClientAccessor: true
11108
- },
11109
11763
  {
11110
11764
  id: "webhook_verification",
11111
11765
  description: "Webhook signature verification and event deserialization (H01/H02)."
@@ -11134,14 +11788,6 @@ const PYTHON_NON_SPEC_WIRING = {
11134
11788
  ctorArg: "self",
11135
11789
  docstring: "Passwordless authentication sessions."
11136
11790
  },
11137
- vault: {
11138
- importLine: "from .vault import AsyncVault, Vault",
11139
- prop: "vault",
11140
- syncClass: "Vault",
11141
- asyncClass: "AsyncVault",
11142
- ctorArg: "self",
11143
- docstring: "Vault encryption, key management, and secret storage."
11144
- },
11145
11791
  actions: {
11146
11792
  importLine: "from .actions import Actions, AsyncActions",
11147
11793
  prop: "actions",
@@ -11469,16 +12115,14 @@ function generateModelFixture$4(model, modelMap, enumMap) {
11469
12115
  });
11470
12116
  for (const field of deduplicatedFields) {
11471
12117
  const wireName = field.name;
11472
- if (field.example !== void 0) fixture[wireName] = field.example;
12118
+ if (field.example !== void 0) fixture[wireName] = normalizeDateTimeExample(field, field.example);
11473
12119
  else fixture[wireName] = generateFieldValue$3(field.type, field.name, model.name, modelMap, enumMap);
11474
12120
  }
11475
12121
  if (model.discriminator) {
11476
12122
  const [firstValue, variantName] = Object.entries(model.discriminator.mapping)[0];
11477
12123
  fixture[model.discriminator.property] = firstValue;
11478
12124
  const variantModel = modelMap.get(variantName);
11479
- if (variantModel) {
11480
- for (const field of variantModel.fields) if (!(field.name in fixture)) fixture[field.name] = field.example !== void 0 ? field.example : generateFieldValue$3(field.type, field.name, model.name, modelMap, enumMap);
11481
- }
12125
+ if (variantModel) for (const field of variantModel.fields) fixture[field.name] = field.example !== void 0 ? normalizeDateTimeExample(field, field.example) : generateFieldValue$3(field.type, field.name, variantName, modelMap, enumMap);
11482
12126
  }
11483
12127
  return fixture;
11484
12128
  }
@@ -11505,6 +12149,20 @@ function generateFieldValue$3(ref, fieldName, modelName, modelMap, enumMap) {
11505
12149
  case "map": return { key: generateFieldValue$3(ref.valueType, "value", modelName, modelMap, enumMap) };
11506
12150
  }
11507
12151
  }
12152
+ /**
12153
+ * Normalize date-time example values to millisecond precision so that
12154
+ * round-trip tests (from_dict → to_dict) produce identical output.
12155
+ * Python's _format_datetime always serializes with milliseconds.
12156
+ */
12157
+ function normalizeDateTimeExample(field, value) {
12158
+ if (typeof value !== "string") return value;
12159
+ const ref = field.type.kind === "nullable" ? field.type.inner : field.type;
12160
+ if (ref?.kind !== "primitive" || ref?.type !== "string" || ref?.format !== "date-time") return value;
12161
+ const match = value.match(/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(\.\d+)?(Z|[+-]\d{2}:\d{2})$/);
12162
+ if (!match) return value;
12163
+ const [, prefix, , suffix] = match;
12164
+ return `${prefix}.000${suffix}`;
12165
+ }
11508
12166
  function generatePrimitiveValue$3(type, format, name, modelName) {
11509
12167
  switch (type) {
11510
12168
  case "string":
@@ -11609,7 +12267,22 @@ function generateServiceTest$2(service, spec, ctx, accessPaths, resolvedOps) {
11609
12267
  const enumImports = /* @__PURE__ */ new Set();
11610
12268
  for (const op of service.operations) {
11611
12269
  const plan = planOperation(op);
11612
- if (plan.responseModelName) modelImports.add(plan.responseModelName);
12270
+ if (plan.responseModelName) {
12271
+ modelImports.add(plan.responseModelName);
12272
+ if (!plan.isPaginated) {
12273
+ const resolvedVariantClass = resolvePaginatedItemClass(plan.responseModelName, spec);
12274
+ if (resolvedVariantClass && resolvedVariantClass !== className$5(plan.responseModelName)) {
12275
+ const responseModel = spec.models.find((m) => m.name === plan.responseModelName);
12276
+ const disc = responseModel && responseModel.discriminator;
12277
+ if (disc) {
12278
+ for (const variantName of Object.values(disc.mapping)) if (className$5(variantName) === resolvedVariantClass) {
12279
+ modelImports.add(variantName);
12280
+ break;
12281
+ }
12282
+ }
12283
+ }
12284
+ }
12285
+ }
11613
12286
  if (op.pagination?.itemType.kind === "model") {
11614
12287
  modelImports.add(op.pagination.itemType.name);
11615
12288
  let paginationItemName = op.pagination.itemType.name;
@@ -11652,10 +12325,12 @@ function generateServiceTest$2(service, spec, ctx, accessPaths, resolvedOps) {
11652
12325
  if (param.type.kind === "nullable" && param.type.inner.kind === "enum") enumImports.add(param.type.inner.name);
11653
12326
  }
11654
12327
  }
12328
+ const nonPaginatedRefs = collectNonPaginatedResponseModelNames(spec.services);
11655
12329
  const actualImports = [...modelImports].filter((name) => {
11656
12330
  const model = spec.models.find((m) => m.name === name);
11657
12331
  if (!model) return true;
11658
- return !isListWrapperModel(model);
12332
+ if (isListWrapperModel(model) && !nonPaginatedRefs.has(name)) return false;
12333
+ return true;
11659
12334
  });
11660
12335
  const placement = computeSchemaPlacement(spec, ctx);
11661
12336
  const modelToServiceMap = placement.modelToService;
@@ -11794,14 +12469,22 @@ function generateServiceTest$2(service, spec, ctx, accessPaths, resolvedOps) {
11794
12469
  const modelName = plan.responseModelName;
11795
12470
  const fixtureName = `${fileName$2(modelName)}.json`;
11796
12471
  const modelClass = className$5(modelName);
12472
+ const resolvedClass = resolvePaginatedItemClass(modelName, spec) ?? modelClass;
12473
+ const resolvedModelName = resolvedClass !== modelClass ? (() => {
12474
+ const disc = spec.models.find((m) => m.name === modelName)?.discriminator;
12475
+ if (disc) {
12476
+ for (const variantName of Object.values(disc.mapping)) if (className$5(variantName) === resolvedClass) return variantName;
12477
+ }
12478
+ return modelName;
12479
+ })() : modelName;
11797
12480
  lines.push(` def test_${method}(self, workos, httpx_mock):`);
11798
12481
  lines.push(` httpx_mock.add_response(`);
11799
12482
  lines.push(` json=load_fixture("${fixtureName}"),`);
11800
12483
  lines.push(" )");
11801
12484
  const args = buildTestArgs$1(op, spec, hiddenParams);
11802
12485
  lines.push(` result = workos.${propName}.${method}(${args})`);
11803
- lines.push(` assert isinstance(result, ${modelClass})`);
11804
- const assertFields = pickAssertableFields(modelName, spec);
12486
+ lines.push(` assert isinstance(result, ${resolvedClass})`);
12487
+ const assertFields = pickAssertableFields(resolvedModelName, spec);
11805
12488
  for (const af of assertFields) {
11806
12489
  const op_ = af.isBool ? "is" : "==";
11807
12490
  lines.push(` assert result.${af.field} ${op_} ${af.value}`);
@@ -12023,11 +12706,19 @@ function generateServiceTest$2(service, spec, ctx, accessPaths, resolvedOps) {
12023
12706
  const modelName = plan.responseModelName;
12024
12707
  const fixtureName = `${fileName$2(modelName)}.json`;
12025
12708
  const modelClass = className$5(modelName);
12709
+ const asyncResolvedClass = resolvePaginatedItemClass(modelName, spec) ?? modelClass;
12710
+ const asyncResolvedModelName = asyncResolvedClass !== modelClass ? (() => {
12711
+ const disc = spec.models.find((m) => m.name === modelName)?.discriminator;
12712
+ if (disc) {
12713
+ for (const variantName of Object.values(disc.mapping)) if (className$5(variantName) === asyncResolvedClass) return variantName;
12714
+ }
12715
+ return modelName;
12716
+ })() : modelName;
12026
12717
  pushAsyncTestDef(lines, ` async def test_${method}(self, async_workos, httpx_mock):`);
12027
12718
  lines.push(` httpx_mock.add_response(json=load_fixture("${fixtureName}"))`);
12028
12719
  lines.push(` result = await async_workos.${propName}.${method}(${asyncArgs})`);
12029
- lines.push(` assert isinstance(result, ${modelClass})`);
12030
- const assertFields = pickAssertableFields(modelName, spec);
12720
+ lines.push(` assert isinstance(result, ${asyncResolvedClass})`);
12721
+ const assertFields = pickAssertableFields(asyncResolvedModelName, spec);
12031
12722
  for (const af of assertFields) {
12032
12723
  const op_ = af.isBool ? "is" : "==";
12033
12724
  lines.push(` assert result.${af.field} ${op_} ${af.value}`);
@@ -12154,7 +12845,7 @@ function emitWrapperTests$1(lines, resolvedOps, propName, spec, ctx, isAsync) {
12154
12845
  for (const wrapper of r.wrappers) {
12155
12846
  const method = wrapper.name;
12156
12847
  const wrapperParams = resolveWrapperParams(wrapper, ctx);
12157
- const responseType = wrapper.responseModelName ? className$5(wrapper.responseModelName) : null;
12848
+ const resolvedResponseClass = wrapper.responseModelName ? resolvePaginatedItemClass(wrapper.responseModelName, spec) ?? className$5(wrapper.responseModelName) : null;
12158
12849
  const fixtureName = wrapper.responseModelName ? `${fileName$2(wrapper.responseModelName)}.json` : null;
12159
12850
  const argParts = [];
12160
12851
  for (const { paramName, field, isOptional } of wrapperParams) {
@@ -12170,7 +12861,7 @@ function emitWrapperTests$1(lines, resolvedOps, propName, spec, ctx, isAsync) {
12170
12861
  if (fixtureName) {
12171
12862
  lines.push(` httpx_mock.add_response(json=load_fixture("${fixtureName}"))`);
12172
12863
  lines.push(` result = await async_workos.${propName}.${method}(${args})`);
12173
- if (responseType) lines.push(` assert isinstance(result, ${responseType})`);
12864
+ if (resolvedResponseClass) lines.push(` assert isinstance(result, ${resolvedResponseClass})`);
12174
12865
  } else {
12175
12866
  lines.push(" httpx_mock.add_response(json={})");
12176
12867
  lines.push(` await async_workos.${propName}.${method}(${args})`);
@@ -12180,7 +12871,7 @@ function emitWrapperTests$1(lines, resolvedOps, propName, spec, ctx, isAsync) {
12180
12871
  if (fixtureName) {
12181
12872
  lines.push(` httpx_mock.add_response(json=load_fixture("${fixtureName}"))`);
12182
12873
  lines.push(` result = workos.${propName}.${method}(${args})`);
12183
- if (responseType) lines.push(` assert isinstance(result, ${responseType})`);
12874
+ if (resolvedResponseClass) lines.push(` assert isinstance(result, ${resolvedResponseClass})`);
12184
12875
  } else {
12185
12876
  lines.push(" httpx_mock.add_response(json={})");
12186
12877
  lines.push(` workos.${propName}.${method}(${args})`);
@@ -12950,8 +13641,9 @@ function generateModels$5(models, ctx) {
12950
13641
  overwriteExisting: true
12951
13642
  });
12952
13643
  const nonPaginatedRefs = collectNonPaginatedResponseModelNames(ctx.spec.services);
13644
+ const listMetadataNeeded = collectReferencedListMetadataModels(models, nonPaginatedRefs);
12953
13645
  for (const model of models) {
12954
- if (isListMetadataModel(model)) continue;
13646
+ if (isListMetadataModel(model) && !listMetadataNeeded.has(model.name)) continue;
12955
13647
  if (isListWrapperModel(model) && !nonPaginatedRefs.has(model.name)) continue;
12956
13648
  const name = className$4(model.name);
12957
13649
  const lines = [];
@@ -13484,7 +14176,8 @@ function generateMethod$2(lines, op, service, ctx, modelMap, resolvedOp) {
13484
14176
  const method = resolveMethodName$4(op, service, ctx);
13485
14177
  const hiddenParams = new Set([...Object.keys(getOpDefaults(resolvedOp)), ...getOpInferFromClient(resolvedOp)]);
13486
14178
  const isRedirect = isRedirectEndpoint(op, resolvedOp);
13487
- const params = buildMethodParams(op, plan, modelMap, ctx, hiddenParams);
14179
+ const materializeQueryDefaults = !isRedirect;
14180
+ const params = buildMethodParams(op, plan, modelMap, ctx, hiddenParams, { materializeQueryDefaults });
13488
14181
  const returnType = isRedirect ? "string" : getReturnType(plan, ctx);
13489
14182
  const docParts = [];
13490
14183
  if (op.description) docParts.push(op.description);
@@ -13532,7 +14225,7 @@ function generateMethod$2(lines, op, service, ctx, modelMap, resolvedOp) {
13532
14225
  const phpName = fieldName$4(q.name);
13533
14226
  if (seenDocParams.has(phpName)) continue;
13534
14227
  seenDocParams.add(phpName);
13535
- const hasEnumDefault = q.default != null && q.type.kind === "enum";
14228
+ const hasEnumDefault = shouldMaterializeQueryDefault(q, materializeQueryDefaults);
13536
14229
  const nullSuffix = !q.required && !hasEnumDefault && !docType.endsWith("|null") ? "|null" : "";
13537
14230
  const prefix = q.deprecated ? "(deprecated) " : "";
13538
14231
  let desc = q.description ? ` ${prefix}${q.description}` : q.deprecated ? " (deprecated)" : "";
@@ -13563,7 +14256,7 @@ function generateMethod$2(lines, op, service, ctx, modelMap, resolvedOp) {
13563
14256
  const httpMethod = op.httpMethod.toUpperCase();
13564
14257
  const path = buildPathString(op);
13565
14258
  if (isRedirect) {
13566
- const queryLines = buildQueryArray(op, hiddenParams);
14259
+ const queryLines = buildQueryArray(op, hiddenParams, { materializeQueryDefaults });
13567
14260
  const hasDefaults = Object.keys(getOpDefaults(resolvedOp)).length > 0;
13568
14261
  const hasInferred = getOpInferFromClient(resolvedOp).length > 0;
13569
14262
  const hasGroups = (op.parameterGroups?.length ?? 0) > 0;
@@ -13707,7 +14400,7 @@ function generateMethod$2(lines, op, service, ctx, modelMap, resolvedOp) {
13707
14400
  }
13708
14401
  lines.push(" }");
13709
14402
  }
13710
- function buildMethodParams(op, plan, modelMap, ctx, hiddenParams) {
14403
+ function buildMethodParams(op, plan, modelMap, ctx, hiddenParams, opts) {
13711
14404
  const required = [];
13712
14405
  const optional = [];
13713
14406
  const usedNames = /* @__PURE__ */ new Set();
@@ -13755,7 +14448,7 @@ function buildMethodParams(op, plan, modelMap, ctx, hiddenParams) {
13755
14448
  if (usedNames.has(phpName)) continue;
13756
14449
  usedNames.add(phpName);
13757
14450
  if (q.required) required.push(`${phpType} $${phpName}`);
13758
- else if (q.default != null && q.type.kind === "enum") {
14451
+ else if (shouldMaterializeQueryDefault(q, opts?.materializeQueryDefaults ?? true)) {
13759
14452
  const enumType = mapTypeRef$5(q.type, { qualified: true });
13760
14453
  const caseName = toPascalCase(String(q.default));
13761
14454
  optional.push(`${enumType} $${phpName} = ${enumType}::${caseName}`);
@@ -13801,19 +14494,31 @@ function isEnumType(ref) {
13801
14494
  if (ref.kind === "nullable") return isEnumType(ref.inner);
13802
14495
  return false;
13803
14496
  }
13804
- function buildQueryArray(op, hiddenParams) {
14497
+ function isDateTimeType(ref) {
14498
+ if (ref.kind === "primitive" && ref.format === "date-time") return true;
14499
+ if (ref.kind === "nullable") return isDateTimeType(ref.inner);
14500
+ return false;
14501
+ }
14502
+ function buildQueryArray(op, hiddenParams, opts) {
13805
14503
  const hidden = hiddenParams ?? /* @__PURE__ */ new Set();
13806
14504
  const groupedParams = collectGroupedParamNames(op);
13807
14505
  return op.queryParams.filter((q) => !hidden.has(q.name) && !groupedParams.has(q.name)).map((q) => {
13808
14506
  const phpName = fieldName$4(q.name);
13809
14507
  if (isEnumType(q.type)) {
13810
- const hasEnumDefault = q.default != null && q.type.kind === "enum";
14508
+ const hasEnumDefault = shouldMaterializeQueryDefault(q, opts?.materializeQueryDefaults ?? true);
13811
14509
  const nullsafe = q.required || hasEnumDefault ? "" : "?";
13812
14510
  return `'${q.name}' => $${phpName}${nullsafe}->value,`;
13813
14511
  }
14512
+ if (isDateTimeType(q.type)) {
14513
+ const nullsafe = q.required ? "" : "?";
14514
+ return `'${q.name}' => $${phpName}${nullsafe}->format(\\DateTimeInterface::RFC3339_EXTENDED),`;
14515
+ }
13814
14516
  return `'${q.name}' => $${phpName},`;
13815
14517
  });
13816
14518
  }
14519
+ function shouldMaterializeQueryDefault(param, enabled) {
14520
+ return enabled && param.default != null && param.type.kind === "enum";
14521
+ }
13817
14522
  function phpLiteral(value) {
13818
14523
  if (typeof value === "string") return `'${value}'`;
13819
14524
  if (typeof value === "number") return String(value);
@@ -14005,8 +14710,10 @@ function generateFixtures$3(spec) {
14005
14710
  const modelMap = new Map(spec.models.map((m) => [m.name, m]));
14006
14711
  const enumMap = new Map(spec.enums.map((e) => [e.name, e]));
14007
14712
  const files = [];
14713
+ const nonPaginatedRefs = collectNonPaginatedResponseModelNames(spec.services);
14714
+ const listMetadataNeeded = collectReferencedListMetadataModels(spec.models, nonPaginatedRefs);
14008
14715
  for (const model of spec.models) {
14009
- if (isListMetadataModel(model)) continue;
14716
+ if (isListMetadataModel(model) && !listMetadataNeeded.has(model.name)) continue;
14010
14717
  if (isListWrapperModel(model)) continue;
14011
14718
  const fixture = generateModelFixture$3(model, modelMap, enumMap);
14012
14719
  files.push({
@@ -14351,13 +15058,15 @@ function buildTestArgs(op, ctx, opts) {
14351
15058
  }
14352
15059
  function generateTestValue(ref, ctx) {
14353
15060
  switch (ref.kind) {
14354
- case "primitive": switch (ref.type) {
14355
- case "string": return "'test_value'";
14356
- case "integer": return "1";
14357
- case "number": return "1.0";
14358
- case "boolean": return "true";
14359
- default: return "'test_value'";
14360
- }
15061
+ case "primitive":
15062
+ if (ref.format === "date-time") return "new \\DateTimeImmutable('2023-01-01T00:00:00Z')";
15063
+ switch (ref.type) {
15064
+ case "string": return "'test_value'";
15065
+ case "integer": return "1";
15066
+ case "number": return "1.0";
15067
+ case "boolean": return "true";
15068
+ default: return "'test_value'";
15069
+ }
14361
15070
  case "enum":
14362
15071
  if (ctx && ref.name) {
14363
15072
  const e = ctx.spec.enums.find((en) => en.name === ref.name);
@@ -14444,7 +15153,8 @@ function emitQueryAssertions(lines, op, ctx, hidden) {
14444
15153
  if (innerType.kind === "enum" && innerType.name) {
14445
15154
  const e = ctx.spec.enums.find((en) => en.name === innerType.name);
14446
15155
  if (e && e.values.length > 0) lines.push(` $this->assertSame('${e.values[0].value}', $query['${q.name}']);`);
14447
- } else if (innerType.kind === "primitive") switch (innerType.type) {
15156
+ } else if (innerType.kind === "primitive") if (innerType.format === "date-time") lines.push(` $this->assertArrayHasKey('${q.name}', $query);`);
15157
+ else switch (innerType.type) {
14448
15158
  case "string":
14449
15159
  lines.push(` $this->assertSame('test_value', $query['${q.name}']);`);
14450
15160
  break;
@@ -15154,6 +15864,8 @@ function generateEnums$4(enums, ctx) {
15154
15864
  content: lines.join("\n"),
15155
15865
  overwriteExisting: true
15156
15866
  });
15867
+ const eventConstantsFile = generateEventConstantsFile(enums);
15868
+ if (eventConstantsFile) files.push(eventConstantsFile);
15157
15869
  return files;
15158
15870
  }
15159
15871
  /** Known acronyms to preserve as single tokens during humanization. */
@@ -15214,6 +15926,56 @@ function collectEnumAliasOf$2(enums) {
15214
15926
  }
15215
15927
  return aliasOf;
15216
15928
  }
15929
+ function generateEventConstantsFile(enums) {
15930
+ const enumDef = findWebhookEventEnum(enums);
15931
+ if (!enumDef) return null;
15932
+ const lines = [];
15933
+ lines.push("package events");
15934
+ lines.push("");
15935
+ lines.push("// Event is a WorkOS event type.");
15936
+ lines.push("type Event string");
15937
+ lines.push("");
15938
+ lines.push("const (");
15939
+ const seenValues = /* @__PURE__ */ new Set();
15940
+ const usedNames = /* @__PURE__ */ new Set();
15941
+ for (const value of enumDef.values) {
15942
+ const valueStr = String(value.value);
15943
+ if (seenValues.has(valueStr)) continue;
15944
+ seenValues.add(valueStr);
15945
+ const constName = uniqueEventConstantName(valueStr, usedNames);
15946
+ usedNames.add(constName);
15947
+ if (value.description) lines.push(`\t// ${constName} is ${value.description}.`);
15948
+ if (value.deprecated) {
15949
+ if (value.description) lines.push(" //");
15950
+ lines.push(" // Deprecated: this value is deprecated.");
15951
+ }
15952
+ lines.push(`\t${constName} = "${escapeGoString$1(valueStr)}"`);
15953
+ }
15954
+ lines.push(")");
15955
+ lines.push("");
15956
+ return {
15957
+ path: "pkg/events/events.go",
15958
+ content: lines.join("\n"),
15959
+ overwriteExisting: true
15960
+ };
15961
+ }
15962
+ function findWebhookEventEnum(enums) {
15963
+ return enums.find((enumDef) => enumDef.name === "CreateWebhookEndpointEvents") ?? enums.find((enumDef) => isWebhookEventEnumName(enumDef.name) && enumDef.values.length > 0 && enumDef.values.every((value) => typeof value.value === "string" && value.value.includes("."))) ?? null;
15964
+ }
15965
+ function isWebhookEventEnumName(name) {
15966
+ const normalized = name.toLowerCase();
15967
+ return normalized.includes("webhook") && normalized.includes("event");
15968
+ }
15969
+ function uniqueEventConstantName(value, usedNames) {
15970
+ const base = className$3(value);
15971
+ if (!usedNames.has(base)) return base;
15972
+ let suffix = 2;
15973
+ while (usedNames.has(`${base}${suffix}`)) suffix++;
15974
+ return `${base}${suffix}`;
15975
+ }
15976
+ function escapeGoString$1(value) {
15977
+ return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
15978
+ }
15217
15979
  //#endregion
15218
15980
  //#region src/go/wrappers.ts
15219
15981
  /**
@@ -16262,7 +17024,7 @@ function generateClient$4(spec, ctx) {
16262
17024
  return [generateWorkOSFile(spec, ctx)];
16263
17025
  }
16264
17026
  /**
16265
- * Non-spec services marked with `hasClientAccessor: true` (passwordless, vault)
17027
+ * Non-spec services marked with `hasClientAccessor: true` (e.g. passwordless)
16266
17028
  * are included in the generated Client struct, constructor, and accessor methods
16267
17029
  * — identical to spec-driven services. Their service type (e.g. PasswordlessService)
16268
17030
  * is defined in a hand-written @oagen-ignore-file, but the Client wiring is generated.
@@ -20031,12 +20793,19 @@ function generateEnums$2(enums, _ctx) {
20031
20793
  hashGroups.get(hash).push(enumDef);
20032
20794
  }
20033
20795
  const aliasOf = /* @__PURE__ */ new Map();
20796
+ const sharedSortEmitters = /* @__PURE__ */ new Set();
20034
20797
  for (const [, group] of hashGroups) {
20035
- if (group.length <= 1) continue;
20798
+ if (group.length <= 1) {
20799
+ if (group.length === 1 && isSharedSortOrderEnum(group[0])) {
20800
+ enumCanonicalMap.set(group[0].name, "SortOrder");
20801
+ sharedSortEmitters.add(group[0].name);
20802
+ }
20803
+ continue;
20804
+ }
20036
20805
  if (group.every(isSharedSortOrderEnum)) {
20037
20806
  const [canonical, ...rest] = [...group].sort((a, b) => a.name.localeCompare(b.name));
20038
- enumCanonicalMap.set(canonical.name, canonical.name);
20039
- for (const enumDef of rest) enumCanonicalMap.set(enumDef.name, "SortOrder");
20807
+ sharedSortEmitters.add(canonical.name);
20808
+ for (const enumDef of [canonical, ...rest]) enumCanonicalMap.set(enumDef.name, "SortOrder");
20040
20809
  continue;
20041
20810
  }
20042
20811
  const sorted = [...group].sort((a, b) => className$1(a.name).length - className$1(b.name).length || className$1(a.name).localeCompare(className$1(b.name)));
@@ -20050,7 +20819,7 @@ function generateEnums$2(enums, _ctx) {
20050
20819
  for (const enumDef of enums) {
20051
20820
  if (enumDef.values.length === 0) continue;
20052
20821
  const typeName = canonicalEnumTypeName(enumDef);
20053
- const canonicalName = isSharedSortOrderEnum(enumDef) && enumCanonicalMap.get(enumDef.name) === enumDef.name ? void 0 : aliasOf.get(enumDef.name) ?? enumCanonicalMap.get(enumDef.name);
20822
+ const canonicalName = sharedSortEmitters.has(enumDef.name) ? void 0 : aliasOf.get(enumDef.name) ?? enumCanonicalMap.get(enumDef.name);
20054
20823
  if (canonicalName) {
20055
20824
  const canonicalType = className$1(canonicalName);
20056
20825
  if (typeName === canonicalType) continue;
@@ -24298,11 +25067,6 @@ const NON_SPEC_ACCESSORS = {
24298
25067
  className: "Passwordless",
24299
25068
  ctorArg: "self"
24300
25069
  },
24301
- vault: {
24302
- prop: "vault",
24303
- className: "Vault",
24304
- ctorArg: "self"
24305
- },
24306
25070
  actions: {
24307
25071
  prop: "actions",
24308
25072
  className: "Actions",
@@ -26119,6 +26883,7 @@ function classifyGroup(group, op) {
26119
26883
  function renderParamsStruct(name, op, resolved, registry, ctx, groupEmitter) {
26120
26884
  const bodyRequired = isBodyRequired(op);
26121
26885
  const hidden = new Set([...Object.keys(resolved.defaults ?? {}), ...resolved.inferFromClient ?? []]);
26886
+ const materializeSpecDefaults = !resolved.urlBuilder;
26122
26887
  const queryGroupParamNames = /* @__PURE__ */ new Set();
26123
26888
  const bodyGroupParamNames = /* @__PURE__ */ new Set();
26124
26889
  const queryGroupFields = [];
@@ -26155,7 +26920,7 @@ function renderParamsStruct(name, op, resolved, registry, ctx, groupEmitter) {
26155
26920
  });
26156
26921
  if (!p.required && !rust.startsWith("Option<")) rust = makeOptional(rust);
26157
26922
  rust = applySecretRedaction(rust, p.name);
26158
- const defaultExpr = p.default != null ? rustDefaultExpr(p.default, p.type, rust.startsWith("Option<"), ctx) : null;
26923
+ const defaultExpr = materializeSpecDefaults && p.default != null ? rustDefaultExpr(p.default, p.type, rust.startsWith("Option<"), ctx) : null;
26159
26924
  const desc = p.description?.trim();
26160
26925
  if (desc) for (const c of paramDocComment(desc)) fieldLines.push(` ${c}`);
26161
26926
  if (p.default != null) {
@@ -26788,7 +27553,7 @@ function renderResourcesBarrel(exports) {
26788
27553
  }
26789
27554
  unique.sort((a, b) => a.module.localeCompare(b.module));
26790
27555
  const lines = [];
26791
- for (const { module } of unique) lines.push(`mod ${module};`);
27556
+ for (const { module } of unique) lines.push(`pub mod ${module};`);
26792
27557
  lines.push("");
26793
27558
  for (const { module, struct } of unique) lines.push(`pub use ${module}::${struct};`);
26794
27559
  return lines.join("\n") + "\n";
@@ -27650,4 +28415,4 @@ const workosEmittersPlugin = {
27650
28415
  //#endregion
27651
28416
  export { pythonEmitter as _, rustExtractor as a, pythonExtractor as c, rustEmitter as d, rubyEmitter as f, phpEmitter as g, goEmitter as h, kotlinExtractor as i, rubyExtractor as l, dotnetEmitter as m, elixirExtractor as n, goExtractor as o, kotlinEmitter as p, dotnetExtractor as r, phpExtractor as s, workosEmittersPlugin as t, nodeExtractor as u, nodeEmitter as v };
27652
28417
 
27653
- //# sourceMappingURL=plugin-BGVaMGqe.mjs.map
28418
+ //# sourceMappingURL=plugin-C2Hp2Vs2.mjs.map