@workos/oagen 0.28.0 → 0.29.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.
@@ -2255,20 +2255,24 @@ function extractBodyParameterGroups(op, operationContext) {
2255
2255
  if (Object.keys(groupDef.variants).length === 0) continue;
2256
2256
  const variants = Object.entries(groupDef.variants).map(([variantName, paramNames]) => {
2257
2257
  if (!Array.isArray(paramNames) || paramNames.length === 0) throw new Error(`Malformed x-mutually-exclusive-body-groups.${groupName}.variants.${variantName} in ${operationContext}: expected a non-empty array of parameter names.`);
2258
+ const parameters = paramNames.map((pName) => {
2259
+ const fieldSchema = variantFieldSchemas.get(pName);
2260
+ return {
2261
+ name: pName,
2262
+ type: fieldSchema ? schemaToTypeRef(fieldSchema, toPascalCase(pName)) : {
2263
+ kind: "primitive",
2264
+ type: "string"
2265
+ },
2266
+ required: false,
2267
+ description: fieldSchema?.description
2268
+ };
2269
+ });
2270
+ const rawOptionalFields = groupDef.optionalFields && typeof groupDef.optionalFields === "object" ? groupDef.optionalFields[variantName] : void 0;
2271
+ const optionalParameters = Array.isArray(rawOptionalFields) ? rawOptionalFields.filter((name) => typeof name === "string" && paramNames.includes(name)) : [];
2258
2272
  return {
2259
2273
  name: variantName,
2260
- parameters: paramNames.map((pName) => {
2261
- const fieldSchema = variantFieldSchemas.get(pName);
2262
- return {
2263
- name: pName,
2264
- type: fieldSchema ? schemaToTypeRef(fieldSchema, toPascalCase(pName)) : {
2265
- kind: "primitive",
2266
- type: "string"
2267
- },
2268
- required: false,
2269
- description: fieldSchema?.description
2270
- };
2271
- })
2274
+ parameters,
2275
+ ...optionalParameters.length > 0 ? { optionalParameters } : {}
2272
2276
  };
2273
2277
  });
2274
2278
  groups.push({
@@ -7360,14 +7364,107 @@ async function generateCommand(opts) {
7360
7364
  }
7361
7365
  }
7362
7366
  //#endregion
7367
+ //#region src/differ/direction.ts
7368
+ /**
7369
+ * Classify every model in `specs` as request-facing, response-facing, both, or
7370
+ * unknown.
7371
+ *
7372
+ * Pass both the baseline and the candidate spec: a model that was request-facing
7373
+ * in either one keeps its request direction for the whole diff, so a schema that
7374
+ * moves request → response mid-release doesn't get that release's requiredness
7375
+ * changes silently downgraded.
7376
+ *
7377
+ * Models reachable from neither side (unreferenced schemas, or a spec whose
7378
+ * operations weren't supplied) stay `unknown`, which preserves the conservative
7379
+ * pre-direction classification.
7380
+ */
7381
+ function buildDirectionIndex(...specs) {
7382
+ const models = /* @__PURE__ */ new Map();
7383
+ for (const spec of specs) for (const model of spec.models) if (!models.has(model.name)) models.set(model.name, model);
7384
+ const requestSeeds = [];
7385
+ const responseSeeds = [];
7386
+ for (const spec of specs) for (const service of spec.services) for (const op of service.operations) {
7387
+ if (op.requestBody) requestSeeds.push(op.requestBody);
7388
+ for (const param of [
7389
+ ...op.pathParams,
7390
+ ...op.queryParams,
7391
+ ...op.headerParams,
7392
+ ...op.cookieParams ?? []
7393
+ ]) requestSeeds.push(param.type);
7394
+ responseSeeds.push(op.response);
7395
+ for (const success of op.successResponses ?? []) responseSeeds.push(success.type);
7396
+ for (const error of op.errors) if (error.type) responseSeeds.push(error.type);
7397
+ }
7398
+ const request = reachableModels(requestSeeds, models);
7399
+ const response = reachableModels(responseSeeds, models);
7400
+ const index = /* @__PURE__ */ new Map();
7401
+ for (const name of models.keys()) {
7402
+ const inRequest = request.has(name);
7403
+ const inResponse = response.has(name);
7404
+ index.set(name, inRequest && inResponse ? "both" : inRequest ? "request" : inResponse ? "response" : "unknown");
7405
+ }
7406
+ return index;
7407
+ }
7408
+ function directionOf(index, modelName) {
7409
+ return index?.get(modelName) ?? "unknown";
7410
+ }
7411
+ /**
7412
+ * Every model name reachable from `seeds`, following model fields (and
7413
+ * discriminated-union variants) transitively.
7414
+ */
7415
+ function reachableModels(seeds, models) {
7416
+ const found = /* @__PURE__ */ new Set();
7417
+ const queue = [...seeds];
7418
+ while (queue.length > 0) {
7419
+ const ref = queue.pop();
7420
+ switch (ref.kind) {
7421
+ case "model": {
7422
+ if (found.has(ref.name)) break;
7423
+ found.add(ref.name);
7424
+ const model = models.get(ref.name);
7425
+ if (!model) break;
7426
+ for (const field of model.fields) queue.push(field.type);
7427
+ for (const variant of Object.values(model.discriminator?.mapping ?? {})) queue.push({
7428
+ kind: "model",
7429
+ name: variant
7430
+ });
7431
+ break;
7432
+ }
7433
+ case "array":
7434
+ queue.push(ref.items);
7435
+ break;
7436
+ case "nullable":
7437
+ queue.push(ref.inner);
7438
+ break;
7439
+ case "map":
7440
+ queue.push(ref.valueType);
7441
+ break;
7442
+ case "union":
7443
+ queue.push(...ref.variants);
7444
+ break;
7445
+ case "primitive":
7446
+ case "enum":
7447
+ case "literal": break;
7448
+ }
7449
+ }
7450
+ return found;
7451
+ }
7452
+ //#endregion
7363
7453
  //#region src/differ/classify.ts
7364
- function classifyFieldChange(kind, fieldName, isRequired) {
7454
+ function classifyFieldChange(kind, fieldName, isRequired, direction = "unknown") {
7365
7455
  switch (kind) {
7366
- case "field-added": return {
7367
- kind,
7368
- fieldName,
7369
- classification: isRequired ? "breaking" : "additive"
7370
- };
7456
+ case "field-added":
7457
+ if (isRequired && direction === "response") return {
7458
+ kind,
7459
+ fieldName,
7460
+ classification: "additive",
7461
+ details: "required on a response-only model"
7462
+ };
7463
+ return {
7464
+ kind,
7465
+ fieldName,
7466
+ classification: isRequired ? "breaking" : "additive"
7467
+ };
7371
7468
  case "field-removed": return {
7372
7469
  kind,
7373
7470
  fieldName,
@@ -7432,7 +7529,12 @@ function classifyParamChange(kind, paramName, isRequired) {
7432
7529
  }
7433
7530
  //#endregion
7434
7531
  //#region src/differ/models.ts
7435
- function diffModels(oldModels, newModels) {
7532
+ /**
7533
+ * `directions` (from `buildDirectionIndex`) lets requiredness changes be judged
7534
+ * against the side of the wire each model is used on. Omit it and every model is
7535
+ * treated as unknown-direction, i.e. the conservative classification.
7536
+ */
7537
+ function diffModels(oldModels, newModels, directions) {
7436
7538
  const changes = [];
7437
7539
  const oldByName = new Map(oldModels.map((m) => [m.name, m]));
7438
7540
  const newByName = new Map(newModels.map((m) => [m.name, m]));
@@ -7449,7 +7551,7 @@ function diffModels(oldModels, newModels) {
7449
7551
  for (const [name, newModel] of newByName) {
7450
7552
  const oldModel = oldByName.get(name);
7451
7553
  if (!oldModel) continue;
7452
- const fieldChanges = diffFields(oldModel, newModel);
7554
+ const fieldChanges = diffFields(oldModel, newModel, directionOf(directions, name));
7453
7555
  if (fieldChanges.length > 0) {
7454
7556
  const hasBreaking = fieldChanges.some((fc) => fc.classification === "breaking");
7455
7557
  changes.push({
@@ -7462,11 +7564,11 @@ function diffModels(oldModels, newModels) {
7462
7564
  }
7463
7565
  return changes;
7464
7566
  }
7465
- function diffFields(oldModel, newModel) {
7567
+ function diffFields(oldModel, newModel, direction = "unknown") {
7466
7568
  const changes = [];
7467
7569
  const oldByName = new Map(oldModel.fields.map((f) => [f.name, f]));
7468
7570
  const newByName = new Map(newModel.fields.map((f) => [f.name, f]));
7469
- for (const [name, field] of newByName) if (!oldByName.has(name)) changes.push(classifyFieldChange("field-added", name, field.required));
7571
+ for (const [name, field] of newByName) if (!oldByName.has(name)) changes.push(classifyFieldChange("field-added", name, field.required, direction));
7470
7572
  for (const [name] of oldByName) if (!newByName.has(name)) changes.push(classifyFieldChange("field-removed", name));
7471
7573
  for (const [name, newField] of newByName) {
7472
7574
  const oldField = oldByName.get(name);
@@ -7681,8 +7783,9 @@ function diffServices(oldServices, newServices) {
7681
7783
  //#endregion
7682
7784
  //#region src/differ/diff.ts
7683
7785
  function diffSpecs(oldSpec, newSpec) {
7786
+ const directions = buildDirectionIndex(oldSpec, newSpec);
7684
7787
  const changes = [
7685
- ...diffModels(oldSpec.models, newSpec.models),
7788
+ ...diffModels(oldSpec.models, newSpec.models, directions),
7686
7789
  ...diffEnums(oldSpec.enums, newSpec.enums),
7687
7790
  ...diffServices(oldSpec.services, newSpec.services)
7688
7791
  ];