@workos/oagen 0.24.0 → 0.24.2
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/dist/cli/index.mjs +140 -56
- package/dist/cli/index.mjs.map +1 -1
- package/dist/{diff-B7dMUCoJ.mjs → diff-IXR1sWSP.mjs} +141 -57
- package/dist/{diff-B7dMUCoJ.mjs.map → diff-IXR1sWSP.mjs.map} +1 -1
- package/dist/index.d.mts +32 -10
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/verify/index.mjs +1 -1
- package/package.json +1 -1
|
@@ -2954,14 +2954,71 @@ function spliceExtraImports(existingLines, resultLines) {
|
|
|
2954
2954
|
resultLines.splice(insertAt, 0, ...allExtra);
|
|
2955
2955
|
}
|
|
2956
2956
|
//#endregion
|
|
2957
|
+
//#region src/engine/scoped-services.ts
|
|
2958
|
+
/**
|
|
2959
|
+
* Validate a `--services` selection and return it as a set of POST-MOUNT service
|
|
2960
|
+
* names for emitters to scope on.
|
|
2961
|
+
*
|
|
2962
|
+
* Scoped generation does NOT filter the IR: model placement, dedup/aliasing, and
|
|
2963
|
+
* shared-schema decisions are global computations over the full service/model set
|
|
2964
|
+
* (filtering them churns shared `common/` files). Instead the full spec flows to
|
|
2965
|
+
* the emitters unchanged and this set is passed as `ctx.scopedServices`; each
|
|
2966
|
+
* emitter gates only its per-service resource/test emission on it.
|
|
2967
|
+
*
|
|
2968
|
+
* Matching is on the POST-MOUNT name. Mount resolution never mutates
|
|
2969
|
+
* `spec.services` — the post-mount target lives on `ResolvedOperation.mountOn`
|
|
2970
|
+
* (`mountOn = hint?.mountOn ?? mountRules[service.name] ?? service.name`) — so the
|
|
2971
|
+
* post-mount name of a source service is derived here as
|
|
2972
|
+
* `mountRules[service.name] ?? service.name`. Selecting a target (e.g.
|
|
2973
|
+
* `DirectorySync`) is valid because every source tag that mounts into it
|
|
2974
|
+
* (`DirectoryUsers`, `DirectoryGroups`, …) shares that post-mount name, and the
|
|
2975
|
+
* emitters group resources by mount, so all siblings emit together.
|
|
2976
|
+
*
|
|
2977
|
+
* @param spec - The parsed API spec (never mutated; never filtered).
|
|
2978
|
+
* @param selected - Post-mount service names from `--services` (normalized/trimmed).
|
|
2979
|
+
* @param mountRules - Source-service-name → post-mount-target mappings.
|
|
2980
|
+
* @returns The validated selection as a `Set` of post-mount names.
|
|
2981
|
+
* @throws A `ConfigError` listing valid post-mount names when a selection is
|
|
2982
|
+
* unknown — scoped generation never silently emits the wrong set.
|
|
2983
|
+
*/
|
|
2984
|
+
/**
|
|
2985
|
+
* Canonicalize a service name to a casing/separator-insensitive key so that
|
|
2986
|
+
* `MultiFactorAuth` (post-mount IR name), `multi_factor_auth` (snake-cased
|
|
2987
|
+
* manifest record), and `multiFactorAuth` (camel-cased manifest record) all
|
|
2988
|
+
* collapse to `multifactorauth`. Used to match a spec service against the
|
|
2989
|
+
* services a prior manifest recorded as already on disk, where the casing of
|
|
2990
|
+
* the recorded `service` field varies per emitter.
|
|
2991
|
+
*/
|
|
2992
|
+
function canonicalServiceKey(name) {
|
|
2993
|
+
return name.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2994
|
+
}
|
|
2995
|
+
function resolveScopedServices(spec, selected, mountRules) {
|
|
2996
|
+
const mounts = mountRules ?? {};
|
|
2997
|
+
const postMountName = (name) => mounts[name] ?? name;
|
|
2998
|
+
const validPostMount = new Set(spec.services.map((s) => postMountName(s.name)));
|
|
2999
|
+
const selectedSet = new Set(selected);
|
|
3000
|
+
const unknown = [...selectedSet].filter((s) => !validPostMount.has(s));
|
|
3001
|
+
if (unknown.length > 0) {
|
|
3002
|
+
const err = /* @__PURE__ */ new Error(`Unknown --services: ${unknown.sort().join(", ")}. Valid post-mount services: ${[...validPostMount].sort().join(", ")}`);
|
|
3003
|
+
err.name = "ConfigError";
|
|
3004
|
+
throw err;
|
|
3005
|
+
}
|
|
3006
|
+
return selectedSet;
|
|
3007
|
+
}
|
|
3008
|
+
//#endregion
|
|
2957
3009
|
//#region src/engine/generate-files.ts
|
|
2958
3010
|
function buildEmitterContext(spec, options) {
|
|
2959
3011
|
const resolvedOperations = resolveOperations(spec, options.operationHints, options.mountRules);
|
|
2960
3012
|
let scopedModelNames;
|
|
2961
3013
|
let scopedEnumNames;
|
|
2962
3014
|
if (options.scopedServices && options.scopedServices.size > 0) {
|
|
2963
|
-
const
|
|
2964
|
-
const
|
|
3015
|
+
const scope = options.scopedServices;
|
|
3016
|
+
const present = options.presentServiceKeys ?? /* @__PURE__ */ new Set();
|
|
3017
|
+
const postMountOf = (service) => resolvedOperations.find((r) => r.service.name === service.name)?.mountOn ?? service.name;
|
|
3018
|
+
const reachable = collectReferencedNames(spec.services.filter((s) => {
|
|
3019
|
+
const postMount = postMountOf(s);
|
|
3020
|
+
return scope.has(postMount) || present.has(canonicalServiceKey(postMount));
|
|
3021
|
+
}), spec.models, { preserveAllDiscriminated: false });
|
|
2965
3022
|
scopedModelNames = reachable.models;
|
|
2966
3023
|
scopedEnumNames = reachable.enums;
|
|
2967
3024
|
}
|
|
@@ -2978,11 +3035,32 @@ function buildEmitterContext(spec, options) {
|
|
|
2978
3035
|
targetDir: options.target,
|
|
2979
3036
|
priorTargetManifestPaths: options.priorTargetManifestPaths,
|
|
2980
3037
|
scopedServices: options.scopedServices,
|
|
3038
|
+
presentServiceKeys: options.presentServiceKeys,
|
|
2981
3039
|
scopedModelNames,
|
|
2982
3040
|
scopedEnumNames
|
|
2983
3041
|
};
|
|
2984
3042
|
}
|
|
2985
3043
|
/**
|
|
3044
|
+
* The services an aggregate (barrel/client/test) generator may reference in a
|
|
3045
|
+
* scoped run: the selected services plus every service the prior manifest
|
|
3046
|
+
* recorded as already on disk. A service the spec just added that is neither
|
|
3047
|
+
* selected nor already generated is excluded — otherwise the client/barrels
|
|
3048
|
+
* would import a resource/model whose file this run does not emit (the orphaned
|
|
3049
|
+
* `agents/__init__.py → ._resource` class of build break).
|
|
3050
|
+
*
|
|
3051
|
+
* Outside a scoped run (full generation), every service is in surface.
|
|
3052
|
+
*/
|
|
3053
|
+
function aggregateSurfaceServices(spec, ctx) {
|
|
3054
|
+
const scope = ctx.scopedServices;
|
|
3055
|
+
if (!scope || scope.size === 0) return spec.services;
|
|
3056
|
+
const present = ctx.presentServiceKeys ?? /* @__PURE__ */ new Set();
|
|
3057
|
+
const postMountOf = (service) => ctx.resolvedOperations?.find((r) => r.service.name === service.name)?.mountOn ?? service.name;
|
|
3058
|
+
return spec.services.filter((s) => {
|
|
3059
|
+
const postMount = postMountOf(s);
|
|
3060
|
+
return scope.has(postMount) || present.has(canonicalServiceKey(postMount));
|
|
3061
|
+
});
|
|
3062
|
+
}
|
|
3063
|
+
/**
|
|
2986
3064
|
* Collect model and enum names transitively referenced by service operations.
|
|
2987
3065
|
* Walks operation parameters, request bodies, responses, and pagination item
|
|
2988
3066
|
* types, then chases model field references until the set stabilizes.
|
|
@@ -3047,27 +3125,38 @@ function isDiscriminatedModel(model) {
|
|
|
3047
3125
|
/**
|
|
3048
3126
|
* Collect all generated files from an emitter (no headers, no path prefixes).
|
|
3049
3127
|
*
|
|
3050
|
-
*
|
|
3051
|
-
*
|
|
3052
|
-
*
|
|
3053
|
-
* selected service is wired into the client automatically)
|
|
3054
|
-
*
|
|
3055
|
-
*
|
|
3128
|
+
* A full run emits over the whole spec. A scoped (`--services`) run still emits
|
|
3129
|
+
* model placement, dedup, the root client, and aggregate/barrel files
|
|
3130
|
+
* byte-identically for every service ALREADY on disk (plus the brand-new
|
|
3131
|
+
* selected service, which is wired into the client automatically) — but it
|
|
3132
|
+
* narrows that surface to `selected ∪ already-on-disk` services
|
|
3133
|
+
* (`aggregateSurfaceServices`). A service the spec just added that is neither
|
|
3134
|
+
* selected nor already generated is dropped from the surface entirely, so its
|
|
3135
|
+
* models/discriminators are not pulled into reachability and barrels/the client
|
|
3136
|
+
* never import a resource this run does not emit. The emitters additionally
|
|
3137
|
+
* consult `ctx.scopedServices` to emit only the selected services' per-service
|
|
3138
|
+
* resource/test files.
|
|
3056
3139
|
*/
|
|
3057
3140
|
function generateAllFiles(spec, emitter, ctx) {
|
|
3058
|
-
const
|
|
3141
|
+
const scoped = !!ctx.scopedServices && ctx.scopedServices.size > 0;
|
|
3142
|
+
const surfaceServices = aggregateSurfaceServices(spec, ctx);
|
|
3143
|
+
const referenced = collectReferencedNames(surfaceServices, spec.models, { preserveAllDiscriminated: !scoped });
|
|
3059
3144
|
const reachableModels = spec.models.filter((m) => referenced.models.has(m.name));
|
|
3060
3145
|
const reachableEnums = spec.enums.filter((e) => referenced.enums.has(e.name));
|
|
3061
|
-
const
|
|
3146
|
+
const surfaceSpec = {
|
|
3062
3147
|
...spec,
|
|
3148
|
+
services: surfaceServices
|
|
3149
|
+
};
|
|
3150
|
+
const reachableSpec = {
|
|
3151
|
+
...surfaceSpec,
|
|
3063
3152
|
models: reachableModels,
|
|
3064
3153
|
enums: reachableEnums
|
|
3065
3154
|
};
|
|
3066
3155
|
return [
|
|
3067
3156
|
...emitter.generateModels(reachableModels, ctx),
|
|
3068
3157
|
...emitter.generateEnums(reachableEnums, ctx),
|
|
3069
|
-
...emitter.generateResources(
|
|
3070
|
-
...emitter.generateClient(
|
|
3158
|
+
...emitter.generateResources(surfaceServices, ctx),
|
|
3159
|
+
...emitter.generateClient(surfaceSpec, ctx),
|
|
3071
3160
|
...emitter.generateErrors(ctx),
|
|
3072
3161
|
...emitter.generateTypeSignatures?.(reachableSpec, ctx) ?? [],
|
|
3073
3162
|
...emitter.generateTests(reachableSpec, ctx)
|
|
@@ -3084,7 +3173,11 @@ function applyFileHeaders(files, header) {
|
|
|
3084
3173
|
function generateFiles(spec, emitter, options) {
|
|
3085
3174
|
const ctx = buildEmitterContext(spec, options);
|
|
3086
3175
|
const files = generateAllFiles(spec, emitter, ctx);
|
|
3087
|
-
const
|
|
3176
|
+
const surfaceSpec = {
|
|
3177
|
+
...spec,
|
|
3178
|
+
services: aggregateSurfaceServices(spec, ctx)
|
|
3179
|
+
};
|
|
3180
|
+
const operations = emitter.buildOperationsMap?.(surfaceSpec, ctx);
|
|
3088
3181
|
const header = emitter.fileHeader();
|
|
3089
3182
|
return {
|
|
3090
3183
|
files: applyFileHeaders(files, header),
|
|
@@ -3094,47 +3187,6 @@ function generateFiles(spec, emitter, options) {
|
|
|
3094
3187
|
};
|
|
3095
3188
|
}
|
|
3096
3189
|
//#endregion
|
|
3097
|
-
//#region src/engine/scoped-services.ts
|
|
3098
|
-
/**
|
|
3099
|
-
* Validate a `--services` selection and return it as a set of POST-MOUNT service
|
|
3100
|
-
* names for emitters to scope on.
|
|
3101
|
-
*
|
|
3102
|
-
* Scoped generation does NOT filter the IR: model placement, dedup/aliasing, and
|
|
3103
|
-
* shared-schema decisions are global computations over the full service/model set
|
|
3104
|
-
* (filtering them churns shared `common/` files). Instead the full spec flows to
|
|
3105
|
-
* the emitters unchanged and this set is passed as `ctx.scopedServices`; each
|
|
3106
|
-
* emitter gates only its per-service resource/test emission on it.
|
|
3107
|
-
*
|
|
3108
|
-
* Matching is on the POST-MOUNT name. Mount resolution never mutates
|
|
3109
|
-
* `spec.services` — the post-mount target lives on `ResolvedOperation.mountOn`
|
|
3110
|
-
* (`mountOn = hint?.mountOn ?? mountRules[service.name] ?? service.name`) — so the
|
|
3111
|
-
* post-mount name of a source service is derived here as
|
|
3112
|
-
* `mountRules[service.name] ?? service.name`. Selecting a target (e.g.
|
|
3113
|
-
* `DirectorySync`) is valid because every source tag that mounts into it
|
|
3114
|
-
* (`DirectoryUsers`, `DirectoryGroups`, …) shares that post-mount name, and the
|
|
3115
|
-
* emitters group resources by mount, so all siblings emit together.
|
|
3116
|
-
*
|
|
3117
|
-
* @param spec - The parsed API spec (never mutated; never filtered).
|
|
3118
|
-
* @param selected - Post-mount service names from `--services` (normalized/trimmed).
|
|
3119
|
-
* @param mountRules - Source-service-name → post-mount-target mappings.
|
|
3120
|
-
* @returns The validated selection as a `Set` of post-mount names.
|
|
3121
|
-
* @throws A `ConfigError` listing valid post-mount names when a selection is
|
|
3122
|
-
* unknown — scoped generation never silently emits the wrong set.
|
|
3123
|
-
*/
|
|
3124
|
-
function resolveScopedServices(spec, selected, mountRules) {
|
|
3125
|
-
const mounts = mountRules ?? {};
|
|
3126
|
-
const postMountName = (name) => mounts[name] ?? name;
|
|
3127
|
-
const validPostMount = new Set(spec.services.map((s) => postMountName(s.name)));
|
|
3128
|
-
const selectedSet = new Set(selected);
|
|
3129
|
-
const unknown = [...selectedSet].filter((s) => !validPostMount.has(s));
|
|
3130
|
-
if (unknown.length > 0) {
|
|
3131
|
-
const err = /* @__PURE__ */ new Error(`Unknown --services: ${unknown.sort().join(", ")}. Valid post-mount services: ${[...validPostMount].sort().join(", ")}`);
|
|
3132
|
-
err.name = "ConfigError";
|
|
3133
|
-
throw err;
|
|
3134
|
-
}
|
|
3135
|
-
return selectedSet;
|
|
3136
|
-
}
|
|
3137
|
-
//#endregion
|
|
3138
3190
|
//#region src/engine/integrate.ts
|
|
3139
3191
|
/**
|
|
3140
3192
|
* Resolve a relative import path against a source file path to get the
|
|
@@ -3446,6 +3498,35 @@ function mergeScopedManifestRecords(prev, scopedFiles, scopedOperations) {
|
|
|
3446
3498
|
};
|
|
3447
3499
|
}
|
|
3448
3500
|
/**
|
|
3501
|
+
* Extract the set of services a prior manifest records as already generated on
|
|
3502
|
+
* disk, as canonical (casing/separator-insensitive) keys.
|
|
3503
|
+
*
|
|
3504
|
+
* The manifest's `operations` map carries each operation's owning `service`
|
|
3505
|
+
* (snake_case for most emitters, camelCase for Kotlin). A scoped (`--services`)
|
|
3506
|
+
* run uses this to tell "service in the spec that is already on disk" apart from
|
|
3507
|
+
* "service the spec just added that this SDK has never generated" — only the
|
|
3508
|
+
* former may be wired into aggregate/barrel/client files alongside the
|
|
3509
|
+
* selection, so a brand-new out-of-scope service is never referenced by files
|
|
3510
|
+
* whose implementation this run does not emit.
|
|
3511
|
+
*
|
|
3512
|
+
* Returns an empty set when the manifest is null or carries no operations
|
|
3513
|
+
* (a legacy manifest with no operations data); callers treat that as "present
|
|
3514
|
+
* set unknown" and fall back to not narrowing the surface.
|
|
3515
|
+
*/
|
|
3516
|
+
function manifestServiceKeys(manifest) {
|
|
3517
|
+
const keys = /* @__PURE__ */ new Set();
|
|
3518
|
+
const ops = manifest?.operations;
|
|
3519
|
+
if (!ops) return keys;
|
|
3520
|
+
for (const value of Object.values(ops)) {
|
|
3521
|
+
const entries = Array.isArray(value) ? value : [value];
|
|
3522
|
+
for (const entry of entries) {
|
|
3523
|
+
const service = entry?.service;
|
|
3524
|
+
if (typeof service === "string" && service.length > 0) keys.add(canonicalServiceKey(service));
|
|
3525
|
+
}
|
|
3526
|
+
}
|
|
3527
|
+
return keys;
|
|
3528
|
+
}
|
|
3529
|
+
/**
|
|
3449
3530
|
* Delete stale files from `dir`.
|
|
3450
3531
|
*
|
|
3451
3532
|
* When `header` is provided, a file is only deleted if its contents start with
|
|
@@ -3520,11 +3601,14 @@ async function generate(spec, emitter, options) {
|
|
|
3520
3601
|
const skipManifestRead = options.noPrune === true && !scoped;
|
|
3521
3602
|
const outputPrevManifestForCtx = skipManifestRead ? null : await readManifest(options.outputDir);
|
|
3522
3603
|
const targetManifestForCtx = options.target && !skipManifestRead ? await readManifest(options.target) : null;
|
|
3523
|
-
const
|
|
3604
|
+
const priorManifestForCtx = targetManifestForCtx ?? outputPrevManifestForCtx;
|
|
3605
|
+
const priorTargetManifestPaths = priorManifestForCtx ? new Set(priorManifestForCtx.files) : void 0;
|
|
3606
|
+
const presentServiceKeys = scoped ? manifestServiceKeys(priorManifestForCtx) : void 0;
|
|
3524
3607
|
const { files: withHeaders, header, operations } = generateFiles(spec, emitter, {
|
|
3525
3608
|
...options,
|
|
3526
3609
|
priorTargetManifestPaths,
|
|
3527
|
-
scopedServices
|
|
3610
|
+
scopedServices,
|
|
3611
|
+
presentServiceKeys
|
|
3528
3612
|
});
|
|
3529
3613
|
if (options.dryRun) {
|
|
3530
3614
|
if (options.target) {
|
|
@@ -4069,4 +4153,4 @@ function summarize(changes) {
|
|
|
4069
4153
|
//#endregion
|
|
4070
4154
|
export { MANIFEST_FILENAME as a, treeShakeFiles as c, collectReferencedNames as d, generateAllFiles as f, resolveOperations as h, generate as i, applyFileHeaders as l, deriveMethodName as m, getEmitter as n, integrateGeneratedFiles as o, generateFiles as p, registerEmitter as r, mapFilesForTargetIntegration as s, diffSpecs as t, buildEmitterContext as u };
|
|
4071
4155
|
|
|
4072
|
-
//# sourceMappingURL=diff-
|
|
4156
|
+
//# sourceMappingURL=diff-IXR1sWSP.mjs.map
|