@saptools/service-flow 0.1.7 → 0.1.9

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.
@@ -328,45 +328,176 @@ async function parseDecorators(repoPath, filePath) {
328
328
  }
329
329
 
330
330
  // src/parsers/handler-registration-parser.ts
331
+ import fsSync from "fs";
331
332
  import fs5 from "fs/promises";
332
333
  import path6 from "path";
333
- function lineOf2(text, idx) {
334
- return text.slice(0, idx).split("\n").length;
334
+ import ts3 from "typescript";
335
+ var MAX_EXPORT_DEPTH = 5;
336
+ function lineOf2(sourceFile, node) {
337
+ return sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
338
+ }
339
+ function isRelative(source) {
340
+ return source.startsWith("./") || source.startsWith("../");
341
+ }
342
+ function sourceText(node, sourceFile) {
343
+ if (ts3.isIdentifier(node) || ts3.isStringLiteral(node) || ts3.isNumericLiteral(node)) return node.text;
344
+ return node.getText(sourceFile);
345
+ }
346
+ function importSourceFor(identifier, imports) {
347
+ const evidence = imports.get(identifier);
348
+ return evidence ? `${evidence.source}#${evidence.importedName}` : void 0;
335
349
  }
336
350
  async function parseHandlerRegistrations(repoPath, filePath) {
337
- const text = await fs5.readFile(path6.join(repoPath, filePath), "utf8");
351
+ const absolutePath = path6.join(repoPath, filePath);
352
+ const text = await fs5.readFile(absolutePath, "utf8");
353
+ const sourceFile = ts3.createSourceFile(filePath, text, ts3.ScriptTarget.Latest, true, ts3.ScriptKind.TS);
354
+ const imports = collectImports(sourceFile);
355
+ const localArrays = collectLocalArrays(sourceFile, imports, /* @__PURE__ */ new Map(), repoPath, filePath);
338
356
  const out = [];
339
- const imports = /* @__PURE__ */ new Map();
340
- for (const m of text.matchAll(/import\s+\{?\s*([A-Za-z0-9_,\s]+)\s*\}?\s+from\s+['"]([^'"]+)['"]/g)) {
341
- const source = m[2];
342
- for (const name of (m[1] ?? "").split(",")) {
343
- const symbol = name.trim().split(/\s+as\s+/).pop()?.trim();
344
- if (symbol) imports.set(symbol, source);
357
+ function emitFromExpression(expression, call) {
358
+ const classes = resolveArrayExpression(expression, localArrays, imports, repoPath, filePath, /* @__PURE__ */ new Set());
359
+ for (const cls of classes) {
360
+ out.push({
361
+ className: cls.className,
362
+ importSource: cls.importSource,
363
+ registrationFile: normalizePath(filePath),
364
+ registrationLine: lineOf2(sourceFile, call),
365
+ registrationKind: "combined-handler-class",
366
+ confidence: 0.95
367
+ });
345
368
  }
346
- }
347
- for (const m of text.matchAll(
348
- /createCombinedHandler\s*\(|srv\.prepend\s*\(|cds\.serve\s*\(/g
349
- ))
350
- out.push({
351
- registrationFile: normalizePath(filePath),
352
- registrationLine: lineOf2(text, m.index ?? 0),
353
- registrationKind: m[0].startsWith("cds") ? "cds.serve" : "combined-handler",
354
- confidence: 0.8
355
- });
356
- for (const m of text.matchAll(
357
- /(?:const|export\s+const)\s+handlers\s*=\s*\[([\s\S]*?)\]/g
358
- ))
359
- for (const c of (m[1] ?? "").matchAll(/\b(\w+Handler)\b/g))
369
+ if (classes.length === 0) {
360
370
  out.push({
361
- className: c[1],
362
- importSource: imports.get(c[1]),
363
371
  registrationFile: normalizePath(filePath),
364
- registrationLine: lineOf2(text, (m.index ?? 0) + (c.index ?? 0)),
365
- registrationKind: "handler-array",
366
- confidence: 0.9
372
+ registrationLine: lineOf2(sourceFile, call),
373
+ registrationKind: "combined-handler",
374
+ confidence: 0.75
367
375
  });
376
+ }
377
+ }
378
+ function visit(node) {
379
+ if (ts3.isCallExpression(node) && isRegistrationCall(node)) {
380
+ const handlerExpr = handlerExpression(node, sourceFile);
381
+ if (handlerExpr) emitFromExpression(handlerExpr, node);
382
+ else out.push({ registrationFile: normalizePath(filePath), registrationLine: lineOf2(sourceFile, node), registrationKind: "combined-handler", confidence: 0.75 });
383
+ }
384
+ ts3.forEachChild(node, visit);
385
+ }
386
+ visit(sourceFile);
368
387
  return out;
369
388
  }
389
+ function isRegistrationCall(call) {
390
+ const text = call.expression.getText();
391
+ return text.endsWith("createCombinedHandler") || text.endsWith("srv.prepend") || text.endsWith("cds.serve");
392
+ }
393
+ function handlerExpression(call, sourceFile) {
394
+ for (const arg of call.arguments) {
395
+ if (!ts3.isObjectLiteralExpression(arg)) continue;
396
+ for (const prop of arg.properties) {
397
+ if (!ts3.isPropertyAssignment(prop)) continue;
398
+ if (sourceText(prop.name, sourceFile) === "handler") return prop.initializer;
399
+ }
400
+ }
401
+ return void 0;
402
+ }
403
+ function collectImports(sourceFile) {
404
+ const imports = /* @__PURE__ */ new Map();
405
+ for (const statement of sourceFile.statements) {
406
+ if (!ts3.isImportDeclaration(statement) || !ts3.isStringLiteral(statement.moduleSpecifier)) continue;
407
+ const source = statement.moduleSpecifier.text;
408
+ const clause = statement.importClause;
409
+ if (!clause) continue;
410
+ if (clause.name) imports.set(clause.name.text, { importedName: "default", source });
411
+ const named = clause.namedBindings;
412
+ if (named && ts3.isNamedImports(named)) {
413
+ for (const element of named.elements) imports.set(element.name.text, { importedName: element.propertyName?.text ?? element.name.text, source });
414
+ }
415
+ if (named && ts3.isNamespaceImport(named)) imports.set(named.name.text, { importedName: "*", source });
416
+ }
417
+ return imports;
418
+ }
419
+ function collectLocalArrays(sourceFile, imports, seed, repoPath = "", fromFile = "") {
420
+ const arrays = new Map(seed);
421
+ for (const statement of sourceFile.statements) {
422
+ if (ts3.isVariableStatement(statement)) {
423
+ for (const decl of statement.declarationList.declarations) {
424
+ if (ts3.isIdentifier(decl.name) && decl.initializer && ts3.isArrayLiteralExpression(decl.initializer)) {
425
+ arrays.set(decl.name.text, resolveArrayLiteral(decl.initializer, arrays, imports, repoPath, fromFile, /* @__PURE__ */ new Set()));
426
+ }
427
+ }
428
+ }
429
+ }
430
+ return arrays;
431
+ }
432
+ function resolveArrayExpression(expr, arrays, imports, repoPath, fromFile, seen) {
433
+ if (ts3.isArrayLiteralExpression(expr)) return resolveArrayLiteral(expr, arrays, imports, repoPath, fromFile, seen);
434
+ if (ts3.isIdentifier(expr)) {
435
+ const local = arrays.get(expr.text);
436
+ if (local) return local;
437
+ const evidence = imports.get(expr.text);
438
+ if (evidence && isRelative(evidence.source)) return resolveImportedArray(repoPath, fromFile, evidence, seen);
439
+ if (evidence) return [{ className: evidence.importedName === "default" ? expr.text : evidence.importedName, importSource: `${evidence.source}#${evidence.importedName}` }];
440
+ }
441
+ return [];
442
+ }
443
+ function resolveArrayLiteral(array, arrays, imports, repoPath, fromFile, seen) {
444
+ const out = [];
445
+ for (const element of array.elements) {
446
+ if (ts3.isSpreadElement(element)) out.push(...resolveArrayExpression(element.expression, arrays, imports, repoPath, fromFile, seen));
447
+ else if (ts3.isIdentifier(element)) out.push({ className: element.text, importSource: importSourceFor(element.text, imports) });
448
+ }
449
+ return out;
450
+ }
451
+ function resolveImportedArray(repoPath, fromFile, evidence, seen) {
452
+ const moduleFile = resolveRelativeModule(repoPath, fromFile, evidence.source);
453
+ if (!moduleFile) return [];
454
+ const key = `${moduleFile}:${evidence.importedName}`;
455
+ if (seen.has(key) || seen.size > MAX_EXPORT_DEPTH) return [];
456
+ seen.add(key);
457
+ const exports = readExports(repoPath, moduleFile, seen);
458
+ if (evidence.importedName === "default") return exports.defaultArray ?? [];
459
+ return exports.arrays.get(evidence.importedName) ?? exports.arrays.get(exports.aliases.get(evidence.importedName) ?? evidence.importedName) ?? [];
460
+ }
461
+ function resolveRelativeModule(repoPath, fromFile, specifier) {
462
+ const base = path6.resolve(repoPath, path6.dirname(fromFile), specifier);
463
+ for (const candidate of [base, `${base}.ts`, `${base}.js`, path6.join(base, "index.ts"), path6.join(base, "index.js")]) {
464
+ try {
465
+ const stat = fsSync.statSync(candidate);
466
+ if (stat.isFile()) return normalizePath(path6.relative(repoPath, candidate));
467
+ } catch {
468
+ }
469
+ }
470
+ return void 0;
471
+ }
472
+ function readExports(repoPath, filePath, seen) {
473
+ const absolute = path6.join(repoPath, filePath);
474
+ let text;
475
+ try {
476
+ text = fsSync.readFileSync(absolute, "utf8");
477
+ } catch {
478
+ return { arrays: /* @__PURE__ */ new Map(), aliases: /* @__PURE__ */ new Map() };
479
+ }
480
+ const sourceFile = ts3.createSourceFile(filePath, text, ts3.ScriptTarget.Latest, true, ts3.ScriptKind.TS);
481
+ const imports = collectImports(sourceFile);
482
+ const arrays = collectLocalArrays(sourceFile, imports, /* @__PURE__ */ new Map(), repoPath, filePath);
483
+ const aliases = /* @__PURE__ */ new Map();
484
+ let defaultArray;
485
+ for (const statement of sourceFile.statements) {
486
+ if (ts3.isExportAssignment(statement) && ts3.isIdentifier(statement.expression)) defaultArray = arrays.get(statement.expression.text);
487
+ if (ts3.isExportDeclaration(statement) && statement.exportClause && ts3.isNamedExports(statement.exportClause)) {
488
+ const module = statement.moduleSpecifier && ts3.isStringLiteral(statement.moduleSpecifier) ? statement.moduleSpecifier.text : void 0;
489
+ for (const element of statement.exportClause.elements) {
490
+ const local = element.propertyName?.text ?? element.name.text;
491
+ aliases.set(element.name.text, local);
492
+ if (module && isRelative(module)) {
493
+ const imported = resolveImportedArray(repoPath, filePath, { source: module, importedName: local }, seen);
494
+ if (imported.length > 0) arrays.set(element.name.text, imported);
495
+ }
496
+ }
497
+ }
498
+ }
499
+ return { arrays, defaultArray, aliases };
500
+ }
370
501
 
371
502
  // src/utils/redaction.ts
372
503
  var SENSITIVE = /authorization|cookie|token|secret|password|key|credential/i;
@@ -513,15 +644,15 @@ async function parseOutboundCalls(repoPath, filePath) {
513
644
  // src/parsers/service-binding-parser.ts
514
645
  import fs7 from "fs/promises";
515
646
  import path8 from "path";
516
- import ts3 from "typescript";
647
+ import ts4 from "typescript";
517
648
  function lineOf4(sf, node) {
518
649
  return sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
519
650
  }
520
651
  function stringValue(node) {
521
652
  if (!node) return void 0;
522
- if (ts3.isStringLiteralLike(node) || ts3.isNoSubstitutionTemplateLiteral(node))
653
+ if (ts4.isStringLiteralLike(node) || ts4.isNoSubstitutionTemplateLiteral(node))
523
654
  return node.text;
524
- if (ts3.isTemplateExpression(node))
655
+ if (ts4.isTemplateExpression(node))
525
656
  return node.getText().replace(/^`|`$/g, "");
526
657
  return node.getText();
527
658
  }
@@ -530,22 +661,22 @@ function placeholders(value) {
530
661
  }
531
662
  function connectFactFromCall(call) {
532
663
  const expr = call.expression;
533
- if (!ts3.isPropertyAccessExpression(expr) || expr.name.text !== "to")
664
+ if (!ts4.isPropertyAccessExpression(expr) || expr.name.text !== "to")
534
665
  return void 0;
535
666
  const inner = expr.expression;
536
- if (!ts3.isPropertyAccessExpression(inner) || inner.name.text !== "connect" || inner.expression.getText() !== "cds")
667
+ if (!ts4.isPropertyAccessExpression(inner) || inner.name.text !== "connect" || inner.expression.getText() !== "cds")
537
668
  return void 0;
538
669
  const first = call.arguments[0];
539
670
  if (!first) return void 0;
540
671
  const second = call.arguments[1];
541
- const objectArg = ts3.isObjectLiteralExpression(first) ? first : second && ts3.isObjectLiteralExpression(second) ? second : void 0;
672
+ const objectArg = ts4.isObjectLiteralExpression(first) ? first : second && ts4.isObjectLiteralExpression(second) ? second : void 0;
542
673
  let alias;
543
674
  let aliasExpr;
544
- if (ts3.isStringLiteralLike(first) || ts3.isNoSubstitutionTemplateLiteral(first))
675
+ if (ts4.isStringLiteralLike(first) || ts4.isNoSubstitutionTemplateLiteral(first))
545
676
  alias = first.text;
546
- else if (!ts3.isObjectLiteralExpression(first))
677
+ else if (!ts4.isObjectLiteralExpression(first))
547
678
  aliasExpr = stringValue(first);
548
- if ((ts3.isStringLiteralLike(first) || ts3.isNoSubstitutionTemplateLiteral(first)) && !objectArg)
679
+ if ((ts4.isStringLiteralLike(first) || ts4.isNoSubstitutionTemplateLiteral(first)) && !objectArg)
549
680
  return { alias: first.text, isDynamic: false, placeholders: [] };
550
681
  if (!objectArg && aliasExpr)
551
682
  return {
@@ -557,13 +688,13 @@ function connectFactFromCall(call) {
557
688
  let servicePathExpr;
558
689
  function visitObject(obj) {
559
690
  for (const prop of obj.properties) {
560
- if (!ts3.isPropertyAssignment(prop)) continue;
561
- const name = ts3.isIdentifier(prop.name) || ts3.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
691
+ if (!ts4.isPropertyAssignment(prop)) continue;
692
+ const name = ts4.isIdentifier(prop.name) || ts4.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
562
693
  if (name === "destination")
563
694
  destinationExpr = stringValue(prop.initializer);
564
695
  if (name === "path" || name === "servicePath")
565
696
  servicePathExpr = stringValue(prop.initializer);
566
- if (ts3.isObjectLiteralExpression(prop.initializer))
697
+ if (ts4.isObjectLiteralExpression(prop.initializer))
567
698
  visitObject(prop.initializer);
568
699
  }
569
700
  }
@@ -583,8 +714,8 @@ function connectFactFromCall(call) {
583
714
  };
584
715
  }
585
716
  function unwrapCall(expr) {
586
- if (ts3.isAwaitExpression(expr)) return unwrapCall(expr.expression);
587
- if (ts3.isCallExpression(expr)) return expr;
717
+ if (ts4.isAwaitExpression(expr)) return unwrapCall(expr.expression);
718
+ if (ts4.isCallExpression(expr)) return expr;
588
719
  return void 0;
589
720
  }
590
721
  function findConnectInExpression(expr) {
@@ -596,8 +727,8 @@ function findConnectInExpression(expr) {
596
727
  let found;
597
728
  function visit(node) {
598
729
  if (found) return;
599
- if (ts3.isCallExpression(node)) found = connectFactFromCall(node);
600
- if (!found) ts3.forEachChild(node, visit);
730
+ if (ts4.isCallExpression(node)) found = connectFactFromCall(node);
731
+ if (!found) ts4.forEachChild(node, visit);
601
732
  }
602
733
  visit(expr);
603
734
  return found;
@@ -605,12 +736,12 @@ function findConnectInExpression(expr) {
605
736
  async function readSource(abs) {
606
737
  try {
607
738
  const text = await fs7.readFile(abs, "utf8");
608
- return ts3.createSourceFile(
739
+ return ts4.createSourceFile(
609
740
  abs,
610
741
  text,
611
- ts3.ScriptTarget.Latest,
742
+ ts4.ScriptTarget.Latest,
612
743
  true,
613
- ts3.ScriptKind.TS
744
+ ts4.ScriptKind.TS
614
745
  );
615
746
  } catch {
616
747
  return void 0;
@@ -639,7 +770,7 @@ async function resolveImport(repoPath, fromFile, spec) {
639
770
  async function importsFor(repoPath, filePath, sf) {
640
771
  const imports = [];
641
772
  for (const stmt of sf.statements) {
642
- if (!ts3.isImportDeclaration(stmt) || !ts3.isStringLiteralLike(stmt.moduleSpecifier))
773
+ if (!ts4.isImportDeclaration(stmt) || !ts4.isStringLiteralLike(stmt.moduleSpecifier))
643
774
  continue;
644
775
  const sourceFile = await resolveImport(
645
776
  repoPath,
@@ -655,7 +786,7 @@ async function importsFor(repoPath, filePath, sf) {
655
786
  sourceFile
656
787
  });
657
788
  const bindings = clause.namedBindings;
658
- if (bindings && ts3.isNamedImports(bindings))
789
+ if (bindings && ts4.isNamedImports(bindings))
659
790
  for (const el of bindings.elements)
660
791
  imports.push({
661
792
  localName: el.name.text,
@@ -668,17 +799,17 @@ async function importsFor(repoPath, filePath, sf) {
668
799
  function exportedLocalNames(sf) {
669
800
  const exports = /* @__PURE__ */ new Map();
670
801
  for (const stmt of sf.statements) {
671
- const direct = ts3.canHaveModifiers(stmt) ? ts3.getModifiers(stmt)?.some(
672
- (m) => m.kind === ts3.SyntaxKind.ExportKeyword
802
+ const direct = ts4.canHaveModifiers(stmt) ? ts4.getModifiers(stmt)?.some(
803
+ (m) => m.kind === ts4.SyntaxKind.ExportKeyword
673
804
  ) ?? false : false;
674
- if (direct && ts3.isFunctionDeclaration(stmt) && stmt.name)
805
+ if (direct && ts4.isFunctionDeclaration(stmt) && stmt.name)
675
806
  exports.set(stmt.name.text, stmt.name.text);
676
- if (direct && ts3.isVariableStatement(stmt)) {
807
+ if (direct && ts4.isVariableStatement(stmt)) {
677
808
  for (const decl of stmt.declarationList.declarations)
678
- if (ts3.isIdentifier(decl.name)) exports.set(decl.name.text, decl.name.text);
809
+ if (ts4.isIdentifier(decl.name)) exports.set(decl.name.text, decl.name.text);
679
810
  }
680
- if (!ts3.isExportDeclaration(stmt) || !stmt.exportClause) continue;
681
- if (!ts3.isNamedExports(stmt.exportClause)) continue;
811
+ if (!ts4.isExportDeclaration(stmt) || !stmt.exportClause) continue;
812
+ if (!ts4.isNamedExports(stmt.exportClause)) continue;
682
813
  for (const el of stmt.exportClause.elements)
683
814
  exports.set(el.name.text, el.propertyName?.text ?? el.name.text);
684
815
  }
@@ -692,18 +823,18 @@ async function helperBindings(repoPath, filePath) {
692
823
  const exportedLocals = exportedLocalNames(sf);
693
824
  const factsByLocal = /* @__PURE__ */ new Map();
694
825
  for (const stmt of sf.statements) {
695
- if (ts3.isFunctionDeclaration(stmt) && stmt.name) {
826
+ if (ts4.isFunctionDeclaration(stmt) && stmt.name) {
696
827
  let fact;
697
828
  stmt.forEachChild(function visit(node) {
698
- if (!fact && ts3.isReturnStatement(node) && node.expression)
829
+ if (!fact && ts4.isReturnStatement(node) && node.expression)
699
830
  fact = findConnectInExpression(node.expression);
700
- if (!fact) ts3.forEachChild(node, visit);
831
+ if (!fact) ts4.forEachChild(node, visit);
701
832
  });
702
833
  if (fact) factsByLocal.set(stmt.name.text, { ...fact, sourceLine: lineOf4(sf, stmt) });
703
834
  }
704
- if (ts3.isVariableStatement(stmt))
835
+ if (ts4.isVariableStatement(stmt))
705
836
  for (const decl of stmt.declarationList.declarations) {
706
- if (!ts3.isIdentifier(decl.name) || !decl.initializer) continue;
837
+ if (!ts4.isIdentifier(decl.name) || !decl.initializer) continue;
707
838
  const fact = findConnectInExpression(decl.initializer);
708
839
  if (fact)
709
840
  factsByLocal.set(decl.name.text, {
@@ -744,7 +875,7 @@ async function parseServiceBindings(repoPath, filePath) {
744
875
  return helper ? { imp, helper } : void 0;
745
876
  }
746
877
  async function recordVariable(decl) {
747
- if (!ts3.isIdentifier(decl.name) || !decl.initializer) return;
878
+ if (!ts4.isIdentifier(decl.name) || !decl.initializer) return;
748
879
  const call = unwrapCall(decl.initializer);
749
880
  if (!call) return;
750
881
  const direct = connectFactFromCall(call);
@@ -755,7 +886,7 @@ async function parseServiceBindings(repoPath, filePath) {
755
886
  sourceFile: normalizePath(filePath),
756
887
  sourceLine: lineOf4(sourceFileAst, decl)
757
888
  });
758
- else if (ts3.isIdentifier(call.expression)) {
889
+ else if (ts4.isIdentifier(call.expression)) {
759
890
  const resolved = await importedHelper(call.expression.text);
760
891
  if (resolved)
761
892
  out.push({
@@ -782,14 +913,14 @@ async function parseServiceBindings(repoPath, filePath) {
782
913
  }
783
914
  }
784
915
  function recordDestructuredClassHelper(decl) {
785
- if (!ts3.isObjectBindingPattern(decl.name) || !decl.initializer) return;
916
+ if (!ts4.isObjectBindingPattern(decl.name) || !decl.initializer) return;
786
917
  const call = unwrapCall(decl.initializer);
787
- if (!call || !ts3.isPropertyAccessExpression(call.expression)) return;
918
+ if (!call || !ts4.isPropertyAccessExpression(call.expression)) return;
788
919
  const target = call.expression;
789
- if (target.expression.kind !== ts3.SyntaxKind.ThisKeyword) return;
920
+ if (target.expression.kind !== ts4.SyntaxKind.ThisKeyword) return;
790
921
  for (const el of decl.name.elements) {
791
- if (!ts3.isIdentifier(el.name)) continue;
792
- const propertyName = el.propertyName && ts3.isIdentifier(el.propertyName) ? el.propertyName.text : el.name.text;
922
+ if (!ts4.isIdentifier(el.name)) continue;
923
+ const propertyName = el.propertyName && ts4.isIdentifier(el.propertyName) ? el.propertyName.text : el.name.text;
793
924
  const helper = classHelpers.find(
794
925
  (h) => h.helperName === target.name.text && h.propertyName === propertyName
795
926
  );
@@ -815,8 +946,8 @@ async function parseServiceBindings(repoPath, filePath) {
815
946
  }
816
947
  const declarations = [];
817
948
  function collectDeclarations(node) {
818
- if (ts3.isVariableDeclaration(node)) declarations.push(node);
819
- ts3.forEachChild(node, collectDeclarations);
949
+ if (ts4.isVariableDeclaration(node)) declarations.push(node);
950
+ ts4.forEachChild(node, collectDeclarations);
820
951
  }
821
952
  collectDeclarations(sourceFileAst);
822
953
  for (const decl of declarations) {
@@ -828,16 +959,16 @@ async function parseServiceBindings(repoPath, filePath) {
828
959
  function collectClassHelpers(sf) {
829
960
  const helpers = [];
830
961
  for (const stmt of sf.statements) {
831
- if (!ts3.isClassDeclaration(stmt) || !stmt.name) continue;
962
+ if (!ts4.isClassDeclaration(stmt) || !stmt.name) continue;
832
963
  for (const member of stmt.members) {
833
964
  let visit2 = function(node) {
834
- if (ts3.isVariableDeclaration(node) && ts3.isIdentifier(node.name) && node.initializer) {
965
+ if (ts4.isVariableDeclaration(node) && ts4.isIdentifier(node.name) && node.initializer) {
835
966
  const fact = findConnectInExpression(node.initializer);
836
967
  if (fact) bindings.set(node.name.text, fact);
837
968
  }
838
- if (ts3.isReturnStatement(node) && node.expression && ts3.isObjectLiteralExpression(node.expression)) {
969
+ if (ts4.isReturnStatement(node) && node.expression && ts4.isObjectLiteralExpression(node.expression)) {
839
970
  for (const prop of node.expression.properties) {
840
- if (ts3.isShorthandPropertyAssignment(prop)) {
971
+ if (ts4.isShorthandPropertyAssignment(prop)) {
841
972
  const fact = bindings.get(prop.name.text);
842
973
  if (fact)
843
974
  helpers.push({
@@ -849,8 +980,8 @@ function collectClassHelpers(sf) {
849
980
  sourceLine: lineOf4(sf, prop)
850
981
  });
851
982
  }
852
- if (ts3.isPropertyAssignment(prop) && ts3.isIdentifier(prop.initializer)) {
853
- const propertyName = ts3.isIdentifier(prop.name) || ts3.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
983
+ if (ts4.isPropertyAssignment(prop) && ts4.isIdentifier(prop.initializer)) {
984
+ const propertyName = ts4.isIdentifier(prop.name) || ts4.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
854
985
  const fact = propertyName ? bindings.get(prop.initializer.text) : void 0;
855
986
  if (propertyName && fact)
856
987
  helpers.push({
@@ -864,15 +995,15 @@ function collectClassHelpers(sf) {
864
995
  }
865
996
  }
866
997
  }
867
- ts3.forEachChild(node, visit2);
998
+ ts4.forEachChild(node, visit2);
868
999
  };
869
1000
  var visit = visit2;
870
- if (!ts3.isPropertyDeclaration(member) || !member.initializer) continue;
871
- if (!ts3.isIdentifier(member.name)) continue;
1001
+ if (!ts4.isPropertyDeclaration(member) || !member.initializer) continue;
1002
+ if (!ts4.isIdentifier(member.name)) continue;
872
1003
  const className = stmt.name.text;
873
1004
  const helperName = member.name.text;
874
1005
  const initializer = member.initializer;
875
- if (!ts3.isArrowFunction(initializer) && !ts3.isFunctionExpression(initializer))
1006
+ if (!ts4.isArrowFunction(initializer) && !ts4.isFunctionExpression(initializer))
876
1007
  continue;
877
1008
  const bindings = /* @__PURE__ */ new Map();
878
1009
  visit2(initializer);
@@ -1094,23 +1225,176 @@ function callEvidence(call, resolution, servicePath, op, destination) {
1094
1225
  return { sourceFile: call.source_file, sourceLine: call.source_line, file: call.source_file, line: call.source_line, repo: call.repoName, serviceAlias: call.alias, serviceAliasExpr: call.aliasExpr, destination, servicePath, operationPath: op, targetRepo: resolution.target?.repoName, targetOperation: resolution.target?.operationName, helperChain: call.helperChainJson ? JSON.parse(String(call.helperChainJson)) : void 0, candidates: resolution.candidates, candidateCount: resolution.candidates.length, resolutionStatus: resolution.status, resolutionReasons: resolution.reasons, analysisCompleteness: call.unresolved_reason ? "partial" : "complete", parserWarning: call.unresolved_reason ? { code: "parser_warning", message: call.unresolved_reason } : void 0 };
1095
1226
  }
1096
1227
  function linkImplementations(db, workspaceId, generation) {
1097
- const operations = db.prepare(`SELECT o.id operationId,o.operation_path operationPath,o.operation_name operationName,s.service_path servicePath,s.repo_id modelRepoId,r.package_name modelPackage FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=?`).all(workspaceId);
1228
+ const operations = db.prepare(`SELECT o.id operationId,o.operation_path operationPath,o.operation_name operationName,s.service_path servicePath,s.repo_id modelRepoId,r.name modelRepo,r.package_name modelPackage FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=?`).all(workspaceId);
1098
1229
  let edgeCount = 0;
1099
1230
  let resolvedCount = 0;
1100
1231
  let ambiguousCount = 0;
1101
1232
  for (const operation of operations) {
1102
- const rows2 = implementationCandidates(db, workspaceId, operation);
1103
- if (rows2.length === 0) continue;
1104
- const unique = rows2.length === 1 ? rows2[0] : void 0;
1105
- db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "OPERATION_IMPLEMENTED_BY_HANDLER", unique ? "resolved" : "ambiguous", "operation", String(operation.operationId), unique ? "handler_method" : "handler_method_candidates", unique ? String(unique.methodId) : rows2.map((row) => row.methodId).join(","), unique ? 0.95 : 0.5, JSON.stringify({ servicePath: operation.servicePath, operationPath: operation.operationPath, operationName: operation.operationName, candidates: rows2, evidence: "registered_application_dependency" }), 0, unique ? null : "Ambiguous registered handler implementation candidates", generation);
1233
+ const candidates = rankedImplementationCandidates(db, workspaceId, operation);
1234
+ const accepted = candidates.filter((candidate) => candidate.accepted);
1235
+ if (accepted.length === 0) continue;
1236
+ const topScore = accepted[0]?.score ?? 0;
1237
+ const winners = accepted.filter((candidate) => candidate.score === topScore);
1238
+ const unique = winners.length === 1 ? winners[0] : void 0;
1239
+ const evidence = {
1240
+ servicePath: operation.servicePath,
1241
+ operationPath: operation.operationPath,
1242
+ operationName: operation.operationName,
1243
+ modelPackage: { id: operation.modelRepoId, name: operation.modelRepo, packageName: operation.modelPackage },
1244
+ candidates: candidates.map((candidate, index) => candidateEvidence(candidate, index + 1))
1245
+ };
1246
+ db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "OPERATION_IMPLEMENTED_BY_HANDLER", unique ? "resolved" : "ambiguous", "operation", graphId(operation.operationId), unique ? "handler_method" : "handler_method_candidates", unique ? graphId(unique.methodId) : winners.map((row) => graphId(row.methodId)).join(","), unique ? 0.95 : 0.5, JSON.stringify(evidence), 0, unique ? null : "Ambiguous registered handler implementation candidates", generation);
1106
1247
  edgeCount += 1;
1107
1248
  if (unique) resolvedCount += 1;
1108
1249
  else ambiguousCount += 1;
1109
1250
  }
1110
1251
  return { edgeCount, resolvedCount, ambiguousCount };
1111
1252
  }
1253
+ function rankedImplementationCandidates(db, workspaceId, operation) {
1254
+ const rows2 = implementationCandidates(db, workspaceId, operation);
1255
+ return rows2.map((row) => scoreImplementationCandidate(row, operation)).sort((a, b) => b.score - a.score || String(a.className).localeCompare(String(b.className)) || a.methodId - b.methodId);
1256
+ }
1112
1257
  function implementationCandidates(db, workspaceId, operation) {
1113
- return db.prepare(`SELECT DISTINCT hm.id methodId,hc.id classId,hc.class_name className,hc.source_file sourceFile,hc.source_line sourceLine,hr.repo_id applicationRepoId,handlerRepo.name handlerRepo,appRepo.name applicationRepo FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id JOIN repositories handlerRepo ON handlerRepo.id=hc.repo_id JOIN handler_registrations hr ON hr.class_name=hc.class_name JOIN repositories appRepo ON appRepo.id=hr.repo_id JOIN graph_edges modelDep ON modelDep.edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND modelDep.status='resolved' AND modelDep.from_kind='repo' AND modelDep.from_id=CAST(appRepo.id AS TEXT) AND modelDep.to_id=CAST(? AS TEXT) JOIN graph_edges handlerDep ON handlerDep.edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND handlerDep.status='resolved' AND handlerDep.from_kind='repo' AND handlerDep.from_id=CAST(appRepo.id AS TEXT) AND handlerDep.to_id=CAST(handlerRepo.id AS TEXT) WHERE appRepo.workspace_id=? AND (hm.decorator_value=? OR hm.decorator_value=? OR hm.method_name=?)`).all(operation.modelRepoId, workspaceId, normalizedOperation(String(operation.operationPath ?? "")), operation.operationName, operation.operationName);
1258
+ const modelRepoGraphId = graphId(operation.modelRepoId);
1259
+ return db.prepare(`SELECT DISTINCT
1260
+ hm.id methodId,
1261
+ hc.id classId,
1262
+ hc.class_name className,
1263
+ hc.source_file sourceFile,
1264
+ hc.source_line sourceLine,
1265
+ hr.repo_id applicationRepoId,
1266
+ hr.registration_file registrationFile,
1267
+ hr.registration_line registrationLine,
1268
+ hr.registration_kind registrationKind,
1269
+ hr.import_source importSource,
1270
+ handlerRepo.id handlerRepoId,
1271
+ handlerRepo.name handlerRepo,
1272
+ handlerRepo.package_name handlerPackage,
1273
+ appRepo.name applicationRepo,
1274
+ appRepo.package_name applicationPackage,
1275
+ ? modelRepoId,
1276
+ ? modelRepo,
1277
+ ? modelPackage,
1278
+ ? servicePath,
1279
+ ? operationPath,
1280
+ ? operationName,
1281
+ CASE WHEN appRepo.id=? THEN 1 ELSE 0 END modelIsApplicationRepo,
1282
+ CASE WHEN handlerRepo.id=? THEN 1 ELSE 0 END modelIsHandlerRepo,
1283
+ CASE WHEN appRepo.id=handlerRepo.id THEN 1 ELSE 0 END sameRepoRegistration,
1284
+ CASE WHEN EXISTS (SELECT 1 FROM cds_services localService WHERE localService.repo_id=appRepo.id AND localService.service_path=?) THEN 1 ELSE 0 END localServicePathMatch,
1285
+ CASE WHEN EXISTS (SELECT 1 FROM cds_services localService WHERE localService.repo_id=appRepo.id) THEN 1 ELSE 0 END applicationHasLocalServices,
1286
+ CASE WHEN EXISTS (SELECT 1 FROM graph_edges dep WHERE dep.edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND dep.status='resolved' AND dep.from_kind='repo' AND dep.from_id=CAST(appRepo.id AS TEXT) AND dep.to_id=?) THEN 1 ELSE 0 END appDependsOnModel,
1287
+ CASE WHEN EXISTS (SELECT 1 FROM graph_edges dep WHERE dep.edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND dep.status='resolved' AND dep.from_kind='repo' AND dep.from_id=CAST(appRepo.id AS TEXT) AND dep.to_id=CAST(handlerRepo.id AS TEXT)) THEN 1 ELSE 0 END appDependsOnHandler,
1288
+ CASE WHEN EXISTS (SELECT 1 FROM graph_edges dep WHERE dep.edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND dep.status='resolved' AND dep.from_kind='repo' AND dep.from_id=CAST(handlerRepo.id AS TEXT) AND dep.to_id=?) THEN 1 ELSE 0 END handlerDependsOnModel
1289
+ FROM handler_methods hm
1290
+ JOIN handler_classes hc ON hc.id=hm.handler_class_id
1291
+ JOIN repositories handlerRepo ON handlerRepo.id=hc.repo_id
1292
+ JOIN handler_registrations hr ON (hr.handler_class_id=hc.id OR (hr.class_name=hc.class_name AND (hr.repo_id=hc.repo_id OR hr.import_source IS NOT NULL)))
1293
+ JOIN repositories appRepo ON appRepo.id=hr.repo_id
1294
+ WHERE appRepo.workspace_id=?
1295
+ AND (hm.decorator_value=? OR hm.decorator_value=? OR hm.method_name=?)`).all(
1296
+ operation.modelRepoId,
1297
+ operation.modelRepo,
1298
+ operation.modelPackage,
1299
+ operation.servicePath,
1300
+ operation.operationPath,
1301
+ operation.operationName,
1302
+ operation.modelRepoId,
1303
+ operation.modelRepoId,
1304
+ operation.servicePath,
1305
+ modelRepoGraphId,
1306
+ modelRepoGraphId,
1307
+ workspaceId,
1308
+ normalizedOperation(String(operation.operationPath ?? "")),
1309
+ operation.operationName,
1310
+ operation.operationName
1311
+ );
1312
+ }
1313
+ function scoreImplementationCandidate(row, operation) {
1314
+ const acceptedReasons = [];
1315
+ const rejectedReasons = [];
1316
+ let score = 0;
1317
+ const modelIsApplicationRepo = flag(row.modelIsApplicationRepo);
1318
+ const modelIsHandlerRepo = flag(row.modelIsHandlerRepo);
1319
+ const localServicePathMatch = flag(row.localServicePathMatch);
1320
+ const applicationHasLocalServices = flag(row.applicationHasLocalServices);
1321
+ const appDependsOnModel = flag(row.appDependsOnModel);
1322
+ const appDependsOnHandler = flag(row.appDependsOnHandler);
1323
+ const handlerDependsOnModel = flag(row.handlerDependsOnModel);
1324
+ const importSource = typeof row.importSource === "string" && row.importSource.length > 0;
1325
+ if (modelIsApplicationRepo) {
1326
+ score += 100;
1327
+ acceptedReasons.push("model package equals registration package");
1328
+ }
1329
+ if (modelIsHandlerRepo) {
1330
+ score += 100;
1331
+ acceptedReasons.push("model package equals handler package");
1332
+ }
1333
+ if (localServicePathMatch) {
1334
+ score += 80;
1335
+ acceptedReasons.push("registration package contains exact local service path");
1336
+ } else if (applicationHasLocalServices && !appDependsOnModel && !modelIsApplicationRepo) {
1337
+ rejectedReasons.push(`registration package has local services but none match ${String(operation.servicePath ?? "")}`);
1338
+ }
1339
+ if (appDependsOnModel) {
1340
+ score += 70;
1341
+ acceptedReasons.push("registration package depends on model package");
1342
+ }
1343
+ if (appDependsOnHandler) {
1344
+ score += 30;
1345
+ acceptedReasons.push("registration package depends on handler package");
1346
+ }
1347
+ if (handlerDependsOnModel) {
1348
+ score += 20;
1349
+ acceptedReasons.push("handler package depends on model package");
1350
+ }
1351
+ if (importSource) {
1352
+ score += 10;
1353
+ acceptedReasons.push("registration imports handler class");
1354
+ }
1355
+ const hasOwnership = modelIsApplicationRepo || modelIsHandlerRepo;
1356
+ const hasCrossPackage = appDependsOnModel && (modelIsHandlerRepo || appDependsOnHandler || !importSource);
1357
+ const contradicted = applicationHasLocalServices && !localServicePathMatch && !appDependsOnModel && !hasOwnership;
1358
+ if (!hasOwnership && !localServicePathMatch && !hasCrossPackage) rejectedReasons.push("missing direct ownership, exact local service path, or validated cross-package dependency evidence");
1359
+ const accepted = !contradicted && (hasOwnership || localServicePathMatch || hasCrossPackage || handlerDependsOnModel);
1360
+ if (!accepted && rejectedReasons.length === 0) rejectedReasons.push("candidate did not meet implementation ownership policy");
1361
+ return { ...row, methodId: Number(row.methodId), score, accepted, acceptedReasons, rejectedReasons };
1362
+ }
1363
+ function candidateEvidence(candidate, rank) {
1364
+ return {
1365
+ rank,
1366
+ score: candidate.score,
1367
+ accepted: candidate.accepted,
1368
+ acceptedReasons: candidate.acceptedReasons,
1369
+ rejectedReasons: candidate.rejectedReasons,
1370
+ methodId: candidate.methodId,
1371
+ classId: candidate.classId,
1372
+ className: candidate.className,
1373
+ sourceFile: candidate.sourceFile,
1374
+ sourceLine: candidate.sourceLine,
1375
+ registration: { file: candidate.registrationFile, line: candidate.registrationLine, kind: candidate.registrationKind, importSource: candidate.importSource },
1376
+ applicationPackage: { id: candidate.applicationRepoId, name: candidate.applicationRepo, packageName: candidate.applicationPackage },
1377
+ handlerPackage: { id: candidate.handlerRepoId, name: candidate.handlerRepo, packageName: candidate.handlerPackage },
1378
+ modelPackage: { id: candidate.modelRepoId, name: candidate.modelRepo, packageName: candidate.modelPackage },
1379
+ servicePath: candidate.servicePath,
1380
+ operationPath: candidate.operationPath,
1381
+ operationName: candidate.operationName,
1382
+ signals: {
1383
+ directOwnership: { modelIsApplicationRepo: flag(candidate.modelIsApplicationRepo), modelIsHandlerRepo: flag(candidate.modelIsHandlerRepo) },
1384
+ localServicePathMatch: flag(candidate.localServicePathMatch),
1385
+ applicationHasLocalServices: flag(candidate.applicationHasLocalServices),
1386
+ appDependsOnModel: flag(candidate.appDependsOnModel),
1387
+ appDependsOnHandler: flag(candidate.appDependsOnHandler),
1388
+ handlerDependsOnModel: flag(candidate.handlerDependsOnModel),
1389
+ sameRepoRegistration: flag(candidate.sameRepoRegistration)
1390
+ }
1391
+ };
1392
+ }
1393
+ function flag(value) {
1394
+ return Boolean(Number(value ?? 0));
1395
+ }
1396
+ function graphId(value) {
1397
+ return String(value);
1114
1398
  }
1115
1399
  function normalizedOperation(value) {
1116
1400
  return value.startsWith("/") ? value.slice(1) : value;
@@ -1394,4 +1678,4 @@ export {
1394
1678
  linkWorkspace,
1395
1679
  trace
1396
1680
  };
1397
- //# sourceMappingURL=chunk-YFT57U54.js.map
1681
+ //# sourceMappingURL=chunk-HQMAFSRR.js.map