@workos/oagen 0.1.1 → 0.3.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.
Files changed (47) hide show
  1. package/dist/cli/index.d.mts +2 -0
  2. package/dist/cli/index.mjs +6233 -0
  3. package/dist/cli/index.mjs.map +1 -0
  4. package/dist/compat/{index.d.ts → index.d.mts} +43 -114
  5. package/dist/compat/index.d.mts.map +1 -0
  6. package/dist/compat/index.mjs +4562 -0
  7. package/dist/compat/index.mjs.map +1 -0
  8. package/dist/diff-Cn6S74vV.mjs +2432 -0
  9. package/dist/diff-Cn6S74vV.mjs.map +1 -0
  10. package/dist/differ-B6472Yef.mjs +1100 -0
  11. package/dist/differ-B6472Yef.mjs.map +1 -0
  12. package/dist/index.d.mts +324 -0
  13. package/dist/index.d.mts.map +1 -0
  14. package/dist/index.mjs +1553 -0
  15. package/dist/index.mjs.map +1 -0
  16. package/dist/scripts/smoke/baseline.d.mts +1 -0
  17. package/dist/scripts/smoke/baseline.mjs +1951 -0
  18. package/dist/scripts/smoke/baseline.mjs.map +1 -0
  19. package/dist/scripts/smoke/sdk-test.d.mts +1 -0
  20. package/dist/scripts/smoke/sdk-test.mjs +111 -0
  21. package/dist/scripts/smoke/sdk-test.mjs.map +1 -0
  22. package/dist/smoke.d.mts +331 -0
  23. package/dist/smoke.d.mts.map +1 -0
  24. package/dist/smoke.mjs +2119 -0
  25. package/dist/smoke.mjs.map +1 -0
  26. package/dist/tree-sitter-DFRXfWg9.mjs +394 -0
  27. package/dist/tree-sitter-DFRXfWg9.mjs.map +1 -0
  28. package/dist/types-O25qZxMl.d.mts +397 -0
  29. package/dist/types-O25qZxMl.d.mts.map +1 -0
  30. package/dist/verify/index.d.mts +82 -0
  31. package/dist/verify/index.d.mts.map +1 -0
  32. package/dist/verify/index.mjs +327 -0
  33. package/dist/verify/index.mjs.map +1 -0
  34. package/package.json +19 -16
  35. package/dist/chunk-2JXFTDTO.js +0 -1669
  36. package/dist/chunk-CIX5NOBF.js +0 -263
  37. package/dist/chunk-IJ5E5AE7.js +0 -1040
  38. package/dist/cli/index.js +0 -5322
  39. package/dist/compat/index.js +0 -4491
  40. package/dist/index.d.ts +0 -277
  41. package/dist/index.js +0 -1565
  42. package/dist/scripts/smoke/baseline.js +0 -1813
  43. package/dist/scripts/smoke/sdk-test.js +0 -93
  44. package/dist/smoke.js +0 -2025
  45. package/dist/types-B5t9O9Wm.d.ts +0 -367
  46. package/dist/verify/index.d.ts +0 -76
  47. package/dist/verify/index.js +0 -336
