@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
package/dist/cli/index.mjs
CHANGED
|
@@ -5779,14 +5779,71 @@ function resolveResponseModelName(op) {
|
|
|
5779
5779
|
return null;
|
|
5780
5780
|
}
|
|
5781
5781
|
//#endregion
|
|
5782
|
+
//#region src/engine/scoped-services.ts
|
|
5783
|
+
/**
|
|
5784
|
+
* Validate a `--services` selection and return it as a set of POST-MOUNT service
|
|
5785
|
+
* names for emitters to scope on.
|
|
5786
|
+
*
|
|
5787
|
+
* Scoped generation does NOT filter the IR: model placement, dedup/aliasing, and
|
|
5788
|
+
* shared-schema decisions are global computations over the full service/model set
|
|
5789
|
+
* (filtering them churns shared `common/` files). Instead the full spec flows to
|
|
5790
|
+
* the emitters unchanged and this set is passed as `ctx.scopedServices`; each
|
|
5791
|
+
* emitter gates only its per-service resource/test emission on it.
|
|
5792
|
+
*
|
|
5793
|
+
* Matching is on the POST-MOUNT name. Mount resolution never mutates
|
|
5794
|
+
* `spec.services` — the post-mount target lives on `ResolvedOperation.mountOn`
|
|
5795
|
+
* (`mountOn = hint?.mountOn ?? mountRules[service.name] ?? service.name`) — so the
|
|
5796
|
+
* post-mount name of a source service is derived here as
|
|
5797
|
+
* `mountRules[service.name] ?? service.name`. Selecting a target (e.g.
|
|
5798
|
+
* `DirectorySync`) is valid because every source tag that mounts into it
|
|
5799
|
+
* (`DirectoryUsers`, `DirectoryGroups`, …) shares that post-mount name, and the
|
|
5800
|
+
* emitters group resources by mount, so all siblings emit together.
|
|
5801
|
+
*
|
|
5802
|
+
* @param spec - The parsed API spec (never mutated; never filtered).
|
|
5803
|
+
* @param selected - Post-mount service names from `--services` (normalized/trimmed).
|
|
5804
|
+
* @param mountRules - Source-service-name → post-mount-target mappings.
|
|
5805
|
+
* @returns The validated selection as a `Set` of post-mount names.
|
|
5806
|
+
* @throws A `ConfigError` listing valid post-mount names when a selection is
|
|
5807
|
+
* unknown — scoped generation never silently emits the wrong set.
|
|
5808
|
+
*/
|
|
5809
|
+
/**
|
|
5810
|
+
* Canonicalize a service name to a casing/separator-insensitive key so that
|
|
5811
|
+
* `MultiFactorAuth` (post-mount IR name), `multi_factor_auth` (snake-cased
|
|
5812
|
+
* manifest record), and `multiFactorAuth` (camel-cased manifest record) all
|
|
5813
|
+
* collapse to `multifactorauth`. Used to match a spec service against the
|
|
5814
|
+
* services a prior manifest recorded as already on disk, where the casing of
|
|
5815
|
+
* the recorded `service` field varies per emitter.
|
|
5816
|
+
*/
|
|
5817
|
+
function canonicalServiceKey(name) {
|
|
5818
|
+
return name.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
5819
|
+
}
|
|
5820
|
+
function resolveScopedServices(spec, selected, mountRules) {
|
|
5821
|
+
const mounts = mountRules ?? {};
|
|
5822
|
+
const postMountName = (name) => mounts[name] ?? name;
|
|
5823
|
+
const validPostMount = new Set(spec.services.map((s) => postMountName(s.name)));
|
|
5824
|
+
const selectedSet = new Set(selected);
|
|
5825
|
+
const unknown = [...selectedSet].filter((s) => !validPostMount.has(s));
|
|
5826
|
+
if (unknown.length > 0) {
|
|
5827
|
+
const err = /* @__PURE__ */ new Error(`Unknown --services: ${unknown.sort().join(", ")}. Valid post-mount services: ${[...validPostMount].sort().join(", ")}`);
|
|
5828
|
+
err.name = "ConfigError";
|
|
5829
|
+
throw err;
|
|
5830
|
+
}
|
|
5831
|
+
return selectedSet;
|
|
5832
|
+
}
|
|
5833
|
+
//#endregion
|
|
5782
5834
|
//#region src/engine/generate-files.ts
|
|
5783
5835
|
function buildEmitterContext(spec, options) {
|
|
5784
5836
|
const resolvedOperations = resolveOperations(spec, options.operationHints, options.mountRules);
|
|
5785
5837
|
let scopedModelNames;
|
|
5786
5838
|
let scopedEnumNames;
|
|
5787
5839
|
if (options.scopedServices && options.scopedServices.size > 0) {
|
|
5788
|
-
const
|
|
5789
|
-
const
|
|
5840
|
+
const scope = options.scopedServices;
|
|
5841
|
+
const present = options.presentServiceKeys ?? /* @__PURE__ */ new Set();
|
|
5842
|
+
const postMountOf = (service) => resolvedOperations.find((r) => r.service.name === service.name)?.mountOn ?? service.name;
|
|
5843
|
+
const reachable = collectReferencedNames(spec.services.filter((s) => {
|
|
5844
|
+
const postMount = postMountOf(s);
|
|
5845
|
+
return scope.has(postMount) || present.has(canonicalServiceKey(postMount));
|
|
5846
|
+
}), spec.models, { preserveAllDiscriminated: false });
|
|
5790
5847
|
scopedModelNames = reachable.models;
|
|
5791
5848
|
scopedEnumNames = reachable.enums;
|
|
5792
5849
|
}
|
|
@@ -5803,11 +5860,32 @@ function buildEmitterContext(spec, options) {
|
|
|
5803
5860
|
targetDir: options.target,
|
|
5804
5861
|
priorTargetManifestPaths: options.priorTargetManifestPaths,
|
|
5805
5862
|
scopedServices: options.scopedServices,
|
|
5863
|
+
presentServiceKeys: options.presentServiceKeys,
|
|
5806
5864
|
scopedModelNames,
|
|
5807
5865
|
scopedEnumNames
|
|
5808
5866
|
};
|
|
5809
5867
|
}
|
|
5810
5868
|
/**
|
|
5869
|
+
* The services an aggregate (barrel/client/test) generator may reference in a
|
|
5870
|
+
* scoped run: the selected services plus every service the prior manifest
|
|
5871
|
+
* recorded as already on disk. A service the spec just added that is neither
|
|
5872
|
+
* selected nor already generated is excluded — otherwise the client/barrels
|
|
5873
|
+
* would import a resource/model whose file this run does not emit (the orphaned
|
|
5874
|
+
* `agents/__init__.py → ._resource` class of build break).
|
|
5875
|
+
*
|
|
5876
|
+
* Outside a scoped run (full generation), every service is in surface.
|
|
5877
|
+
*/
|
|
5878
|
+
function aggregateSurfaceServices(spec, ctx) {
|
|
5879
|
+
const scope = ctx.scopedServices;
|
|
5880
|
+
if (!scope || scope.size === 0) return spec.services;
|
|
5881
|
+
const present = ctx.presentServiceKeys ?? /* @__PURE__ */ new Set();
|
|
5882
|
+
const postMountOf = (service) => ctx.resolvedOperations?.find((r) => r.service.name === service.name)?.mountOn ?? service.name;
|
|
5883
|
+
return spec.services.filter((s) => {
|
|
5884
|
+
const postMount = postMountOf(s);
|
|
5885
|
+
return scope.has(postMount) || present.has(canonicalServiceKey(postMount));
|
|
5886
|
+
});
|
|
5887
|
+
}
|
|
5888
|
+
/**
|
|
5811
5889
|
* Collect model and enum names transitively referenced by service operations.
|
|
5812
5890
|
* Walks operation parameters, request bodies, responses, and pagination item
|
|
5813
5891
|
* types, then chases model field references until the set stabilizes.
|
|
@@ -5872,27 +5950,38 @@ function isDiscriminatedModel(model) {
|
|
|
5872
5950
|
/**
|
|
5873
5951
|
* Collect all generated files from an emitter (no headers, no path prefixes).
|
|
5874
5952
|
*
|
|
5875
|
-
*
|
|
5876
|
-
*
|
|
5877
|
-
*
|
|
5878
|
-
* selected service is wired into the client automatically)
|
|
5879
|
-
*
|
|
5880
|
-
*
|
|
5953
|
+
* A full run emits over the whole spec. A scoped (`--services`) run still emits
|
|
5954
|
+
* model placement, dedup, the root client, and aggregate/barrel files
|
|
5955
|
+
* byte-identically for every service ALREADY on disk (plus the brand-new
|
|
5956
|
+
* selected service, which is wired into the client automatically) — but it
|
|
5957
|
+
* narrows that surface to `selected ∪ already-on-disk` services
|
|
5958
|
+
* (`aggregateSurfaceServices`). A service the spec just added that is neither
|
|
5959
|
+
* selected nor already generated is dropped from the surface entirely, so its
|
|
5960
|
+
* models/discriminators are not pulled into reachability and barrels/the client
|
|
5961
|
+
* never import a resource this run does not emit. The emitters additionally
|
|
5962
|
+
* consult `ctx.scopedServices` to emit only the selected services' per-service
|
|
5963
|
+
* resource/test files.
|
|
5881
5964
|
*/
|
|
5882
5965
|
function generateAllFiles(spec, emitter, ctx) {
|
|
5883
|
-
const
|
|
5966
|
+
const scoped = !!ctx.scopedServices && ctx.scopedServices.size > 0;
|
|
5967
|
+
const surfaceServices = aggregateSurfaceServices(spec, ctx);
|
|
5968
|
+
const referenced = collectReferencedNames(surfaceServices, spec.models, { preserveAllDiscriminated: !scoped });
|
|
5884
5969
|
const reachableModels = spec.models.filter((m) => referenced.models.has(m.name));
|
|
5885
5970
|
const reachableEnums = spec.enums.filter((e) => referenced.enums.has(e.name));
|
|
5886
|
-
const
|
|
5971
|
+
const surfaceSpec = {
|
|
5887
5972
|
...spec,
|
|
5973
|
+
services: surfaceServices
|
|
5974
|
+
};
|
|
5975
|
+
const reachableSpec = {
|
|
5976
|
+
...surfaceSpec,
|
|
5888
5977
|
models: reachableModels,
|
|
5889
5978
|
enums: reachableEnums
|
|
5890
5979
|
};
|
|
5891
5980
|
return [
|
|
5892
5981
|
...emitter.generateModels(reachableModels, ctx),
|
|
5893
5982
|
...emitter.generateEnums(reachableEnums, ctx),
|
|
5894
|
-
...emitter.generateResources(
|
|
5895
|
-
...emitter.generateClient(
|
|
5983
|
+
...emitter.generateResources(surfaceServices, ctx),
|
|
5984
|
+
...emitter.generateClient(surfaceSpec, ctx),
|
|
5896
5985
|
...emitter.generateErrors(ctx),
|
|
5897
5986
|
...emitter.generateTypeSignatures?.(reachableSpec, ctx) ?? [],
|
|
5898
5987
|
...emitter.generateTests(reachableSpec, ctx)
|
|
@@ -5909,7 +5998,11 @@ function applyFileHeaders(files, header) {
|
|
|
5909
5998
|
function generateFiles(spec, emitter, options) {
|
|
5910
5999
|
const ctx = buildEmitterContext(spec, options);
|
|
5911
6000
|
const files = generateAllFiles(spec, emitter, ctx);
|
|
5912
|
-
const
|
|
6001
|
+
const surfaceSpec = {
|
|
6002
|
+
...spec,
|
|
6003
|
+
services: aggregateSurfaceServices(spec, ctx)
|
|
6004
|
+
};
|
|
6005
|
+
const operations = emitter.buildOperationsMap?.(surfaceSpec, ctx);
|
|
5913
6006
|
const header = emitter.fileHeader();
|
|
5914
6007
|
return {
|
|
5915
6008
|
files: applyFileHeaders(files, header),
|
|
@@ -5919,47 +6012,6 @@ function generateFiles(spec, emitter, options) {
|
|
|
5919
6012
|
};
|
|
5920
6013
|
}
|
|
5921
6014
|
//#endregion
|
|
5922
|
-
//#region src/engine/scoped-services.ts
|
|
5923
|
-
/**
|
|
5924
|
-
* Validate a `--services` selection and return it as a set of POST-MOUNT service
|
|
5925
|
-
* names for emitters to scope on.
|
|
5926
|
-
*
|
|
5927
|
-
* Scoped generation does NOT filter the IR: model placement, dedup/aliasing, and
|
|
5928
|
-
* shared-schema decisions are global computations over the full service/model set
|
|
5929
|
-
* (filtering them churns shared `common/` files). Instead the full spec flows to
|
|
5930
|
-
* the emitters unchanged and this set is passed as `ctx.scopedServices`; each
|
|
5931
|
-
* emitter gates only its per-service resource/test emission on it.
|
|
5932
|
-
*
|
|
5933
|
-
* Matching is on the POST-MOUNT name. Mount resolution never mutates
|
|
5934
|
-
* `spec.services` — the post-mount target lives on `ResolvedOperation.mountOn`
|
|
5935
|
-
* (`mountOn = hint?.mountOn ?? mountRules[service.name] ?? service.name`) — so the
|
|
5936
|
-
* post-mount name of a source service is derived here as
|
|
5937
|
-
* `mountRules[service.name] ?? service.name`. Selecting a target (e.g.
|
|
5938
|
-
* `DirectorySync`) is valid because every source tag that mounts into it
|
|
5939
|
-
* (`DirectoryUsers`, `DirectoryGroups`, …) shares that post-mount name, and the
|
|
5940
|
-
* emitters group resources by mount, so all siblings emit together.
|
|
5941
|
-
*
|
|
5942
|
-
* @param spec - The parsed API spec (never mutated; never filtered).
|
|
5943
|
-
* @param selected - Post-mount service names from `--services` (normalized/trimmed).
|
|
5944
|
-
* @param mountRules - Source-service-name → post-mount-target mappings.
|
|
5945
|
-
* @returns The validated selection as a `Set` of post-mount names.
|
|
5946
|
-
* @throws A `ConfigError` listing valid post-mount names when a selection is
|
|
5947
|
-
* unknown — scoped generation never silently emits the wrong set.
|
|
5948
|
-
*/
|
|
5949
|
-
function resolveScopedServices(spec, selected, mountRules) {
|
|
5950
|
-
const mounts = mountRules ?? {};
|
|
5951
|
-
const postMountName = (name) => mounts[name] ?? name;
|
|
5952
|
-
const validPostMount = new Set(spec.services.map((s) => postMountName(s.name)));
|
|
5953
|
-
const selectedSet = new Set(selected);
|
|
5954
|
-
const unknown = [...selectedSet].filter((s) => !validPostMount.has(s));
|
|
5955
|
-
if (unknown.length > 0) {
|
|
5956
|
-
const err = /* @__PURE__ */ new Error(`Unknown --services: ${unknown.sort().join(", ")}. Valid post-mount services: ${[...validPostMount].sort().join(", ")}`);
|
|
5957
|
-
err.name = "ConfigError";
|
|
5958
|
-
throw err;
|
|
5959
|
-
}
|
|
5960
|
-
return selectedSet;
|
|
5961
|
-
}
|
|
5962
|
-
//#endregion
|
|
5963
6015
|
//#region src/engine/integrate.ts
|
|
5964
6016
|
/**
|
|
5965
6017
|
* Resolve a relative import path against a source file path to get the
|
|
@@ -6271,6 +6323,35 @@ function mergeScopedManifestRecords(prev, scopedFiles, scopedOperations) {
|
|
|
6271
6323
|
};
|
|
6272
6324
|
}
|
|
6273
6325
|
/**
|
|
6326
|
+
* Extract the set of services a prior manifest records as already generated on
|
|
6327
|
+
* disk, as canonical (casing/separator-insensitive) keys.
|
|
6328
|
+
*
|
|
6329
|
+
* The manifest's `operations` map carries each operation's owning `service`
|
|
6330
|
+
* (snake_case for most emitters, camelCase for Kotlin). A scoped (`--services`)
|
|
6331
|
+
* run uses this to tell "service in the spec that is already on disk" apart from
|
|
6332
|
+
* "service the spec just added that this SDK has never generated" — only the
|
|
6333
|
+
* former may be wired into aggregate/barrel/client files alongside the
|
|
6334
|
+
* selection, so a brand-new out-of-scope service is never referenced by files
|
|
6335
|
+
* whose implementation this run does not emit.
|
|
6336
|
+
*
|
|
6337
|
+
* Returns an empty set when the manifest is null or carries no operations
|
|
6338
|
+
* (a legacy manifest with no operations data); callers treat that as "present
|
|
6339
|
+
* set unknown" and fall back to not narrowing the surface.
|
|
6340
|
+
*/
|
|
6341
|
+
function manifestServiceKeys(manifest) {
|
|
6342
|
+
const keys = /* @__PURE__ */ new Set();
|
|
6343
|
+
const ops = manifest?.operations;
|
|
6344
|
+
if (!ops) return keys;
|
|
6345
|
+
for (const value of Object.values(ops)) {
|
|
6346
|
+
const entries = Array.isArray(value) ? value : [value];
|
|
6347
|
+
for (const entry of entries) {
|
|
6348
|
+
const service = entry?.service;
|
|
6349
|
+
if (typeof service === "string" && service.length > 0) keys.add(canonicalServiceKey(service));
|
|
6350
|
+
}
|
|
6351
|
+
}
|
|
6352
|
+
return keys;
|
|
6353
|
+
}
|
|
6354
|
+
/**
|
|
6274
6355
|
* Delete stale files from `dir`.
|
|
6275
6356
|
*
|
|
6276
6357
|
* When `header` is provided, a file is only deleted if its contents start with
|
|
@@ -6345,11 +6426,14 @@ async function generate(spec, emitter, options) {
|
|
|
6345
6426
|
const skipManifestRead = options.noPrune === true && !scoped;
|
|
6346
6427
|
const outputPrevManifestForCtx = skipManifestRead ? null : await readManifest(options.outputDir);
|
|
6347
6428
|
const targetManifestForCtx = options.target && !skipManifestRead ? await readManifest(options.target) : null;
|
|
6348
|
-
const
|
|
6429
|
+
const priorManifestForCtx = targetManifestForCtx ?? outputPrevManifestForCtx;
|
|
6430
|
+
const priorTargetManifestPaths = priorManifestForCtx ? new Set(priorManifestForCtx.files) : void 0;
|
|
6431
|
+
const presentServiceKeys = scoped ? manifestServiceKeys(priorManifestForCtx) : void 0;
|
|
6349
6432
|
const { files: withHeaders, header, operations } = generateFiles(spec, emitter, {
|
|
6350
6433
|
...options,
|
|
6351
6434
|
priorTargetManifestPaths,
|
|
6352
|
-
scopedServices
|
|
6435
|
+
scopedServices,
|
|
6436
|
+
presentServiceKeys
|
|
6353
6437
|
});
|
|
6354
6438
|
if (options.dryRun) {
|
|
6355
6439
|
if (options.target) {
|