@workos/oagen 0.18.0 → 0.18.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/README.md +1 -1
- package/dist/cli/index.mjs +151 -40
- package/dist/cli/index.mjs.map +1 -1
- package/dist/{diff-DLqJSVYR.mjs → diff-JU1bdT5_.mjs} +107 -17
- package/dist/{diff-DLqJSVYR.mjs.map → diff-JU1bdT5_.mjs.map} +1 -1
- package/dist/index.d.mts +33 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +46 -25
- package/dist/index.mjs.map +1 -1
- package/dist/scripts/smoke/baseline.mjs +45 -24
- package/dist/scripts/smoke/baseline.mjs.map +1 -1
- package/dist/smoke.mjs +45 -24
- package/dist/smoke.mjs.map +1 -1
- package/dist/verify/index.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# oagen
|
|
2
2
|
|
|
3
|
-
oagen is a framework for building custom SDK generators from OpenAPI 3.x specifications.
|
|
3
|
+
oagen ([**oʊweɪdd͡ʒɛn**](https://ipa-reader.com/?text=o%CA%8Awe%C9%AAdd%CD%A1%CA%92%C9%9Bn)) is a framework for building custom SDK generators from OpenAPI 3.x specifications.
|
|
4
4
|
|
|
5
5
|
Its core job is narrow:
|
|
6
6
|
|
package/dist/cli/index.mjs
CHANGED
|
@@ -1435,7 +1435,7 @@ const LIMIT_PARAMS = [
|
|
|
1435
1435
|
* Returns null if no pagination pattern is detected.
|
|
1436
1436
|
*/
|
|
1437
1437
|
function detectPagination(response, queryParams, dataPath) {
|
|
1438
|
-
const cursorParam = queryParams.find((p) =>
|
|
1438
|
+
const cursorParam = CURSOR_PARAMS.map((name) => queryParams.find((p) => p.name === name)).find((p) => p !== void 0);
|
|
1439
1439
|
if (cursorParam) {
|
|
1440
1440
|
const itemType = response.kind === "array" ? response.items : response;
|
|
1441
1441
|
return {
|
|
@@ -1811,7 +1811,7 @@ const HTTP_METHODS = [
|
|
|
1811
1811
|
"options",
|
|
1812
1812
|
"trace"
|
|
1813
1813
|
];
|
|
1814
|
-
function extractOperations(paths, operationIdTransform) {
|
|
1814
|
+
function extractOperations(paths, operationIdTransform, componentSchemas) {
|
|
1815
1815
|
if (!paths) return {
|
|
1816
1816
|
services: [],
|
|
1817
1817
|
inlineModels: []
|
|
@@ -1824,7 +1824,7 @@ function extractOperations(paths, operationIdTransform) {
|
|
|
1824
1824
|
const op = pathItem[method];
|
|
1825
1825
|
if (!op) continue;
|
|
1826
1826
|
const serviceName = inferServiceName(path, op.tags?.[0]);
|
|
1827
|
-
const { operation, inlineModels: opModels } = buildOperation(method, path, op, pathLevelParams, operationIdTransform, serviceName);
|
|
1827
|
+
const { operation, inlineModels: opModels } = buildOperation(method, path, op, pathLevelParams, operationIdTransform, serviceName, componentSchemas);
|
|
1828
1828
|
inlineModels.push(...opModels);
|
|
1829
1829
|
const ops = serviceMap.get(serviceName) ?? [];
|
|
1830
1830
|
ops.push(operation);
|
|
@@ -2067,14 +2067,14 @@ function extractBodyParameterGroups(op, operationContext) {
|
|
|
2067
2067
|
}
|
|
2068
2068
|
return groups.length > 0 ? groups : void 0;
|
|
2069
2069
|
}
|
|
2070
|
-
function buildOperation(method, path, op, pathLevelParams, operationIdTransform, serviceName) {
|
|
2070
|
+
function buildOperation(method, path, op, pathLevelParams, operationIdTransform, serviceName, componentSchemas) {
|
|
2071
2071
|
const allParams = [...pathLevelParams, ...op.parameters ?? []];
|
|
2072
2072
|
const hasIdempotencyHeader = allParams.some((p) => p.in === "header" && p.name.toLowerCase() === "idempotency-key");
|
|
2073
2073
|
const opContext = serviceName;
|
|
2074
|
-
const pathParams = extractParams(allParams, "path", opContext);
|
|
2075
|
-
const queryParams = extractParams(allParams, "query", opContext);
|
|
2076
|
-
const headerParams = extractParams(allParams, "header", opContext).filter((p) => p.name.toLowerCase() !== "idempotency-key");
|
|
2077
|
-
const cookieParams = extractParams(allParams, "cookie", opContext);
|
|
2074
|
+
const pathParams = extractParams(allParams, "path", opContext, componentSchemas);
|
|
2075
|
+
const queryParams = extractParams(allParams, "query", opContext, componentSchemas);
|
|
2076
|
+
const headerParams = extractParams(allParams, "header", opContext, componentSchemas).filter((p) => p.name.toLowerCase() !== "idempotency-key");
|
|
2077
|
+
const cookieParams = extractParams(allParams, "cookie", opContext, componentSchemas);
|
|
2078
2078
|
const reqBodyModels = [];
|
|
2079
2079
|
const { body: requestBody, encoding: requestBodyEncoding } = extractRequestBody(op.requestBody, op, reqBodyModels);
|
|
2080
2080
|
const { response, successResponses, errors, inlineModels, isPaginated, dataPath: responseDataPath, itemType: responseItemType } = extractResponses(op.responses, op, path, method);
|
|
@@ -2146,21 +2146,42 @@ function buildDescription(summary, description) {
|
|
|
2146
2146
|
if (summary && description && summary !== description) return `${summary}\n\n${description}`;
|
|
2147
2147
|
return description ?? summary;
|
|
2148
2148
|
}
|
|
2149
|
-
function extractParams(params, location, operationContext) {
|
|
2150
|
-
return params.filter((p) => p.in === location).map((p) =>
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
type:
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2149
|
+
function extractParams(params, location, operationContext, componentSchemas) {
|
|
2150
|
+
return params.filter((p) => p.in === location).map((p) => {
|
|
2151
|
+
const refTarget = resolveParamSchemaRef(p.schema, componentSchemas);
|
|
2152
|
+
return {
|
|
2153
|
+
name: p.name,
|
|
2154
|
+
type: p.schema ? schemaToTypeRef(p.schema, p.name, operationContext ? toPascalCase(operationContext) : void 0) : {
|
|
2155
|
+
kind: "primitive",
|
|
2156
|
+
type: "string"
|
|
2157
|
+
},
|
|
2158
|
+
required: p.required ?? false,
|
|
2159
|
+
description: p.description,
|
|
2160
|
+
deprecated: p.deprecated || p.schema?.deprecated || refTarget?.deprecated || void 0,
|
|
2161
|
+
default: p.schema?.default ?? refTarget?.default,
|
|
2162
|
+
example: p.example ?? p.schema?.example ?? refTarget?.example,
|
|
2163
|
+
style: p.style,
|
|
2164
|
+
explode: p.explode
|
|
2165
|
+
};
|
|
2166
|
+
});
|
|
2167
|
+
}
|
|
2168
|
+
/**
|
|
2169
|
+
* If a parameter's schema is a `$ref` to `#/components/schemas/X`, return the
|
|
2170
|
+
* referenced component schema. Used to surface schema-level metadata (default,
|
|
2171
|
+
* example, deprecated) that wouldn't otherwise be visible at the param level.
|
|
2172
|
+
*
|
|
2173
|
+
* Refs are not pre-resolved by the bundler (`dereference: false` in refs.ts),
|
|
2174
|
+
* so without this, a param like `schema: { $ref: '.../PaginationOrder' }`
|
|
2175
|
+
* silently loses the target's `default: desc` and the SDK stops emitting the
|
|
2176
|
+
* spec-mandated default value in generated method signatures.
|
|
2177
|
+
*/
|
|
2178
|
+
function resolveParamSchemaRef(schema, componentSchemas) {
|
|
2179
|
+
if (!schema || !componentSchemas) return void 0;
|
|
2180
|
+
const ref = schema.$ref;
|
|
2181
|
+
if (!ref) return void 0;
|
|
2182
|
+
const match = /^#\/components\/schemas\/(.+)$/.exec(ref);
|
|
2183
|
+
if (!match) return void 0;
|
|
2184
|
+
return componentSchemas[match[1]];
|
|
2164
2185
|
}
|
|
2165
2186
|
function extractRequestBody(body, op, inlineModels) {
|
|
2166
2187
|
if (!body?.content) return {};
|
|
@@ -2514,7 +2535,7 @@ async function parseSpec(specPath, options) {
|
|
|
2514
2535
|
const version = spec.openapi ?? "";
|
|
2515
2536
|
if (!version.startsWith("3.")) throw new SpecParseError(`Unsupported OpenAPI version: ${version}. oagen requires OpenAPI 3.x`, `Update the spec to OpenAPI 3.0 or 3.1. If you are using Swagger 2.x, convert it first with \`npx swagger2openapi ${specPath}\`.`);
|
|
2516
2537
|
const { models, enums } = extractSchemas(spec.components?.schemas, { schemaNameTransform: options?.schemaNameTransform });
|
|
2517
|
-
const { services, inlineModels } = extractOperations(spec.paths, options?.operationIdTransform);
|
|
2538
|
+
const { services, inlineModels } = extractOperations(spec.paths, options?.operationIdTransform, spec.components?.schemas);
|
|
2518
2539
|
clearSchemaNameTransform();
|
|
2519
2540
|
const finalModels = collapseJsonSuffixModels(mergeFieldInlineModels(mergeInlineResponseModels(models, inlineModels), extractInlineModelsFromSchemas(spec.components?.schemas)), services);
|
|
2520
2541
|
collectInlineEnumsFromModels(finalModels, enums);
|
|
@@ -6223,6 +6244,11 @@ function classifyFieldChange(kind, fieldName, isRequired) {
|
|
|
6223
6244
|
fieldName,
|
|
6224
6245
|
classification: "breaking"
|
|
6225
6246
|
};
|
|
6247
|
+
case "field-format-changed": return {
|
|
6248
|
+
kind,
|
|
6249
|
+
fieldName,
|
|
6250
|
+
classification: "additive"
|
|
6251
|
+
};
|
|
6226
6252
|
case "field-required-changed": return {
|
|
6227
6253
|
kind,
|
|
6228
6254
|
fieldName,
|
|
@@ -6253,11 +6279,21 @@ function classifyParamChange(kind, paramName, isRequired) {
|
|
|
6253
6279
|
paramName,
|
|
6254
6280
|
classification: "breaking"
|
|
6255
6281
|
};
|
|
6282
|
+
case "param-format-changed": return {
|
|
6283
|
+
kind,
|
|
6284
|
+
paramName,
|
|
6285
|
+
classification: "additive"
|
|
6286
|
+
};
|
|
6256
6287
|
case "param-required-changed": return {
|
|
6257
6288
|
kind,
|
|
6258
6289
|
paramName,
|
|
6259
6290
|
classification: isRequired ? "breaking" : "additive"
|
|
6260
6291
|
};
|
|
6292
|
+
case "param-default-changed": return {
|
|
6293
|
+
kind,
|
|
6294
|
+
paramName,
|
|
6295
|
+
classification: "breaking"
|
|
6296
|
+
};
|
|
6261
6297
|
}
|
|
6262
6298
|
}
|
|
6263
6299
|
//#endregion
|
|
@@ -6301,7 +6337,11 @@ function diffFields(oldModel, newModel) {
|
|
|
6301
6337
|
for (const [name, newField] of newByName) {
|
|
6302
6338
|
const oldField = oldByName.get(name);
|
|
6303
6339
|
if (!oldField) continue;
|
|
6304
|
-
if (!typeRefsEqual(oldField.type, newField.type)) changes.push({
|
|
6340
|
+
if (!typeRefsEqual(oldField.type, newField.type)) if (primitivesDifferOnlyByFormat(oldField.type, newField.type)) changes.push({
|
|
6341
|
+
...classifyFieldChange("field-format-changed", name),
|
|
6342
|
+
details: formatChangeDetails(oldField.type, newField.type)
|
|
6343
|
+
});
|
|
6344
|
+
else changes.push({
|
|
6305
6345
|
kind: "field-type-changed",
|
|
6306
6346
|
fieldName: name,
|
|
6307
6347
|
classification: "breaking",
|
|
@@ -6312,6 +6352,23 @@ function diffFields(oldModel, newModel) {
|
|
|
6312
6352
|
}
|
|
6313
6353
|
return changes;
|
|
6314
6354
|
}
|
|
6355
|
+
/**
|
|
6356
|
+
* True when both refs are primitives with the same base `type` but different
|
|
6357
|
+
* `format`. Used to downgrade format-only diffs from breaking type changes to
|
|
6358
|
+
* additive `field-format-changed` / `param-format-changed` events: the wire
|
|
6359
|
+
* base type is unchanged, so the diff is a hint to language emitters rather
|
|
6360
|
+
* than a source-level break.
|
|
6361
|
+
*/
|
|
6362
|
+
function primitivesDifferOnlyByFormat(a, b) {
|
|
6363
|
+
if (a.kind !== "primitive" || b.kind !== "primitive") return false;
|
|
6364
|
+
if (a.type !== b.type) return false;
|
|
6365
|
+
return (a.format ?? "") !== (b.format ?? "");
|
|
6366
|
+
}
|
|
6367
|
+
function formatChangeDetails(a, b) {
|
|
6368
|
+
const oldFmt = a.kind === "primitive" ? a.format : void 0;
|
|
6369
|
+
const newFmt = b.kind === "primitive" ? b.format : void 0;
|
|
6370
|
+
return `format ${oldFmt ?? "<unset>"} → ${newFmt ?? "<unset>"}`;
|
|
6371
|
+
}
|
|
6315
6372
|
function typeRefsEqual(a, b) {
|
|
6316
6373
|
if (a.kind !== b.kind) return false;
|
|
6317
6374
|
switch (a.kind) {
|
|
@@ -6413,28 +6470,57 @@ function classifyErrorsChange(oldErrors, newErrors) {
|
|
|
6413
6470
|
}
|
|
6414
6471
|
function diffParams(oldOp, newOp) {
|
|
6415
6472
|
const changes = [];
|
|
6473
|
+
const tagged = (loc, params) => params.map((p) => ({
|
|
6474
|
+
p,
|
|
6475
|
+
loc
|
|
6476
|
+
}));
|
|
6416
6477
|
const allOldParams = [
|
|
6417
|
-
...oldOp.pathParams,
|
|
6418
|
-
...oldOp.queryParams,
|
|
6419
|
-
...oldOp.headerParams
|
|
6478
|
+
...tagged("path", oldOp.pathParams),
|
|
6479
|
+
...tagged("query", oldOp.queryParams),
|
|
6480
|
+
...tagged("header", oldOp.headerParams)
|
|
6420
6481
|
];
|
|
6421
6482
|
const allNewParams = [
|
|
6422
|
-
...newOp.pathParams,
|
|
6423
|
-
...newOp.queryParams,
|
|
6424
|
-
...newOp.headerParams
|
|
6483
|
+
...tagged("path", newOp.pathParams),
|
|
6484
|
+
...tagged("query", newOp.queryParams),
|
|
6485
|
+
...tagged("header", newOp.headerParams)
|
|
6425
6486
|
];
|
|
6426
|
-
const oldByName = new Map(allOldParams.map((
|
|
6427
|
-
const newByName = new Map(allNewParams.map((
|
|
6428
|
-
for (const [name,
|
|
6487
|
+
const oldByName = new Map(allOldParams.map((e) => [e.p.name, e]));
|
|
6488
|
+
const newByName = new Map(allNewParams.map((e) => [e.p.name, e]));
|
|
6489
|
+
for (const [name, entry] of newByName) if (!oldByName.has(name)) changes.push(classifyParamChange("param-added", name, entry.p.required));
|
|
6429
6490
|
for (const [name] of oldByName) if (!newByName.has(name)) changes.push(classifyParamChange("param-removed", name));
|
|
6430
|
-
for (const [name,
|
|
6431
|
-
const
|
|
6432
|
-
if (!
|
|
6433
|
-
if (!typeRefsEqual(
|
|
6434
|
-
|
|
6491
|
+
for (const [name, newEntry] of newByName) {
|
|
6492
|
+
const oldEntry = oldByName.get(name);
|
|
6493
|
+
if (!oldEntry) continue;
|
|
6494
|
+
if (!typeRefsEqual(oldEntry.p.type, newEntry.p.type)) if (primitivesDifferOnlyByFormat(oldEntry.p.type, newEntry.p.type)) {
|
|
6495
|
+
const change = classifyParamChange("param-format-changed", name);
|
|
6496
|
+
change.details = formatChangeDetails(oldEntry.p.type, newEntry.p.type);
|
|
6497
|
+
changes.push(change);
|
|
6498
|
+
} else changes.push(classifyParamChange("param-type-changed", name));
|
|
6499
|
+
if (oldEntry.p.required !== newEntry.p.required) changes.push(classifyParamChange("param-required-changed", name, newEntry.p.required));
|
|
6500
|
+
const oldDefault = serializeDefault(oldEntry.p.default);
|
|
6501
|
+
const newDefault = serializeDefault(newEntry.p.default);
|
|
6502
|
+
if (oldDefault !== newDefault) {
|
|
6503
|
+
const change = classifyParamChange("param-default-changed", name);
|
|
6504
|
+
change.oldDefault = oldDefault;
|
|
6505
|
+
change.newDefault = newDefault;
|
|
6506
|
+
change.paramLocation = newEntry.loc;
|
|
6507
|
+
change.details = `default ${oldDefault ?? "<unset>"} → ${newDefault ?? "<unset>"}`;
|
|
6508
|
+
changes.push(change);
|
|
6509
|
+
}
|
|
6435
6510
|
}
|
|
6436
6511
|
return changes;
|
|
6437
6512
|
}
|
|
6513
|
+
function serializeDefault(value) {
|
|
6514
|
+
if (value === void 0) return null;
|
|
6515
|
+
if (value === null) return "null";
|
|
6516
|
+
if (typeof value === "string") return value;
|
|
6517
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
6518
|
+
try {
|
|
6519
|
+
return JSON.stringify(value);
|
|
6520
|
+
} catch {
|
|
6521
|
+
return String(value);
|
|
6522
|
+
}
|
|
6523
|
+
}
|
|
6438
6524
|
//#endregion
|
|
6439
6525
|
//#region src/differ/services.ts
|
|
6440
6526
|
function diffServices(oldServices, newServices) {
|
|
@@ -6466,13 +6552,38 @@ function diffSpecs(oldSpec, newSpec) {
|
|
|
6466
6552
|
...diffEnums(oldSpec.enums, newSpec.enums),
|
|
6467
6553
|
...diffServices(oldSpec.services, newSpec.services)
|
|
6468
6554
|
];
|
|
6555
|
+
const behaviorChanges = collectBehaviorChanges(changes);
|
|
6469
6556
|
return {
|
|
6470
6557
|
oldVersion: oldSpec.version,
|
|
6471
6558
|
newVersion: newSpec.version,
|
|
6472
6559
|
changes,
|
|
6473
|
-
|
|
6560
|
+
behaviorChanges,
|
|
6561
|
+
summary: {
|
|
6562
|
+
...summarize(changes),
|
|
6563
|
+
behaviorChanges: behaviorChanges.length
|
|
6564
|
+
}
|
|
6474
6565
|
};
|
|
6475
6566
|
}
|
|
6567
|
+
function collectBehaviorChanges(changes) {
|
|
6568
|
+
const out = [];
|
|
6569
|
+
for (const c of changes) {
|
|
6570
|
+
if (c.kind !== "operation-modified") continue;
|
|
6571
|
+
for (const pc of c.paramChanges) {
|
|
6572
|
+
if (pc.kind !== "param-default-changed") continue;
|
|
6573
|
+
out.push({
|
|
6574
|
+
kind: "param-default-changed",
|
|
6575
|
+
serviceName: c.serviceName,
|
|
6576
|
+
operationName: c.operationName,
|
|
6577
|
+
paramName: pc.paramName,
|
|
6578
|
+
paramLocation: pc.paramLocation ?? "query",
|
|
6579
|
+
oldDefault: pc.oldDefault ?? null,
|
|
6580
|
+
newDefault: pc.newDefault ?? null,
|
|
6581
|
+
classification: "breaking"
|
|
6582
|
+
});
|
|
6583
|
+
}
|
|
6584
|
+
}
|
|
6585
|
+
return out;
|
|
6586
|
+
}
|
|
6476
6587
|
function diffEnums(oldEnums, newEnums) {
|
|
6477
6588
|
const changes = [];
|
|
6478
6589
|
const oldByName = new Map(oldEnums.map((e) => [e.name, e]));
|