@@ -1,1040 +0,0 @@
1
- import {
2
- RegistryError,
3
- splitWords,
4
- toSnakeCase,
5
- walkTypeRef
6
- } from "./chunk-CIX5NOBF.js";
7
-
8
- // src/compat/extractor-registry.ts
9
- var extractors = /* @__PURE__ */ new Map();
10
- function registerExtractor(extractor) {
11
- extractors.set(extractor.language, extractor);
12
- }
13
- function getExtractor(language) {
14
- const extractor = extractors.get(language);
15
- if (!extractor) {
16
- const available = [...extractors.keys()].join(", ") || "(none)";
17
- throw new RegistryError(
18
- `No extractor registered for language: ${language}. Available: ${available}`,
19
- `Register an extractor for "${language}" via registerExtractor() or add one in your oagen.config.ts plugin.`
20
- );
21
- }
22
- return extractor;
23
- }
24
-
25
- // src/compat/language-hints.ts
26
- var UNTYPED_MAP_PATTERNS = /* @__PURE__ */ new Set([
27
- "Record<string, unknown>",
28
- "Record<string, any>",
29
- "{ [key: string]: any; }",
30
- "{ [key: string]: unknown; }",
31
- "any"
32
- ]);
33
- var NAMED_TYPE_RE = /^[A-Z][a-zA-Z0-9]*$/;
34
- function typeExistsInSurface(name, surface) {
35
- return !!(surface.interfaces[name] || surface.classes[name] || surface.enums[name]);
36
- }
37
- function splitPascalWords(s) {
38
- return splitWords(s).filter((w) => w.length > 2);
39
- }
40
- function namedTypeWordsOverlap(a, b, minOverlap = 1) {
41
- const aNoResp = a.replace(/Response$/, "");
42
- const bNoResp = b.replace(/Response$/, "");
43
- const aWords = new Set(splitPascalWords(aNoResp).map((w) => w.toLowerCase()));
44
- const bWords = new Set(splitPascalWords(bNoResp).map((w) => w.toLowerCase()));
45
- const overlap = [...aWords].filter((w) => bWords.has(w));
46
- return overlap.length >= minOverlap && overlap.length >= Math.min(aWords.size, bWords.size) - 1;
47
- }
48
- function parseUnionMembers(s) {
49
- return s.split("|").map((p) => p.trim()).filter(Boolean);
50
- }
51
- function defaultIsNullableOnlyDifference(hints, a, b) {
52
- return (hints.stripNullable(a) ?? a) === (hints.stripNullable(b) ?? b) && a !== b;
53
- }
54
- var nodeHints = {
55
- stripNullable(type) {
56
- const members = parseUnionMembers(type);
57
- const stripped = members.filter((p) => p !== "null");
58
- if (stripped.length === members.length) {
59
- return null;
60
- }
61
- return stripped.join(" | ");
62
- },
63
- isNullableOnlyDifference(a, b) {
64
- return (nodeHints.stripNullable(a) ?? a) === (nodeHints.stripNullable(b) ?? b) && a !== b;
65
- },
66
- isUnionReorder(a, b) {
67
- const membersA = parseUnionMembers(a).sort();
68
- const membersB = parseUnionMembers(b).sort();
69
- if (membersA.length !== membersB.length || membersA.length < 2) return false;
70
- return membersA.every((m, i) => m === membersB[i]);
71
- },
72
- isGenericTypeParam(type) {
73
- if (/^[A-Z]$/.test(type)) return true;
74
- if (/^T[A-Z][a-zA-Z]*$/.test(type)) return true;
75
- if (/^[A-Z]\[\]$/.test(type) || /^T[A-Z][a-zA-Z]*\[\]$/.test(type)) return true;
76
- return false;
77
- },
78
- isExtractionArtifact(type) {
79
- return type === "any" || type === "Record<string, unknown>";
80
- },
81
- tolerateCategoryMismatch: true,
82
- extractReturnTypeName(returnType) {
83
- let inner = returnType;
84
- while (inner.startsWith("Promise<") && inner.endsWith(">")) {
85
- inner = inner.slice(8, -1);
86
- }
87
- const genericMatch = inner.match(/^[A-Za-z]+<(.+)>$/);
88
- if (genericMatch) {
89
- inner = genericMatch[1];
90
- }
91
- inner = inner.replace(/\[\]$/, "");
92
- if (["void", "string", "number", "boolean", "any", "unknown", "null", "undefined"].includes(inner)) {
93
- return null;
94
- }
95
- return inner;
96
- },
97
- extractParamTypeName(paramType) {
98
- if (["string", "number", "boolean", "any", "unknown"].includes(paramType)) {
99
- return null;
100
- }
101
- return paramType;
102
- },
103
- propertyMatchesClass(propertyName, className) {
104
- const normalizedProp = propertyName.replace(/[-_]/g, "").toLowerCase();
105
- return normalizedProp === className.toLowerCase();
106
- },
107
- derivedModelNames(modelName) {
108
- return [`${modelName}Response`, `Serialized${modelName}`];
109
- },
110
- isTypeEquivalent(baselineType, candidateType, candidateSurface) {
111
- const candEnum = candidateSurface.enums[candidateType];
112
- if (candEnum) {
113
- const enumValuesSet = new Set(Object.values(candEnum.members).flatMap((v) => [`"${v}"`, `'${v}'`]));
114
- const baseMembers = parseUnionMembers(baselineType);
115
- if (baseMembers.length > 0 && baseMembers.every((m) => enumValuesSet.has(m))) {
116
- return true;
117
- }
118
- }
119
- if (UNTYPED_MAP_PATTERNS.has(baselineType) && UNTYPED_MAP_PATTERNS.has(candidateType)) {
120
- return true;
121
- }
122
- if (baselineType.startsWith("{") && baselineType.endsWith("}")) {
123
- if (typeExistsInSurface(candidateType, candidateSurface)) {
124
- return true;
125
- }
126
- }
127
- const baseClean = baselineType.replace(/\[\]$/, "");
128
- const candClean = candidateType.replace(/\[\]$/, "");
129
- const sameArrayness = baselineType.endsWith("[]") === candidateType.endsWith("[]");
130
- if (sameArrayness) {
131
- if (candClean === baseClean + "Response" || baseClean === candClean + "Response") {
132
- return true;
133
- }
134
- }
135
- if (sameArrayness && NAMED_TYPE_RE.test(baseClean) && NAMED_TYPE_RE.test(candClean)) {
136
- if (typeExistsInSurface(candClean, candidateSurface)) {
137
- if (candClean.includes(baseClean) || baseClean.includes(candClean)) {
138
- return true;
139
- }
140
- const baseNoResp = baseClean.replace(/Response$/, "");
141
- const candNoResp = candClean.replace(/Response$/, "");
142
- if (candNoResp.includes(baseNoResp) || baseNoResp.includes(candNoResp)) {
143
- return true;
144
- }
145
- if (namedTypeWordsOverlap(baseClean, candClean, 2)) {
146
- return true;
147
- }
148
- }
149
- }
150
- return false;
151
- }
152
- };
153
- function resolveHints(overrides) {
154
- return { ...nodeHints, ...overrides };
155
- }
156
-
157
- // src/compat/overlay.ts
158
- function findClassForProperty(surface, sdkResourceProperty, hints) {
159
- for (const [className, cls] of Object.entries(surface.classes)) {
160
- if (hints.propertyMatchesClass(sdkResourceProperty, className)) {
161
- return className;
162
- }
163
- for (const [propName, prop] of Object.entries(cls.properties)) {
164
- if (propName === sdkResourceProperty) {
165
- const propType = prop.type;
166
- if (propType && surface.classes[propType]) {
167
- return propType;
168
- }
169
- return void 0;
170
- }
171
- }
172
- }
173
- const propWords = splitWords(sdkResourceProperty).map((w) => w.toLowerCase());
174
- if (propWords.length > 1) {
175
- for (const [className] of Object.entries(surface.classes)) {
176
- const classWords = splitWords(className).map((w) => w.toLowerCase());
177
- if (classWords.length > 0 && classWords.length < propWords.length) {
178
- const suffix = propWords.slice(propWords.length - classWords.length);
179
- if (classWords.every((w, i) => w === suffix[i])) {
180
- return className;
181
- }
182
- }
183
- }
184
- }
185
- return void 0;
186
- }
187
- function buildOverlayLookup(surface, manifest, spec, hints, options) {
188
- const resolvedHints = hints ?? nodeHints;
189
- const lookup = {
190
- methodByOperation: /* @__PURE__ */ new Map(),
191
- httpKeyByMethod: /* @__PURE__ */ new Map(),
192
- interfaceByName: /* @__PURE__ */ new Map(),
193
- typeAliasByName: /* @__PURE__ */ new Map(),
194
- requiredExports: /* @__PURE__ */ new Map(),
195
- modelNameByIR: /* @__PURE__ */ new Map(),
196
- fileBySymbol: /* @__PURE__ */ new Map()
197
- };
198
- if (manifest) {
199
- for (const entry of manifest) {
200
- const key = `${entry.httpMethod.toUpperCase()} ${entry.path}`;
201
- const className = findClassForProperty(surface, entry.sdkResourceProperty, resolvedHints);
202
- if (className) {
203
- let resolvedMethodName = entry.sdkMethodName;
204
- let methods = surface.classes[className]?.methods[resolvedMethodName];
205
- if (!methods) {
206
- const classMethods = surface.classes[className]?.methods ?? {};
207
- const prefix = resolvedMethodName.toLowerCase();
208
- const candidates = [];
209
- for (const [name, overloads] of Object.entries(classMethods)) {
210
- if (name.toLowerCase().startsWith(prefix) && name !== resolvedMethodName) {
211
- candidates.push([name, overloads]);
212
- }
213
- }
214
- if (candidates.length === 1) {
215
- methods = candidates[0][1];
216
- resolvedMethodName = candidates[0][0];
217
- }
218
- }
219
- if (!methods) {
220
- const classMethods = surface.classes[className]?.methods ?? {};
221
- const suffix = resolvedMethodName.toLowerCase();
222
- const candidates = [];
223
- for (const [name, overloads] of Object.entries(classMethods)) {
224
- if (name.toLowerCase().endsWith(suffix) && name !== resolvedMethodName) {
225
- candidates.push([name, overloads]);
226
- }
227
- }
228
- if (candidates.length === 1) {
229
- methods = candidates[0][1];
230
- resolvedMethodName = candidates[0][0];
231
- }
232
- }
233
- const method = methods?.[0];
234
- if (method) {
235
- lookup.methodByOperation.set(key, {
236
- className,
237
- methodName: resolvedMethodName,
238
- params: method.params,
239
- returnType: method.returnType
240
- });
241
- lookup.httpKeyByMethod.set(`${className}.${resolvedMethodName}`, key);
242
- }
243
- }
244
- }
245
- }
246
- for (const name of Object.keys(surface.interfaces)) {
247
- lookup.interfaceByName.set(name, name);
248
- }
249
- for (const name of Object.keys(surface.typeAliases)) {
250
- lookup.typeAliasByName.set(name, name);
251
- }
252
- for (const [path, symbols] of Object.entries(surface.exports)) {
253
- lookup.requiredExports.set(path, new Set(symbols));
254
- }
255
- for (const record of [surface.classes, surface.interfaces, surface.typeAliases, surface.enums]) {
256
- for (const [name, item] of Object.entries(record)) {
257
- if (item.sourceFile) lookup.fileBySymbol.set(name, item.sourceFile);
258
- }
259
- }
260
- if (spec) {
261
- buildModelNameMap(surface, spec, lookup, resolvedHints, options);
262
- for (const [irName, sdkName] of lookup.modelNameByIR) {
263
- const filePath = lookup.fileBySymbol.get(sdkName);
264
- if (filePath) {
265
- lookup.fileBySymbol.set(irName, filePath);
266
- }
267
- }
268
- }
269
- return lookup;
270
- }
271
- function normalizeFieldName(name) {
272
- return toSnakeCase(name);
273
- }
274
- function jaccardSimilarity(a, b) {
275
- if (a.size === 0 && b.size === 0) return { score: 0, intersection: 0 };
276
- let intersection = 0;
277
- for (const item of a) {
278
- if (b.has(item)) intersection++;
279
- }
280
- const union = a.size + b.size - intersection;
281
- return { score: union === 0 ? 0 : intersection / union, intersection };
282
- }
283
- function irModelFieldSignature(model) {
284
- return new Set(model.fields.map((f) => normalizeFieldName(f.name)));
285
- }
286
- function sdkInterfaceFieldSignature(iface) {
287
- return new Set(Object.keys(iface.fields).map(normalizeFieldName));
288
- }
289
- function buildModelNameMap(surface, spec, lookup, hints, options) {
290
- const mapped = /* @__PURE__ */ new Set();
291
- const usedSdkNames = /* @__PURE__ */ new Set();
292
- if (lookup.methodByOperation.size > 0) {
293
- for (const service of spec.services) {
294
- for (const op of service.operations) {
295
- const httpKey = `${op.httpMethod.toUpperCase()} ${op.path}`;
296
- const methodOverlay = lookup.methodByOperation.get(httpKey);
297
- if (!methodOverlay) continue;
298
- if (op.response.kind === "model" && !mapped.has(op.response.name)) {
299
- const sdkTypeName = hints.extractReturnTypeName(methodOverlay.returnType);
300
- if (sdkTypeName && surface.interfaces[sdkTypeName]) {
301
- lookup.modelNameByIR.set(op.response.name, sdkTypeName);
302
- mapped.add(op.response.name);
303
- usedSdkNames.add(sdkTypeName);
304
- }
305
- }
306
- if (op.requestBody?.kind === "model" && !mapped.has(op.requestBody.name)) {
307
- for (const param of methodOverlay.params) {
308
- const sdkTypeName = hints.extractParamTypeName(param.type);
309
- if (sdkTypeName && surface.interfaces[sdkTypeName]) {
310
- lookup.modelNameByIR.set(op.requestBody.name, sdkTypeName);
311
- mapped.add(op.requestBody.name);
312
- usedSdkNames.add(sdkTypeName);
313
- break;
314
- }
315
- }
316
- }
317
- }
318
- }
319
- }
320
- const sdkSignatures = [];
321
- for (const [name, iface] of Object.entries(surface.interfaces)) {
322
- if (usedSdkNames.has(name)) continue;
323
- const fields = sdkInterfaceFieldSignature(iface);
324
- if (fields.size >= 2) {
325
- sdkSignatures.push({ name, fields, fieldCount: fields.size });
326
- }
327
- }
328
- const sdkSignatureNames = new Set(sdkSignatures.map((s) => s.name));
329
- for (const model of spec.models) {
330
- if (mapped.has(model.name)) continue;
331
- if (model.fields.length < 2) continue;
332
- if (sdkSignatureNames.has(model.name) && !usedSdkNames.has(model.name)) {
333
- lookup.modelNameByIR.set(model.name, model.name);
334
- mapped.add(model.name);
335
- usedSdkNames.add(model.name);
336
- continue;
337
- }
338
- const derivedNames = hints.derivedModelNames(model.name);
339
- for (const derived of derivedNames) {
340
- if (sdkSignatureNames.has(derived) && !usedSdkNames.has(derived)) {
341
- lookup.modelNameByIR.set(model.name, derived);
342
- mapped.add(model.name);
343
- usedSdkNames.add(derived);
344
- break;
345
- }
346
- }
347
- if (mapped.has(model.name)) continue;
348
- if (options?.strictModelMatch) continue;
349
- const irFields = irModelFieldSignature(model);
350
- let bestMatch = null;
351
- let bestScore = 0;
352
- for (const sdk of sdkSignatures) {
353
- if (usedSdkNames.has(sdk.name)) continue;
354
- const { score, intersection } = jaccardSimilarity(irFields, sdk.fields);
355
- const minIntersection = Math.max(3, Math.ceil(irFields.size * 0.5));
356
- if (score > bestScore && score >= 0.6 && intersection >= minIntersection) {
357
- bestScore = score;
358
- bestMatch = sdk.name;
359
- }
360
- }
361
- if (bestMatch) {
362
- lookup.modelNameByIR.set(model.name, bestMatch);
363
- mapped.add(model.name);
364
- usedSdkNames.add(bestMatch);
365
- }
366
- }
367
- }
368
- function patchOverlay(overlay, violations, baseline) {
369
- const patched = {
370
- methodByOperation: new Map(overlay.methodByOperation),
371
- httpKeyByMethod: new Map(overlay.httpKeyByMethod),
372
- interfaceByName: new Map(overlay.interfaceByName),
373
- typeAliasByName: new Map(overlay.typeAliasByName),
374
- requiredExports: new Map(Array.from(overlay.requiredExports.entries()).map(([k, v]) => [k, new Set(v)])),
375
- modelNameByIR: new Map(overlay.modelNameByIR),
376
- fileBySymbol: new Map(overlay.fileBySymbol)
377
- };
378
- let warnedManifest = false;
379
- for (const v of violations) {
380
- if (v.category === "public-api") {
381
- const parts = v.symbolPath.split(".");
382
- if (parts.length === 2) {
383
- const [className, methodName] = parts;
384
- const baseClass = baseline.classes[className];
385
- if (baseClass && baseClass.methods[methodName]?.length > 0) {
386
- const httpKey = overlay.httpKeyByMethod.get(`${className}.${methodName}`);
387
- if (!httpKey && overlay.httpKeyByMethod.size === 0 && !warnedManifest) {
388
- console.warn(
389
- "Warning: No smoke-manifest.json available. Method-level violations cannot be auto-patched. Implement generateManifest in your emitter for the self-correcting loop to resolve these."
390
- );
391
- warnedManifest = true;
392
- }
393
- if (httpKey) {
394
- const method = baseClass.methods[methodName][0];
395
- patched.methodByOperation.set(httpKey, {
396
- className,
397
- methodName,
398
- params: method.params,
399
- returnType: method.returnType
400
- });
401
- }
402
- }
403
- const baseIface = baseline.interfaces[className];
404
- if (baseIface) {
405
- patched.interfaceByName.set(className, className);
406
- }
407
- } else if (parts.length === 1) {
408
- const name = parts[0];
409
- if (baseline.interfaces[name]) {
410
- patched.interfaceByName.set(name, name);
411
- }
412
- if (baseline.typeAliases[name]) {
413
- patched.typeAliasByName.set(name, name);
414
- }
415
- }
416
- }
417
- if (v.category === "export-structure") {
418
- const match = v.symbolPath.match(/^exports\[(.+?)\]\.(.+)$/);
419
- if (match) {
420
- const [, path, symbol] = match;
421
- if (!patched.requiredExports.has(path)) {
422
- patched.requiredExports.set(path, /* @__PURE__ */ new Set());
423
- }
424
- patched.requiredExports.get(path).add(symbol);
425
- }
426
- }
427
- }
428
- return patched;
429
- }
430
-
431
- // src/compat/spec-filter.ts
432
- function specDerivedNames(spec, hints) {
433
- const names = /* @__PURE__ */ new Set();
434
- const topLevelEnumNames = new Set(spec.enums.map((e) => e.name));
435
- for (const service of spec.services) {
436
- names.add(service.name);
437
- for (const op of service.operations) {
438
- collectTypeRefNames(op.response, names, hints, topLevelEnumNames);
439
- if (op.requestBody) collectTypeRefNames(op.requestBody, names, hints, topLevelEnumNames);
440
- for (const p of [...op.pathParams, ...op.queryParams, ...op.headerParams, ...op.cookieParams ?? []]) {
441
- collectTypeRefNames(p.type, names, hints, topLevelEnumNames);
442
- }
443
- }
444
- }
445
- for (const model of spec.models) {
446
- names.add(model.name);
447
- for (const derived of hints.derivedModelNames(model.name)) {
448
- names.add(derived);
449
- }
450
- for (const field of model.fields) {
451
- collectTypeRefNames(field.type, names, hints, topLevelEnumNames);
452
- }
453
- }
454
- for (const e of spec.enums) {
455
- names.add(e.name);
456
- }
457
- return names;
458
- }
459
- function specDerivedEnumValues(spec) {
460
- const result = /* @__PURE__ */ new Map();
461
- for (const e of spec.enums) {
462
- result.set(e.name, new Set(e.values.map((v) => v.value)));
463
- }
464
- return result;
465
- }
466
- function specDerivedFieldPaths(spec, hints) {
467
- const paths = /* @__PURE__ */ new Set();
468
- for (const model of spec.models) {
469
- for (const field of model.fields) {
470
- paths.add(`${model.name}.${field.name}`);
471
- }
472
- for (const derived of hints.derivedModelNames(model.name)) {
473
- for (const field of model.fields) {
474
- paths.add(`${derived}.${field.name}`);
475
- }
476
- }
477
- }
478
- return paths;
479
- }
480
- function specDerivedMethodPaths(spec) {
481
- const paths = /* @__PURE__ */ new Set();
482
- for (const service of spec.services) {
483
- for (const op of service.operations) {
484
- paths.add(`${service.name}.${op.name}`);
485
- }
486
- }
487
- return paths;
488
- }
489
- function specDerivedHttpKeys(spec) {
490
- const keys = /* @__PURE__ */ new Set();
491
- for (const service of spec.services) {
492
- for (const op of service.operations) {
493
- keys.add(`${op.httpMethod.toUpperCase()} ${op.path}`);
494
- }
495
- }
496
- return keys;
497
- }
498
- function collectTypeRefNames(ref, out, hints, topLevelEnums) {
499
- walkTypeRef(ref, {
500
- model: (r) => {
501
- out.add(r.name);
502
- for (const derived of hints.derivedModelNames(r.name)) {
503
- out.add(derived);
504
- }
505
- },
506
- enum: (r) => {
507
- if (!topLevelEnums || topLevelEnums.has(r.name)) {
508
- out.add(r.name);
509
- }
510
- }
511
- });
512
- }
513
- function filterRecord(record, allowed) {
514
- const result = {};
515
- for (const [name, value] of Object.entries(record)) {
516
- if (allowed.has(name)) result[name] = value;
517
- }
518
- return result;
519
- }
520
- function filterSurface(surface, allowedNames, opts) {
521
- const { fieldPaths, methodPaths, enumValues } = opts ?? {};
522
- const filteredInterfaces = filterRecord(surface.interfaces, allowedNames);
523
- if (fieldPaths) {
524
- for (const [name, iface] of Object.entries(filteredInterfaces)) {
525
- const filteredFields = {};
526
- for (const [fieldName, field] of Object.entries(iface.fields)) {
527
- if (fieldPaths.has(`${name}.${fieldName}`)) {
528
- filteredFields[fieldName] = field;
529
- }
530
- }
531
- filteredInterfaces[name] = { ...iface, fields: filteredFields };
532
- }
533
- }
534
- let filteredClasses = filterRecord(surface.classes, allowedNames);
535
- if (methodPaths) {
536
- const serviceClassNames = /* @__PURE__ */ new Set();
537
- for (const path of methodPaths) {
538
- const dot = path.indexOf(".");
539
- if (dot !== -1) serviceClassNames.add(path.slice(0, dot));
540
- }
541
- const newClasses = {};
542
- for (const [name, cls] of Object.entries(filteredClasses)) {
543
- if (serviceClassNames.has(name)) {
544
- const filteredMethods = {};
545
- for (const [methodName, overloads] of Object.entries(cls.methods)) {
546
- if (methodPaths.has(`${name}.${methodName}`)) {
547
- filteredMethods[methodName] = overloads;
548
- }
549
- }
550
- const filteredProperties = {};
551
- for (const [propName, prop] of Object.entries(cls.properties)) {
552
- if (methodPaths.has(`${name}.${propName}`)) {
553
- filteredProperties[propName] = prop;
554
- }
555
- }
556
- newClasses[name] = {
557
- ...cls,
558
- methods: filteredMethods,
559
- properties: filteredProperties
560
- };
561
- } else if (fieldPaths) {
562
- const filteredProperties = {};
563
- for (const [propName, prop] of Object.entries(cls.properties)) {
564
- if (fieldPaths.has(`${name}.${propName}`)) {
565
- filteredProperties[propName] = prop;
566
- }
567
- }
568
- const filteredMethods = {};
569
- for (const [methodName, overloads] of Object.entries(cls.methods)) {
570
- if (fieldPaths.has(`${name}.${methodName}`) || methodPaths.has(`${name}.${methodName}`)) {
571
- filteredMethods[methodName] = overloads;
572
- }
573
- }
574
- newClasses[name] = {
575
- ...cls,
576
- methods: filteredMethods,
577
- properties: filteredProperties
578
- };
579
- } else {
580
- newClasses[name] = cls;
581
- }
582
- }
583
- filteredClasses = newClasses;
584
- }
585
- let filteredEnums = filterRecord(surface.enums, allowedNames);
586
- if (enumValues) {
587
- const newEnums = {};
588
- for (const [name, enumDef] of Object.entries(filteredEnums)) {
589
- const specValues = enumValues.get(name);
590
- if (!specValues) {
591
- newEnums[name] = enumDef;
592
- continue;
593
- }
594
- const specValuesLower = new Set([...specValues].map((v) => String(v).toLowerCase()));
595
- const filteredMembers = {};
596
- for (const [member, value] of Object.entries(enumDef.members)) {
597
- if (specValues.has(value) || specValuesLower.has(String(value).toLowerCase())) {
598
- filteredMembers[member] = value;
599
- }
600
- }
601
- newEnums[name] = { ...enumDef, members: filteredMembers };
602
- }
603
- filteredEnums = newEnums;
604
- }
605
- return {
606
- ...surface,
607
- classes: filteredClasses,
608
- interfaces: filteredInterfaces,
609
- typeAliases: filterRecord(surface.typeAliases, allowedNames),
610
- enums: filteredEnums,
611
- exports: {}
612
- // exports are structural, not symbol-level — skip for scoped comparison
613
- };
614
- }
615
-
616
- // src/compat/differ.ts
617
- function diffSurfaces(baseline, candidate, hints) {
618
- const violations = [];
619
- const additions = [];
620
- let totalBaseline = 0;
621
- let preserved = 0;
622
- for (const [name, baseClass] of Object.entries(baseline.classes)) {
623
- totalBaseline++;
624
- const candClass = candidate.classes[name];
625
- if (!candClass) {
626
- violations.push({
627
- category: "public-api",
628
- severity: "breaking",
629
- symbolPath: name,
630
- baseline: name,
631
- candidate: "(missing)",
632
- message: `Class "${name}" exists in baseline but not in generated output`
633
- });
634
- totalBaseline += Object.values(baseClass.methods).reduce((sum, overloads) => sum + overloads.length, 0);
635
- totalBaseline += Object.keys(baseClass.properties).length;
636
- continue;
637
- }
638
- preserved++;
639
- for (const [methodName, baseOverloads] of Object.entries(baseClass.methods)) {
640
- const candOverloads = candClass.methods[methodName];
641
- for (const baseMethod of baseOverloads) {
642
- totalBaseline++;
643
- if (!candOverloads || candOverloads.length === 0) {
644
- violations.push({
645
- category: "public-api",
646
- severity: "breaking",
647
- symbolPath: `${name}.${methodName}`,
648
- baseline: methodName,
649
- candidate: "(missing)",
650
- message: `Method "${name}.${methodName}" exists in baseline but not in generated output`
651
- });
652
- continue;
653
- }
654
- const candMethod = candOverloads.find((c) => signaturesMatch(baseMethod, c));
655
- if (!candMethod) {
656
- const equivalentMethod = hints.isSignatureEquivalent ? candOverloads.find((c) => hints.isSignatureEquivalent(baseMethod, c, candidate)) : void 0;
657
- if (equivalentMethod) {
658
- preserved++;
659
- continue;
660
- }
661
- violations.push({
662
- category: "signature",
663
- severity: "breaking",
664
- symbolPath: `${name}.${methodName}`,
665
- baseline: formatSignature(baseMethod),
666
- candidate: formatSignature(candOverloads[0]),
667
- message: `Signature mismatch for "${name}.${methodName}"`
668
- });
669
- continue;
670
- }
671
- preserved++;
672
- }
673
- }
674
- for (const methodName of Object.keys(candClass.methods)) {
675
- if (!baseClass.methods[methodName]) {
676
- additions.push({ symbolPath: `${name}.${methodName}`, symbolType: "method" });
677
- }
678
- }
679
- for (const [propName, baseProp] of Object.entries(baseClass.properties)) {
680
- totalBaseline++;
681
- const candProp = candClass.properties[propName];
682
- if (!candProp) {
683
- violations.push({
684
- category: "public-api",
685
- severity: "breaking",
686
- symbolPath: `${name}.${propName}`,
687
- baseline: baseProp.type,
688
- candidate: "(missing)",
689
- message: `Property "${name}.${propName}" exists in baseline but not in generated output`
690
- });
691
- continue;
692
- }
693
- if (baseProp.type !== candProp.type) {
694
- const nullableOnly = hints.isNullableOnlyDifference(baseProp.type, candProp.type);
695
- violations.push({
696
- category: "signature",
697
- severity: nullableOnly ? "warning" : "breaking",
698
- symbolPath: `${name}.${propName}`,
699
- baseline: baseProp.type,
700
- candidate: candProp.type,
701
- message: `Property type mismatch for "${name}.${propName}"`
702
- });
703
- if (nullableOnly) preserved++;
704
- continue;
705
- }
706
- preserved++;
707
- }
708
- for (const propName of Object.keys(candClass.properties)) {
709
- if (!baseClass.properties[propName]) {
710
- additions.push({ symbolPath: `${name}.${propName}`, symbolType: "property" });
711
- }
712
- }
713
- }
714
- for (const name of Object.keys(candidate.classes)) {
715
- if (!baseline.classes[name]) {
716
- additions.push({ symbolPath: name, symbolType: "class" });
717
- }
718
- }
719
- const candIfaceFieldSets = /* @__PURE__ */ new Map();
720
- for (const [n, iface] of Object.entries(candidate.interfaces)) {
721
- candIfaceFieldSets.set(n, new Set(Object.keys(iface.fields).map((f) => f.toLowerCase())));
722
- }
723
- const candClassPropSets = /* @__PURE__ */ new Map();
724
- for (const [n, cls] of Object.entries(candidate.classes)) {
725
- candClassPropSets.set(n, new Set(Object.keys(cls.properties).map((f) => f.toLowerCase())));
726
- }
727
- for (const [name, baseIface] of Object.entries(baseline.interfaces)) {
728
- totalBaseline++;
729
- const candIface = candidate.interfaces[name];
730
- if (!candIface) {
731
- let tolerated = false;
732
- if (hints.tolerateCategoryMismatch && name.startsWith("Serialized")) {
733
- const baseName = name.slice("Serialized".length);
734
- if (candidate.interfaces[baseName] || candidate.classes[baseName]) {
735
- tolerated = true;
736
- }
737
- }
738
- if (tolerated) {
739
- preserved++;
740
- totalBaseline += Object.keys(baseIface.fields).length;
741
- preserved += Object.keys(baseIface.fields).length;
742
- continue;
743
- }
744
- if (!tolerated) {
745
- const baseFieldNamesLower = new Set(Object.keys(baseIface.fields).map((f) => f.toLowerCase()));
746
- if (baseFieldNamesLower.size > 0) {
747
- for (const [, candFieldNamesLower] of candIfaceFieldSets) {
748
- if (candFieldNamesLower.size === baseFieldNamesLower.size && [...baseFieldNamesLower].every((f) => candFieldNamesLower.has(f))) {
749
- tolerated = true;
750
- break;
751
- }
752
- }
753
- if (!tolerated) {
754
- for (const [, candPropNamesLower] of candClassPropSets) {
755
- if (candPropNamesLower.size === baseFieldNamesLower.size && [...baseFieldNamesLower].every((f) => candPropNamesLower.has(f))) {
756
- tolerated = true;
757
- break;
758
- }
759
- }
760
- }
761
- } else {
762
- tolerated = true;
763
- }
764
- }
765
- if (tolerated) {
766
- preserved++;
767
- totalBaseline += Object.keys(baseIface.fields).length;
768
- preserved += Object.keys(baseIface.fields).length;
769
- continue;
770
- }
771
- violations.push({
772
- category: "public-api",
773
- severity: "breaking",
774
- symbolPath: name,
775
- baseline: name,
776
- candidate: "(missing)",
777
- message: `Interface "${name}" exists in baseline but not in generated output`
778
- });
779
- totalBaseline += Object.keys(baseIface.fields).length;
780
- continue;
781
- }
782
- preserved++;
783
- for (const [fieldName, baseField] of Object.entries(baseIface.fields)) {
784
- totalBaseline++;
785
- const candField = candIface.fields[fieldName];
786
- if (!candField) {
787
- const baseTypeClean = baseField.type.replace(/\[\]$/, "").replace(/ \| null$/, "");
788
- const typeIsUnresolvable = NAMED_TYPE_RE.test(baseTypeClean) && !typeExistsInSurface(baseTypeClean, candidate);
789
- violations.push({
790
- category: "public-api",
791
- severity: typeIsUnresolvable ? "warning" : "breaking",
792
- symbolPath: `${name}.${fieldName}`,
793
- baseline: baseField.type,
794
- candidate: "(missing)",
795
- message: `Field "${name}.${fieldName}" exists in baseline but not in generated output`
796
- });
797
- if (typeIsUnresolvable) preserved++;
798
- continue;
799
- }
800
- if (baseField.type !== candField.type) {
801
- if (hints.isUnionReorder(baseField.type, candField.type)) {
802
- preserved++;
803
- continue;
804
- }
805
- if (hints.isTypeEquivalent?.(baseField.type, candField.type, candidate)) {
806
- preserved++;
807
- continue;
808
- }
809
- const nullableOnly = hints.isNullableOnlyDifference(baseField.type, candField.type);
810
- const genericParam = hints.isGenericTypeParam(baseField.type);
811
- const extractionArtifact = hints.isExtractionArtifact(candField.type);
812
- const isWarning = nullableOnly || genericParam || extractionArtifact;
813
- violations.push({
814
- category: "signature",
815
- severity: isWarning ? "warning" : "breaking",
816
- symbolPath: `${name}.${fieldName}`,
817
- baseline: baseField.type,
818
- candidate: candField.type,
819
- message: `Field type mismatch for "${name}.${fieldName}"`
820
- });
821
- if (isWarning) preserved++;
822
- continue;
823
- }
824
- preserved++;
825
- }
826
- for (const fieldName of Object.keys(candIface.fields)) {
827
- if (!baseIface.fields[fieldName]) {
828
- additions.push({ symbolPath: `${name}.${fieldName}`, symbolType: "property" });
829
- }
830
- }
831
- }
832
- for (const name of Object.keys(candidate.interfaces)) {
833
- if (!baseline.interfaces[name]) {
834
- additions.push({ symbolPath: name, symbolType: "interface" });
835
- }
836
- }
837
- for (const [name, baseAlias] of Object.entries(baseline.typeAliases)) {
838
- totalBaseline++;
839
- const candAlias = candidate.typeAliases[name];
840
- if (!candAlias) {
841
- if (hints.tolerateCategoryMismatch && typeExistsInSurface(name, candidate)) {
842
- preserved++;
843
- continue;
844
- }
845
- violations.push({
846
- category: "public-api",
847
- severity: "breaking",
848
- symbolPath: name,
849
- baseline: baseAlias.value,
850
- candidate: "(missing)",
851
- message: `Type alias "${name}" exists in baseline but not in generated output`
852
- });
853
- continue;
854
- }
855
- if (baseAlias.value !== candAlias.value) {
856
- if (hints.isUnionReorder(baseAlias.value, candAlias.value)) {
857
- preserved++;
858
- continue;
859
- }
860
- const nullableOnly = hints.isNullableOnlyDifference(baseAlias.value, candAlias.value);
861
- violations.push({
862
- category: "signature",
863
- severity: nullableOnly ? "warning" : "breaking",
864
- symbolPath: name,
865
- baseline: baseAlias.value,
866
- candidate: candAlias.value,
867
- message: `Type alias value mismatch for "${name}"`
868
- });
869
- if (nullableOnly) preserved++;
870
- continue;
871
- }
872
- preserved++;
873
- }
874
- for (const name of Object.keys(candidate.typeAliases)) {
875
- if (!baseline.typeAliases[name]) {
876
- additions.push({ symbolPath: name, symbolType: "type-alias" });
877
- }
878
- }
879
- for (const [name, baseEnum] of Object.entries(baseline.enums)) {
880
- totalBaseline++;
881
- const candEnum = candidate.enums[name];
882
- if (!candEnum) {
883
- violations.push({
884
- category: "public-api",
885
- severity: "breaking",
886
- symbolPath: name,
887
- baseline: name,
888
- candidate: "(missing)",
889
- message: `Enum "${name}" exists in baseline but not in generated output`
890
- });
891
- continue;
892
- }
893
- const candValueToMembers = /* @__PURE__ */ new Map();
894
- for (const [candMember, candValue] of Object.entries(candEnum.members)) {
895
- const existing = candValueToMembers.get(candValue);
896
- if (existing) {
897
- existing.push(candMember);
898
- } else {
899
- candValueToMembers.set(candValue, [candMember]);
900
- }
901
- }
902
- let enumMatch = true;
903
- for (const [member, value] of Object.entries(baseEnum.members)) {
904
- if (candEnum.members[member] === value) {
905
- continue;
906
- }
907
- const valueMatches = candValueToMembers.get(value);
908
- if (valueMatches && valueMatches.length > 0) {
909
- violations.push({
910
- category: "signature",
911
- severity: "warning",
912
- symbolPath: `${name}.${member}`,
913
- baseline: `${member}=${String(value)}`,
914
- candidate: `${valueMatches[0]}=${String(value)}`,
915
- message: `Enum member name differs for "${name}.${member}" (value "${value}" preserved as "${valueMatches[0]}")`
916
- });
917
- continue;
918
- }
919
- const lowerValue = String(value).toLowerCase();
920
- const caseInsensitiveMatch = [...candValueToMembers.entries()].find(
921
- ([candVal]) => String(candVal).toLowerCase() === lowerValue
922
- );
923
- if (caseInsensitiveMatch) {
924
- violations.push({
925
- category: "signature",
926
- severity: "warning",
927
- symbolPath: `${name}.${member}`,
928
- baseline: `${member}=${String(value)}`,
929
- candidate: `${caseInsensitiveMatch[1][0]}=${String(caseInsensitiveMatch[0])}`,
930
- message: `Enum member value case differs for "${name}.${member}" (baseline "${value}" vs candidate "${caseInsensitiveMatch[0]}")`
931
- });
932
- continue;
933
- }
934
- const isExtractionArtifact = String(value) === member || // Value equals member name → annotation artifact
935
- member === "JsonEnumDefaultValue" || member === "JsonProperty";
936
- if (isExtractionArtifact) {
937
- violations.push({
938
- category: "signature",
939
- severity: "warning",
940
- symbolPath: `${name}.${member}`,
941
- baseline: String(value),
942
- candidate: "(extraction artifact)",
943
- message: `Enum member "${name}.${member}" appears to be an extraction artifact`
944
- });
945
- continue;
946
- }
947
- violations.push({
948
- category: "signature",
949
- severity: "breaking",
950
- symbolPath: `${name}.${member}`,
951
- baseline: String(value),
952
- candidate: member in candEnum.members ? String(candEnum.members[member]) : "(missing)",
953
- message: `Enum member mismatch for "${name}.${member}"`
954
- });
955
- enumMatch = false;
956
- }
957
- if (enumMatch) {
958
- preserved++;
959
- }
960
- }
961
- for (const name of Object.keys(candidate.enums)) {
962
- if (!baseline.enums[name]) {
963
- additions.push({ symbolPath: name, symbolType: "enum" });
964
- }
965
- }
966
- for (const [path, baseExports] of Object.entries(baseline.exports)) {
967
- const candExports = candidate.exports[path];
968
- if (!candExports) {
969
- for (const exp of baseExports) {
970
- violations.push({
971
- category: "export-structure",
972
- severity: "warning",
973
- symbolPath: `exports[${path}].${exp}`,
974
- baseline: exp,
975
- candidate: "(missing)",
976
- message: `Export "${exp}" from "${path}" not found in generated output`
977
- });
978
- }
979
- continue;
980
- }
981
- const candSet = new Set(candExports);
982
- for (const exp of baseExports) {
983
- if (!candSet.has(exp)) {
984
- violations.push({
985
- category: "export-structure",
986
- severity: "warning",
987
- symbolPath: `exports[${path}].${exp}`,
988
- baseline: exp,
989
- candidate: "(missing)",
990
- message: `Export "${exp}" from "${path}" not found in generated output`
991
- });
992
- }
993
- }
994
- }
995
- return {
996
- preservationScore: totalBaseline > 0 ? Math.round(preserved / totalBaseline * 100) : 100,
997
- totalBaselineSymbols: totalBaseline,
998
- preservedSymbols: preserved,
999
- violations,
1000
- additions
1001
- };
1002
- }
1003
- function signaturesMatch(baseline, candidate) {
1004
- if (baseline.returnType !== candidate.returnType) return false;
1005
- for (let i = 0; i < baseline.params.length; i++) {
1006
- const baseParam = baseline.params[i];
1007
- const candParam = candidate.params[i];
1008
- if (!candParam) return false;
1009
- if (baseParam.type !== candParam.type) return false;
1010
- if (baseParam.name !== candParam.name) return false;
1011
- }
1012
- for (let i = baseline.params.length; i < candidate.params.length; i++) {
1013
- if (!candidate.params[i].optional) return false;
1014
- }
1015
- return true;
1016
- }
1017
- function formatSignature(method) {
1018
- const params = method.params.map((p) => `${p.name}${p.optional ? "?" : ""}: ${p.type}`).join(", ");
1019
- return `(${params}) => ${method.returnType}`;
1020
- }
1021
-
1022
- export {
1023
- registerExtractor,
1024
- getExtractor,
1025
- NAMED_TYPE_RE,
1026
- typeExistsInSurface,
1027
- namedTypeWordsOverlap,
1028
- defaultIsNullableOnlyDifference,
1029
- nodeHints,
1030
- resolveHints,
1031
- buildOverlayLookup,
1032
- patchOverlay,
1033
- specDerivedNames,
1034
- specDerivedEnumValues,
1035
- specDerivedFieldPaths,
1036
- specDerivedMethodPaths,
1037
- specDerivedHttpKeys,
1038
- filterSurface,
1039
- diffSurfaces
1040
- };