@workos/oagen-emitters 0.14.4 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +12 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{plugin-BGVaMGqe.mjs → plugin-CO4RFgAW.mjs} +959 -251
- package/dist/plugin-CO4RFgAW.mjs.map +1 -0
- package/dist/plugin.mjs +1 -1
- package/package.json +7 -7
- package/renovate.json +1 -61
- package/src/go/client.ts +1 -1
- package/src/go/enums.ts +77 -0
- package/src/kotlin/enums.ts +11 -4
- package/src/node/client.ts +119 -2
- package/src/node/discriminated-models.ts +8 -0
- package/src/node/field-plan.ts +64 -8
- package/src/node/index.ts +59 -3
- package/src/node/models.ts +73 -30
- package/src/node/naming.ts +14 -1
- package/src/node/node-overrides.ts +4 -37
- package/src/node/options.ts +29 -1
- package/src/node/resources.ts +533 -83
- package/src/node/tests.ts +108 -7
- package/src/php/fixtures.ts +4 -1
- package/src/php/models.ts +3 -1
- package/src/php/resources.ts +40 -11
- package/src/php/tests.ts +22 -12
- package/src/python/client.ts +0 -8
- package/src/python/enums.ts +41 -15
- package/src/python/fixtures.ts +23 -7
- package/src/python/models.ts +26 -5
- package/src/python/resources.ts +71 -3
- package/src/python/tests.ts +70 -12
- package/src/python/wrappers.ts +25 -4
- package/src/ruby/client.ts +0 -1
- package/src/rust/resources.ts +10 -7
- package/src/shared/non-spec-services.ts +0 -5
- package/test/go/enums.test.ts +24 -0
- package/test/node/resources.test.ts +11 -1
- package/test/node/tests.test.ts +3 -3
- package/test/php/client.test.ts +0 -1
- package/test/php/resources.test.ts +50 -0
- package/test/rust/resources.test.ts +9 -0
- 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
|
|
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 (!
|
|
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
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
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,10 +4730,56 @@ 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:
|
|
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
4784
|
return returnType?.match(/\bAutoPaginatable<\s*([A-Za-z_$][\w$]*)/)?.[1];
|
|
4654
4785
|
}
|
|
@@ -4738,43 +4869,83 @@ function deduplicateMethodNames(plans, _ctx) {
|
|
|
4738
4869
|
}
|
|
4739
4870
|
}
|
|
4740
4871
|
/**
|
|
4741
|
-
* Emit one interface file per
|
|
4742
|
-
*
|
|
4743
|
-
*
|
|
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.
|
|
4872
|
+
* Emit one interface/type file per generated options-object operation.
|
|
4873
|
+
* Placing the options type under `interfaces/` lets the per-service barrel
|
|
4874
|
+
* pick it up via `export * from './interfaces'`.
|
|
4747
4875
|
*/
|
|
4748
|
-
function
|
|
4876
|
+
function generateOptionsInterfaces(service, ctx, specEnumNames) {
|
|
4749
4877
|
const files = [];
|
|
4750
|
-
const resolvedName = resolveResourceClassName$3(service, ctx);
|
|
4751
4878
|
const serviceDir = resolveResourceDir(service, ctx);
|
|
4879
|
+
const resolvedLookup = buildResolvedLookup(ctx);
|
|
4752
4880
|
const plans = service.operations.map((op) => ({
|
|
4753
4881
|
op,
|
|
4754
4882
|
plan: planOperation(op),
|
|
4755
4883
|
method: resolveMethodName$6(op, service, ctx)
|
|
4756
4884
|
}));
|
|
4757
4885
|
for (const { op, plan, method } of plans) {
|
|
4758
|
-
|
|
4759
|
-
const
|
|
4760
|
-
if (
|
|
4761
|
-
|
|
4762
|
-
const
|
|
4886
|
+
const resolvedOp = lookupResolved(op, resolvedLookup);
|
|
4887
|
+
const optionInfo = optionsObjectInfo(service, method, op, plan, ctx, baselineMethodFor$1(service, method, ctx), resolvedOp);
|
|
4888
|
+
if (!optionInfo?.generated) continue;
|
|
4889
|
+
if (baselineTypeSourceFile(ctx, optionInfo.type)) continue;
|
|
4890
|
+
const optionsName = optionInfo.type;
|
|
4891
|
+
const optionFileStem = `${fileName$3(optionsName)}.interface`;
|
|
4892
|
+
const filePath = `src/${serviceDir}/interfaces/${optionFileStem}.ts`;
|
|
4893
|
+
if (!liveSurfaceHasFile(filePath) || existingInterfaceBarrelExports(ctx, serviceDir, optionFileStem)) recordGeneratedOptionInterface(ctx, serviceDir, optionFileStem, optionsName);
|
|
4894
|
+
const optEnums = /* @__PURE__ */ new Set();
|
|
4895
|
+
const optModels = /* @__PURE__ */ new Set();
|
|
4896
|
+
for (const param of [...op.pathParams, ...visibleQueryParamsForOptions(op, plan, resolvedOp)]) collectParamTypeRefs(param.type, optEnums, optModels);
|
|
4897
|
+
const bodyInfo = extractRequestBodyType(op, ctx);
|
|
4898
|
+
if (bodyInfo?.kind === "model") {
|
|
4899
|
+
const bodyModel = ctx.spec.models.find((m) => m.name === bodyInfo.name);
|
|
4900
|
+
if (bodyModel) for (const field of bodyModel.fields) collectParamTypeRefs(field.type, optEnums, optModels);
|
|
4901
|
+
} else if (bodyInfo?.kind === "union") for (const name of bodyInfo.modelNames) optModels.add(name);
|
|
4763
4902
|
const lines = [];
|
|
4764
|
-
lines.push("import type { PaginationOptions } from '../../common/interfaces/pagination-options.interface';");
|
|
4903
|
+
if (plan.isPaginated) lines.push("import type { PaginationOptions } from '../../common/interfaces/pagination-options.interface';");
|
|
4904
|
+
const { modelToService, resolveDir } = createServiceDirResolver(ctx.spec.models, ctx.spec.services, ctx);
|
|
4905
|
+
if (optEnums.size > 0) {
|
|
4906
|
+
const enumToService = assignEnumsToServices(ctx.spec.enums, ctx.spec.services, ctx.spec.models, ctx);
|
|
4907
|
+
for (const name of optEnums) {
|
|
4908
|
+
if (!specEnumNames.has(name)) continue;
|
|
4909
|
+
if (isInlineEnum(name)) continue;
|
|
4910
|
+
const enumDir = resolveDir(enumToService.get(name));
|
|
4911
|
+
const relPath = enumDir === serviceDir ? `./${fileName$3(name)}.interface` : `../../${enumDir}/interfaces/${fileName$3(name)}.interface`;
|
|
4912
|
+
lines.push(`import type { ${name} } from '${relPath}';`);
|
|
4913
|
+
}
|
|
4914
|
+
}
|
|
4915
|
+
for (const name of optModels) {
|
|
4916
|
+
const modelDir = resolveDir(modelToService.get(name));
|
|
4917
|
+
const relPath = modelDir === serviceDir ? `./${fileName$3(name)}.interface` : `../../${modelDir}/interfaces/${fileName$3(name)}.interface`;
|
|
4918
|
+
lines.push(`import type { ${resolveInterfaceName(name, ctx)} } from '${relPath}';`);
|
|
4919
|
+
}
|
|
4765
4920
|
lines.push("");
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
const opt = !
|
|
4769
|
-
if (
|
|
4921
|
+
const headerParts = [];
|
|
4922
|
+
const pushField = (name, required, type, description, deprecated) => {
|
|
4923
|
+
const opt = !required ? "?" : "";
|
|
4924
|
+
if (description || deprecated) {
|
|
4770
4925
|
const parts = [];
|
|
4771
|
-
if (
|
|
4772
|
-
if (
|
|
4773
|
-
|
|
4926
|
+
if (description) parts.push(description);
|
|
4927
|
+
if (deprecated) parts.push("@deprecated");
|
|
4928
|
+
headerParts.push(...docComment$2(parts.join("\n"), 2));
|
|
4774
4929
|
}
|
|
4775
|
-
|
|
4930
|
+
headerParts.push(` ${name}${opt}: ${type};`);
|
|
4931
|
+
};
|
|
4932
|
+
for (const param of op.pathParams) pushField(fieldName$6(param.name), true, mapParamType(param.type, specEnumNames), param.description, param.deprecated);
|
|
4933
|
+
for (const param of visibleQueryParamsForOptions(op, plan, resolvedOp)) pushField(fieldName$6(param.name), param.required, mapParamType(param.type, specEnumNames), param.description, param.deprecated);
|
|
4934
|
+
if (bodyInfo?.kind === "model") {
|
|
4935
|
+
const bodyModel = ctx.spec.models.find((m) => m.name === bodyInfo.name);
|
|
4936
|
+
if (bodyModel) for (const field of bodyModel.fields) pushField(fieldName$6(field.name), field.required, mapParamType(field.type, specEnumNames), field.description, field.deprecated);
|
|
4937
|
+
lines.push(`export interface ${optionsName}${plan.isPaginated ? " extends PaginationOptions" : ""} {`);
|
|
4938
|
+
lines.push(...headerParts);
|
|
4939
|
+
lines.push("}");
|
|
4940
|
+
} else if (bodyInfo?.kind === "union") {
|
|
4941
|
+
const baseType = headerParts.length > 0 ? `{\n${headerParts.join("\n")}\n}` : "{}";
|
|
4942
|
+
lines.push(`export type ${optionsName} = ${baseType} & (${bodyInfo.typeStr});`);
|
|
4943
|
+
} else if (plan.isPaginated && headerParts.length === 0) lines.push(`export type ${optionsName} = PaginationOptions;`);
|
|
4944
|
+
else {
|
|
4945
|
+
lines.push(`export interface ${optionsName}${plan.isPaginated ? " extends PaginationOptions" : ""} {`);
|
|
4946
|
+
lines.push(...headerParts);
|
|
4947
|
+
lines.push("}");
|
|
4776
4948
|
}
|
|
4777
|
-
lines.push("}");
|
|
4778
4949
|
files.push({
|
|
4779
4950
|
path: filePath,
|
|
4780
4951
|
content: lines.join("\n")
|
|
@@ -4819,7 +4990,7 @@ function generateResources$7(services, ctx) {
|
|
|
4819
4990
|
}
|
|
4820
4991
|
for (const service of mergedServices) {
|
|
4821
4992
|
if (!isNodeOwnedService(ctx, service.name, resolveResourceClassName$3(service, ctx)) && isServiceCoveredByExisting(service, ctx) && !hasMethodsAbsentFromBaseline(service, ctx)) continue;
|
|
4822
|
-
files.push(...
|
|
4993
|
+
files.push(...generateOptionsInterfaces(service, ctx, topLevelEnumNames));
|
|
4823
4994
|
}
|
|
4824
4995
|
return files;
|
|
4825
4996
|
}
|
|
@@ -4844,27 +5015,40 @@ function generateResourceClass(service, ctx) {
|
|
|
4844
5015
|
return (httpKeyOrder.get(aKey) ?? Number.MAX_SAFE_INTEGER) - (httpKeyOrder.get(bKey) ?? Number.MAX_SAFE_INTEGER);
|
|
4845
5016
|
});
|
|
4846
5017
|
}
|
|
5018
|
+
const ignoredMethodNames = ignoredResourceMethodNames(ctx, resourcePath);
|
|
4847
5019
|
const baselineMethodNames = new Set(Object.keys(ctx.apiSurface?.classes?.[serviceClass]?.methods ?? {}));
|
|
4848
5020
|
const planCountBeforeFilter = plans.length;
|
|
5021
|
+
if (ignoredMethodNames.size > 0) plans = plans.filter((p) => !ignoredMethodNames.has(p.method));
|
|
4849
5022
|
if (!isNodeOwnedService(ctx, service.name, serviceClass) && baselineMethodNames.size > 0) plans = plans.filter((p) => !baselineMethodNames.has(p.method));
|
|
4850
5023
|
const filteredOut = planCountBeforeFilter - plans.length;
|
|
4851
5024
|
if (plans.length === 0 && filteredOut > 0) return null;
|
|
4852
5025
|
const hasPaginated = plans.some((p) => p.plan.isPaginated);
|
|
4853
|
-
const
|
|
5026
|
+
const resolvedLookup = buildResolvedLookup(ctx);
|
|
5027
|
+
const needsPaginationOptionsImport = plans.some((p) => {
|
|
5028
|
+
const baseline = baselineMethodFor$1(service, p.method, ctx);
|
|
5029
|
+
const optionInfo = optionsObjectInfo(service, p.method, p.op, p.plan, ctx, baseline, lookupResolved(p.op, resolvedLookup));
|
|
5030
|
+
const needsWireSerializer = p.op.queryParams.filter((param) => !PAGINATION_PARAM_NAMES.has(param.name)).some((param) => fieldName$6(param.name) !== wireFieldName(param.name));
|
|
5031
|
+
return p.plan.isPaginated && (needsWireSerializer || !optionInfo || /\bPaginationOptions\b/.test(preferredBaselineReturnType(ctx, baselineMethodFor$1(service, p.method, ctx)?.returnType) ?? ""));
|
|
5032
|
+
});
|
|
4854
5033
|
const modelMap = new Map(ctx.spec.models.map((m) => [m.name, m]));
|
|
4855
5034
|
const responseModels = /* @__PURE__ */ new Set();
|
|
5035
|
+
const responseModelsForSignature = /* @__PURE__ */ new Set();
|
|
4856
5036
|
const requestModels = /* @__PURE__ */ new Set();
|
|
4857
5037
|
const requestModelsForSignature = /* @__PURE__ */ new Set();
|
|
4858
5038
|
const paramEnums = /* @__PURE__ */ new Set();
|
|
4859
5039
|
const paramModels = /* @__PURE__ */ new Set();
|
|
4860
5040
|
const optionObjectTypes = /* @__PURE__ */ new Set();
|
|
5041
|
+
const returnTypeImports = /* @__PURE__ */ new Set();
|
|
4861
5042
|
const baselineResponseTypes = /* @__PURE__ */ new Set();
|
|
4862
|
-
for (const { op, plan } of plans) {
|
|
4863
|
-
const baselineMethod = baselineMethodFor$1(service,
|
|
4864
|
-
const existingOptions =
|
|
5043
|
+
for (const { op, plan, method } of plans) {
|
|
5044
|
+
const baselineMethod = baselineMethodFor$1(service, method, ctx);
|
|
5045
|
+
const existingOptions = optionsObjectInfo(service, method, op, plan, ctx, baselineMethod, lookupResolved(op, resolvedLookup));
|
|
4865
5046
|
if (existingOptions) optionObjectTypes.add(existingOptions.type);
|
|
4866
|
-
const
|
|
4867
|
-
|
|
5047
|
+
for (const typeName of operationOverrideFor$1(ctx, op)?.returnTypeImports ?? []) returnTypeImports.add(typeName);
|
|
5048
|
+
if (!existingOptions) {
|
|
5049
|
+
const queryParams = plan.isPaginated ? [] : op.queryParams;
|
|
5050
|
+
for (const param of [...queryParams, ...op.pathParams]) collectParamTypeRefs(param.type, paramEnums, paramModels);
|
|
5051
|
+
}
|
|
4868
5052
|
const baselinePaginatedItemType = existingOptions ? preferredBaselineTypeName(ctx, autoPaginatableItemType$1(baselineMethod?.returnType)) : void 0;
|
|
4869
5053
|
if (plan.isPaginated && baselinePaginatedItemType) baselineResponseTypes.add(baselinePaginatedItemType);
|
|
4870
5054
|
else if (plan.isPaginated && op.pagination?.itemType.kind === "model") {
|
|
@@ -4875,7 +5059,13 @@ function generateResourceClass(service, ctx) {
|
|
|
4875
5059
|
if (unwrapped) itemName = unwrapped.name;
|
|
4876
5060
|
}
|
|
4877
5061
|
responseModels.add(itemName);
|
|
4878
|
-
|
|
5062
|
+
responseModelsForSignature.add(itemName);
|
|
5063
|
+
} else if (plan.responseModelName) {
|
|
5064
|
+
responseModels.add(plan.responseModelName);
|
|
5065
|
+
const override = operationOverrideFor$1(ctx, op);
|
|
5066
|
+
const resolvedResponseName = resolveInterfaceName(plan.responseModelName, ctx);
|
|
5067
|
+
if (!override?.returnType || override.returnType.includes(resolvedResponseName)) responseModelsForSignature.add(plan.responseModelName);
|
|
5068
|
+
}
|
|
4879
5069
|
const bodyInfo = extractRequestBodyType(op, ctx);
|
|
4880
5070
|
if (bodyInfo?.kind === "model") {
|
|
4881
5071
|
requestModels.add(bodyInfo.name);
|
|
@@ -4889,7 +5079,6 @@ function generateResourceClass(service, ctx) {
|
|
|
4889
5079
|
if (!existingOptions) requestModelsForSignature.add(name);
|
|
4890
5080
|
}
|
|
4891
5081
|
}
|
|
4892
|
-
const resolvedLookup = buildResolvedLookup(ctx);
|
|
4893
5082
|
for (const { op } of plans) {
|
|
4894
5083
|
const resolved = lookupResolved(op, resolvedLookup);
|
|
4895
5084
|
if (resolved) {
|
|
@@ -4898,7 +5087,7 @@ function generateResourceClass(service, ctx) {
|
|
|
4898
5087
|
}
|
|
4899
5088
|
}
|
|
4900
5089
|
const allModels = new Set([
|
|
4901
|
-
...
|
|
5090
|
+
...responseModelsForSignature,
|
|
4902
5091
|
...requestModelsForSignature,
|
|
4903
5092
|
...paramModels
|
|
4904
5093
|
]);
|
|
@@ -4909,12 +5098,8 @@ function generateResourceClass(service, ctx) {
|
|
|
4909
5098
|
lines.push("import { AutoPaginatable } from '../common/utils/pagination';");
|
|
4910
5099
|
lines.push("import { fetchAndDeserialize } from '../common/utils/fetch-and-deserialize';");
|
|
4911
5100
|
}
|
|
4912
|
-
|
|
4913
|
-
|
|
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
|
-
}
|
|
5101
|
+
const shouldEmitVaultCryptoHelpers = serviceClass === "Vault" && !ignoredMethodNames.has("encrypt") && !ignoredMethodNames.has("decrypt");
|
|
5102
|
+
if (shouldEmitVaultCryptoHelpers) lines.push("import { base64ToUint8Array, uint8ArrayToBase64 } from '../common/utils/base64';");
|
|
4918
5103
|
const hasIdempotentPost = plans.some((p) => p.plan.isIdempotentPost);
|
|
4919
5104
|
const hasCustomEncoding = plans.some((p) => p.op.requestBodyEncoding && p.op.requestBodyEncoding !== "json" && p.plan.hasBody);
|
|
4920
5105
|
if (hasIdempotentPost || hasCustomEncoding) lines.push("import type { PostOptions } from '../common/interfaces/post-options.interface';");
|
|
@@ -4922,7 +5107,16 @@ function generateResourceClass(service, ctx) {
|
|
|
4922
5107
|
for (const optionType of optionObjectTypes) {
|
|
4923
5108
|
if (importedTypeNames.has(optionType)) continue;
|
|
4924
5109
|
importedTypeNames.add(optionType);
|
|
4925
|
-
|
|
5110
|
+
const sourceFile = baselineTypeSourceFile(ctx, optionType);
|
|
5111
|
+
const relPath = sourceFile ? relativeImport(resourcePath, sourceFile) : `./interfaces/${fileName$3(optionType)}.interface`;
|
|
5112
|
+
lines.push(`import type { ${optionType} } from '${relPath}';`);
|
|
5113
|
+
}
|
|
5114
|
+
for (const typeName of returnTypeImports) {
|
|
5115
|
+
if (importedTypeNames.has(typeName)) continue;
|
|
5116
|
+
const sourceFile = baselineTypeSourceFile(ctx, typeName) ?? liveSurfaceInterfacePath(typeName);
|
|
5117
|
+
if (!sourceFile) continue;
|
|
5118
|
+
importedTypeNames.add(typeName);
|
|
5119
|
+
lines.push(`import type { ${typeName} } from '${relativeImport(resourcePath, sourceFile)}';`);
|
|
4926
5120
|
}
|
|
4927
5121
|
const { modelToService, resolveDir } = createServiceDirResolver(ctx.spec.models, ctx.spec.services, ctx);
|
|
4928
5122
|
const usedWireTypes = /* @__PURE__ */ new Set();
|
|
@@ -4954,6 +5148,17 @@ function generateResourceClass(service, ctx) {
|
|
|
4954
5148
|
lines.push(`import type { ${wireName} } from '${relPath}';`);
|
|
4955
5149
|
importedTypeNames.add(wireName);
|
|
4956
5150
|
}
|
|
5151
|
+
for (const name of responseModels) {
|
|
5152
|
+
if (allModels.has(name)) continue;
|
|
5153
|
+
const resolved = resolveInterfaceName(name, ctx);
|
|
5154
|
+
if (!usedWireTypes.has(resolved)) continue;
|
|
5155
|
+
const wireName = wireInterfaceName(resolved);
|
|
5156
|
+
if (importedTypeNames.has(wireName)) continue;
|
|
5157
|
+
const modelServiceDir = resolveDir(modelToService.get(name));
|
|
5158
|
+
const relPath = modelServiceDir === serviceDir ? `./interfaces/${fileName$3(name)}.interface` : `../${modelServiceDir}/interfaces/${fileName$3(name)}.interface`;
|
|
5159
|
+
lines.push(`import type { ${wireName} } from '${relPath}';`);
|
|
5160
|
+
importedTypeNames.add(wireName);
|
|
5161
|
+
}
|
|
4957
5162
|
for (const name of baselineResponseTypes) {
|
|
4958
5163
|
if (importedTypeNames.has(name)) continue;
|
|
4959
5164
|
const wireName = wireInterfaceName(name);
|
|
@@ -5017,47 +5222,60 @@ function generateResourceClass(service, ctx) {
|
|
|
5017
5222
|
}
|
|
5018
5223
|
}
|
|
5019
5224
|
lines.push("");
|
|
5020
|
-
for (const { op, plan, method } of plans)
|
|
5021
|
-
const
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
const
|
|
5032
|
-
|
|
5033
|
-
|
|
5225
|
+
for (const { op, plan, method } of plans) {
|
|
5226
|
+
const resolved = lookupResolved(op, resolvedLookup);
|
|
5227
|
+
const optionInfo = optionsObjectInfo(service, method, op, plan, ctx, baselineMethodFor$1(service, method, ctx), resolved);
|
|
5228
|
+
if (plan.isPaginated) {
|
|
5229
|
+
const extraParams = op.queryParams.filter((p) => !PAGINATION_PARAM_NAMES.has(p.name));
|
|
5230
|
+
if (extraParams.length > 0) {
|
|
5231
|
+
const optionsName = optionInfo?.type ?? paginatedOptionsName(method, resolvedName);
|
|
5232
|
+
if (extraParams.some((p) => fieldName$6(p.name) !== wireFieldName(p.name))) {
|
|
5233
|
+
const serializerName = `serialize${optionsName}`;
|
|
5234
|
+
lines.push(`const ${serializerName} = (options: ${optionsName}): PaginationOptions => {`);
|
|
5235
|
+
lines.push(" const wire: Record<string, unknown> = {");
|
|
5236
|
+
for (const p of PAGINATION_PARAM_NAMES) lines.push(` ${p}: options.${p},`);
|
|
5237
|
+
lines.push(" };");
|
|
5238
|
+
for (const param of extraParams) {
|
|
5239
|
+
const camel = fieldName$6(param.name);
|
|
5240
|
+
const snake = wireFieldName(param.name);
|
|
5241
|
+
lines.push(` if (options.${camel} !== undefined) wire.${snake} = options.${camel};`);
|
|
5242
|
+
}
|
|
5243
|
+
lines.push(" return wire as PaginationOptions;");
|
|
5244
|
+
lines.push("};");
|
|
5245
|
+
lines.push("");
|
|
5034
5246
|
}
|
|
5035
|
-
lines.push(" return wire as PaginationOptions;");
|
|
5036
|
-
lines.push("};");
|
|
5037
|
-
lines.push("");
|
|
5038
5247
|
}
|
|
5039
|
-
}
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5248
|
+
} else if (!optionInfo && !plan.isPaginated && !plan.hasBody && !plan.isDelete && op.queryParams.length > 0) {
|
|
5249
|
+
const resolved = lookupResolved(op, resolvedLookup);
|
|
5250
|
+
const opHiddenParams = new Set([...Object.keys(getOpDefaults(resolved)), ...getOpInferFromClient(resolved)]);
|
|
5251
|
+
const visibleParams = op.queryParams.filter((p) => !opHiddenParams.has(p.name));
|
|
5252
|
+
if (visibleParams.length > 0) {
|
|
5253
|
+
const optionsName = toPascalCase(method) + "Options";
|
|
5254
|
+
lines.push(`export interface ${optionsName} {`);
|
|
5255
|
+
for (const param of visibleParams) {
|
|
5256
|
+
const opt = !param.required ? "?" : "";
|
|
5257
|
+
if (param.description || param.deprecated) {
|
|
5258
|
+
const parts = [];
|
|
5259
|
+
if (param.description) parts.push(param.description);
|
|
5260
|
+
if (param.deprecated) parts.push("@deprecated");
|
|
5261
|
+
lines.push(...docComment$2(parts.join("\n"), 2));
|
|
5262
|
+
}
|
|
5263
|
+
lines.push(` ${fieldName$6(param.name)}${opt}: ${mapParamType(param.type, specEnumNames)};`);
|
|
5054
5264
|
}
|
|
5055
|
-
lines.push(
|
|
5265
|
+
lines.push("}");
|
|
5266
|
+
lines.push("");
|
|
5056
5267
|
}
|
|
5057
|
-
lines.push("}");
|
|
5058
|
-
lines.push("");
|
|
5059
5268
|
}
|
|
5060
5269
|
}
|
|
5270
|
+
if (shouldEmitVaultCryptoHelpers) {
|
|
5271
|
+
lines.push("export interface VaultEncryptedData {");
|
|
5272
|
+
lines.push(" ciphertext: string;");
|
|
5273
|
+
lines.push(" encryptedKeys: string;");
|
|
5274
|
+
lines.push(" iv: string;");
|
|
5275
|
+
lines.push(" tag: string;");
|
|
5276
|
+
lines.push("}");
|
|
5277
|
+
lines.push("");
|
|
5278
|
+
}
|
|
5061
5279
|
if (service.description) lines.push(...docComment$2(service.description));
|
|
5062
5280
|
lines.push(`export class ${serviceClass} {`);
|
|
5063
5281
|
lines.push(" constructor(private readonly workos: WorkOS) {}");
|
|
@@ -5067,6 +5285,10 @@ function generateResourceClass(service, ctx) {
|
|
|
5067
5285
|
lines.push(...renderMethod$2(op, plan, method, service, ctx, modelMap, specEnumNames, resolved));
|
|
5068
5286
|
if (resolved?.wrappers && resolved.wrappers.length > 0) lines.push(...generateWrapperMethods$5(resolved, ctx));
|
|
5069
5287
|
}
|
|
5288
|
+
if (shouldEmitVaultCryptoHelpers) {
|
|
5289
|
+
lines.push("");
|
|
5290
|
+
lines.push(...renderVaultCryptoMethods());
|
|
5291
|
+
}
|
|
5070
5292
|
lines.push("}");
|
|
5071
5293
|
return {
|
|
5072
5294
|
path: resourcePath,
|
|
@@ -5074,6 +5296,51 @@ function generateResourceClass(service, ctx) {
|
|
|
5074
5296
|
skipIfExists: true
|
|
5075
5297
|
};
|
|
5076
5298
|
}
|
|
5299
|
+
function renderVaultCryptoMethods() {
|
|
5300
|
+
return [
|
|
5301
|
+
" async encrypt(",
|
|
5302
|
+
" plaintext: string,",
|
|
5303
|
+
" context: Record<string, string>,",
|
|
5304
|
+
" aad?: string,",
|
|
5305
|
+
" ): Promise<VaultEncryptedData> {",
|
|
5306
|
+
" const dataKeyPair = await this.createDataKey({ context });",
|
|
5307
|
+
" const encodedPlaintext = new TextEncoder().encode(plaintext);",
|
|
5308
|
+
" const encodedAad = aad ? new TextEncoder().encode(aad) : undefined;",
|
|
5309
|
+
" const encrypted = await this.workos.getCryptoProvider().encrypt(",
|
|
5310
|
+
" encodedPlaintext,",
|
|
5311
|
+
" base64ToUint8Array(dataKeyPair.dataKey.key),",
|
|
5312
|
+
" undefined,",
|
|
5313
|
+
" encodedAad,",
|
|
5314
|
+
" );",
|
|
5315
|
+
"",
|
|
5316
|
+
" return {",
|
|
5317
|
+
" ciphertext: uint8ArrayToBase64(encrypted.ciphertext),",
|
|
5318
|
+
" encryptedKeys: dataKeyPair.encryptedKeys,",
|
|
5319
|
+
" iv: uint8ArrayToBase64(encrypted.iv),",
|
|
5320
|
+
" tag: uint8ArrayToBase64(encrypted.tag),",
|
|
5321
|
+
" };",
|
|
5322
|
+
" }",
|
|
5323
|
+
"",
|
|
5324
|
+
" async decrypt(",
|
|
5325
|
+
" encryptedData: VaultEncryptedData,",
|
|
5326
|
+
" aad?: string,",
|
|
5327
|
+
" ): Promise<string> {",
|
|
5328
|
+
" const dataKey = await this.decryptDataKey({",
|
|
5329
|
+
" keys: encryptedData.encryptedKeys,",
|
|
5330
|
+
" });",
|
|
5331
|
+
" const encodedAad = aad ? new TextEncoder().encode(aad) : undefined;",
|
|
5332
|
+
" const decrypted = await this.workos.getCryptoProvider().decrypt(",
|
|
5333
|
+
" base64ToUint8Array(encryptedData.ciphertext),",
|
|
5334
|
+
" base64ToUint8Array(dataKey.key),",
|
|
5335
|
+
" base64ToUint8Array(encryptedData.iv),",
|
|
5336
|
+
" base64ToUint8Array(encryptedData.tag),",
|
|
5337
|
+
" encodedAad,",
|
|
5338
|
+
" );",
|
|
5339
|
+
"",
|
|
5340
|
+
" return new TextDecoder().decode(decrypted);",
|
|
5341
|
+
" }"
|
|
5342
|
+
];
|
|
5343
|
+
}
|
|
5077
5344
|
function renderMethod$2(op, plan, method, service, ctx, modelMap, specEnumNames, resolvedOp) {
|
|
5078
5345
|
const lines = [];
|
|
5079
5346
|
const responseModel = plan.responseModelName ? resolveInterfaceName(plan.responseModelName, ctx) : null;
|
|
@@ -5081,9 +5348,11 @@ function renderMethod$2(op, plan, method, service, ctx, modelMap, specEnumNames,
|
|
|
5081
5348
|
const hiddenParams = new Set([...Object.keys(getOpDefaults(resolvedOp)), ...getOpInferFromClient(resolvedOp)]);
|
|
5082
5349
|
const httpKey = `${op.httpMethod.toUpperCase()} ${op.path}`;
|
|
5083
5350
|
const baselineClassMethod = baselineMethodFor$1(service, method, ctx);
|
|
5351
|
+
const optionInfo = optionsObjectInfo(service, method, op, plan, ctx, baselineClassMethod, resolvedOp);
|
|
5084
5352
|
const overlayMethod = ctx.overlayLookup?.methodByOperation?.get(httpKey) ?? baselineClassMethod;
|
|
5085
5353
|
let validParamNames = null;
|
|
5086
|
-
if (
|
|
5354
|
+
if (optionInfo) validParamNames = new Set(["options"]);
|
|
5355
|
+
else if (overlayMethod) validParamNames = new Set(overlayMethod.params.map((p) => p.name));
|
|
5087
5356
|
else {
|
|
5088
5357
|
const actualParams = /* @__PURE__ */ new Set();
|
|
5089
5358
|
for (const p of op.pathParams) actualParams.add(fieldName$6(p.name));
|
|
@@ -5219,7 +5488,7 @@ function renderMethod$2(op, plan, method, service, ctx, modelMap, specEnumNames,
|
|
|
5219
5488
|
}
|
|
5220
5489
|
}
|
|
5221
5490
|
}
|
|
5222
|
-
if (renderOptionsObjectMethod(lines, op, plan, method, service, ctx, modelMap, specEnumNames, baselineClassMethod)) return lines;
|
|
5491
|
+
if (renderOptionsObjectMethod(lines, op, plan, method, service, ctx, modelMap, specEnumNames, baselineClassMethod, resolvedOp)) return lines;
|
|
5223
5492
|
const preDecisionCount = lines.length;
|
|
5224
5493
|
if (plan.isPaginated && op.pagination && op.httpMethod === "get") {
|
|
5225
5494
|
let paginatedItemRawName = op.pagination.itemType.kind === "model" ? op.pagination.itemType.name : null;
|
|
@@ -5246,12 +5515,17 @@ function renderMethod$2(op, plan, method, service, ctx, modelMap, specEnumNames,
|
|
|
5246
5515
|
}
|
|
5247
5516
|
return lines;
|
|
5248
5517
|
}
|
|
5249
|
-
function renderOptionsObjectMethod(lines, op, plan, method, service, ctx, modelMap, specEnumNames, baselineMethod) {
|
|
5250
|
-
const optionParam =
|
|
5518
|
+
function renderOptionsObjectMethod(lines, op, plan, method, service, ctx, modelMap, specEnumNames, baselineMethod, resolvedOp) {
|
|
5519
|
+
const optionParam = optionsObjectInfo(service, method, op, plan, ctx, baselineMethod, resolvedOp);
|
|
5251
5520
|
if (!optionParam) return false;
|
|
5252
5521
|
const responseModel = plan.responseModelName ? resolveInterfaceName(plan.responseModelName, ctx) : null;
|
|
5253
5522
|
const pathBindings = buildOptionsObjectPathBindings(op, optionParam.type, ctx);
|
|
5254
5523
|
const pathStr = buildPathStr(op, buildOptionsObjectPathParamMap(op, optionParam.type, ctx));
|
|
5524
|
+
const override = operationOverrideFor$1(ctx, op);
|
|
5525
|
+
const bodyFieldMap = override?.bodyFieldMap;
|
|
5526
|
+
const visibleQueryParams = visibleQueryParamsForOptions(op, plan, resolvedOp);
|
|
5527
|
+
const queryBindings = visibleQueryParams.map((param) => fieldName$6(param.name));
|
|
5528
|
+
const queryOptionsArg = visibleQueryParams.length > 0 || Object.keys(getOpDefaults(resolvedOp)).length > 0 || getOpInferFromClient(resolvedOp).length > 0 ? `, { query: ${renderQueryExprWithOptions(visibleQueryParams, optionParam.optional, resolvedOp)} }` : "";
|
|
5255
5529
|
if (plan.isPaginated && op.pagination && op.httpMethod === "get") {
|
|
5256
5530
|
let itemRawName = op.pagination.itemType.kind === "model" ? op.pagination.itemType.name : null;
|
|
5257
5531
|
if (itemRawName) {
|
|
@@ -5265,14 +5539,15 @@ function renderOptionsObjectMethod(lines, op, plan, method, service, ctx, modelM
|
|
|
5265
5539
|
if (!itemType) return false;
|
|
5266
5540
|
const wireType = wireInterfaceName(itemType);
|
|
5267
5541
|
const returnType = preferredBaselineReturnType(ctx, baselineMethod?.returnType) ?? `Promise<AutoPaginatable<${itemType}>>`;
|
|
5268
|
-
|
|
5542
|
+
const listOptionsExpr = op.queryParams.filter((p) => !PAGINATION_PARAM_NAMES.has(p.name)).some((p) => fieldName$6(p.name) !== wireFieldName(p.name)) ? `options ? serialize${optionParam.type}(options) : undefined` : "paginationOptions";
|
|
5543
|
+
lines.push(` async ${method}(${renderOptionsParam(optionParam)}): ${returnType} {`);
|
|
5269
5544
|
renderOptionsObjectDestructure(lines, pathBindings, "paginationOptions");
|
|
5270
5545
|
lines.push(` return new AutoPaginatable(`);
|
|
5271
5546
|
lines.push(` await fetchAndDeserialize<${wireType}, ${itemType}>(`);
|
|
5272
5547
|
lines.push(` this.workos,`);
|
|
5273
5548
|
lines.push(` ${pathStr},`);
|
|
5274
5549
|
lines.push(` deserialize${itemType},`);
|
|
5275
|
-
lines.push(`
|
|
5550
|
+
lines.push(` ${listOptionsExpr},`);
|
|
5276
5551
|
lines.push(` ),`);
|
|
5277
5552
|
lines.push(` (params) =>`);
|
|
5278
5553
|
lines.push(` fetchAndDeserialize<${wireType}, ${itemType}>(`);
|
|
@@ -5287,9 +5562,9 @@ function renderOptionsObjectMethod(lines, op, plan, method, service, ctx, modelM
|
|
|
5287
5562
|
return true;
|
|
5288
5563
|
}
|
|
5289
5564
|
if (plan.isDelete && !plan.hasBody) {
|
|
5290
|
-
lines.push(` async ${method}(
|
|
5565
|
+
lines.push(` async ${method}(${renderOptionsParam(optionParam)}): Promise<void> {`);
|
|
5291
5566
|
renderOptionsObjectDestructure(lines, pathBindings);
|
|
5292
|
-
lines.push(` await this.workos.delete(${pathStr});`);
|
|
5567
|
+
lines.push(` await this.workos.delete(${pathStr}${queryOptionsArg});`);
|
|
5293
5568
|
lines.push(" }");
|
|
5294
5569
|
return true;
|
|
5295
5570
|
}
|
|
@@ -5312,40 +5587,56 @@ function renderOptionsObjectMethod(lines, op, plan, method, service, ctx, modelM
|
|
|
5312
5587
|
entityType = requestType;
|
|
5313
5588
|
}
|
|
5314
5589
|
if (plan.isDelete) {
|
|
5315
|
-
lines.push(` async ${method}(
|
|
5316
|
-
renderOptionsObjectDestructure(lines, pathBindings, "payload");
|
|
5317
|
-
|
|
5590
|
+
lines.push(` async ${method}(${renderOptionsParam(optionParam)}): Promise<void> {`);
|
|
5591
|
+
renderOptionsObjectDestructure(lines, [...pathBindings, ...queryBindings], "payload");
|
|
5592
|
+
const bodyParam = renderOptionsObjectBodyFieldMap(lines, bodyFieldMap);
|
|
5593
|
+
bodyExpr = bodyArgExprWithParam(bodyExpr, bodyParam);
|
|
5594
|
+
lines.push(` await this.workos.deleteWithBody<${entityType}>(${pathStr}, ${bodyExpr}${queryOptionsArg});`);
|
|
5318
5595
|
lines.push(" }");
|
|
5319
5596
|
return true;
|
|
5320
5597
|
}
|
|
5321
5598
|
if (!responseModel) {
|
|
5322
|
-
lines.push(` async ${method}(
|
|
5323
|
-
renderOptionsObjectDestructure(lines, pathBindings, "payload");
|
|
5324
|
-
|
|
5599
|
+
lines.push(` async ${method}(${renderOptionsParam(optionParam)}): Promise<void> {`);
|
|
5600
|
+
renderOptionsObjectDestructure(lines, [...pathBindings, ...queryBindings], "payload");
|
|
5601
|
+
const bodyParam = renderOptionsObjectBodyFieldMap(lines, bodyFieldMap);
|
|
5602
|
+
bodyExpr = bodyArgExprWithParam(bodyExpr, bodyParam);
|
|
5603
|
+
lines.push(` await this.workos.${op.httpMethod}<void, ${entityType}>(${pathStr}, ${bodyExpr}${queryOptionsArg});`);
|
|
5325
5604
|
lines.push(" }");
|
|
5326
5605
|
return true;
|
|
5327
5606
|
}
|
|
5328
5607
|
const returnType = plan.isArrayResponse ? `${responseModel}[]` : responseModel;
|
|
5608
|
+
const methodReturnType = override?.returnType ?? `Promise<${returnType}>`;
|
|
5329
5609
|
const wireType = plan.isArrayResponse ? `${wireInterfaceName(responseModel)}[]` : wireInterfaceName(responseModel);
|
|
5330
5610
|
const returnExpr = plan.isArrayResponse ? `data.map(deserialize${responseModel})` : `deserialize${responseModel}(data)`;
|
|
5331
|
-
|
|
5332
|
-
|
|
5611
|
+
const finalReturnExpr = override?.returnDataProperty ? `${returnExpr}.${override.returnDataProperty}` : returnExpr;
|
|
5612
|
+
lines.push(` async ${method}(${renderOptionsParam(optionParam)}): ${methodReturnType} {`);
|
|
5613
|
+
renderOptionsObjectDestructure(lines, [...pathBindings, ...queryBindings], "payload");
|
|
5614
|
+
const bodyParam = renderOptionsObjectBodyFieldMap(lines, bodyFieldMap);
|
|
5615
|
+
bodyExpr = bodyArgExprWithParam(bodyExpr, bodyParam);
|
|
5333
5616
|
lines.push(` const { data } = await this.workos.${op.httpMethod}<${wireType}, ${entityType}>(`);
|
|
5334
5617
|
lines.push(` ${pathStr},`);
|
|
5335
|
-
lines.push(` ${bodyExpr},`);
|
|
5618
|
+
lines.push(` ${bodyExpr}${queryOptionsArg},`);
|
|
5336
5619
|
lines.push(" );");
|
|
5337
|
-
|
|
5620
|
+
if (override?.returnExpression) {
|
|
5621
|
+
lines.push(` const result = ${returnExpr};`);
|
|
5622
|
+
lines.push(` return ${override.returnExpression};`);
|
|
5623
|
+
} else lines.push(` return ${finalReturnExpr};`);
|
|
5338
5624
|
lines.push(" }");
|
|
5339
5625
|
return true;
|
|
5340
5626
|
}
|
|
5341
5627
|
if (responseModel) {
|
|
5342
5628
|
const returnType = plan.isArrayResponse ? `${responseModel}[]` : responseModel;
|
|
5629
|
+
const methodReturnType = override?.returnType ?? `Promise<${returnType}>`;
|
|
5343
5630
|
const wireType = plan.isArrayResponse ? `${wireInterfaceName(responseModel)}[]` : wireInterfaceName(responseModel);
|
|
5344
5631
|
const returnExpr = plan.isArrayResponse ? `data.map(deserialize${responseModel})` : `deserialize${responseModel}(data)`;
|
|
5345
|
-
|
|
5632
|
+
const finalReturnExpr = override?.returnDataProperty ? `${returnExpr}.${override.returnDataProperty}` : returnExpr;
|
|
5633
|
+
lines.push(` async ${method}(${renderOptionsParam(optionParam)}): ${methodReturnType} {`);
|
|
5346
5634
|
renderOptionsObjectDestructure(lines, pathBindings);
|
|
5347
|
-
lines.push(` const { data } = await this.workos.${op.httpMethod}<${wireType}>(${pathStr});`);
|
|
5348
|
-
|
|
5635
|
+
lines.push(` const { data } = await this.workos.${op.httpMethod}<${wireType}>(${pathStr}${queryOptionsArg});`);
|
|
5636
|
+
if (override?.returnExpression) {
|
|
5637
|
+
lines.push(` const result = ${returnExpr};`);
|
|
5638
|
+
lines.push(` return ${override.returnExpression};`);
|
|
5639
|
+
} else lines.push(` return ${finalReturnExpr};`);
|
|
5349
5640
|
lines.push(" }");
|
|
5350
5641
|
return true;
|
|
5351
5642
|
}
|
|
@@ -5356,6 +5647,18 @@ function renderOptionsObjectDestructure(lines, pathBindings, restName) {
|
|
|
5356
5647
|
else if (pathBindings.length > 0) lines.push(` const { ${pathBindings.join(", ")} } = options;`);
|
|
5357
5648
|
else if (restName) lines.push(` const ${restName} = options;`);
|
|
5358
5649
|
}
|
|
5650
|
+
function renderOptionsObjectBodyFieldMap(lines, bodyFieldMap) {
|
|
5651
|
+
const entries = Object.entries(bodyFieldMap ?? {});
|
|
5652
|
+
if (entries.length === 0) return "payload";
|
|
5653
|
+
lines.push(" const requestPayload = {");
|
|
5654
|
+
lines.push(" ...payload,");
|
|
5655
|
+
for (const [source, target] of entries) lines.push(` ${target}: payload.${source},`);
|
|
5656
|
+
lines.push(" };");
|
|
5657
|
+
return "requestPayload";
|
|
5658
|
+
}
|
|
5659
|
+
function bodyArgExprWithParam(bodyExpr, paramName) {
|
|
5660
|
+
return paramName === "payload" ? bodyExpr : bodyExpr.replace(/\bpayload\b/g, paramName);
|
|
5661
|
+
}
|
|
5359
5662
|
function buildOptionsObjectPathBindings(op, optionType, ctx) {
|
|
5360
5663
|
return op.pathParams.map((param) => resolveOptionsObjectField$1(fieldName$6(param.name), optionType, ctx));
|
|
5361
5664
|
}
|
|
@@ -5634,6 +5937,20 @@ function renderQueryExpr(queryParams) {
|
|
|
5634
5937
|
}
|
|
5635
5938
|
return `options ? { ${parts.join(", ")} } : undefined`;
|
|
5636
5939
|
}
|
|
5940
|
+
function renderQueryExprWithOptions(queryParams, optional, resolvedOp) {
|
|
5941
|
+
const parts = [];
|
|
5942
|
+
for (const param of queryParams) {
|
|
5943
|
+
const camel = fieldName$6(param.name);
|
|
5944
|
+
const snake = wireFieldName(param.name);
|
|
5945
|
+
const access = optional ? `options?.${camel}` : `options.${camel}`;
|
|
5946
|
+
if (param.required) parts.push(`${snake}: ${access}`);
|
|
5947
|
+
else parts.push(`...(${access} !== undefined && { ${snake}: ${access} })`);
|
|
5948
|
+
}
|
|
5949
|
+
for (const [key, value] of Object.entries(getOpDefaults(resolvedOp))) parts.push(`${key}: ${tsLiteral(value)}`);
|
|
5950
|
+
for (const field of getOpInferFromClient(resolvedOp)) parts.push(`${field}: ${clientFieldExpression$8(field)}`);
|
|
5951
|
+
if (parts.length === 0) return optional ? "options" : "{}";
|
|
5952
|
+
return `{ ${parts.join(", ")} }`;
|
|
5953
|
+
}
|
|
5637
5954
|
function buildPathStr(op, paramNameMap) {
|
|
5638
5955
|
return buildNodePathExpression(op.path, paramNameMap);
|
|
5639
5956
|
}
|
|
@@ -6074,6 +6391,17 @@ function generateModels$7(models, ctx, shared) {
|
|
|
6074
6391
|
const discriminatedSkip = ctx._discriminatedModelNames;
|
|
6075
6392
|
const nonPaginatedRefs = collectNonPaginatedResponseModelNames(ctx.spec.services);
|
|
6076
6393
|
const listMetadataNeeded = collectReferencedListMetadataModels(models, nonPaginatedRefs);
|
|
6394
|
+
for (const originalModel of models) {
|
|
6395
|
+
const model = projectedByName.get(originalModel.name) ?? originalModel;
|
|
6396
|
+
if (!reachableModels.has(model.name)) continue;
|
|
6397
|
+
if (interfaceEligibleModels && !interfaceEligibleModels.has(model.name)) continue;
|
|
6398
|
+
if (!isNodeOwnedService(ctx, modelToService.get(model.name)) && !modelHasNewFields(model, ctx) && !forceGenerate.has(model.name)) continue;
|
|
6399
|
+
const canonicalName = dedup.get(model.name);
|
|
6400
|
+
if (canonicalName) {
|
|
6401
|
+
forceGenerate.add(canonicalName);
|
|
6402
|
+
if (interfaceEligibleModels) interfaceEligibleModels.add(canonicalName);
|
|
6403
|
+
}
|
|
6404
|
+
}
|
|
6077
6405
|
for (const originalModel of models) {
|
|
6078
6406
|
const model = projectedByName.get(originalModel.name) ?? originalModel;
|
|
6079
6407
|
if (!reachableModels.has(model.name)) continue;
|
|
@@ -6200,9 +6528,11 @@ function generateModels$7(models, ctx, shared) {
|
|
|
6200
6528
|
}
|
|
6201
6529
|
for (const dep of deps.models) {
|
|
6202
6530
|
const depName = resolveInterfaceName(dep, ctx);
|
|
6203
|
-
const
|
|
6531
|
+
const depService = modelToService.get(dep);
|
|
6532
|
+
const depDir = resolveDir(depService);
|
|
6533
|
+
const depIsOwned = isNodeOwnedService(ctx, depService);
|
|
6204
6534
|
const currentFilePath = `src/${dirName}/interfaces/${fileName$3(model.name)}.interface.ts`;
|
|
6205
|
-
const baselineSrc = (ctx.apiSurface?.interfaces?.[depName])?.sourceFile;
|
|
6535
|
+
const baselineSrc = depIsOwned ? void 0 : (ctx.apiSurface?.interfaces?.[depName])?.sourceFile;
|
|
6206
6536
|
if (baselineSrc === currentFilePath) continue;
|
|
6207
6537
|
let relPath;
|
|
6208
6538
|
if (baselineSrc) relPath = relativeImport(currentFilePath, baselineSrc).replace(/\.ts$/, "");
|
|
@@ -6385,6 +6715,7 @@ function generateSerializers(models, ctx, shared) {
|
|
|
6385
6715
|
const resourceUsage = buildGeneratedResourceModelUsage(models, ctx);
|
|
6386
6716
|
const serializerEligibleModels = resourceUsage ? expandModelRoots(resourceUsage.serializerRoots, projectedByName) : void 0;
|
|
6387
6717
|
const responseReachableModels = resourceUsage ? expandModelRoots(resourceUsage.responseRoots, projectedByName) : void 0;
|
|
6718
|
+
const requestReachableModels = resourceUsage ? expandModelRoots(resourceUsage.requestRoots, projectedByName) : void 0;
|
|
6388
6719
|
const serializerReachable = computeNonEventReachable(ctx.spec.services, models);
|
|
6389
6720
|
const liveRoot = ctx.targetDir ?? ctx.outputDir;
|
|
6390
6721
|
if (liveRoot) for (const originalModel of models) {
|
|
@@ -6417,6 +6748,17 @@ function generateSerializers(models, ctx, shared) {
|
|
|
6417
6748
|
const discriminatedSerializerSkip = ctx._discriminatedModelNames;
|
|
6418
6749
|
const serializerNonPaginatedRefs = collectNonPaginatedResponseModelNames(ctx.spec.services);
|
|
6419
6750
|
const serializerListMetadataNeeded = collectReferencedListMetadataModels(models, serializerNonPaginatedRefs);
|
|
6751
|
+
for (const originalModel of models) {
|
|
6752
|
+
const model = projectedByName.get(originalModel.name) ?? originalModel;
|
|
6753
|
+
if (!serializerReachable.has(model.name)) continue;
|
|
6754
|
+
if (serializerEligibleModels && !serializerEligibleModels.has(model.name)) continue;
|
|
6755
|
+
if (!isNodeOwnedService(ctx, modelToService.get(model.name)) && !modelHasNewFields(model, ctx) && !forceGenerateSerializer.has(model.name)) continue;
|
|
6756
|
+
const canonicalName = dedup.get(model.name);
|
|
6757
|
+
if (canonicalName) {
|
|
6758
|
+
forceGenerateSerializer.add(canonicalName);
|
|
6759
|
+
if (serializerEligibleModels) serializerEligibleModels.add(canonicalName);
|
|
6760
|
+
}
|
|
6761
|
+
}
|
|
6420
6762
|
const eligibleModels = [];
|
|
6421
6763
|
for (const originalModel of models) {
|
|
6422
6764
|
const model = projectedByName.get(originalModel.name) ?? originalModel;
|
|
@@ -6435,7 +6777,7 @@ function generateSerializers(models, ctx, shared) {
|
|
|
6435
6777
|
const responseName = wireInterfaceName(domainName);
|
|
6436
6778
|
const baselineResponse = ctx.apiSurface?.interfaces?.[responseName];
|
|
6437
6779
|
const baselineDomain = ctx.apiSurface?.interfaces?.[domainName];
|
|
6438
|
-
if (shouldSkipSerializeForModel(model, baselineResponse, baselineDomain, dedup, skippedSerializeModels, ctx)) skippedSerializeModels.add(model.name);
|
|
6780
|
+
if (requestReachableModels !== void 0 && !requestReachableModels.has(model.name) || shouldSkipSerializeForModel(model, baselineResponse, baselineDomain, dedup, skippedSerializeModels, ctx)) skippedSerializeModels.add(model.name);
|
|
6439
6781
|
}
|
|
6440
6782
|
for (const model of eligibleModels) {
|
|
6441
6783
|
const service = modelToService.get(model.name);
|
|
@@ -6451,20 +6793,24 @@ function generateSerializers(models, ctx, shared) {
|
|
|
6451
6793
|
const canonSerializerPath = `src/${canonDir}/serializers/${fileName$3(canonicalName)}.serializer.ts`;
|
|
6452
6794
|
if (serializerPath === canonSerializerPath) continue;
|
|
6453
6795
|
if (domainName === canonDomainName) continue;
|
|
6454
|
-
const
|
|
6455
|
-
const
|
|
6456
|
-
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6796
|
+
const aliasNeedsDeserialize = responseReachableModels === void 0 || responseReachableModels.has(model.name);
|
|
6797
|
+
const canonicalHasDeserialize = responseReachableModels === void 0 || responseReachableModels.has(canonicalName);
|
|
6798
|
+
if (!aliasNeedsDeserialize || canonicalHasDeserialize) {
|
|
6799
|
+
const rel = relativeImport(serializerPath, canonSerializerPath);
|
|
6800
|
+
const canonSkipSerialize = skippedSerializeModels.has(canonicalName) || skippedSerializeModels.has(model.name);
|
|
6801
|
+
const canonSkipDeserialize = responseReachableModels !== void 0 && !responseReachableModels.has(canonicalName) && !responseReachableModels.has(model.name);
|
|
6802
|
+
if (canonSkipSerialize && canonSkipDeserialize) continue;
|
|
6803
|
+
const parts = [];
|
|
6804
|
+
if (!canonSkipDeserialize) parts.push(`deserialize${canonDomainName} as deserialize${domainName}`);
|
|
6805
|
+
if (!canonSkipSerialize) parts.push(`serialize${canonDomainName} as serialize${domainName}`);
|
|
6806
|
+
const reexportContent = `export { ${parts.join(", ")} } from '${rel}';`;
|
|
6807
|
+
files.push({
|
|
6808
|
+
path: serializerPath,
|
|
6809
|
+
content: reexportContent,
|
|
6810
|
+
overwriteExisting: true
|
|
6811
|
+
});
|
|
6812
|
+
continue;
|
|
6813
|
+
}
|
|
6468
6814
|
}
|
|
6469
6815
|
const dirName = resolveDir(service);
|
|
6470
6816
|
const isDedupCanonical = [...dedup.values()].includes(model.name);
|
|
@@ -6504,7 +6850,7 @@ function generateSerializers(models, ctx, shared) {
|
|
|
6504
6850
|
}
|
|
6505
6851
|
const liveRootForBarrel = ctx.outputDir ?? ctx.targetDir;
|
|
6506
6852
|
for (const [dir, stems] of serializersByDir) {
|
|
6507
|
-
if (liveRootForBarrel) {
|
|
6853
|
+
if (liveRootForBarrel && !isNodeOwnedService(ctx, dir)) {
|
|
6508
6854
|
const serializersDir = path.join(liveRootForBarrel, "src", dir, "serializers");
|
|
6509
6855
|
try {
|
|
6510
6856
|
for (const entry of fs.readdirSync(serializersDir)) {
|
|
@@ -6750,6 +7096,25 @@ function generateWorkOSClient$1(spec, ctx) {
|
|
|
6750
7096
|
skipIfExists: true
|
|
6751
7097
|
};
|
|
6752
7098
|
}
|
|
7099
|
+
function sourceFileForExportedType(ctx, typeName) {
|
|
7100
|
+
return (ctx.apiSurface?.interfaces?.[typeName])?.sourceFile ?? (ctx.apiSurface?.typeAliases?.[typeName])?.sourceFile ?? (ctx.apiSurface?.enums?.[typeName])?.sourceFile ?? liveSurfaceInterfacePath(typeName);
|
|
7101
|
+
}
|
|
7102
|
+
function exportedNamesForSource(ctx, sourceFile) {
|
|
7103
|
+
const names = /* @__PURE__ */ new Set();
|
|
7104
|
+
const addNamesFromSurface = (items) => {
|
|
7105
|
+
if (!items) return;
|
|
7106
|
+
for (const [name, item] of Object.entries(items)) if (item.sourceFile === sourceFile) names.add(name);
|
|
7107
|
+
};
|
|
7108
|
+
addNamesFromSurface(ctx.apiSurface?.interfaces);
|
|
7109
|
+
addNamesFromSurface(ctx.apiSurface?.typeAliases);
|
|
7110
|
+
addNamesFromSurface(ctx.apiSurface?.enums);
|
|
7111
|
+
const rootDir = ctx.targetDir ?? ctx.outputDir;
|
|
7112
|
+
if (rootDir) try {
|
|
7113
|
+
const content = fs.readFileSync(path.join(rootDir, sourceFile), "utf-8");
|
|
7114
|
+
for (const match of content.matchAll(/export\s+(?:interface|type|enum|class|const|function)\s+(\w+)/g)) names.add(match[1]);
|
|
7115
|
+
} catch {}
|
|
7116
|
+
return [...names];
|
|
7117
|
+
}
|
|
6753
7118
|
/**
|
|
6754
7119
|
* Generate per-service barrel files (interfaces/index.ts) that re-export
|
|
6755
7120
|
* all interface and enum files for each service directory. This reduces
|
|
@@ -6762,11 +7127,14 @@ function generateServiceBarrels(spec, ctx) {
|
|
|
6762
7127
|
const enumToService = assignEnumsToServices(spec.enums, spec.services, spec.models, ctx);
|
|
6763
7128
|
const dirExports = /* @__PURE__ */ new Map();
|
|
6764
7129
|
const dirSymbols = /* @__PURE__ */ new Map();
|
|
7130
|
+
const ownedDirNames = /* @__PURE__ */ new Set();
|
|
7131
|
+
for (const service of spec.services) if (isNodeOwnedService(ctx, service.name)) ownedDirNames.add(resolveDir(service.name));
|
|
6765
7132
|
const dirSymbolsFromBaseline = /* @__PURE__ */ new Map();
|
|
6766
7133
|
const seedFromBaseline = (sourceFile, name) => {
|
|
6767
7134
|
const match = sourceFile.match(/^src\/([^/]+)\/interfaces\/(.+)\.ts$/);
|
|
6768
7135
|
if (!match) return;
|
|
6769
7136
|
const dirName = match[1];
|
|
7137
|
+
if (ownedDirNames.has(dirName)) return;
|
|
6770
7138
|
const fileStem = match[2];
|
|
6771
7139
|
if (!dirSymbols.has(dirName)) dirSymbols.set(dirName, /* @__PURE__ */ new Set());
|
|
6772
7140
|
dirSymbols.get(dirName).add(name);
|
|
@@ -6819,9 +7187,63 @@ function generateServiceBarrels(spec, ctx) {
|
|
|
6819
7187
|
globalExistingSymbols.add(enumDef.name);
|
|
6820
7188
|
dirExports.get(dirName).push(`export * from './${fileName$3(enumDef.name)}.interface';`);
|
|
6821
7189
|
}
|
|
7190
|
+
const overrideWildcardSources = /* @__PURE__ */ new Set();
|
|
7191
|
+
const overrideNamedExports = /* @__PURE__ */ new Set();
|
|
7192
|
+
const addOverrideTypeExport = (typeName) => {
|
|
7193
|
+
if (!typeName) return;
|
|
7194
|
+
const sourceFile = sourceFileForExportedType(ctx, typeName);
|
|
7195
|
+
if (!sourceFile) return;
|
|
7196
|
+
const match = sourceFile?.match(/^src\/([^/]+)\/interfaces\/(.+)\.ts$/);
|
|
7197
|
+
if (!match) return;
|
|
7198
|
+
const dirName = match[1];
|
|
7199
|
+
const stem = match[2].replace(/\.ts$/, "");
|
|
7200
|
+
if (!dirExports.has(dirName)) {
|
|
7201
|
+
dirExports.set(dirName, []);
|
|
7202
|
+
if (!dirSymbols.has(dirName)) dirSymbols.set(dirName, /* @__PURE__ */ new Set());
|
|
7203
|
+
}
|
|
7204
|
+
const symbols = dirSymbols.get(dirName);
|
|
7205
|
+
const sourceKey = `${dirName}/${stem}`;
|
|
7206
|
+
if (overrideWildcardSources.has(sourceKey)) return;
|
|
7207
|
+
const sourceNames = exportedNamesForSource(ctx, sourceFile);
|
|
7208
|
+
if (sourceNames.some((name) => name !== typeName && globalExistingSymbols.has(name))) {
|
|
7209
|
+
const exportKey = `${sourceKey}:${typeName}`;
|
|
7210
|
+
if (overrideNamedExports.has(exportKey)) return;
|
|
7211
|
+
dirExports.get(dirName).push(`export type { ${typeName} } from './${stem}';`);
|
|
7212
|
+
overrideNamedExports.add(exportKey);
|
|
7213
|
+
symbols.add(typeName);
|
|
7214
|
+
globalExistingSymbols.add(typeName);
|
|
7215
|
+
return;
|
|
7216
|
+
}
|
|
7217
|
+
dirExports.get(dirName).push(`export * from './${stem}';`);
|
|
7218
|
+
overrideWildcardSources.add(sourceKey);
|
|
7219
|
+
const namesToRegister = sourceNames.length > 0 ? sourceNames : [typeName];
|
|
7220
|
+
for (const name of namesToRegister) {
|
|
7221
|
+
symbols.add(name);
|
|
7222
|
+
globalExistingSymbols.add(name);
|
|
7223
|
+
}
|
|
7224
|
+
};
|
|
7225
|
+
for (const override of Object.values(nodeOptions(ctx).operationOverrides ?? {})) {
|
|
7226
|
+
addOverrideTypeExport(override.optionsType);
|
|
7227
|
+
for (const typeName of override.returnTypeImports ?? []) addOverrideTypeExport(typeName);
|
|
7228
|
+
}
|
|
7229
|
+
const generatedOptionExports = ctx._nodeGeneratedOptionInterfaceExports;
|
|
7230
|
+
for (const [dirName, options] of generatedOptionExports ?? []) {
|
|
7231
|
+
if (!dirExports.has(dirName)) {
|
|
7232
|
+
dirExports.set(dirName, []);
|
|
7233
|
+
if (!dirSymbols.has(dirName)) dirSymbols.set(dirName, /* @__PURE__ */ new Set());
|
|
7234
|
+
}
|
|
7235
|
+
const symbols = dirSymbols.get(dirName);
|
|
7236
|
+
for (const { stem, typeName } of options) {
|
|
7237
|
+
if (globalExistingSymbols.has(typeName)) continue;
|
|
7238
|
+
symbols.add(typeName);
|
|
7239
|
+
globalExistingSymbols.add(typeName);
|
|
7240
|
+
dirExports.get(dirName).push(`export * from './${stem}';`);
|
|
7241
|
+
}
|
|
7242
|
+
}
|
|
6822
7243
|
for (const [dirName, exports] of dirExports) {
|
|
6823
7244
|
const exportSet = new Set(exports);
|
|
6824
|
-
|
|
7245
|
+
const isDirOwned = ownedDirNames.has(dirName);
|
|
7246
|
+
if (ctx.apiSurface && !isDirOwned) {
|
|
6825
7247
|
const addBaselineExports = (items) => {
|
|
6826
7248
|
if (!items) return;
|
|
6827
7249
|
for (const item of Object.values(items)) {
|
|
@@ -6834,7 +7256,7 @@ function generateServiceBarrels(spec, ctx) {
|
|
|
6834
7256
|
addBaselineExports(ctx.apiSurface.interfaces);
|
|
6835
7257
|
addBaselineExports(ctx.apiSurface.typeAliases);
|
|
6836
7258
|
addBaselineExports(ctx.apiSurface.enums);
|
|
6837
|
-
if (ctx.targetDir) {
|
|
7259
|
+
if (ctx.targetDir && !isDirOwned) {
|
|
6838
7260
|
const interfacesDir = path.join(ctx.targetDir, "src", dirName, "interfaces");
|
|
6839
7261
|
try {
|
|
6840
7262
|
const barrelPath = path.join(interfacesDir, "index.ts");
|
|
@@ -7088,6 +7510,9 @@ function serverConstName(description) {
|
|
|
7088
7510
|
}
|
|
7089
7511
|
//#endregion
|
|
7090
7512
|
//#region src/node/tests.ts
|
|
7513
|
+
function operationOverrideFor(ctx, op) {
|
|
7514
|
+
return nodeOptions(ctx).operationOverrides?.[`${op.httpMethod.toUpperCase()} ${op.path}`];
|
|
7515
|
+
}
|
|
7091
7516
|
function baselineMethodFor(service, method, ctx) {
|
|
7092
7517
|
const serviceClass = resolveResourceClassName$3(service, ctx);
|
|
7093
7518
|
return ctx.apiSurface?.classes?.[serviceClass]?.methods?.[method]?.[0];
|
|
@@ -7103,6 +7528,34 @@ function optionsObjectParam(method) {
|
|
|
7103
7528
|
type: param.type
|
|
7104
7529
|
};
|
|
7105
7530
|
}
|
|
7531
|
+
function configuredOptionsMethod(ctx, op) {
|
|
7532
|
+
const optionsType = operationOverrideFor(ctx, op)?.optionsType;
|
|
7533
|
+
if (!optionsType) return void 0;
|
|
7534
|
+
return { params: [{
|
|
7535
|
+
name: "options",
|
|
7536
|
+
type: optionsType,
|
|
7537
|
+
passingStyle: "options_object"
|
|
7538
|
+
}] };
|
|
7539
|
+
}
|
|
7540
|
+
function methodOptionsName(method, serviceClass) {
|
|
7541
|
+
if (method === "list") return `${toPascalCase(serviceClass)}ListOptions`;
|
|
7542
|
+
return `${toPascalCase(method)}Options`;
|
|
7543
|
+
}
|
|
7544
|
+
function hasOptionsInput(op, plan) {
|
|
7545
|
+
return op.pathParams.length > 0 || plan.hasBody || plan.isPaginated || op.queryParams.length > 0;
|
|
7546
|
+
}
|
|
7547
|
+
function optionsMethodFor(service, method, op, plan, ctx) {
|
|
7548
|
+
const baseline = baselineMethodFor(service, method, ctx);
|
|
7549
|
+
if (optionsObjectParam(baseline)) return baseline;
|
|
7550
|
+
const configured = configuredOptionsMethod(ctx, op);
|
|
7551
|
+
if (configured) return configured;
|
|
7552
|
+
if (!hasOptionsInput(op, plan)) return baseline;
|
|
7553
|
+
return { params: [{
|
|
7554
|
+
name: "options",
|
|
7555
|
+
type: methodOptionsName(method, resolveResourceClassName$3(service, ctx)),
|
|
7556
|
+
passingStyle: "options_object"
|
|
7557
|
+
}] };
|
|
7558
|
+
}
|
|
7106
7559
|
function autoPaginatableItemType(returnType) {
|
|
7107
7560
|
return returnType?.match(/\bAutoPaginatable<\s*([A-Za-z_$][\w$]*)/)?.[1];
|
|
7108
7561
|
}
|
|
@@ -7212,7 +7665,7 @@ function generateServiceTest$3(service, spec, ctx, modelMap, mountAccessors) {
|
|
|
7212
7665
|
lines.push(`describe('${serviceClass}', () => {`);
|
|
7213
7666
|
lines.push(" beforeEach(() => fetch.resetMocks());");
|
|
7214
7667
|
for (const { op, plan, method } of plans) {
|
|
7215
|
-
const existingMethod =
|
|
7668
|
+
const existingMethod = optionsMethodFor(service, method, op, plan, ctx);
|
|
7216
7669
|
lines.push("");
|
|
7217
7670
|
lines.push(` describe('${method}', () => {`);
|
|
7218
7671
|
if (plan.isPaginated) renderPaginatedTest(lines, op, plan, method, serviceProp, modelMap, ctx, entityHelperNames, existingMethod);
|
|
@@ -7329,6 +7782,12 @@ function renderBodyTest(lines, op, plan, method, serviceProp, modelMap, ctx, ent
|
|
|
7329
7782
|
if (payload) lines.push(` expect(fetchBody()).toEqual(expect.objectContaining(${payload.snakeCaseObj}));`);
|
|
7330
7783
|
else lines.push(" expect(fetchBody()).toBeDefined();");
|
|
7331
7784
|
if (isArrayResponse) lines.push(" expect(Array.isArray(result)).toBe(true);");
|
|
7785
|
+
const override = ctx ? operationOverrideFor(ctx, op) : void 0;
|
|
7786
|
+
if (override?.returnExpression || override?.returnDataProperty) {
|
|
7787
|
+
lines.push(" expect(result).toBeDefined();");
|
|
7788
|
+
lines.push(" });");
|
|
7789
|
+
return;
|
|
7790
|
+
}
|
|
7332
7791
|
const bodyHelperName = ctx ? `expect${resolveInterfaceName(responseModelName, ctx)}` : null;
|
|
7333
7792
|
if (bodyHelperName && entityHelpers?.has(bodyHelperName)) lines.push(` ${bodyHelperName}(${accessor});`);
|
|
7334
7793
|
else {
|
|
@@ -7358,6 +7817,13 @@ function renderGetTest(lines, op, plan, method, serviceProp, modelMap, ctx, enti
|
|
|
7358
7817
|
const expectedPathGet = buildExpectedPath$4(op);
|
|
7359
7818
|
lines.push(` expect(new URL(String(fetchURL())).pathname).toBe('${expectedPathGet}');`);
|
|
7360
7819
|
if (isArrayResponse) lines.push(" expect(Array.isArray(result)).toBe(true);");
|
|
7820
|
+
const returnDataProperty = ctx ? operationOverrideFor(ctx, op)?.returnDataProperty : void 0;
|
|
7821
|
+
if (returnDataProperty) {
|
|
7822
|
+
if ((modelMap.get(responseModelName)?.fields.find((field) => fieldName$6(field.name) === returnDataProperty))?.type.kind === "array") lines.push(" expect(Array.isArray(result)).toBe(true);");
|
|
7823
|
+
else lines.push(" expect(result).toBeDefined();");
|
|
7824
|
+
lines.push(" });");
|
|
7825
|
+
return;
|
|
7826
|
+
}
|
|
7361
7827
|
const helperName = ctx ? `expect${resolveInterfaceName(responseModelName, ctx)}` : null;
|
|
7362
7828
|
if (helperName && entityHelpers?.has(helperName)) lines.push(` ${helperName}(${accessor});`);
|
|
7363
7829
|
else {
|
|
@@ -7395,12 +7861,37 @@ function buildOptionsObjectTestArg(op, plan, baselineMethod, modelMap, ctx) {
|
|
|
7395
7861
|
const optionField = resolveOptionsObjectField(localName, optionParam.type, ctx);
|
|
7396
7862
|
entries.push(`${optionField}: ${JSON.stringify(pathParamTestValue(param, localName))}`);
|
|
7397
7863
|
}
|
|
7864
|
+
if (plan.isPaginated) entries.push("order: 'desc'");
|
|
7865
|
+
const queryParams = plan.isPaginated ? op.queryParams.filter((param) => ![
|
|
7866
|
+
"limit",
|
|
7867
|
+
"before",
|
|
7868
|
+
"after",
|
|
7869
|
+
"order"
|
|
7870
|
+
].includes(param.name)) : op.queryParams;
|
|
7871
|
+
for (const param of queryParams) {
|
|
7872
|
+
const localName = fieldName$6(param.name);
|
|
7873
|
+
const value = fixtureValueForType(param.type, param.name, "Options", modelMap) ?? "'test'";
|
|
7874
|
+
entries.push(`${localName}: ${value}`);
|
|
7875
|
+
}
|
|
7398
7876
|
if (plan.hasBody) {
|
|
7399
7877
|
const payload = buildTestPayload(op, modelMap);
|
|
7400
|
-
if (payload)
|
|
7878
|
+
if (payload) {
|
|
7879
|
+
const bodyFieldMap = ctx ? operationOverrideFor(ctx, op)?.bodyFieldMap : void 0;
|
|
7880
|
+
entries.push(...mapBodyOptionEntries(objectLiteralEntries(payload.camelCaseObj), bodyFieldMap));
|
|
7881
|
+
} else if (entries.length === 0) return "({} as any)";
|
|
7401
7882
|
}
|
|
7402
7883
|
return `{ ${entries.join(", ")} }`;
|
|
7403
7884
|
}
|
|
7885
|
+
function mapBodyOptionEntries(entries, bodyFieldMap) {
|
|
7886
|
+
const reverseMap = new Map(Object.entries(bodyFieldMap ?? {}).map(([source, target]) => [target, source]));
|
|
7887
|
+
if (reverseMap.size === 0) return entries;
|
|
7888
|
+
return entries.map((entry) => {
|
|
7889
|
+
const match = entry.match(/^([A-Za-z_$][\w$]*)\s*:/);
|
|
7890
|
+
if (!match) return entry;
|
|
7891
|
+
const source = reverseMap.get(match[1]);
|
|
7892
|
+
return source ? entry.replace(match[1], source) : entry;
|
|
7893
|
+
});
|
|
7894
|
+
}
|
|
7404
7895
|
function objectLiteralEntries(literal) {
|
|
7405
7896
|
const trimmed = literal.trim();
|
|
7406
7897
|
if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) return [];
|
|
@@ -7476,7 +7967,8 @@ function buildFieldAssertions(model, accessor, modelMap) {
|
|
|
7476
7967
|
for (const field of model.fields) {
|
|
7477
7968
|
if (!field.required) continue;
|
|
7478
7969
|
const domainField = fieldName$6(field.name);
|
|
7479
|
-
const
|
|
7970
|
+
const isDateTime = isDateTimeFieldType(field.type);
|
|
7971
|
+
const fieldAccessor = isDateTime ? `${accessor}.${domainField}.toISOString()` : `${accessor}.${domainField}`;
|
|
7480
7972
|
if (field.example !== void 0) {
|
|
7481
7973
|
if (field.example === null) {
|
|
7482
7974
|
assertions.push(`expect(${accessor}.${domainField}).toBeNull();`);
|
|
@@ -7484,7 +7976,7 @@ function buildFieldAssertions(model, accessor, modelMap) {
|
|
|
7484
7976
|
}
|
|
7485
7977
|
if (typeof field.example === "object") assertions.push(`expect(${accessor}.${domainField}).toEqual(${JSON.stringify(field.example)});`);
|
|
7486
7978
|
else {
|
|
7487
|
-
const exampleLiteral = typeof field.example === "string" ? `'${field.example}'` : String(field.example);
|
|
7979
|
+
const exampleLiteral = isDateTime && typeof field.example === "string" ? `'${new Date(field.example).toISOString()}'` : typeof field.example === "string" ? `'${field.example}'` : String(field.example);
|
|
7488
7980
|
assertions.push(`expect(${fieldAccessor}).toBe(${exampleLiteral});`);
|
|
7489
7981
|
}
|
|
7490
7982
|
continue;
|
|
@@ -7513,7 +8005,7 @@ function buildFieldAssertions(model, accessor, modelMap) {
|
|
|
7513
8005
|
*/
|
|
7514
8006
|
function fixtureValueForType(ref, name, modelName, modelMap, wire) {
|
|
7515
8007
|
switch (ref.kind) {
|
|
7516
|
-
case "primitive": return fixtureValueForPrimitive(ref.type, ref.format, name, modelName);
|
|
8008
|
+
case "primitive": return fixtureValueForPrimitive(ref.type, ref.format, name, modelName, wire);
|
|
7517
8009
|
case "literal": return typeof ref.value === "string" ? `'${ref.value}'` : String(ref.value);
|
|
7518
8010
|
case "enum":
|
|
7519
8011
|
if (ref.values?.length) {
|
|
@@ -7543,10 +8035,10 @@ function fixtureValueForType(ref, name, modelName, modelMap, wire) {
|
|
|
7543
8035
|
default: return null;
|
|
7544
8036
|
}
|
|
7545
8037
|
}
|
|
7546
|
-
function fixtureValueForPrimitive(type, format, name, modelName) {
|
|
8038
|
+
function fixtureValueForPrimitive(type, format, name, modelName, wire) {
|
|
7547
8039
|
switch (type) {
|
|
7548
8040
|
case "string":
|
|
7549
|
-
if (format === "date-time") return "'2023-01-01T00:00:00.000Z'";
|
|
8041
|
+
if (format === "date-time") return wire ? "'2023-01-01T00:00:00.000Z'" : "new Date('2023-01-01T00:00:00.000Z')";
|
|
7550
8042
|
if (format === "date") return "'2023-01-01'";
|
|
7551
8043
|
if (format === "uuid") return "'00000000-0000-0000-0000-000000000000'";
|
|
7552
8044
|
if (name === "id") return `'${ID_PREFIXES$4[modelName] ?? ""}01234'`;
|
|
@@ -7771,11 +8263,14 @@ function detectDiscriminatedShape(modelName, rawSchemas) {
|
|
|
7771
8263
|
};
|
|
7772
8264
|
}).filter((v) => v !== null);
|
|
7773
8265
|
if (variants.length !== flattenedVariants.length) return null;
|
|
8266
|
+
const baseFields = baseObject ? collectObjectFields(baseObject, modelName) : [];
|
|
8267
|
+
const discriminatorDescription = flattenedVariants[0].alwaysProperties.get(discProp)?.description;
|
|
7774
8268
|
return {
|
|
7775
8269
|
modelName,
|
|
7776
|
-
baseFields
|
|
8270
|
+
baseFields,
|
|
7777
8271
|
discriminatorProperty: discProp,
|
|
7778
8272
|
discriminatorPropertyDomain: toCamelCase(discProp),
|
|
8273
|
+
discriminatorDescription,
|
|
7779
8274
|
variants
|
|
7780
8275
|
};
|
|
7781
8276
|
}
|
|
@@ -8052,6 +8547,7 @@ function buildInterfaceBody(name, shape, variant, isWire) {
|
|
|
8052
8547
|
lines.push(`export interface ${name} {`);
|
|
8053
8548
|
for (const field of shape.baseFields) pushFieldLine(lines, field, isWire);
|
|
8054
8549
|
const discKey = isWire ? shape.discriminatorProperty : shape.discriminatorPropertyDomain;
|
|
8550
|
+
if (shape.discriminatorDescription) lines.push(` /** ${shape.discriminatorDescription} */`);
|
|
8055
8551
|
lines.push(` ${discKey}: '${variant.discriminatorValue}';`);
|
|
8056
8552
|
for (const field of variant.fields) pushFieldLine(lines, field, isWire);
|
|
8057
8553
|
lines.push("}");
|
|
@@ -8180,20 +8676,6 @@ function scalarModelDepName(field) {
|
|
|
8180
8676
|
}
|
|
8181
8677
|
//#endregion
|
|
8182
8678
|
//#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
8679
|
const contextCache = /* @__PURE__ */ new WeakMap();
|
|
8198
8680
|
function operationKey(resolved) {
|
|
8199
8681
|
return `${resolved.operation.httpMethod.toUpperCase()} ${resolved.operation.path}`;
|
|
@@ -8245,9 +8727,10 @@ function withNodeOperationOverrides(ctx) {
|
|
|
8245
8727
|
contextCache.set(ctx, next);
|
|
8246
8728
|
return next;
|
|
8247
8729
|
}
|
|
8730
|
+
const configOverrides = nodeOptions(ctx).operationOverrides ?? {};
|
|
8248
8731
|
let opsChanged = false;
|
|
8249
8732
|
const nextResolved = resolvedOperations.map((resolved) => {
|
|
8250
|
-
const override =
|
|
8733
|
+
const override = configOverrides[operationKey(resolved)];
|
|
8251
8734
|
if (!override) return resolved;
|
|
8252
8735
|
const methodName = override.methodName ?? resolved.methodName;
|
|
8253
8736
|
const mountOn = override.mountOn ?? resolved.mountOn;
|
|
@@ -8475,20 +8958,48 @@ function isOwnedPath(relPath, policy) {
|
|
|
8475
8958
|
const dir = topLevelDir(relPath);
|
|
8476
8959
|
return dir !== void 0 && policy.ownedServiceDirs.has(dir);
|
|
8477
8960
|
}
|
|
8961
|
+
function extractRelativeImportPaths(content, fromPath) {
|
|
8962
|
+
const dir = path$1.dirname(fromPath);
|
|
8963
|
+
const paths = [];
|
|
8964
|
+
const re = /from\s+['"](\.[^'"]+)['"]/g;
|
|
8965
|
+
let match;
|
|
8966
|
+
while ((match = re.exec(content)) !== null) paths.push(path$1.normalize(path$1.join(dir, match[1])) + ".ts");
|
|
8967
|
+
return paths;
|
|
8968
|
+
}
|
|
8478
8969
|
function applyLiveSurface(files, ctx, surface) {
|
|
8479
8970
|
const out = [];
|
|
8480
8971
|
const policy = buildLiveSurfacePolicy(ctx, surface);
|
|
8972
|
+
const filesByPath = new Map(files.map((f) => [f.path, f]));
|
|
8973
|
+
const dependencyAllowedPaths = /* @__PURE__ */ new Set();
|
|
8974
|
+
const queue = [];
|
|
8975
|
+
for (const f of files) {
|
|
8976
|
+
if (f.integrateTarget === false) continue;
|
|
8977
|
+
if (!canCreateNewPath(f.path, policy)) continue;
|
|
8978
|
+
for (const importPath of extractRelativeImportPaths(f.content, f.path)) if (filesByPath.has(importPath) && !canCreateNewPath(importPath, policy) && !dependencyAllowedPaths.has(importPath)) {
|
|
8979
|
+
dependencyAllowedPaths.add(importPath);
|
|
8980
|
+
queue.push(importPath);
|
|
8981
|
+
}
|
|
8982
|
+
}
|
|
8983
|
+
while (queue.length > 0) {
|
|
8984
|
+
const relPath = queue.pop();
|
|
8985
|
+
const file = filesByPath.get(relPath);
|
|
8986
|
+
if (!file) continue;
|
|
8987
|
+
for (const importPath of extractRelativeImportPaths(file.content, relPath)) if (filesByPath.has(importPath) && !canCreateNewPath(importPath, policy) && !dependencyAllowedPaths.has(importPath)) {
|
|
8988
|
+
dependencyAllowedPaths.add(importPath);
|
|
8989
|
+
queue.push(importPath);
|
|
8990
|
+
}
|
|
8991
|
+
}
|
|
8481
8992
|
for (const f of files) {
|
|
8482
8993
|
const ownedPath = isOwnedPath(f.path, policy);
|
|
8483
8994
|
if (f.integrateTarget === false) continue;
|
|
8484
8995
|
if (surface.protectedFiles.has(f.path)) continue;
|
|
8485
|
-
if (policy.hasExistingSdk && !policy.managedPaths.has(f.path) && !canCreateNewPath(f.path, policy)) continue;
|
|
8996
|
+
if (policy.hasExistingSdk && !policy.managedPaths.has(f.path) && !canCreateNewPath(f.path, policy) && !dependencyAllowedPaths.has(f.path)) continue;
|
|
8486
8997
|
if (surface.files.has(f.path) && !surface.autogenFiles.has(f.path) && !ownedPath) continue;
|
|
8487
8998
|
if (isUserOwnedAfterFirstEmit(f.path)) {
|
|
8488
8999
|
const dir = topLevelDir(f.path);
|
|
8489
9000
|
const isAdoptedDir = dir !== void 0 && policy.adoptedServiceDirs.has(dir);
|
|
8490
9001
|
const isManagedDir = ownedPath || isAdoptedDir;
|
|
8491
|
-
if (surface.files.has(f.path) && !surface.autogenFiles.has(f.path)) continue;
|
|
9002
|
+
if (surface.files.has(f.path) && !surface.autogenFiles.has(f.path) && !(ownedPath && policy.regenerateOwnedTests)) continue;
|
|
8492
9003
|
if (!isManagedDir && !surface.autogenFiles.has(f.path)) continue;
|
|
8493
9004
|
if (isManagedDir && !policy.regenerateOwnedTests) continue;
|
|
8494
9005
|
}
|
|
@@ -8582,7 +9093,9 @@ const nodeEmitter = {
|
|
|
8582
9093
|
const surface = getSurface(nodeCtx);
|
|
8583
9094
|
const enriched = enrichModelsForNode(models);
|
|
8584
9095
|
const discPlans = planDiscriminatedModels(enriched, nodeCtx);
|
|
8585
|
-
|
|
9096
|
+
const discriminatedNames = new Set(discPlans.keys());
|
|
9097
|
+
nodeCtx._discriminatedModelNames = discriminatedNames;
|
|
9098
|
+
setDiscriminatedModelNames(discriminatedNames);
|
|
8586
9099
|
const standardFiles = generateModelsAndSerializers(enriched, nodeCtx);
|
|
8587
9100
|
const discFiles = generateDiscriminatedFiles(discPlans, nodeCtx);
|
|
8588
9101
|
return applyLiveSurface([...standardFiles, ...discFiles], nodeCtx, surface);
|
|
@@ -9195,11 +9708,24 @@ function generateEnums$6(enums, ctx) {
|
|
|
9195
9708
|
const canonicalCls = className$5(canonicalName);
|
|
9196
9709
|
const aliasCls = className$5(enumDef.name);
|
|
9197
9710
|
const lines = [];
|
|
9198
|
-
|
|
9199
|
-
|
|
9200
|
-
|
|
9201
|
-
|
|
9202
|
-
|
|
9711
|
+
if (canonicalDir === dirName) {
|
|
9712
|
+
lines.push("from typing import TypeAlias");
|
|
9713
|
+
lines.push(`from .${fileName$2(canonicalName)} import ${canonicalCls}`);
|
|
9714
|
+
lines.push("");
|
|
9715
|
+
lines.push(`${aliasCls}: TypeAlias = ${canonicalCls}`);
|
|
9716
|
+
} else {
|
|
9717
|
+
const modPath = `${ctx.namespace}.${dirToModule(canonicalDir)}.models.${fileName$2(canonicalName)}`;
|
|
9718
|
+
lines.push("from typing import TYPE_CHECKING");
|
|
9719
|
+
lines.push("");
|
|
9720
|
+
lines.push("if TYPE_CHECKING:");
|
|
9721
|
+
lines.push(` from ${modPath} import ${canonicalCls} as ${aliasCls}`);
|
|
9722
|
+
lines.push("else:");
|
|
9723
|
+
lines.push(" def __getattr__(name: str):");
|
|
9724
|
+
lines.push(` if name == "${aliasCls}":`);
|
|
9725
|
+
lines.push(` from ${modPath} import ${canonicalCls}`);
|
|
9726
|
+
lines.push(` return ${canonicalCls}`);
|
|
9727
|
+
lines.push(" raise AttributeError(f\"module {__name__!r} has no attribute {name!r}\")");
|
|
9728
|
+
}
|
|
9203
9729
|
lines.push(`__all__ = ["${aliasCls}"]`);
|
|
9204
9730
|
files.push({
|
|
9205
9731
|
path: `src/${ctx.namespace}/${dirName}/models/${fileName$2(enumDef.name)}.py`,
|
|
@@ -9208,16 +9734,33 @@ function generateEnums$6(enums, ctx) {
|
|
|
9208
9734
|
overwriteExisting: true
|
|
9209
9735
|
});
|
|
9210
9736
|
for (const aliasName of compatAliases.get(enumDef.name) ?? []) {
|
|
9211
|
-
|
|
9212
|
-
|
|
9213
|
-
|
|
9214
|
-
|
|
9215
|
-
|
|
9216
|
-
|
|
9737
|
+
let compatContent;
|
|
9738
|
+
if (canonicalDir === dirName) compatContent = [
|
|
9739
|
+
"from typing import TypeAlias",
|
|
9740
|
+
`from .${fileName$2(canonicalName)} import ${canonicalCls}`,
|
|
9741
|
+
"",
|
|
9742
|
+
`${aliasName}: TypeAlias = ${canonicalCls}`,
|
|
9743
|
+
`__all__ = ["${aliasName}"]`
|
|
9744
|
+
].join("\n");
|
|
9745
|
+
else {
|
|
9746
|
+
const modPath = `${ctx.namespace}.${dirToModule(canonicalDir)}.models.${fileName$2(canonicalName)}`;
|
|
9747
|
+
compatContent = [
|
|
9748
|
+
"from typing import TYPE_CHECKING",
|
|
9217
9749
|
"",
|
|
9218
|
-
|
|
9750
|
+
"if TYPE_CHECKING:",
|
|
9751
|
+
` from ${modPath} import ${canonicalCls} as ${aliasName}`,
|
|
9752
|
+
"else:",
|
|
9753
|
+
" def __getattr__(name: str):",
|
|
9754
|
+
` if name == "${aliasName}":`,
|
|
9755
|
+
` from ${modPath} import ${canonicalCls}`,
|
|
9756
|
+
` return ${canonicalCls}`,
|
|
9757
|
+
" raise AttributeError(f\"module {__name__!r} has no attribute {name!r}\")",
|
|
9219
9758
|
`__all__ = ["${aliasName}"]`
|
|
9220
|
-
].join("\n")
|
|
9759
|
+
].join("\n");
|
|
9760
|
+
}
|
|
9761
|
+
files.push({
|
|
9762
|
+
path: `src/${ctx.namespace}/${dirName}/models/${fileName$2(aliasName)}.py`,
|
|
9763
|
+
content: compatContent,
|
|
9221
9764
|
integrateTarget: true,
|
|
9222
9765
|
overwriteExisting: true
|
|
9223
9766
|
});
|
|
@@ -9391,6 +9934,8 @@ function generateModels$6(models, ctx) {
|
|
|
9391
9934
|
const emittedFilePaths = /* @__PURE__ */ new Set();
|
|
9392
9935
|
const nonPaginatedRefs = collectNonPaginatedResponseModelNames(ctx.spec.services);
|
|
9393
9936
|
const listMetadataNeeded = collectReferencedListMetadataModels(models, nonPaginatedRefs);
|
|
9937
|
+
const discriminatorNames = /* @__PURE__ */ new Set();
|
|
9938
|
+
for (const m of models) if (m.discriminator) discriminatorNames.add(m.name);
|
|
9394
9939
|
for (const model of models) {
|
|
9395
9940
|
if (isListWrapperModel(model) && !nonPaginatedRefs.has(model.name)) continue;
|
|
9396
9941
|
if (isListMetadataModel(model) && !listMetadataNeeded.has(model.name)) continue;
|
|
@@ -9526,7 +10071,7 @@ function generateModels$6(models, ctx) {
|
|
|
9526
10071
|
typingImports.add("Dict");
|
|
9527
10072
|
for (const field of deduplicatedFields) collectTypingImports(field.type, typingImports);
|
|
9528
10073
|
if (deduplicatedFields.some((f) => isOptionalField(model.name, f, ctx))) typingImports.add("Optional");
|
|
9529
|
-
const usesDateTime = deduplicatedFields.some((f) => isDateTimeType(f.type));
|
|
10074
|
+
const usesDateTime = deduplicatedFields.some((f) => isDateTimeType$1(f.type));
|
|
9530
10075
|
const usesEnum = deps.enums.size > 0;
|
|
9531
10076
|
lines.push("from __future__ import annotations");
|
|
9532
10077
|
lines.push("");
|
|
@@ -9542,8 +10087,9 @@ function generateModels$6(models, ctx) {
|
|
|
9542
10087
|
for (const modelName of [...deps.models].sort()) {
|
|
9543
10088
|
if (modelName === model.name) continue;
|
|
9544
10089
|
const modelDir = resolveDir(modelToService.get(modelName));
|
|
9545
|
-
|
|
9546
|
-
|
|
10090
|
+
const importNames = discriminatorNames.has(modelName) ? `${className$5(modelName)}, ${className$5(modelName)}Variant` : className$5(modelName);
|
|
10091
|
+
if (modelDir === dirName) lines.push(`from .${fileName$2(modelName)} import ${importNames}`);
|
|
10092
|
+
else lines.push(`from ${ctx.namespace}.${dirToModule(modelDir)}.models.${fileName$2(modelName)} import ${importNames}`);
|
|
9547
10093
|
}
|
|
9548
10094
|
}
|
|
9549
10095
|
if (deps.enums.size > 0) for (const enumName of [...deps.enums].sort()) {
|
|
@@ -9564,9 +10110,16 @@ function generateModels$6(models, ctx) {
|
|
|
9564
10110
|
lines.push("");
|
|
9565
10111
|
const requiredFields = deduplicatedFields.filter((f) => !isOptionalField(model.name, f, ctx));
|
|
9566
10112
|
const optionalFields = deduplicatedFields.filter((f) => isOptionalField(model.name, f, ctx));
|
|
10113
|
+
const rewriteDiscriminatorType = (typeStr) => {
|
|
10114
|
+
for (const discName of discriminatorNames) {
|
|
10115
|
+
const quoted = `"${className$5(discName)}"`;
|
|
10116
|
+
if (typeStr.includes(quoted)) typeStr = typeStr.replace(quoted, `"${className$5(discName)}Variant"`);
|
|
10117
|
+
}
|
|
10118
|
+
return typeStr;
|
|
10119
|
+
};
|
|
9567
10120
|
for (const field of requiredFields) {
|
|
9568
10121
|
const pyFieldName = fieldName$5(field.name);
|
|
9569
|
-
const pyType = resolveModelFieldType(field.type);
|
|
10122
|
+
const pyType = rewriteDiscriminatorType(resolveModelFieldType(field.type));
|
|
9570
10123
|
if (field.description || field.deprecated) {
|
|
9571
10124
|
const parts = [];
|
|
9572
10125
|
if (field.description) parts.push(field.description);
|
|
@@ -9577,7 +10130,7 @@ function generateModels$6(models, ctx) {
|
|
|
9577
10130
|
}
|
|
9578
10131
|
for (const field of optionalFields) {
|
|
9579
10132
|
const pyFieldName = fieldName$5(field.name);
|
|
9580
|
-
const pyType = `Optional[${field.type.kind === "nullable" ? resolveModelFieldType(field.type.inner) : resolveModelFieldType(field.type)}]`;
|
|
10133
|
+
const pyType = `Optional[${rewriteDiscriminatorType(field.type.kind === "nullable" ? resolveModelFieldType(field.type.inner) : resolveModelFieldType(field.type))}]`;
|
|
9581
10134
|
if (field.description || field.deprecated) {
|
|
9582
10135
|
const parts = [];
|
|
9583
10136
|
if (field.description) parts.push(field.description);
|
|
@@ -9821,12 +10374,12 @@ function pythonLiteralDefault(value) {
|
|
|
9821
10374
|
return String(value);
|
|
9822
10375
|
}
|
|
9823
10376
|
function resolveModelFieldType(ref) {
|
|
9824
|
-
if (ref.kind === "nullable" && isDateTimeType(ref.inner)) return "Optional[datetime]";
|
|
9825
|
-
if (isDateTimeType(ref)) return "datetime";
|
|
10377
|
+
if (ref.kind === "nullable" && isDateTimeType$1(ref.inner)) return "Optional[datetime]";
|
|
10378
|
+
if (isDateTimeType$1(ref)) return "datetime";
|
|
9826
10379
|
return mapTypeRef$6(ref);
|
|
9827
10380
|
}
|
|
9828
|
-
function isDateTimeType(ref) {
|
|
9829
|
-
if (ref.kind === "nullable") return isDateTimeType(ref.inner);
|
|
10381
|
+
function isDateTimeType$1(ref) {
|
|
10382
|
+
if (ref.kind === "nullable") return isDateTimeType$1(ref.inner);
|
|
9830
10383
|
return ref.kind === "primitive" && ref.type === "string" && ref.format === "date-time";
|
|
9831
10384
|
}
|
|
9832
10385
|
/**
|
|
@@ -9887,7 +10440,7 @@ function renderDiscriminatedUnionPrelude(field, pyFieldName, wireKey, parentClas
|
|
|
9887
10440
|
prelude.push(`${indent}if ${rawVar} is None:`);
|
|
9888
10441
|
prelude.push(`${indent} ${valueVar} = None`);
|
|
9889
10442
|
prelude.push(`${indent}else:`);
|
|
9890
|
-
prelude.push(...dispatchBlock(
|
|
10443
|
+
prelude.push(...dispatchBlock(" "));
|
|
9891
10444
|
prelude.push(`${indent} ${valueVar} = ${clsVar}.from_dict(${dataVar})`);
|
|
9892
10445
|
return {
|
|
9893
10446
|
prelude,
|
|
@@ -9895,7 +10448,7 @@ function renderDiscriminatedUnionPrelude(field, pyFieldName, wireKey, parentClas
|
|
|
9895
10448
|
};
|
|
9896
10449
|
}
|
|
9897
10450
|
function deserializeField(ref, accessor, isRequired, walrusVar = "_v") {
|
|
9898
|
-
if (isDateTimeType(ref)) {
|
|
10451
|
+
if (isDateTimeType$1(ref)) {
|
|
9899
10452
|
if (isRequired) return `_parse_datetime(${accessor})`;
|
|
9900
10453
|
return `_parse_datetime(${walrusVar}) if (${walrusVar} := ${accessor}) is not None else None`;
|
|
9901
10454
|
}
|
|
@@ -9934,7 +10487,7 @@ function deserializeField(ref, accessor, isRequired, walrusVar = "_v") {
|
|
|
9934
10487
|
}
|
|
9935
10488
|
}
|
|
9936
10489
|
function serializeField(ref, accessor) {
|
|
9937
|
-
if (isDateTimeType(ref)) return `_format_datetime(${accessor})`;
|
|
10490
|
+
if (isDateTimeType$1(ref)) return `_format_datetime(${accessor})`;
|
|
9938
10491
|
switch (ref.kind) {
|
|
9939
10492
|
case "model": return `${accessor}.to_dict()`;
|
|
9940
10493
|
case "enum": return `${accessor}.value if isinstance(${accessor}, Enum) else ${accessor}`;
|
|
@@ -10078,7 +10631,8 @@ function emitWrapperMethod$5(lines, resolvedOp, wrapper, ctx, isAsync) {
|
|
|
10078
10631
|
else lines.push(` ${pyName}: ${pyType},`);
|
|
10079
10632
|
}
|
|
10080
10633
|
lines.push(" request_options: Optional[RequestOptions] = None,");
|
|
10081
|
-
const
|
|
10634
|
+
const isDiscriminatorResponse = wrapper.responseModelName ? !!ctx.spec.models.find((m) => m.name === wrapper.responseModelName)?.discriminator : false;
|
|
10635
|
+
const responseType = wrapper.responseModelName ? isDiscriminatorResponse ? className$5(wrapper.responseModelName) + "Variant" : className$5(wrapper.responseModelName) : "None";
|
|
10082
10636
|
lines.push(` ) -> ${responseType}:`);
|
|
10083
10637
|
lines.push(` """${formatWrapperDescription(wrapper.name)}."""`);
|
|
10084
10638
|
lines.push(" body: Dict[str, Any] = {");
|
|
@@ -10103,7 +10657,19 @@ function emitWrapperMethod$5(lines, resolvedOp, wrapper, ctx, isAsync) {
|
|
|
10103
10657
|
const pathExpr = buildPythonPathExpression(op.path);
|
|
10104
10658
|
const awaitPrefix = isAsync ? "await " : "";
|
|
10105
10659
|
lines.push("");
|
|
10106
|
-
if (wrapper.responseModelName) {
|
|
10660
|
+
if (wrapper.responseModelName && isDiscriminatorResponse) {
|
|
10661
|
+
const variantType = className$5(wrapper.responseModelName) + "Variant";
|
|
10662
|
+
lines.push(` return cast(`);
|
|
10663
|
+
lines.push(` ${variantType},`);
|
|
10664
|
+
lines.push(` ${awaitPrefix}self._client.request(`);
|
|
10665
|
+
lines.push(` method="${op.httpMethod.toUpperCase()}",`);
|
|
10666
|
+
lines.push(` path=${pathExpr},`);
|
|
10667
|
+
lines.push(" body=body,");
|
|
10668
|
+
lines.push(` model=${className$5(wrapper.responseModelName)}, # type: ignore[arg-type] # dispatcher; request only calls from_dict`);
|
|
10669
|
+
lines.push(" request_options=request_options,");
|
|
10670
|
+
lines.push(" )");
|
|
10671
|
+
lines.push(" )");
|
|
10672
|
+
} else if (wrapper.responseModelName) {
|
|
10107
10673
|
lines.push(` return ${awaitPrefix}self._client.request(`);
|
|
10108
10674
|
lines.push(` method="${op.httpMethod.toUpperCase()}",`);
|
|
10109
10675
|
lines.push(` path=${pathExpr},`);
|
|
@@ -10335,8 +10901,8 @@ function emitMethodSignature(lines, op, plan, method, isAsync, specEnumNames, mo
|
|
|
10335
10901
|
else if (isPaginated) {
|
|
10336
10902
|
const resolvedItem = resolvePageItemName(op.pagination.itemType, listWrapperNames, ctx);
|
|
10337
10903
|
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);
|
|
10904
|
+
} else if (isArrayResponse) returnType = `List[${ctx.spec.models.find((m) => m.name === plan.responseModelName)?.discriminator ? className$5(plan.responseModelName) + "Variant" : className$5(plan.responseModelName)}]`;
|
|
10905
|
+
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
10906
|
else returnType = "None";
|
|
10341
10907
|
lines.push(` ) -> ${returnType}:`);
|
|
10342
10908
|
return {
|
|
@@ -10627,6 +11193,7 @@ function emitMethodBody(lines, op, plan, meta, isAsync, modelImports, listWrappe
|
|
|
10627
11193
|
lines.push(" )");
|
|
10628
11194
|
} else if (plan.hasBody && op.requestBody) {
|
|
10629
11195
|
const responseModel = plan.responseModelName ? className$5(plan.responseModelName) : "None";
|
|
11196
|
+
const isBodyResponseDiscriminator = !!(plan.responseModelName ? ctx.spec.models.find((m) => m.name === plan.responseModelName) : null)?.discriminator;
|
|
10630
11197
|
const bodyGroupedParams = collectGroupedParamNames(op);
|
|
10631
11198
|
const bodyModel = ctx.spec.models.find((m) => op.requestBody?.kind === "model" && m.name === op.requestBody.name);
|
|
10632
11199
|
const bodyFieldNamesSet = /* @__PURE__ */ new Set();
|
|
@@ -10664,6 +11231,20 @@ function emitMethodBody(lines, op, plan, meta, isAsync, modelImports, listWrappe
|
|
|
10664
11231
|
lines.push(" request_options=request_options,");
|
|
10665
11232
|
lines.push(" )");
|
|
10666
11233
|
lines.push(` return [${itemModel}.from_dict(cast(Dict[str, Any], item)) for item in raw]`);
|
|
11234
|
+
} else if (isBodyResponseDiscriminator && responseModel !== "None") {
|
|
11235
|
+
const variantType = responseModel + "Variant";
|
|
11236
|
+
lines.push(` return cast(`);
|
|
11237
|
+
lines.push(` ${variantType},`);
|
|
11238
|
+
lines.push(` ${awaitPrefix}self._client.request(`);
|
|
11239
|
+
lines.push(` method="${httpMethod}",`);
|
|
11240
|
+
lines.push(` path=${pathStr},`);
|
|
11241
|
+
lines.push(` body=${bodyVarName},`);
|
|
11242
|
+
if (bodyHasParams) lines.push(" params=params,");
|
|
11243
|
+
lines.push(` model=${responseModel}, # type: ignore[arg-type] # dispatcher; request only calls from_dict`);
|
|
11244
|
+
if (plan.isIdempotentPost) lines.push(" idempotency_key=idempotency_key,");
|
|
11245
|
+
lines.push(" request_options=request_options,");
|
|
11246
|
+
lines.push(" )");
|
|
11247
|
+
lines.push(" )");
|
|
10667
11248
|
} else {
|
|
10668
11249
|
const bodyReturnPrefix = responseModel !== "None" ? "return " : "";
|
|
10669
11250
|
lines.push(` ${bodyReturnPrefix}${awaitPrefix}self._client.request(`);
|
|
@@ -10678,6 +11259,7 @@ function emitMethodBody(lines, op, plan, meta, isAsync, modelImports, listWrappe
|
|
|
10678
11259
|
}
|
|
10679
11260
|
} else {
|
|
10680
11261
|
const responseModel = plan.responseModelName ? className$5(plan.responseModelName) : "None";
|
|
11262
|
+
const isGetResponseDiscriminator = !!(plan.responseModelName ? ctx.spec.models.find((m) => m.name === plan.responseModelName) : null)?.discriminator;
|
|
10681
11263
|
const getGroupedParams = collectGroupedParamNames(op);
|
|
10682
11264
|
const hasGroups = (op.parameterGroups?.length ?? 0) > 0;
|
|
10683
11265
|
const visibleQueryParams = op.queryParams.filter((p) => !hiddenParams.has(p.name) && !getGroupedParams.has(p.name));
|
|
@@ -10720,6 +11302,18 @@ function emitMethodBody(lines, op, plan, meta, isAsync, modelImports, listWrappe
|
|
|
10720
11302
|
lines.push(" request_options=request_options,");
|
|
10721
11303
|
lines.push(" )");
|
|
10722
11304
|
lines.push(` return [${itemModel}.from_dict(cast(Dict[str, Any], item)) for item in raw]`);
|
|
11305
|
+
} else if (isGetResponseDiscriminator && responseModel !== "None") {
|
|
11306
|
+
const variantType = responseModel + "Variant";
|
|
11307
|
+
lines.push(` return cast(`);
|
|
11308
|
+
lines.push(` ${variantType},`);
|
|
11309
|
+
lines.push(` ${awaitPrefix}self._client.request(`);
|
|
11310
|
+
lines.push(` method="${httpMethod}",`);
|
|
11311
|
+
lines.push(` path=${pathStr},`);
|
|
11312
|
+
if (emittedParams) lines.push(" params=params,");
|
|
11313
|
+
lines.push(` model=${responseModel}, # type: ignore[arg-type] # dispatcher; request only calls from_dict`);
|
|
11314
|
+
lines.push(" request_options=request_options,");
|
|
11315
|
+
lines.push(" )");
|
|
11316
|
+
lines.push(" )");
|
|
10723
11317
|
} else {
|
|
10724
11318
|
const returnPrefix = responseModel !== "None" ? "return " : "";
|
|
10725
11319
|
lines.push(` ${returnPrefix}${awaitPrefix}self._client.request(`);
|
|
@@ -10808,6 +11402,9 @@ function generateResources$6(services, ctx) {
|
|
|
10808
11402
|
const plan = planOperation(op);
|
|
10809
11403
|
if (plan.responseModelName) {
|
|
10810
11404
|
if (!listWrapperNames.has(plan.responseModelName) || !plan.isPaginated) modelImports.add(plan.responseModelName);
|
|
11405
|
+
if (!plan.isPaginated) {
|
|
11406
|
+
if (ctx.spec.models.find((m) => m.name === plan.responseModelName)?.discriminator) modelImports.add(plan.responseModelName + "Variant");
|
|
11407
|
+
}
|
|
10811
11408
|
}
|
|
10812
11409
|
if (op.requestBody?.kind === "model") {
|
|
10813
11410
|
const requestBodyRef = op.requestBody;
|
|
@@ -10867,10 +11464,15 @@ function generateResources$6(services, ctx) {
|
|
|
10867
11464
|
}
|
|
10868
11465
|
}
|
|
10869
11466
|
const localSet = new Set(localModels);
|
|
11467
|
+
const variantToFile = /* @__PURE__ */ new Map();
|
|
11468
|
+
for (const model of ctx.spec.models) if (model.discriminator) variantToFile.set(model.name + "Variant", fileName$2(model.name));
|
|
10870
11469
|
if (localModels.length > 0) lines.push(`from .models import ${localModels.map((n) => className$5(n)).join(", ")}`);
|
|
10871
11470
|
for (const [csDir, names] of [...crossServiceModels].sort()) {
|
|
10872
11471
|
const unique = names.filter((n) => !localSet.has(n));
|
|
10873
|
-
for (const n of unique)
|
|
11472
|
+
for (const n of unique) {
|
|
11473
|
+
const filePath = variantToFile.get(n) ?? fileName$2(n);
|
|
11474
|
+
lines.push(`from ${ctx.namespace}.${dirToModule(csDir)}.models.${filePath} import ${className$5(n)}`);
|
|
11475
|
+
}
|
|
10874
11476
|
}
|
|
10875
11477
|
const enumToServiceMap = placement.enumToService;
|
|
10876
11478
|
const localEnums = [];
|
|
@@ -11101,11 +11703,6 @@ const NON_SPEC_SERVICES = [
|
|
|
11101
11703
|
description: "Passwordless (magic-link) session endpoints, not yet in the OpenAPI spec.",
|
|
11102
11704
|
hasClientAccessor: true
|
|
11103
11705
|
},
|
|
11104
|
-
{
|
|
11105
|
-
id: "vault",
|
|
11106
|
-
description: "Vault KV storage, key operations, and client-side AES-GCM encrypt/decrypt.",
|
|
11107
|
-
hasClientAccessor: true
|
|
11108
|
-
},
|
|
11109
11706
|
{
|
|
11110
11707
|
id: "webhook_verification",
|
|
11111
11708
|
description: "Webhook signature verification and event deserialization (H01/H02)."
|
|
@@ -11134,14 +11731,6 @@ const PYTHON_NON_SPEC_WIRING = {
|
|
|
11134
11731
|
ctorArg: "self",
|
|
11135
11732
|
docstring: "Passwordless authentication sessions."
|
|
11136
11733
|
},
|
|
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
11734
|
actions: {
|
|
11146
11735
|
importLine: "from .actions import Actions, AsyncActions",
|
|
11147
11736
|
prop: "actions",
|
|
@@ -11469,16 +12058,14 @@ function generateModelFixture$4(model, modelMap, enumMap) {
|
|
|
11469
12058
|
});
|
|
11470
12059
|
for (const field of deduplicatedFields) {
|
|
11471
12060
|
const wireName = field.name;
|
|
11472
|
-
if (field.example !== void 0) fixture[wireName] = field.example;
|
|
12061
|
+
if (field.example !== void 0) fixture[wireName] = normalizeDateTimeExample(field, field.example);
|
|
11473
12062
|
else fixture[wireName] = generateFieldValue$3(field.type, field.name, model.name, modelMap, enumMap);
|
|
11474
12063
|
}
|
|
11475
12064
|
if (model.discriminator) {
|
|
11476
12065
|
const [firstValue, variantName] = Object.entries(model.discriminator.mapping)[0];
|
|
11477
12066
|
fixture[model.discriminator.property] = firstValue;
|
|
11478
12067
|
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
|
-
}
|
|
12068
|
+
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
12069
|
}
|
|
11483
12070
|
return fixture;
|
|
11484
12071
|
}
|
|
@@ -11505,6 +12092,20 @@ function generateFieldValue$3(ref, fieldName, modelName, modelMap, enumMap) {
|
|
|
11505
12092
|
case "map": return { key: generateFieldValue$3(ref.valueType, "value", modelName, modelMap, enumMap) };
|
|
11506
12093
|
}
|
|
11507
12094
|
}
|
|
12095
|
+
/**
|
|
12096
|
+
* Normalize date-time example values to millisecond precision so that
|
|
12097
|
+
* round-trip tests (from_dict → to_dict) produce identical output.
|
|
12098
|
+
* Python's _format_datetime always serializes with milliseconds.
|
|
12099
|
+
*/
|
|
12100
|
+
function normalizeDateTimeExample(field, value) {
|
|
12101
|
+
if (typeof value !== "string") return value;
|
|
12102
|
+
const ref = field.type.kind === "nullable" ? field.type.inner : field.type;
|
|
12103
|
+
if (ref?.kind !== "primitive" || ref?.type !== "string" || ref?.format !== "date-time") return value;
|
|
12104
|
+
const match = value.match(/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(\.\d+)?(Z|[+-]\d{2}:\d{2})$/);
|
|
12105
|
+
if (!match) return value;
|
|
12106
|
+
const [, prefix, , suffix] = match;
|
|
12107
|
+
return `${prefix}.000${suffix}`;
|
|
12108
|
+
}
|
|
11508
12109
|
function generatePrimitiveValue$3(type, format, name, modelName) {
|
|
11509
12110
|
switch (type) {
|
|
11510
12111
|
case "string":
|
|
@@ -11609,7 +12210,22 @@ function generateServiceTest$2(service, spec, ctx, accessPaths, resolvedOps) {
|
|
|
11609
12210
|
const enumImports = /* @__PURE__ */ new Set();
|
|
11610
12211
|
for (const op of service.operations) {
|
|
11611
12212
|
const plan = planOperation(op);
|
|
11612
|
-
if (plan.responseModelName)
|
|
12213
|
+
if (plan.responseModelName) {
|
|
12214
|
+
modelImports.add(plan.responseModelName);
|
|
12215
|
+
if (!plan.isPaginated) {
|
|
12216
|
+
const resolvedVariantClass = resolvePaginatedItemClass(plan.responseModelName, spec);
|
|
12217
|
+
if (resolvedVariantClass && resolvedVariantClass !== className$5(plan.responseModelName)) {
|
|
12218
|
+
const responseModel = spec.models.find((m) => m.name === plan.responseModelName);
|
|
12219
|
+
const disc = responseModel && responseModel.discriminator;
|
|
12220
|
+
if (disc) {
|
|
12221
|
+
for (const variantName of Object.values(disc.mapping)) if (className$5(variantName) === resolvedVariantClass) {
|
|
12222
|
+
modelImports.add(variantName);
|
|
12223
|
+
break;
|
|
12224
|
+
}
|
|
12225
|
+
}
|
|
12226
|
+
}
|
|
12227
|
+
}
|
|
12228
|
+
}
|
|
11613
12229
|
if (op.pagination?.itemType.kind === "model") {
|
|
11614
12230
|
modelImports.add(op.pagination.itemType.name);
|
|
11615
12231
|
let paginationItemName = op.pagination.itemType.name;
|
|
@@ -11652,10 +12268,12 @@ function generateServiceTest$2(service, spec, ctx, accessPaths, resolvedOps) {
|
|
|
11652
12268
|
if (param.type.kind === "nullable" && param.type.inner.kind === "enum") enumImports.add(param.type.inner.name);
|
|
11653
12269
|
}
|
|
11654
12270
|
}
|
|
12271
|
+
const nonPaginatedRefs = collectNonPaginatedResponseModelNames(spec.services);
|
|
11655
12272
|
const actualImports = [...modelImports].filter((name) => {
|
|
11656
12273
|
const model = spec.models.find((m) => m.name === name);
|
|
11657
12274
|
if (!model) return true;
|
|
11658
|
-
|
|
12275
|
+
if (isListWrapperModel(model) && !nonPaginatedRefs.has(name)) return false;
|
|
12276
|
+
return true;
|
|
11659
12277
|
});
|
|
11660
12278
|
const placement = computeSchemaPlacement(spec, ctx);
|
|
11661
12279
|
const modelToServiceMap = placement.modelToService;
|
|
@@ -11794,14 +12412,22 @@ function generateServiceTest$2(service, spec, ctx, accessPaths, resolvedOps) {
|
|
|
11794
12412
|
const modelName = plan.responseModelName;
|
|
11795
12413
|
const fixtureName = `${fileName$2(modelName)}.json`;
|
|
11796
12414
|
const modelClass = className$5(modelName);
|
|
12415
|
+
const resolvedClass = resolvePaginatedItemClass(modelName, spec) ?? modelClass;
|
|
12416
|
+
const resolvedModelName = resolvedClass !== modelClass ? (() => {
|
|
12417
|
+
const disc = spec.models.find((m) => m.name === modelName)?.discriminator;
|
|
12418
|
+
if (disc) {
|
|
12419
|
+
for (const variantName of Object.values(disc.mapping)) if (className$5(variantName) === resolvedClass) return variantName;
|
|
12420
|
+
}
|
|
12421
|
+
return modelName;
|
|
12422
|
+
})() : modelName;
|
|
11797
12423
|
lines.push(` def test_${method}(self, workos, httpx_mock):`);
|
|
11798
12424
|
lines.push(` httpx_mock.add_response(`);
|
|
11799
12425
|
lines.push(` json=load_fixture("${fixtureName}"),`);
|
|
11800
12426
|
lines.push(" )");
|
|
11801
12427
|
const args = buildTestArgs$1(op, spec, hiddenParams);
|
|
11802
12428
|
lines.push(` result = workos.${propName}.${method}(${args})`);
|
|
11803
|
-
lines.push(` assert isinstance(result, ${
|
|
11804
|
-
const assertFields = pickAssertableFields(
|
|
12429
|
+
lines.push(` assert isinstance(result, ${resolvedClass})`);
|
|
12430
|
+
const assertFields = pickAssertableFields(resolvedModelName, spec);
|
|
11805
12431
|
for (const af of assertFields) {
|
|
11806
12432
|
const op_ = af.isBool ? "is" : "==";
|
|
11807
12433
|
lines.push(` assert result.${af.field} ${op_} ${af.value}`);
|
|
@@ -12023,11 +12649,19 @@ function generateServiceTest$2(service, spec, ctx, accessPaths, resolvedOps) {
|
|
|
12023
12649
|
const modelName = plan.responseModelName;
|
|
12024
12650
|
const fixtureName = `${fileName$2(modelName)}.json`;
|
|
12025
12651
|
const modelClass = className$5(modelName);
|
|
12652
|
+
const asyncResolvedClass = resolvePaginatedItemClass(modelName, spec) ?? modelClass;
|
|
12653
|
+
const asyncResolvedModelName = asyncResolvedClass !== modelClass ? (() => {
|
|
12654
|
+
const disc = spec.models.find((m) => m.name === modelName)?.discriminator;
|
|
12655
|
+
if (disc) {
|
|
12656
|
+
for (const variantName of Object.values(disc.mapping)) if (className$5(variantName) === asyncResolvedClass) return variantName;
|
|
12657
|
+
}
|
|
12658
|
+
return modelName;
|
|
12659
|
+
})() : modelName;
|
|
12026
12660
|
pushAsyncTestDef(lines, ` async def test_${method}(self, async_workos, httpx_mock):`);
|
|
12027
12661
|
lines.push(` httpx_mock.add_response(json=load_fixture("${fixtureName}"))`);
|
|
12028
12662
|
lines.push(` result = await async_workos.${propName}.${method}(${asyncArgs})`);
|
|
12029
|
-
lines.push(` assert isinstance(result, ${
|
|
12030
|
-
const assertFields = pickAssertableFields(
|
|
12663
|
+
lines.push(` assert isinstance(result, ${asyncResolvedClass})`);
|
|
12664
|
+
const assertFields = pickAssertableFields(asyncResolvedModelName, spec);
|
|
12031
12665
|
for (const af of assertFields) {
|
|
12032
12666
|
const op_ = af.isBool ? "is" : "==";
|
|
12033
12667
|
lines.push(` assert result.${af.field} ${op_} ${af.value}`);
|
|
@@ -12154,7 +12788,7 @@ function emitWrapperTests$1(lines, resolvedOps, propName, spec, ctx, isAsync) {
|
|
|
12154
12788
|
for (const wrapper of r.wrappers) {
|
|
12155
12789
|
const method = wrapper.name;
|
|
12156
12790
|
const wrapperParams = resolveWrapperParams(wrapper, ctx);
|
|
12157
|
-
const
|
|
12791
|
+
const resolvedResponseClass = wrapper.responseModelName ? resolvePaginatedItemClass(wrapper.responseModelName, spec) ?? className$5(wrapper.responseModelName) : null;
|
|
12158
12792
|
const fixtureName = wrapper.responseModelName ? `${fileName$2(wrapper.responseModelName)}.json` : null;
|
|
12159
12793
|
const argParts = [];
|
|
12160
12794
|
for (const { paramName, field, isOptional } of wrapperParams) {
|
|
@@ -12170,7 +12804,7 @@ function emitWrapperTests$1(lines, resolvedOps, propName, spec, ctx, isAsync) {
|
|
|
12170
12804
|
if (fixtureName) {
|
|
12171
12805
|
lines.push(` httpx_mock.add_response(json=load_fixture("${fixtureName}"))`);
|
|
12172
12806
|
lines.push(` result = await async_workos.${propName}.${method}(${args})`);
|
|
12173
|
-
if (
|
|
12807
|
+
if (resolvedResponseClass) lines.push(` assert isinstance(result, ${resolvedResponseClass})`);
|
|
12174
12808
|
} else {
|
|
12175
12809
|
lines.push(" httpx_mock.add_response(json={})");
|
|
12176
12810
|
lines.push(` await async_workos.${propName}.${method}(${args})`);
|
|
@@ -12180,7 +12814,7 @@ function emitWrapperTests$1(lines, resolvedOps, propName, spec, ctx, isAsync) {
|
|
|
12180
12814
|
if (fixtureName) {
|
|
12181
12815
|
lines.push(` httpx_mock.add_response(json=load_fixture("${fixtureName}"))`);
|
|
12182
12816
|
lines.push(` result = workos.${propName}.${method}(${args})`);
|
|
12183
|
-
if (
|
|
12817
|
+
if (resolvedResponseClass) lines.push(` assert isinstance(result, ${resolvedResponseClass})`);
|
|
12184
12818
|
} else {
|
|
12185
12819
|
lines.push(" httpx_mock.add_response(json={})");
|
|
12186
12820
|
lines.push(` workos.${propName}.${method}(${args})`);
|
|
@@ -12950,8 +13584,9 @@ function generateModels$5(models, ctx) {
|
|
|
12950
13584
|
overwriteExisting: true
|
|
12951
13585
|
});
|
|
12952
13586
|
const nonPaginatedRefs = collectNonPaginatedResponseModelNames(ctx.spec.services);
|
|
13587
|
+
const listMetadataNeeded = collectReferencedListMetadataModels(models, nonPaginatedRefs);
|
|
12953
13588
|
for (const model of models) {
|
|
12954
|
-
if (isListMetadataModel(model)) continue;
|
|
13589
|
+
if (isListMetadataModel(model) && !listMetadataNeeded.has(model.name)) continue;
|
|
12955
13590
|
if (isListWrapperModel(model) && !nonPaginatedRefs.has(model.name)) continue;
|
|
12956
13591
|
const name = className$4(model.name);
|
|
12957
13592
|
const lines = [];
|
|
@@ -13484,7 +14119,8 @@ function generateMethod$2(lines, op, service, ctx, modelMap, resolvedOp) {
|
|
|
13484
14119
|
const method = resolveMethodName$4(op, service, ctx);
|
|
13485
14120
|
const hiddenParams = new Set([...Object.keys(getOpDefaults(resolvedOp)), ...getOpInferFromClient(resolvedOp)]);
|
|
13486
14121
|
const isRedirect = isRedirectEndpoint(op, resolvedOp);
|
|
13487
|
-
const
|
|
14122
|
+
const materializeQueryDefaults = !isRedirect;
|
|
14123
|
+
const params = buildMethodParams(op, plan, modelMap, ctx, hiddenParams, { materializeQueryDefaults });
|
|
13488
14124
|
const returnType = isRedirect ? "string" : getReturnType(plan, ctx);
|
|
13489
14125
|
const docParts = [];
|
|
13490
14126
|
if (op.description) docParts.push(op.description);
|
|
@@ -13532,7 +14168,7 @@ function generateMethod$2(lines, op, service, ctx, modelMap, resolvedOp) {
|
|
|
13532
14168
|
const phpName = fieldName$4(q.name);
|
|
13533
14169
|
if (seenDocParams.has(phpName)) continue;
|
|
13534
14170
|
seenDocParams.add(phpName);
|
|
13535
|
-
const hasEnumDefault = q
|
|
14171
|
+
const hasEnumDefault = shouldMaterializeQueryDefault(q, materializeQueryDefaults);
|
|
13536
14172
|
const nullSuffix = !q.required && !hasEnumDefault && !docType.endsWith("|null") ? "|null" : "";
|
|
13537
14173
|
const prefix = q.deprecated ? "(deprecated) " : "";
|
|
13538
14174
|
let desc = q.description ? ` ${prefix}${q.description}` : q.deprecated ? " (deprecated)" : "";
|
|
@@ -13563,7 +14199,7 @@ function generateMethod$2(lines, op, service, ctx, modelMap, resolvedOp) {
|
|
|
13563
14199
|
const httpMethod = op.httpMethod.toUpperCase();
|
|
13564
14200
|
const path = buildPathString(op);
|
|
13565
14201
|
if (isRedirect) {
|
|
13566
|
-
const queryLines = buildQueryArray(op, hiddenParams);
|
|
14202
|
+
const queryLines = buildQueryArray(op, hiddenParams, { materializeQueryDefaults });
|
|
13567
14203
|
const hasDefaults = Object.keys(getOpDefaults(resolvedOp)).length > 0;
|
|
13568
14204
|
const hasInferred = getOpInferFromClient(resolvedOp).length > 0;
|
|
13569
14205
|
const hasGroups = (op.parameterGroups?.length ?? 0) > 0;
|
|
@@ -13707,7 +14343,7 @@ function generateMethod$2(lines, op, service, ctx, modelMap, resolvedOp) {
|
|
|
13707
14343
|
}
|
|
13708
14344
|
lines.push(" }");
|
|
13709
14345
|
}
|
|
13710
|
-
function buildMethodParams(op, plan, modelMap, ctx, hiddenParams) {
|
|
14346
|
+
function buildMethodParams(op, plan, modelMap, ctx, hiddenParams, opts) {
|
|
13711
14347
|
const required = [];
|
|
13712
14348
|
const optional = [];
|
|
13713
14349
|
const usedNames = /* @__PURE__ */ new Set();
|
|
@@ -13755,7 +14391,7 @@ function buildMethodParams(op, plan, modelMap, ctx, hiddenParams) {
|
|
|
13755
14391
|
if (usedNames.has(phpName)) continue;
|
|
13756
14392
|
usedNames.add(phpName);
|
|
13757
14393
|
if (q.required) required.push(`${phpType} $${phpName}`);
|
|
13758
|
-
else if (q
|
|
14394
|
+
else if (shouldMaterializeQueryDefault(q, opts?.materializeQueryDefaults ?? true)) {
|
|
13759
14395
|
const enumType = mapTypeRef$5(q.type, { qualified: true });
|
|
13760
14396
|
const caseName = toPascalCase(String(q.default));
|
|
13761
14397
|
optional.push(`${enumType} $${phpName} = ${enumType}::${caseName}`);
|
|
@@ -13801,19 +14437,31 @@ function isEnumType(ref) {
|
|
|
13801
14437
|
if (ref.kind === "nullable") return isEnumType(ref.inner);
|
|
13802
14438
|
return false;
|
|
13803
14439
|
}
|
|
13804
|
-
function
|
|
14440
|
+
function isDateTimeType(ref) {
|
|
14441
|
+
if (ref.kind === "primitive" && ref.format === "date-time") return true;
|
|
14442
|
+
if (ref.kind === "nullable") return isDateTimeType(ref.inner);
|
|
14443
|
+
return false;
|
|
14444
|
+
}
|
|
14445
|
+
function buildQueryArray(op, hiddenParams, opts) {
|
|
13805
14446
|
const hidden = hiddenParams ?? /* @__PURE__ */ new Set();
|
|
13806
14447
|
const groupedParams = collectGroupedParamNames(op);
|
|
13807
14448
|
return op.queryParams.filter((q) => !hidden.has(q.name) && !groupedParams.has(q.name)).map((q) => {
|
|
13808
14449
|
const phpName = fieldName$4(q.name);
|
|
13809
14450
|
if (isEnumType(q.type)) {
|
|
13810
|
-
const hasEnumDefault = q
|
|
14451
|
+
const hasEnumDefault = shouldMaterializeQueryDefault(q, opts?.materializeQueryDefaults ?? true);
|
|
13811
14452
|
const nullsafe = q.required || hasEnumDefault ? "" : "?";
|
|
13812
14453
|
return `'${q.name}' => $${phpName}${nullsafe}->value,`;
|
|
13813
14454
|
}
|
|
14455
|
+
if (isDateTimeType(q.type)) {
|
|
14456
|
+
const nullsafe = q.required ? "" : "?";
|
|
14457
|
+
return `'${q.name}' => $${phpName}${nullsafe}->format(\\DateTimeInterface::RFC3339_EXTENDED),`;
|
|
14458
|
+
}
|
|
13814
14459
|
return `'${q.name}' => $${phpName},`;
|
|
13815
14460
|
});
|
|
13816
14461
|
}
|
|
14462
|
+
function shouldMaterializeQueryDefault(param, enabled) {
|
|
14463
|
+
return enabled && param.default != null && param.type.kind === "enum";
|
|
14464
|
+
}
|
|
13817
14465
|
function phpLiteral(value) {
|
|
13818
14466
|
if (typeof value === "string") return `'${value}'`;
|
|
13819
14467
|
if (typeof value === "number") return String(value);
|
|
@@ -14005,8 +14653,10 @@ function generateFixtures$3(spec) {
|
|
|
14005
14653
|
const modelMap = new Map(spec.models.map((m) => [m.name, m]));
|
|
14006
14654
|
const enumMap = new Map(spec.enums.map((e) => [e.name, e]));
|
|
14007
14655
|
const files = [];
|
|
14656
|
+
const nonPaginatedRefs = collectNonPaginatedResponseModelNames(spec.services);
|
|
14657
|
+
const listMetadataNeeded = collectReferencedListMetadataModels(spec.models, nonPaginatedRefs);
|
|
14008
14658
|
for (const model of spec.models) {
|
|
14009
|
-
if (isListMetadataModel(model)) continue;
|
|
14659
|
+
if (isListMetadataModel(model) && !listMetadataNeeded.has(model.name)) continue;
|
|
14010
14660
|
if (isListWrapperModel(model)) continue;
|
|
14011
14661
|
const fixture = generateModelFixture$3(model, modelMap, enumMap);
|
|
14012
14662
|
files.push({
|
|
@@ -14351,13 +15001,15 @@ function buildTestArgs(op, ctx, opts) {
|
|
|
14351
15001
|
}
|
|
14352
15002
|
function generateTestValue(ref, ctx) {
|
|
14353
15003
|
switch (ref.kind) {
|
|
14354
|
-
case "primitive":
|
|
14355
|
-
|
|
14356
|
-
|
|
14357
|
-
|
|
14358
|
-
|
|
14359
|
-
|
|
14360
|
-
|
|
15004
|
+
case "primitive":
|
|
15005
|
+
if (ref.format === "date-time") return "new \\DateTimeImmutable('2023-01-01T00:00:00Z')";
|
|
15006
|
+
switch (ref.type) {
|
|
15007
|
+
case "string": return "'test_value'";
|
|
15008
|
+
case "integer": return "1";
|
|
15009
|
+
case "number": return "1.0";
|
|
15010
|
+
case "boolean": return "true";
|
|
15011
|
+
default: return "'test_value'";
|
|
15012
|
+
}
|
|
14361
15013
|
case "enum":
|
|
14362
15014
|
if (ctx && ref.name) {
|
|
14363
15015
|
const e = ctx.spec.enums.find((en) => en.name === ref.name);
|
|
@@ -14444,7 +15096,8 @@ function emitQueryAssertions(lines, op, ctx, hidden) {
|
|
|
14444
15096
|
if (innerType.kind === "enum" && innerType.name) {
|
|
14445
15097
|
const e = ctx.spec.enums.find((en) => en.name === innerType.name);
|
|
14446
15098
|
if (e && e.values.length > 0) lines.push(` $this->assertSame('${e.values[0].value}', $query['${q.name}']);`);
|
|
14447
|
-
} else if (innerType.kind === "primitive")
|
|
15099
|
+
} else if (innerType.kind === "primitive") if (innerType.format === "date-time") lines.push(` $this->assertArrayHasKey('${q.name}', $query);`);
|
|
15100
|
+
else switch (innerType.type) {
|
|
14448
15101
|
case "string":
|
|
14449
15102
|
lines.push(` $this->assertSame('test_value', $query['${q.name}']);`);
|
|
14450
15103
|
break;
|
|
@@ -15154,6 +15807,8 @@ function generateEnums$4(enums, ctx) {
|
|
|
15154
15807
|
content: lines.join("\n"),
|
|
15155
15808
|
overwriteExisting: true
|
|
15156
15809
|
});
|
|
15810
|
+
const eventConstantsFile = generateEventConstantsFile(enums);
|
|
15811
|
+
if (eventConstantsFile) files.push(eventConstantsFile);
|
|
15157
15812
|
return files;
|
|
15158
15813
|
}
|
|
15159
15814
|
/** Known acronyms to preserve as single tokens during humanization. */
|
|
@@ -15214,6 +15869,56 @@ function collectEnumAliasOf$2(enums) {
|
|
|
15214
15869
|
}
|
|
15215
15870
|
return aliasOf;
|
|
15216
15871
|
}
|
|
15872
|
+
function generateEventConstantsFile(enums) {
|
|
15873
|
+
const enumDef = findWebhookEventEnum(enums);
|
|
15874
|
+
if (!enumDef) return null;
|
|
15875
|
+
const lines = [];
|
|
15876
|
+
lines.push("package events");
|
|
15877
|
+
lines.push("");
|
|
15878
|
+
lines.push("// Event is a WorkOS event type.");
|
|
15879
|
+
lines.push("type Event string");
|
|
15880
|
+
lines.push("");
|
|
15881
|
+
lines.push("const (");
|
|
15882
|
+
const seenValues = /* @__PURE__ */ new Set();
|
|
15883
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
15884
|
+
for (const value of enumDef.values) {
|
|
15885
|
+
const valueStr = String(value.value);
|
|
15886
|
+
if (seenValues.has(valueStr)) continue;
|
|
15887
|
+
seenValues.add(valueStr);
|
|
15888
|
+
const constName = uniqueEventConstantName(valueStr, usedNames);
|
|
15889
|
+
usedNames.add(constName);
|
|
15890
|
+
if (value.description) lines.push(`\t// ${constName} is ${value.description}.`);
|
|
15891
|
+
if (value.deprecated) {
|
|
15892
|
+
if (value.description) lines.push(" //");
|
|
15893
|
+
lines.push(" // Deprecated: this value is deprecated.");
|
|
15894
|
+
}
|
|
15895
|
+
lines.push(`\t${constName} = "${escapeGoString$1(valueStr)}"`);
|
|
15896
|
+
}
|
|
15897
|
+
lines.push(")");
|
|
15898
|
+
lines.push("");
|
|
15899
|
+
return {
|
|
15900
|
+
path: "pkg/events/events.go",
|
|
15901
|
+
content: lines.join("\n"),
|
|
15902
|
+
overwriteExisting: true
|
|
15903
|
+
};
|
|
15904
|
+
}
|
|
15905
|
+
function findWebhookEventEnum(enums) {
|
|
15906
|
+
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;
|
|
15907
|
+
}
|
|
15908
|
+
function isWebhookEventEnumName(name) {
|
|
15909
|
+
const normalized = name.toLowerCase();
|
|
15910
|
+
return normalized.includes("webhook") && normalized.includes("event");
|
|
15911
|
+
}
|
|
15912
|
+
function uniqueEventConstantName(value, usedNames) {
|
|
15913
|
+
const base = className$3(value);
|
|
15914
|
+
if (!usedNames.has(base)) return base;
|
|
15915
|
+
let suffix = 2;
|
|
15916
|
+
while (usedNames.has(`${base}${suffix}`)) suffix++;
|
|
15917
|
+
return `${base}${suffix}`;
|
|
15918
|
+
}
|
|
15919
|
+
function escapeGoString$1(value) {
|
|
15920
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
15921
|
+
}
|
|
15217
15922
|
//#endregion
|
|
15218
15923
|
//#region src/go/wrappers.ts
|
|
15219
15924
|
/**
|
|
@@ -16262,7 +16967,7 @@ function generateClient$4(spec, ctx) {
|
|
|
16262
16967
|
return [generateWorkOSFile(spec, ctx)];
|
|
16263
16968
|
}
|
|
16264
16969
|
/**
|
|
16265
|
-
* Non-spec services marked with `hasClientAccessor: true` (passwordless
|
|
16970
|
+
* Non-spec services marked with `hasClientAccessor: true` (e.g. passwordless)
|
|
16266
16971
|
* are included in the generated Client struct, constructor, and accessor methods
|
|
16267
16972
|
* — identical to spec-driven services. Their service type (e.g. PasswordlessService)
|
|
16268
16973
|
* is defined in a hand-written @oagen-ignore-file, but the Client wiring is generated.
|
|
@@ -20031,12 +20736,19 @@ function generateEnums$2(enums, _ctx) {
|
|
|
20031
20736
|
hashGroups.get(hash).push(enumDef);
|
|
20032
20737
|
}
|
|
20033
20738
|
const aliasOf = /* @__PURE__ */ new Map();
|
|
20739
|
+
const sharedSortEmitters = /* @__PURE__ */ new Set();
|
|
20034
20740
|
for (const [, group] of hashGroups) {
|
|
20035
|
-
if (group.length <= 1)
|
|
20741
|
+
if (group.length <= 1) {
|
|
20742
|
+
if (group.length === 1 && isSharedSortOrderEnum(group[0])) {
|
|
20743
|
+
enumCanonicalMap.set(group[0].name, "SortOrder");
|
|
20744
|
+
sharedSortEmitters.add(group[0].name);
|
|
20745
|
+
}
|
|
20746
|
+
continue;
|
|
20747
|
+
}
|
|
20036
20748
|
if (group.every(isSharedSortOrderEnum)) {
|
|
20037
20749
|
const [canonical, ...rest] = [...group].sort((a, b) => a.name.localeCompare(b.name));
|
|
20038
|
-
|
|
20039
|
-
for (const enumDef of rest) enumCanonicalMap.set(enumDef.name, "SortOrder");
|
|
20750
|
+
sharedSortEmitters.add(canonical.name);
|
|
20751
|
+
for (const enumDef of [canonical, ...rest]) enumCanonicalMap.set(enumDef.name, "SortOrder");
|
|
20040
20752
|
continue;
|
|
20041
20753
|
}
|
|
20042
20754
|
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 +20762,7 @@ function generateEnums$2(enums, _ctx) {
|
|
|
20050
20762
|
for (const enumDef of enums) {
|
|
20051
20763
|
if (enumDef.values.length === 0) continue;
|
|
20052
20764
|
const typeName = canonicalEnumTypeName(enumDef);
|
|
20053
|
-
const canonicalName =
|
|
20765
|
+
const canonicalName = sharedSortEmitters.has(enumDef.name) ? void 0 : aliasOf.get(enumDef.name) ?? enumCanonicalMap.get(enumDef.name);
|
|
20054
20766
|
if (canonicalName) {
|
|
20055
20767
|
const canonicalType = className$1(canonicalName);
|
|
20056
20768
|
if (typeName === canonicalType) continue;
|
|
@@ -24298,11 +25010,6 @@ const NON_SPEC_ACCESSORS = {
|
|
|
24298
25010
|
className: "Passwordless",
|
|
24299
25011
|
ctorArg: "self"
|
|
24300
25012
|
},
|
|
24301
|
-
vault: {
|
|
24302
|
-
prop: "vault",
|
|
24303
|
-
className: "Vault",
|
|
24304
|
-
ctorArg: "self"
|
|
24305
|
-
},
|
|
24306
25013
|
actions: {
|
|
24307
25014
|
prop: "actions",
|
|
24308
25015
|
className: "Actions",
|
|
@@ -26119,6 +26826,7 @@ function classifyGroup(group, op) {
|
|
|
26119
26826
|
function renderParamsStruct(name, op, resolved, registry, ctx, groupEmitter) {
|
|
26120
26827
|
const bodyRequired = isBodyRequired(op);
|
|
26121
26828
|
const hidden = new Set([...Object.keys(resolved.defaults ?? {}), ...resolved.inferFromClient ?? []]);
|
|
26829
|
+
const materializeSpecDefaults = !resolved.urlBuilder;
|
|
26122
26830
|
const queryGroupParamNames = /* @__PURE__ */ new Set();
|
|
26123
26831
|
const bodyGroupParamNames = /* @__PURE__ */ new Set();
|
|
26124
26832
|
const queryGroupFields = [];
|
|
@@ -26155,7 +26863,7 @@ function renderParamsStruct(name, op, resolved, registry, ctx, groupEmitter) {
|
|
|
26155
26863
|
});
|
|
26156
26864
|
if (!p.required && !rust.startsWith("Option<")) rust = makeOptional(rust);
|
|
26157
26865
|
rust = applySecretRedaction(rust, p.name);
|
|
26158
|
-
const defaultExpr = p.default != null ? rustDefaultExpr(p.default, p.type, rust.startsWith("Option<"), ctx) : null;
|
|
26866
|
+
const defaultExpr = materializeSpecDefaults && p.default != null ? rustDefaultExpr(p.default, p.type, rust.startsWith("Option<"), ctx) : null;
|
|
26159
26867
|
const desc = p.description?.trim();
|
|
26160
26868
|
if (desc) for (const c of paramDocComment(desc)) fieldLines.push(` ${c}`);
|
|
26161
26869
|
if (p.default != null) {
|
|
@@ -26788,7 +27496,7 @@ function renderResourcesBarrel(exports) {
|
|
|
26788
27496
|
}
|
|
26789
27497
|
unique.sort((a, b) => a.module.localeCompare(b.module));
|
|
26790
27498
|
const lines = [];
|
|
26791
|
-
for (const { module } of unique) lines.push(`mod ${module};`);
|
|
27499
|
+
for (const { module } of unique) lines.push(`pub mod ${module};`);
|
|
26792
27500
|
lines.push("");
|
|
26793
27501
|
for (const { module, struct } of unique) lines.push(`pub use ${module}::${struct};`);
|
|
26794
27502
|
return lines.join("\n") + "\n";
|
|
@@ -27650,4 +28358,4 @@ const workosEmittersPlugin = {
|
|
|
27650
28358
|
//#endregion
|
|
27651
28359
|
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
28360
|
|
|
27653
|
-
//# sourceMappingURL=plugin-
|
|
28361
|
+
//# sourceMappingURL=plugin-CO4RFgAW.mjs.map
|