@workos/oagen 0.0.1 → 0.1.1

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/index.js CHANGED
@@ -8,9 +8,8 @@ import {
8
8
  getEmitter,
9
9
  integrateGeneratedFiles,
10
10
  mapFilesForTargetIntegration,
11
- registerEmitter,
12
- writeFiles
13
- } from "./chunk-P5XUDUGY.js";
11
+ registerEmitter
12
+ } from "./chunk-2JXFTDTO.js";
14
13
  import {
15
14
  ACRONYM_SET,
16
15
  CommandError,
@@ -139,17 +138,26 @@ function buildInlineModel(name, schema) {
139
138
 
140
139
  // src/parser/schemas.ts
141
140
  function extractSchemas(schemas) {
142
- const models = [];
143
141
  const enums = [];
144
- if (!schemas) return { models, enums };
142
+ if (!schemas) return { models: [], enums };
143
+ const modelsByCleanName = /* @__PURE__ */ new Map();
145
144
  for (const [name, schema] of Object.entries(schemas)) {
146
145
  const pascalName = cleanSchemaName(toPascalCase(name));
147
146
  if (schema.enum) {
148
147
  enums.push(extractEnum(pascalName, schema));
149
148
  } else {
150
- models.push(extractModel(pascalName, schema, schemas));
149
+ const model = extractModel(pascalName, schema, schemas);
150
+ const existing = modelsByCleanName.get(pascalName);
151
+ if (existing) {
152
+ if (model.fields.length > existing.fields.length) {
153
+ modelsByCleanName.set(pascalName, model);
154
+ }
155
+ } else {
156
+ modelsByCleanName.set(pascalName, model);
157
+ }
151
158
  }
152
159
  }
160
+ const models = [...modelsByCleanName.values()];
153
161
  const modelNames = new Set(models.map((m) => m.name));
154
162
  const inlineQueue = [...models];
155
163
  while (inlineQueue.length > 0) {
@@ -240,9 +248,9 @@ function extractNestedSchema(name, schema) {
240
248
  if (variant.properties && (variant.type === "object" || !variant.type)) {
241
249
  const requiredSet = new Set(variant.required ?? []);
242
250
  const fields = [];
243
- for (const [fn, fs2] of Object.entries(variant.properties)) {
244
- if (!fs2) continue;
245
- fields.push(buildFieldFromSchema(fn, fs2, name, requiredSet));
251
+ for (const [fn, fs] of Object.entries(variant.properties)) {
252
+ if (!fs) continue;
253
+ fields.push(buildFieldFromSchema(fn, fs, name, requiredSet));
246
254
  }
247
255
  const variantName = models.length === 0 ? name : `${name}${models.length + 1}`;
248
256
  models.push({ name: variantName, description: variant.description, fields });
@@ -253,18 +261,18 @@ function extractNestedSchema(name, schema) {
253
261
  if (schema.properties && (schema.type === "object" || !schema.type)) {
254
262
  const requiredSet = new Set(schema.required ?? []);
255
263
  const fields = [];
256
- for (const [fn, fs2] of Object.entries(schema.properties)) {
257
- if (!fs2) continue;
258
- fields.push(buildFieldFromSchema(fn, fs2, name, requiredSet));
264
+ for (const [fn, fs] of Object.entries(schema.properties)) {
265
+ if (!fs) continue;
266
+ fields.push(buildFieldFromSchema(fn, fs, name, requiredSet));
259
267
  }
260
268
  return [{ name, description: schema.description, fields }];
261
269
  }
262
270
  if (schema.type === "array" && schema.items?.properties) {
263
271
  const requiredSet = new Set(schema.items.required ?? []);
264
272
  const fields = [];
265
- for (const [fn, fs2] of Object.entries(schema.items.properties)) {
266
- if (!fs2) continue;
267
- fields.push(buildFieldFromSchema(fn, fs2, name, requiredSet));
273
+ for (const [fn, fs] of Object.entries(schema.items.properties)) {
274
+ if (!fs) continue;
275
+ fields.push(buildFieldFromSchema(fn, fs, name, requiredSet));
268
276
  }
269
277
  return [{ name, description: schema.items.description, fields }];
270
278
  }
@@ -579,6 +587,18 @@ function classifyAndExtractResponse(schema, contextName) {
579
587
  }
580
588
  return extractDirectResource(schema, contextName);
581
589
  }
590
+ var KNOWN_DATA_PATHS = /* @__PURE__ */ new Set(["data", "items", "results", "records", "entries", "values", "nodes", "edges"]);
591
+ var PAGINATION_METADATA_PATTERNS = [
592
+ "metadata",
593
+ "pagination",
594
+ "cursor",
595
+ "has_more",
596
+ "page_info",
597
+ "total",
598
+ "next_page",
599
+ "previous_page",
600
+ "offset"
601
+ ];
582
602
  function detectListEnvelope(schema) {
583
603
  const propSources = [];
584
604
  if (schema.allOf) {
@@ -595,17 +615,26 @@ function detectListEnvelope(schema) {
595
615
  Object.assign(mergedProps, source);
596
616
  }
597
617
  const arrayProps = [];
598
- let nonArrayCount = 0;
618
+ const nonArrayKeys = [];
599
619
  for (const [key, propSchema] of Object.entries(mergedProps)) {
600
620
  if (!propSchema) continue;
601
621
  if (propSchema.type === "array") {
602
622
  arrayProps.push(key);
603
623
  } else {
604
- nonArrayCount++;
624
+ nonArrayKeys.push(key);
605
625
  }
606
626
  }
607
- if (arrayProps.length === 1 && nonArrayCount >= 1) {
608
- return { isEnvelope: true, dataPath: arrayProps[0] };
627
+ if (arrayProps.length === 1 && nonArrayKeys.length >= 1) {
628
+ const arrayPropName = arrayProps[0];
629
+ if (KNOWN_DATA_PATHS.has(arrayPropName)) {
630
+ return { isEnvelope: true, dataPath: arrayPropName };
631
+ }
632
+ const hasPaginationCompanion = nonArrayKeys.some(
633
+ (key) => PAGINATION_METADATA_PATTERNS.some((pattern) => key.toLowerCase().includes(pattern))
634
+ );
635
+ if (hasPaginationCompanion) {
636
+ return { isEnvelope: true, dataPath: arrayPropName };
637
+ }
609
638
  }
610
639
  return { isEnvelope: false, dataPath: null };
611
640
  }
@@ -870,15 +899,15 @@ function extractOperations(paths, operationIdTransform) {
870
899
  if (!paths) return { services: [], inlineModels: [] };
871
900
  const serviceMap = /* @__PURE__ */ new Map();
872
901
  const inlineModels = [];
873
- for (const [path2, pathItem] of Object.entries(paths)) {
902
+ for (const [path, pathItem] of Object.entries(paths)) {
874
903
  const pathLevelParams = pathItem.parameters ?? [];
875
904
  for (const method of HTTP_METHODS) {
876
905
  const op = pathItem[method];
877
906
  if (!op) continue;
878
- const serviceName = inferServiceName(path2, op.tags?.[0]);
907
+ const serviceName = inferServiceName(path, op.tags?.[0]);
879
908
  const { operation, inlineModels: opModels } = buildOperation(
880
909
  method,
881
- path2,
910
+ path,
882
911
  op,
883
912
  pathLevelParams,
884
913
  operationIdTransform
@@ -898,28 +927,28 @@ function extractOperations(paths, operationIdTransform) {
898
927
  }));
899
928
  return { services, inlineModels };
900
929
  }
901
- function inferServiceName(path2, tag) {
930
+ function inferServiceName(path, tag) {
902
931
  if (tag) {
903
932
  return toPascalCase(tag);
904
933
  }
905
- const segments = path2.split("/").filter(Boolean);
934
+ const segments = path.split("/").filter(Boolean);
906
935
  const first = segments[0] ?? "Default";
907
936
  if (first.startsWith("{")) return "Default";
908
937
  return toPascalCase(first);
909
938
  }
910
- function inferOperationName(method, path2, operationId, operationIdTransform) {
939
+ function inferOperationName(method, path, operationId, operationIdTransform) {
911
940
  if (operationId) {
912
941
  if (operationIdTransform) {
913
942
  return operationIdTransform(operationId);
914
943
  }
915
944
  return toCamelCase(operationId);
916
945
  }
917
- const verb = inferVerb(method, path2);
918
- const resource = inferResourceFromPath(path2);
946
+ const verb = inferVerb(method, path);
947
+ const resource = inferResourceFromPath(path);
919
948
  return resource ? toCamelCase(`${verb}_${resource}`) : verb;
920
949
  }
921
- function inferVerb(method, path2) {
922
- const segments = path2.split("/").filter(Boolean);
950
+ function inferVerb(method, path) {
951
+ const segments = path.split("/").filter(Boolean);
923
952
  const hasTrailingParam = segments.length > 0 && segments[segments.length - 1].startsWith("{");
924
953
  switch (method) {
925
954
  case "get":
@@ -941,8 +970,8 @@ function inferVerb(method, path2) {
941
970
  return method;
942
971
  }
943
972
  }
944
- function inferResourceFromPath(path2) {
945
- const segments = path2.split("/").filter(Boolean);
973
+ function inferResourceFromPath(path) {
974
+ const segments = path.split("/").filter(Boolean);
946
975
  if (segments.length <= 1) return null;
947
976
  let resourceSegment = null;
948
977
  for (let i = segments.length - 1; i >= 0; i--) {
@@ -990,15 +1019,15 @@ function disambiguateOperationNames(operations) {
990
1019
  }
991
1020
  }
992
1021
  }
993
- function inferDeeperContext(path2) {
994
- const segments = path2.split("/").filter(Boolean);
1022
+ function inferDeeperContext(path) {
1023
+ const segments = path.split("/").filter(Boolean);
995
1024
  const nonParams = segments.filter((s) => !s.startsWith("{"));
996
1025
  if (nonParams.length >= 3) {
997
1026
  return toPascalCase(nonParams.slice(1, -1).join("_"));
998
1027
  }
999
1028
  return null;
1000
1029
  }
1001
- function buildOperation(method, path2, op, pathLevelParams, operationIdTransform) {
1030
+ function buildOperation(method, path, op, pathLevelParams, operationIdTransform) {
1002
1031
  const allParams = [...pathLevelParams, ...op.parameters ?? []];
1003
1032
  const hasIdempotencyHeader = allParams.some((p) => p.in === "header" && p.name.toLowerCase() === "idempotency-key");
1004
1033
  const pathParams = extractParams(allParams, "path");
@@ -1015,7 +1044,7 @@ function buildOperation(method, path2, op, pathLevelParams, operationIdTransform
1015
1044
  isPaginated,
1016
1045
  dataPath: responseDataPath,
1017
1046
  itemType: responseItemType
1018
- } = extractResponses(op.responses, op, path2, method);
1047
+ } = extractResponses(op.responses, op, path, method);
1019
1048
  inlineModels.push(...reqBodyModels);
1020
1049
  const paginationFromParams = detectPagination(response, queryParams, responseDataPath);
1021
1050
  let pagination;
@@ -1033,10 +1062,10 @@ function buildOperation(method, path2, op, pathLevelParams, operationIdTransform
1033
1062
  }
1034
1063
  return {
1035
1064
  operation: {
1036
- name: inferOperationName(method, path2, op.operationId, operationIdTransform),
1065
+ name: inferOperationName(method, path, op.operationId, operationIdTransform),
1037
1066
  description: buildDescription(op.summary, op.description),
1038
1067
  httpMethod: method,
1039
- path: path2,
1068
+ path,
1040
1069
  pathParams,
1041
1070
  queryParams,
1042
1071
  headerParams,
@@ -1167,15 +1196,15 @@ function deriveOneOfVariantName(variant, baseName, existingModels) {
1167
1196
  }
1168
1197
  return name;
1169
1198
  }
1170
- function deriveResponseName(op, path2, method) {
1199
+ function deriveResponseName(op, path, method) {
1171
1200
  if (op?.operationId) {
1172
1201
  return cleanSchemaName(toPascalCase(op.operationId) + "Response");
1173
1202
  }
1174
- const segments = path2.split("/").filter(Boolean);
1203
+ const segments = path.split("/").filter(Boolean);
1175
1204
  const resource = segments[0] ?? "Unknown";
1176
1205
  return toPascalCase(resource) + toPascalCase(method) + "Response";
1177
1206
  }
1178
- function extractResponses(responses, op, path2, method) {
1207
+ function extractResponses(responses, op, path, method) {
1179
1208
  const errors = [];
1180
1209
  let response = { kind: "primitive", type: "string" };
1181
1210
  let inlineModels = [];
@@ -1190,7 +1219,7 @@ function extractResponses(responses, op, path2, method) {
1190
1219
  let extractedType = { kind: "primitive", type: "unknown" };
1191
1220
  const jsonContent = resp.content?.["application/json"];
1192
1221
  if (jsonContent?.schema) {
1193
- const contextName = deriveResponseName(op, path2 ?? "/", method ?? "get");
1222
+ const contextName = deriveResponseName(op, path ?? "/", method ?? "get");
1194
1223
  const result = classifyAndExtractResponse(jsonContent.schema, contextName);
1195
1224
  extractedType = result.response;
1196
1225
  inlineModels = result.inlineModels;
@@ -1209,7 +1238,7 @@ function extractResponses(responses, op, path2, method) {
1209
1238
  if (firstContent?.schema) {
1210
1239
  extractedType = schemaToTypeRef(
1211
1240
  firstContent.schema,
1212
- deriveResponseName(op, path2 ?? "/", method ?? "get")
1241
+ deriveResponseName(op, path ?? "/", method ?? "get")
1213
1242
  );
1214
1243
  }
1215
1244
  }
@@ -1242,20 +1271,24 @@ function keepLargestByName(models) {
1242
1271
  }
1243
1272
  return [...byName.values()];
1244
1273
  }
1245
- function rewriteModelRefsInServices(services, oldName, newName) {
1274
+ function rewriteModelRef(ref, oldName, newName) {
1275
+ walkTypeRef(ref, {
1276
+ model: (r) => {
1277
+ if (r.name === oldName) r.name = newName;
1278
+ }
1279
+ });
1280
+ }
1281
+ function rewriteModelRefs(models, services, oldName, newName) {
1282
+ for (const model of models) {
1283
+ for (const field of model.fields) {
1284
+ rewriteModelRef(field.type, oldName, newName);
1285
+ }
1286
+ }
1246
1287
  for (const service of services) {
1247
1288
  for (const op of service.operations) {
1248
- walkTypeRef(op.response, {
1249
- model: (r) => {
1250
- if (r.name === oldName) r.name = newName;
1251
- }
1252
- });
1289
+ rewriteModelRef(op.response, oldName, newName);
1253
1290
  if (op.requestBody) {
1254
- walkTypeRef(op.requestBody, {
1255
- model: (r) => {
1256
- if (r.name === oldName) r.name = newName;
1257
- }
1258
- });
1291
+ rewriteModelRef(op.requestBody, oldName, newName);
1259
1292
  }
1260
1293
  }
1261
1294
  }
@@ -1304,9 +1337,30 @@ function mergeFieldInlineModels(existingModels, fieldInlineModels) {
1304
1337
  });
1305
1338
  return [...existingModels, ...keepLargestByName(deduplicatedFieldModels)];
1306
1339
  }
1340
+ function hasModelConstDiscriminant(model) {
1341
+ return model.fields.some(
1342
+ (f) => (f.name === "object" || f.name === "type") && f.type.kind === "literal" && typeof f.type.value === "string"
1343
+ );
1344
+ }
1345
+ function isModelReferencedByOthers(name, models, excludeNames) {
1346
+ for (const model of models) {
1347
+ if (excludeNames.has(model.name)) continue;
1348
+ for (const field of model.fields) {
1349
+ let found = false;
1350
+ walkTypeRef(field.type, {
1351
+ model: (r) => {
1352
+ if (r.name === name) found = true;
1353
+ }
1354
+ });
1355
+ if (found) return true;
1356
+ }
1357
+ }
1358
+ return false;
1359
+ }
1307
1360
  function collapseJsonSuffixModels(models, services) {
1308
1361
  const normalizedModels = [...models];
1309
1362
  const byName = new Map(normalizedModels.map((m) => [m.name, m]));
1363
+ const mergeCandidates = [];
1310
1364
  for (const model of normalizedModels) {
1311
1365
  if (!model.name.endsWith("Json")) continue;
1312
1366
  const baseName = model.name.slice(0, -4);
@@ -1314,13 +1368,20 @@ function collapseJsonSuffixModels(models, services) {
1314
1368
  if (!baseModel || model.fields.length <= baseModel.fields.length) continue;
1315
1369
  const isSuperset = baseModel.fields.every((f) => model.fields.some((mf) => mf.name === f.name));
1316
1370
  if (!isSuperset) continue;
1317
- baseModel.fields = model.fields;
1318
- const jsonIdx = normalizedModels.indexOf(model);
1319
- if (jsonIdx !== -1) normalizedModels.splice(jsonIdx, 1);
1320
- rewriteModelRefsInServices(services, model.name, baseName);
1321
- console.warn(`[oagen] Merged "${model.name}" into "${baseName}" (${model.fields.length} fields, superset)`);
1371
+ if (hasModelConstDiscriminant(model) && hasModelConstDiscriminant(baseModel)) continue;
1372
+ if (isModelReferencedByOthers(model.name, normalizedModels, /* @__PURE__ */ new Set([model.name, baseName]))) continue;
1373
+ mergeCandidates.push({ jsonModel: model, baseModel });
1374
+ }
1375
+ const toRemove = /* @__PURE__ */ new Set();
1376
+ for (const { jsonModel, baseModel } of mergeCandidates) {
1377
+ baseModel.fields = jsonModel.fields;
1378
+ toRemove.add(jsonModel.name);
1379
+ rewriteModelRefs(normalizedModels, services, jsonModel.name, baseModel.name);
1380
+ console.warn(
1381
+ `[oagen] Merged "${jsonModel.name}" into "${baseModel.name}" (${jsonModel.fields.length} fields, superset)`
1382
+ );
1322
1383
  }
1323
- return normalizedModels;
1384
+ return normalizedModels.filter((m) => !toRemove.has(m.name));
1324
1385
  }
1325
1386
 
1326
1387
  // src/parser/collect-inline-enums.ts
@@ -1382,11 +1443,11 @@ async function parseSpec(specPath, options) {
1382
1443
  options?.operationIdTransform
1383
1444
  );
1384
1445
  const responseNormalizedModels = mergeInlineResponseModels(models, inlineModels);
1385
- const jsonCollapsedModels = collapseJsonSuffixModels(responseNormalizedModels, services);
1386
1446
  const fieldInlineModels = extractInlineModelsFromSchemas(
1387
1447
  spec.components?.schemas
1388
1448
  );
1389
- const finalModels = mergeFieldInlineModels(jsonCollapsedModels, fieldInlineModels);
1449
+ const fieldMergedModels = mergeFieldInlineModels(responseNormalizedModels, fieldInlineModels);
1450
+ const finalModels = collapseJsonSuffixModels(fieldMergedModels, services);
1390
1451
  collectInlineEnumsFromModels(finalModels, enums);
1391
1452
  const auth = extractAuthSchemes(spec.components?.securitySchemes);
1392
1453
  const serverEntries = (spec.servers ?? []).map((s) => ({ url: s.url ?? "", description: s.description })).filter((s) => s.url);
@@ -1419,233 +1480,6 @@ function extractAuthSchemes(securitySchemes) {
1419
1480
  return schemes.length > 0 ? schemes : void 0;
1420
1481
  }
1421
1482
 
1422
- // src/differ/file-map.ts
1423
- function mapChangesToFiles(changes, emitter, ctx) {
1424
- const regenerate = /* @__PURE__ */ new Set();
1425
- const toDelete = /* @__PURE__ */ new Set();
1426
- const modelFiles = /* @__PURE__ */ new Map();
1427
- const enumFiles = /* @__PURE__ */ new Map();
1428
- const serviceFiles = /* @__PURE__ */ new Map();
1429
- for (const model of ctx.spec.models) {
1430
- const files = emitter.generateModels([model], ctx);
1431
- const sigs = emitter.generateTypeSignatures?.({ ...ctx.spec, models: [model], enums: [], services: [] }, ctx) ?? [];
1432
- modelFiles.set(
1433
- model.name,
1434
- [...files, ...sigs].map((f) => f.path)
1435
- );
1436
- }
1437
- for (const enumDef of ctx.spec.enums) {
1438
- const files = emitter.generateEnums([enumDef], ctx);
1439
- enumFiles.set(
1440
- enumDef.name,
1441
- files.map((f) => f.path)
1442
- );
1443
- }
1444
- for (const service of ctx.spec.services) {
1445
- const files = [
1446
- ...emitter.generateResources([service], ctx),
1447
- ...emitter.generateTests({ ...ctx.spec, services: [service], models: [], enums: [] }, ctx)
1448
- ];
1449
- const sigs = emitter.generateTypeSignatures?.({ ...ctx.spec, services: [service], models: [], enums: [] }, ctx) ?? [];
1450
- serviceFiles.set(
1451
- service.name,
1452
- [...files, ...sigs].map((f) => f.path)
1453
- );
1454
- }
1455
- const modelToServices = buildReferenceGraph(ctx.spec);
1456
- for (const change of changes) {
1457
- switch (change.kind) {
1458
- case "model-added":
1459
- case "model-modified": {
1460
- const files = modelFiles.get(change.name) ?? [];
1461
- files.forEach((f) => regenerate.add(f));
1462
- const refs = modelToServices.get(change.name) ?? [];
1463
- for (const svc of refs) {
1464
- const svcFiles = serviceFiles.get(svc) ?? [];
1465
- svcFiles.forEach((f) => regenerate.add(f));
1466
- }
1467
- break;
1468
- }
1469
- case "model-removed": {
1470
- const files = modelFiles.get(change.name) ?? [];
1471
- files.forEach((f) => toDelete.add(f));
1472
- const refs = modelToServices.get(change.name) ?? [];
1473
- for (const svc of refs) {
1474
- const svcFiles = serviceFiles.get(svc) ?? [];
1475
- svcFiles.forEach((f) => regenerate.add(f));
1476
- }
1477
- break;
1478
- }
1479
- case "enum-added":
1480
- case "enum-modified": {
1481
- const files = enumFiles.get(change.name) ?? [];
1482
- files.forEach((f) => regenerate.add(f));
1483
- const refs = modelToServices.get(change.name) ?? [];
1484
- for (const svc of refs) {
1485
- const svcFiles = serviceFiles.get(svc) ?? [];
1486
- svcFiles.forEach((f) => regenerate.add(f));
1487
- }
1488
- break;
1489
- }
1490
- case "enum-removed": {
1491
- const files = enumFiles.get(change.name) ?? [];
1492
- files.forEach((f) => toDelete.add(f));
1493
- const refs = modelToServices.get(change.name) ?? [];
1494
- for (const svc of refs) {
1495
- const svcFiles = serviceFiles.get(svc) ?? [];
1496
- svcFiles.forEach((f) => regenerate.add(f));
1497
- }
1498
- break;
1499
- }
1500
- case "service-added": {
1501
- const files = serviceFiles.get(change.name) ?? [];
1502
- files.forEach((f) => regenerate.add(f));
1503
- break;
1504
- }
1505
- case "service-removed": {
1506
- const files = serviceFiles.get(change.name) ?? [];
1507
- files.forEach((f) => toDelete.add(f));
1508
- break;
1509
- }
1510
- case "operation-added":
1511
- case "operation-removed":
1512
- case "operation-modified": {
1513
- const files = serviceFiles.get(change.serviceName) ?? [];
1514
- files.forEach((f) => regenerate.add(f));
1515
- break;
1516
- }
1517
- }
1518
- }
1519
- for (const f of toDelete) {
1520
- regenerate.delete(f);
1521
- }
1522
- return {
1523
- regenerate: [...regenerate].sort(),
1524
- delete: [...toDelete].sort()
1525
- };
1526
- }
1527
- function buildReferenceGraph(spec) {
1528
- const refs = /* @__PURE__ */ new Map();
1529
- function trackRef(typeRef, serviceName) {
1530
- const addName = (name) => {
1531
- if (!refs.has(name)) refs.set(name, /* @__PURE__ */ new Set());
1532
- refs.get(name).add(serviceName);
1533
- };
1534
- walkTypeRef(typeRef, {
1535
- model: (r) => addName(r.name),
1536
- enum: (r) => addName(r.name)
1537
- });
1538
- }
1539
- for (const service of spec.services) {
1540
- for (const op of service.operations) {
1541
- for (const p of [...op.pathParams, ...op.queryParams, ...op.headerParams]) {
1542
- trackRef(p.type, service.name);
1543
- }
1544
- if (op.requestBody) trackRef(op.requestBody, service.name);
1545
- trackRef(op.response, service.name);
1546
- }
1547
- }
1548
- const modelDeps = /* @__PURE__ */ new Map();
1549
- function collectDeps(typeRef, deps) {
1550
- walkTypeRef(typeRef, {
1551
- model: (r) => deps.add(r.name),
1552
- enum: (r) => deps.add(r.name)
1553
- });
1554
- }
1555
- for (const model of spec.models) {
1556
- const deps = /* @__PURE__ */ new Set();
1557
- for (const field of model.fields) {
1558
- collectDeps(field.type, deps);
1559
- }
1560
- deps.delete(model.name);
1561
- if (deps.size > 0) {
1562
- modelDeps.set(model.name, deps);
1563
- }
1564
- }
1565
- let changed = true;
1566
- while (changed) {
1567
- changed = false;
1568
- for (const [modelName, services] of refs) {
1569
- const deps = modelDeps.get(modelName);
1570
- if (!deps) continue;
1571
- for (const dep of deps) {
1572
- if (!refs.has(dep)) refs.set(dep, /* @__PURE__ */ new Set());
1573
- const depServices = refs.get(dep);
1574
- for (const svc of services) {
1575
- if (!depServices.has(svc)) {
1576
- depServices.add(svc);
1577
- changed = true;
1578
- }
1579
- }
1580
- }
1581
- }
1582
- }
1583
- const result = /* @__PURE__ */ new Map();
1584
- for (const [name, svcSet] of refs) {
1585
- result.set(name, [...svcSet]);
1586
- }
1587
- return result;
1588
- }
1589
-
1590
- // src/engine/incremental.ts
1591
- import * as fs from "fs/promises";
1592
- import * as path from "path";
1593
- async function generateIncremental(oldSpec, newSpec, emitter, options) {
1594
- const diff = diffSpecs(oldSpec, newSpec);
1595
- if (diff.changes.length === 0) {
1596
- return { generated: [], deleted: [], diff };
1597
- }
1598
- const ctx = buildEmitterContext(newSpec, options);
1599
- const affected = mapChangesToFiles(diff.changes, emitter, ctx);
1600
- const allFiles = generateAllFiles(newSpec, emitter, ctx);
1601
- const header = emitter.fileHeader();
1602
- const affectedSet = new Set(affected.regenerate);
1603
- const generated = applyFileHeaders(
1604
- allFiles.filter((f) => affectedSet.has(f.path)),
1605
- header
1606
- );
1607
- if (!options.dryRun) {
1608
- const writeResult = await writeFiles(generated, options.outputDir, {
1609
- language: emitter.language,
1610
- header
1611
- });
1612
- if (writeResult.ignored.length > 0) {
1613
- console.log(`Ignored ${writeResult.ignored.length} files (@oagen-ignore-file)`);
1614
- }
1615
- if (options.target) {
1616
- const targetResult = await integrateGeneratedFiles({
1617
- files: generated,
1618
- language: emitter.language,
1619
- targetDir: options.target,
1620
- header
1621
- });
1622
- if (targetResult.written.length > 0) {
1623
- console.log(`Target: created ${targetResult.written.length} new files`);
1624
- }
1625
- if (targetResult.merged.length > 0) {
1626
- console.log(`Target: merged into ${targetResult.merged.length} existing files (additive only)`);
1627
- }
1628
- if (targetResult.skipped.length > 0) {
1629
- console.log(`Target: skipped ${targetResult.skipped.length} files (excluded or no grammar)`);
1630
- }
1631
- if (targetResult.ignored.length > 0) {
1632
- console.log(`Target: ignored ${targetResult.ignored.length} files (@oagen-ignore-file)`);
1633
- }
1634
- }
1635
- if (options.force) {
1636
- for (const filePath of affected.delete) {
1637
- const fullPath = path.join(options.outputDir, filePath);
1638
- await fs.rm(fullPath, { force: true });
1639
- }
1640
- }
1641
- }
1642
- return {
1643
- generated,
1644
- deleted: options.force ? affected.delete : [],
1645
- diff
1646
- };
1647
- }
1648
-
1649
1483
  // src/engine/operation-plan.ts
1650
1484
  function planOperation(op) {
1651
1485
  const isDelete = op.httpMethod === "delete";
@@ -1714,10 +1548,8 @@ export {
1714
1548
  generate,
1715
1549
  generateAllFiles,
1716
1550
  generateFiles,
1717
- generateIncremental,
1718
1551
  getEmitter,
1719
1552
  integrateGeneratedFiles,
1720
- mapChangesToFiles,
1721
1553
  mapFilesForTargetIntegration,
1722
1554
  mapTypeRef,
1723
1555
  parseSpec,