@saptools/service-flow 0.1.49 → 0.1.50

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.
@@ -315,6 +315,46 @@ import fs4 from "fs/promises";
315
315
  import path5 from "path";
316
316
  import ts2 from "typescript";
317
317
 
318
+ // src/linker/operation-decorator-normalizer.ts
319
+ function lowerFirst(value) {
320
+ return value ? `${value[0]?.toLowerCase() ?? ""}${value.slice(1)}` : value;
321
+ }
322
+ function normalizedOperationName(value) {
323
+ return value.replace(/^\//, "");
324
+ }
325
+ function clean(value) {
326
+ return value.replace(/^[`'"]|[`'"]$/g, "");
327
+ }
328
+ function generatedOperationNameFromConstant(value) {
329
+ for (const prefix of ["Action", "Func"]) {
330
+ if (value.startsWith(prefix) && value.length > prefix.length && /^[A-Z]/.test(value.slice(prefix.length))) return lowerFirst(value.slice(prefix.length));
331
+ }
332
+ return void 0;
333
+ }
334
+ function resolved(value, raw) {
335
+ const literal2 = clean(value);
336
+ const generated = generatedOperationNameFromConstant(literal2);
337
+ return { status: "resolved", operationName: generated ?? normalizedOperationName(literal2), raw };
338
+ }
339
+ function normalizeDecoratorOperationSignal(value, raw, candidateOperation) {
340
+ if (value) return resolved(value, raw);
341
+ if (!raw || raw.trim().length === 0) return { status: "none", raw };
342
+ const expression = raw.trim();
343
+ const nameMatch = /(?:^|\.)(Action[A-Z][\w$]*|Func[A-Z][\w$]*)\.name$/.exec(expression);
344
+ if (nameMatch?.[1]) return resolved(nameMatch[1], expression);
345
+ const stringMatch = /^String\(([A-Za-z_$][\w$]*)\)$/.exec(expression);
346
+ if (stringMatch?.[1]) {
347
+ const identifier = stringMatch[1];
348
+ const generated = generatedOperationNameFromConstant(identifier);
349
+ const normalizedCandidate2 = candidateOperation ? normalizedOperationName(candidateOperation) : void 0;
350
+ if (generated) return { status: "resolved", operationName: generated, raw: expression };
351
+ if (normalizedCandidate2 && identifier === normalizedCandidate2) return { status: "resolved", operationName: identifier, raw: expression };
352
+ return { status: "unsupported", raw: expression, reason: "string_wrapper_identifier_not_resolved" };
353
+ }
354
+ if (/^[`'"]/.test(expression) && !/[`'"]$/.test(expression)) return { status: "malformed", raw: expression, reason: "unterminated_literal" };
355
+ return { status: "unsupported", raw: expression, reason: "unsupported_decorator_expression" };
356
+ }
357
+
318
358
  // src/parsers/ts-project.ts
319
359
  import ts from "typescript";
320
360
  function createSourceFile(filePath, text) {
@@ -340,16 +380,102 @@ function callName(d) {
340
380
  }
341
381
  function firstArg(d) {
342
382
  const e = d.expression;
343
- return ts2.isCallExpression(e) && e.arguments[0] ? e.arguments[0].getText() : "";
383
+ return ts2.isCallExpression(e) ? e.arguments[0] : void 0;
384
+ }
385
+ function unwrapExpression(expression) {
386
+ let current = expression;
387
+ while (ts2.isParenthesizedExpression(current) || ts2.isAsExpression(current) || ts2.isTypeAssertionExpression(current) || ts2.isSatisfiesExpression(current)) current = current.expression;
388
+ return current;
389
+ }
390
+ function stringValue(expression) {
391
+ if (!expression) return void 0;
392
+ const unwrapped = unwrapExpression(expression);
393
+ return ts2.isStringLiteralLike(unwrapped) ? unwrapped.text : void 0;
394
+ }
395
+ function propertyName(node) {
396
+ if (ts2.isIdentifier(node) || ts2.isStringLiteralLike(node)) return node.text;
397
+ return void 0;
398
+ }
399
+ function collectEnumMembers(statement, lookups) {
400
+ for (const member of statement.members) {
401
+ const name = propertyName(member.name);
402
+ const value = stringValue(member.initializer);
403
+ if (name && value !== void 0)
404
+ lookups.enumMembers.set(`${statement.name.text}.${name}`, value);
405
+ }
406
+ }
407
+ function collectObjectProperties(name, initializer, lookups) {
408
+ const object = unwrapExpression(initializer);
409
+ if (!ts2.isObjectLiteralExpression(object)) return;
410
+ for (const property of object.properties) {
411
+ if (!ts2.isPropertyAssignment(property)) continue;
412
+ const key = propertyName(property.name);
413
+ const value = stringValue(property.initializer);
414
+ if (key && value !== void 0)
415
+ lookups.objectProperties.set(`${name}.${key}`, value);
416
+ }
417
+ }
418
+ function collectStringLookups(source) {
419
+ const lookups = {
420
+ identifiers: /* @__PURE__ */ new Map(),
421
+ enumMembers: /* @__PURE__ */ new Map(),
422
+ objectProperties: /* @__PURE__ */ new Map()
423
+ };
424
+ for (const statement of source.statements) {
425
+ if (ts2.isEnumDeclaration(statement)) collectEnumMembers(statement, lookups);
426
+ if (!ts2.isVariableStatement(statement) || !(statement.declarationList.flags & ts2.NodeFlags.Const)) continue;
427
+ for (const declaration of statement.declarationList.declarations) {
428
+ if (!ts2.isIdentifier(declaration.name) || !declaration.initializer) continue;
429
+ const value = stringValue(declaration.initializer);
430
+ if (value !== void 0) lookups.identifiers.set(declaration.name.text, value);
431
+ collectObjectProperties(declaration.name.text, declaration.initializer, lookups);
432
+ }
433
+ }
434
+ return lookups;
435
+ }
436
+ function unresolved(rawExpression, reason) {
437
+ return { rawExpression, resolutionKind: "unresolved", unresolvedReason: reason };
438
+ }
439
+ function generatedConstant(rawExpression) {
440
+ const match = /(?:^|\.)(Action[A-Z][\w$]*|Func[A-Z][\w$]*)\.name$/.exec(rawExpression.trim());
441
+ return match?.[1] ? generatedOperationNameFromConstant(match[1]) : void 0;
442
+ }
443
+ function resolveDecoratorArgument(argument, lookups) {
444
+ if (!argument) return unresolved("", "decorator_argument_missing");
445
+ const rawExpression = argument.getText();
446
+ const expression = unwrapExpression(argument);
447
+ if (ts2.isStringLiteralLike(expression))
448
+ return { rawExpression, resolvedValue: expression.text, resolutionKind: "literal" };
449
+ if (ts2.isIdentifier(expression)) {
450
+ const value = lookups.identifiers.get(expression.text);
451
+ return value === void 0 ? unresolved(rawExpression, "identifier_not_resolved_to_local_const_string") : { rawExpression, resolvedValue: value, resolutionKind: "const_identifier" };
452
+ }
453
+ if (ts2.isPropertyAccessExpression(expression) && ts2.isIdentifier(expression.expression)) {
454
+ const key = `${expression.expression.text}.${expression.name.text}`;
455
+ const enumValue = lookups.enumMembers.get(key);
456
+ if (enumValue !== void 0)
457
+ return { rawExpression, resolvedValue: enumValue, resolutionKind: "enum_member" };
458
+ const objectValue2 = lookups.objectProperties.get(key);
459
+ if (objectValue2 !== void 0)
460
+ return { rawExpression, resolvedValue: objectValue2, resolutionKind: "const_object_property" };
461
+ }
462
+ const generatedValue = generatedConstant(rawExpression);
463
+ if (generatedValue !== void 0)
464
+ return {
465
+ rawExpression,
466
+ resolvedValue: generatedValue,
467
+ resolutionKind: "generated_constant_name"
468
+ };
469
+ if (ts2.isPropertyAccessExpression(expression))
470
+ return unresolved(rawExpression, "property_access_not_resolved_to_local_string");
471
+ return unresolved(rawExpression, "unsupported_decorator_expression");
344
472
  }
345
473
  async function parseDecorators(repoPath, filePath) {
346
474
  const text = await fs4.readFile(path5.join(repoPath, filePath), "utf8");
347
475
  const sf = createSourceFile(filePath, text);
348
- const constants = /* @__PURE__ */ new Map();
476
+ const lookups = collectStringLookups(sf);
349
477
  const handlers = [];
350
478
  function visit(node) {
351
- if (ts2.isVariableDeclaration(node) && ts2.isIdentifier(node.name) && node.initializer && ts2.isStringLiteralLike(node.initializer))
352
- constants.set(node.name.text, node.initializer.text);
353
479
  if (ts2.isClassDeclaration(node)) {
354
480
  const className = node.name?.text ?? "AnonymousHandler";
355
481
  const hasHandler = decs(node).some((d) => callName(d) === "Handler");
@@ -357,13 +483,13 @@ async function parseDecorators(repoPath, filePath) {
357
483
  (m) => decs(m).filter(
358
484
  (d) => ["Func", "Action", "On", "Event"].includes(callName(d))
359
485
  ).map((d) => {
360
- const raw = firstArg(d);
361
- const value = raw.startsWith('"') || raw.startsWith("'") || raw.startsWith("`") ? stripQuotes(raw) : constants.get(raw) ?? (raw.endsWith(".name") ? raw.split(".").at(-2) : void 0);
486
+ const decoratorResolution = resolveDecoratorArgument(firstArg(d), lookups);
362
487
  return {
363
488
  methodName: m.name.getText(),
364
489
  decoratorKind: callName(d),
365
- decoratorValue: value,
366
- decoratorRawExpression: raw,
490
+ decoratorValue: decoratorResolution.resolvedValue,
491
+ decoratorRawExpression: decoratorResolution.rawExpression,
492
+ decoratorResolution,
367
493
  sourceFile: normalizePath(filePath),
368
494
  sourceLine: line(sf, m.getStart())
369
495
  };
@@ -588,7 +714,7 @@ import ts4 from "typescript";
588
714
  function lineOf3(sf, node) {
589
715
  return sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
590
716
  }
591
- function stringValue(node) {
717
+ function stringValue2(node) {
592
718
  if (!node) return void 0;
593
719
  if (ts4.isStringLiteralLike(node) || ts4.isNoSubstitutionTemplateLiteral(node)) return node.text;
594
720
  if (ts4.isTemplateExpression(node)) return node.getText().replace(/^`|`$/g, "");
@@ -609,7 +735,7 @@ function connectFactFromCall(call) {
609
735
  let alias;
610
736
  let aliasExpr;
611
737
  if (ts4.isStringLiteralLike(first) || ts4.isNoSubstitutionTemplateLiteral(first)) alias = first.text;
612
- else if (!ts4.isObjectLiteralExpression(first)) aliasExpr = stringValue(first);
738
+ else if (!ts4.isObjectLiteralExpression(first)) aliasExpr = stringValue2(first);
613
739
  if ((ts4.isStringLiteralLike(first) || ts4.isNoSubstitutionTemplateLiteral(first)) && !objectArg) return { alias: first.text, isDynamic: false, placeholders: [] };
614
740
  if (!objectArg && aliasExpr) return { aliasExpr, isDynamic: true, placeholders: placeholders(aliasExpr) };
615
741
  const expressions = objectArg ? objectExpressions(objectArg) : {};
@@ -622,8 +748,8 @@ function objectExpressions(objectArg) {
622
748
  for (const prop of obj.properties) {
623
749
  if (!ts4.isPropertyAssignment(prop)) continue;
624
750
  const name = ts4.isIdentifier(prop.name) || ts4.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
625
- if (name === "destination") out.destinationExpr = stringValue(prop.initializer);
626
- if (name === "path" || name === "servicePath") out.servicePathExpr = stringValue(prop.initializer);
751
+ if (name === "destination") out.destinationExpr = stringValue2(prop.initializer);
752
+ if (name === "path" || name === "servicePath") out.servicePathExpr = stringValue2(prop.initializer);
627
753
  if (ts4.isObjectLiteralExpression(prop.initializer)) visitObject(prop.initializer);
628
754
  }
629
755
  }
@@ -733,8 +859,8 @@ function collectReturnedObjectBindings(fn) {
733
859
  for (const prop of node.expression.properties) {
734
860
  if (ts5.isShorthandPropertyAssignment(prop)) returns.set(prop.name.text, prop.name.text);
735
861
  if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.initializer)) {
736
- const propertyName2 = ts5.isIdentifier(prop.name) || ts5.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
737
- if (propertyName2) returns.set(propertyName2, prop.initializer.text);
862
+ const propertyName3 = ts5.isIdentifier(prop.name) || ts5.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
863
+ if (propertyName3) returns.set(propertyName3, prop.initializer.text);
738
864
  }
739
865
  }
740
866
  }
@@ -742,9 +868,9 @@ function collectReturnedObjectBindings(fn) {
742
868
  }
743
869
  visit(fn);
744
870
  const out = /* @__PURE__ */ new Map();
745
- for (const [propertyName2, variableName] of returns) {
871
+ for (const [propertyName3, variableName] of returns) {
746
872
  const fact = bindings.get(variableName);
747
- if (fact) out.set(propertyName2, fact);
873
+ if (fact) out.set(propertyName3, fact);
748
874
  }
749
875
  return out;
750
876
  }
@@ -1028,8 +1154,8 @@ async function parseServiceBindings(repoPath, filePath) {
1028
1154
  if (helpers.length === 0) return;
1029
1155
  for (const el of decl.name.elements) {
1030
1156
  if (!ts5.isIdentifier(el.name)) continue;
1031
- const propertyName2 = el.propertyName && ts5.isIdentifier(el.propertyName) ? el.propertyName.text : el.name.text;
1032
- const matches2 = helpers.filter((row) => row.helper.returnedProperty === propertyName2);
1157
+ const propertyName3 = el.propertyName && ts5.isIdentifier(el.propertyName) ? el.propertyName.text : el.name.text;
1158
+ const matches2 = helpers.filter((row) => row.helper.returnedProperty === propertyName3);
1033
1159
  if (matches2.length !== 1) continue;
1034
1160
  const resolved2 = matches2[0];
1035
1161
  out.push({
@@ -1042,7 +1168,7 @@ async function parseServiceBindings(repoPath, filePath) {
1042
1168
  placeholders: resolved2.helper.placeholders,
1043
1169
  sourceFile: normalizePath(filePath),
1044
1170
  sourceLine: lineOf3(sourceFileAst, decl),
1045
- helperChain: [...resolved2.helper.helperChain ?? [], { callerVariable: el.name.text, helperFunction: call.expression.getText(sourceFileAst), returnedProperty: propertyName2, importSource: resolved2.imp?.sourceFile, exportedSymbol: resolved2.imp?.exportedName, helperSourceFile: resolved2.helper.sourceFile, helperSourceLine: resolved2.helper.sourceLine }]
1171
+ helperChain: [...resolved2.helper.helperChain ?? [], { callerVariable: el.name.text, helperFunction: call.expression.getText(sourceFileAst), returnedProperty: propertyName3, importSource: resolved2.imp?.sourceFile, exportedSymbol: resolved2.imp?.exportedName, helperSourceFile: resolved2.helper.sourceFile, helperSourceLine: resolved2.helper.sourceLine }]
1046
1172
  });
1047
1173
  }
1048
1174
  }
@@ -1052,17 +1178,17 @@ async function parseServiceBindings(repoPath, filePath) {
1052
1178
  const helpers = await helpersForCall(call);
1053
1179
  if (helpers.length === 0) return;
1054
1180
  for (const prop of pattern.properties) {
1055
- let propertyName2;
1181
+ let propertyName3;
1056
1182
  let targetName;
1057
1183
  if (ts5.isShorthandPropertyAssignment(prop)) {
1058
- propertyName2 = prop.name.text;
1184
+ propertyName3 = prop.name.text;
1059
1185
  targetName = prop.name.text;
1060
1186
  } else if (ts5.isPropertyAssignment(prop)) {
1061
- propertyName2 = ts5.isIdentifier(prop.name) || ts5.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
1187
+ propertyName3 = ts5.isIdentifier(prop.name) || ts5.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
1062
1188
  targetName = ts5.isIdentifier(prop.initializer) ? prop.initializer.text : void 0;
1063
1189
  }
1064
- if (!propertyName2 || !targetName) continue;
1065
- const matches2 = helpers.filter((row) => row.helper.returnedProperty === propertyName2);
1190
+ if (!propertyName3 || !targetName) continue;
1191
+ const matches2 = helpers.filter((row) => row.helper.returnedProperty === propertyName3);
1066
1192
  if (matches2.length !== 1) continue;
1067
1193
  const resolved2 = matches2[0];
1068
1194
  out.push({
@@ -1075,7 +1201,7 @@ async function parseServiceBindings(repoPath, filePath) {
1075
1201
  placeholders: resolved2.helper.placeholders,
1076
1202
  sourceFile: normalizePath(filePath),
1077
1203
  sourceLine: lineOf3(sourceFileAst, node),
1078
- helperChain: [...resolved2.helper.helperChain ?? [], { callerVariable: targetName, assignedFrom: call.expression.getText(sourceFileAst), aliasKind: "assignment", scopeRule: "same-file-source-order", returnedProperty: propertyName2, importSource: resolved2.imp?.sourceFile, exportedSymbol: resolved2.imp?.exportedName, helperSourceFile: resolved2.helper.sourceFile, helperSourceLine: resolved2.helper.sourceLine }]
1204
+ helperChain: [...resolved2.helper.helperChain ?? [], { callerVariable: targetName, assignedFrom: call.expression.getText(sourceFileAst), aliasKind: "assignment", scopeRule: "same-file-source-order", returnedProperty: propertyName3, importSource: resolved2.imp?.sourceFile, exportedSymbol: resolved2.imp?.exportedName, helperSourceFile: resolved2.helper.sourceFile, helperSourceLine: resolved2.helper.sourceLine }]
1079
1205
  });
1080
1206
  }
1081
1207
  }
@@ -1087,9 +1213,9 @@ async function parseServiceBindings(repoPath, filePath) {
1087
1213
  if (target.expression.kind !== ts5.SyntaxKind.ThisKeyword) return;
1088
1214
  for (const el of decl.name.elements) {
1089
1215
  if (!ts5.isIdentifier(el.name)) continue;
1090
- const propertyName2 = el.propertyName && ts5.isIdentifier(el.propertyName) ? el.propertyName.text : el.name.text;
1216
+ const propertyName3 = el.propertyName && ts5.isIdentifier(el.propertyName) ? el.propertyName.text : el.name.text;
1091
1217
  const helper = classHelpers.find(
1092
- (h) => h.helperName === target.name.text && h.propertyName === propertyName2
1218
+ (h) => h.helperName === target.name.text && h.propertyName === propertyName3
1093
1219
  );
1094
1220
  if (!helper) continue;
1095
1221
  out.push({
@@ -1242,13 +1368,13 @@ function collectClassHelpers(sf) {
1242
1368
  });
1243
1369
  }
1244
1370
  if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.initializer)) {
1245
- const propertyName2 = ts5.isIdentifier(prop.name) || ts5.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
1246
- const fact = propertyName2 ? bindings.get(prop.initializer.text) : void 0;
1247
- if (propertyName2 && fact)
1371
+ const propertyName3 = ts5.isIdentifier(prop.name) || ts5.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
1372
+ const fact = propertyName3 ? bindings.get(prop.initializer.text) : void 0;
1373
+ if (propertyName3 && fact)
1248
1374
  helpers.push({
1249
1375
  className,
1250
1376
  helperName,
1251
- propertyName: propertyName2,
1377
+ propertyName: propertyName3,
1252
1378
  variableName: prop.initializer.text,
1253
1379
  fact,
1254
1380
  sourceLine: lineOf3(sf, prop)
@@ -1944,12 +2070,12 @@ function mappedParameterIndex(expr, parameters) {
1944
2070
  }
1945
2071
  function propertyExpression(object, key) {
1946
2072
  for (const property of object.properties) {
1947
- if (ts7.isPropertyAssignment(property) && propertyName(property.name) === key) return property.initializer;
2073
+ if (ts7.isPropertyAssignment(property) && propertyName2(property.name) === key) return property.initializer;
1948
2074
  if (ts7.isShorthandPropertyAssignment(property) && property.name.text === key) return property.name;
1949
2075
  }
1950
2076
  return void 0;
1951
2077
  }
1952
- function propertyName(name) {
2078
+ function propertyName2(name) {
1953
2079
  return ts7.isIdentifier(name) || ts7.isStringLiteralLike(name) ? name.text : void 0;
1954
2080
  }
1955
2081
  function literal(expr) {
@@ -2918,46 +3044,6 @@ function linkHelperPackages(db, workspaceId, generation) {
2918
3044
  return summary;
2919
3045
  }
2920
3046
 
2921
- // src/linker/operation-decorator-normalizer.ts
2922
- function lowerFirst(value) {
2923
- return value ? `${value[0]?.toLowerCase() ?? ""}${value.slice(1)}` : value;
2924
- }
2925
- function normalizedOperationName(value) {
2926
- return value.replace(/^\//, "");
2927
- }
2928
- function clean(value) {
2929
- return value.replace(/^[`'"]|[`'"]$/g, "");
2930
- }
2931
- function generatedFromConstantName(value) {
2932
- for (const prefix of ["Action", "Func"]) {
2933
- if (value.startsWith(prefix) && value.length > prefix.length && /^[A-Z]/.test(value.slice(prefix.length))) return lowerFirst(value.slice(prefix.length));
2934
- }
2935
- return void 0;
2936
- }
2937
- function resolved(value, raw) {
2938
- const literal2 = clean(value);
2939
- const generated = generatedFromConstantName(literal2);
2940
- return { status: "resolved", operationName: generated ?? normalizedOperationName(literal2), raw };
2941
- }
2942
- function normalizeDecoratorOperationSignal(value, raw, candidateOperation) {
2943
- if (value) return resolved(value, raw);
2944
- if (!raw || raw.trim().length === 0) return { status: "none", raw };
2945
- const expression = raw.trim();
2946
- const nameMatch = /(?:^|\.)(Action[A-Z][\w$]*|Func[A-Z][\w$]*)\.name$/.exec(expression);
2947
- if (nameMatch?.[1]) return resolved(nameMatch[1], expression);
2948
- const stringMatch = /^String\(([A-Za-z_$][\w$]*)\)$/.exec(expression);
2949
- if (stringMatch?.[1]) {
2950
- const identifier = stringMatch[1];
2951
- const generated = generatedFromConstantName(identifier);
2952
- const normalizedCandidate2 = candidateOperation ? normalizedOperationName(candidateOperation) : void 0;
2953
- if (generated) return { status: "resolved", operationName: generated, raw: expression };
2954
- if (normalizedCandidate2 && identifier === normalizedCandidate2) return { status: "resolved", operationName: identifier, raw: expression };
2955
- return { status: "unsupported", raw: expression, reason: "string_wrapper_identifier_not_resolved" };
2956
- }
2957
- if (/^[`'"]/.test(expression) && !/[`'"]$/.test(expression)) return { status: "malformed", raw: expression, reason: "unterminated_literal" };
2958
- return { status: "unsupported", raw: expression, reason: "unsupported_decorator_expression" };
2959
- }
2960
-
2961
3047
  // src/linker/cross-repo-linker.ts
2962
3048
  function linkWorkspace(db, workspaceId, vars = {}) {
2963
3049
  return db.transaction(() => {
@@ -3140,7 +3226,7 @@ function objectJson(value) {
3140
3226
  }
3141
3227
  function callEvidence(call, resolution, servicePath, op, destination, normalized, odataPathIntent) {
3142
3228
  const bindingHasDynamicExpression = Boolean(Number(call.isDynamic ?? 0));
3143
- const routingPlaceholderKeys = placeholderKeys([servicePath, destination, stringValue2(call.aliasExpr), stringValue2(call.alias)]);
3229
+ const routingPlaceholderKeys = placeholderKeys([servicePath, destination, stringValue3(call.aliasExpr), stringValue3(call.alias)]);
3144
3230
  return { sourceFile: call.source_file, sourceLine: call.source_line, file: call.source_file, line: call.source_line, callId: call.id, repo: call.repoName, serviceAlias: call.alias, serviceAliasExpr: call.aliasExpr, destination, servicePath, operationPath: op, rawOperationPath: normalized?.wasInvocation ? normalized.rawOperationPath : odataPathIntent?.rawPath, normalizedOperationPath: normalized?.wasInvocation ? normalized.normalizedOperationPath : void 0, invocationArguments: normalized?.wasInvocation ? normalized.invocationArguments : void 0, invocationArgumentPlaceholderKeys: normalized?.invocationArgumentPlaceholderKeys.length ? normalized.invocationArgumentPlaceholderKeys : void 0, routingPlaceholderKeys: routingPlaceholderKeys.length ? routingPlaceholderKeys : void 0, odataOperationNormalizationReason: normalized?.normalizationReason, odataOperationNormalizationRejectedReason: normalized?.normalizationRejectedReason, localServiceName: call.local_service_name, localServiceLookup: call.local_service_lookup, aliasChain: parseJson(call.alias_chain_json), transport: call.call_type === "local_service_call" ? "local" : void 0, targetRepo: resolution.target?.repoName, targetServicePath: resolution.target?.servicePath, targetOperationPath: resolution.target?.operationPath, targetOperation: resolution.target?.operationName, helperChain: parseJson(call.helperChainJson), candidates: resolution.candidates, candidateScores: compactCandidateScores(resolution.candidates), candidateCount: resolution.candidates.length, resolutionStatus: resolution.status, resolutionReasons: resolution.reasons, odataPathIntent, queryStringPresent: odataPathIntent?.hasQueryString || void 0, queryPlaceholderKeys: odataPathIntent?.placeholderKeys.length ? odataPathIntent.placeholderKeys : void 0, bindingHasDynamicExpression: bindingHasDynamicExpression || void 0, outboundEvidence: objectJson(call.evidence_json), analysisCompleteness: call.unresolved_reason ? "partial" : "complete", parserWarning: call.unresolved_reason ? { code: "parser_warning", message: call.unresolved_reason } : void 0 };
3145
3231
  }
3146
3232
  function compactCandidateScores(candidates) {
@@ -3157,7 +3243,7 @@ function placeholderKeys(values) {
3157
3243
  function objectValue(value) {
3158
3244
  return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
3159
3245
  }
3160
- function stringValue2(value) {
3246
+ function stringValue3(value) {
3161
3247
  return typeof value === "string" ? value : void 0;
3162
3248
  }
3163
3249
  function linkImplementations(db, workspaceId, generation) {
@@ -3212,13 +3298,54 @@ function implementationContextForOperation(db, operation) {
3212
3298
  }
3213
3299
  function rankedImplementationCandidates(db, workspaceId, operation) {
3214
3300
  const rows2 = implementationCandidates(db, workspaceId, operation);
3215
- return deduplicateCandidates(rows2.map((row) => scoreImplementationCandidate(row, operation))).sort((a, b) => b.score - a.score || String(a.className).localeCompare(String(b.className)) || a.methodId - b.methodId);
3301
+ const invalid = rows2.filter((row) => !validRegistrationPair(row));
3302
+ recordRegistrationInvariantDiagnostics(db, invalid);
3303
+ return deduplicateCandidates(
3304
+ rows2.filter(validRegistrationPair).map((row) => scoreImplementationCandidate(row, operation))
3305
+ ).sort((a, b) => b.score - a.score || String(a.className).localeCompare(String(b.className)) || a.methodId - b.methodId);
3306
+ }
3307
+ function validRegistrationPair(row) {
3308
+ if (row.registrationHandlerClassId === null || row.registrationHandlerClassId === void 0)
3309
+ return registrationPairingStrategy(row) !== "unproven";
3310
+ return Number(row.registrationHandlerClassId) === Number(row.classId);
3311
+ }
3312
+ function registrationPairingStrategy(row) {
3313
+ if (row.registrationHandlerClassId !== null && row.registrationHandlerClassId !== void 0)
3314
+ return "exact_handler_class_id";
3315
+ if (Number(row.applicationRepoId) === Number(row.handlerRepoId))
3316
+ return "same_repository_class_name_fallback";
3317
+ const source = stringValue3(row.importSource);
3318
+ const separator = source?.lastIndexOf("#") ?? -1;
3319
+ if (!source || separator <= 0) return "unproven";
3320
+ const moduleName = source.slice(0, separator);
3321
+ const importedName = source.slice(separator + 1);
3322
+ const matchesClass = importedName === row.className || importedName === "default" && row.registrationClassName === row.className;
3323
+ return moduleName === row.handlerPackage && matchesClass ? "explicit_package_import" : "unproven";
3324
+ }
3325
+ function recordRegistrationInvariantDiagnostics(db, rows2) {
3326
+ const insert = db.prepare(`INSERT INTO diagnostics(repo_id,severity,code,message,source_file,source_line)
3327
+ SELECT ?,'error','handler_registration_class_mismatch',
3328
+ 'Implementation candidate registration did not match its persisted handler class id',?,?
3329
+ WHERE NOT EXISTS (
3330
+ SELECT 1 FROM diagnostics
3331
+ WHERE repo_id=? AND code='handler_registration_class_mismatch'
3332
+ AND source_file=? AND source_line=?
3333
+ )`);
3334
+ for (const row of rows2)
3335
+ insert.run(
3336
+ row.applicationRepoId,
3337
+ row.registrationFile,
3338
+ row.registrationLine,
3339
+ row.applicationRepoId,
3340
+ row.registrationFile,
3341
+ row.registrationLine
3342
+ );
3216
3343
  }
3217
3344
  function deduplicateCandidates(rows2) {
3218
3345
  const merged = /* @__PURE__ */ new Map();
3219
3346
  for (const row of rows2) {
3220
3347
  const key = [row.methodId, row.classId, row.handlerRepoId].join(":");
3221
- const registration = { file: row.registrationFile, line: row.registrationLine, kind: row.registrationKind, importSource: row.importSource };
3348
+ const registration = registrationEvidence(row);
3222
3349
  const existing = merged.get(key);
3223
3350
  if (!existing) {
3224
3351
  merged.set(key, { ...row, registrations: [registration] });
@@ -3232,6 +3359,17 @@ function deduplicateCandidates(rows2) {
3232
3359
  }
3233
3360
  return [...merged.values()];
3234
3361
  }
3362
+ function registrationEvidence(row) {
3363
+ return {
3364
+ id: row.registrationId,
3365
+ handlerClassId: row.registrationHandlerClassId,
3366
+ file: row.registrationFile,
3367
+ line: row.registrationLine,
3368
+ kind: row.registrationKind,
3369
+ importSource: row.importSource,
3370
+ pairingStrategy: registrationPairingStrategy(row)
3371
+ };
3372
+ }
3235
3373
  function uniqueRegistrations(rows2) {
3236
3374
  const seen = /* @__PURE__ */ new Set();
3237
3375
  return rows2.filter((row) => {
@@ -3260,10 +3398,14 @@ function implementationCandidates(db, workspaceId, operation) {
3260
3398
  hm.method_name methodName,
3261
3399
  hm.decorator_value decoratorValue,
3262
3400
  hm.decorator_raw_expression decoratorRawExpression,
3401
+ hm.decorator_resolution_json decoratorResolutionJson,
3263
3402
  hc.id classId,
3264
3403
  hc.class_name className,
3265
3404
  hc.source_file sourceFile,
3266
3405
  hc.source_line sourceLine,
3406
+ hr.id registrationId,
3407
+ hr.handler_class_id registrationHandlerClassId,
3408
+ hr.class_name registrationClassName,
3267
3409
  hr.repo_id applicationRepoId,
3268
3410
  hr.registration_file registrationFile,
3269
3411
  hr.registration_line registrationLine,
@@ -3286,14 +3428,36 @@ function implementationCandidates(db, workspaceId, operation) {
3286
3428
  CASE WHEN appRepo.id=handlerRepo.id THEN 1 ELSE 0 END sameRepoRegistration,
3287
3429
  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,
3288
3430
  CASE WHEN EXISTS (SELECT 1 FROM cds_services localService WHERE localService.repo_id=appRepo.id) THEN 1 ELSE 0 END applicationHasLocalServices,
3289
- CASE WHEN EXISTS (SELECT 1 FROM handler_registrations localReg JOIN handler_classes localClass ON (localClass.id=localReg.handler_class_id OR localClass.class_name=localReg.class_name) JOIN handler_methods localMethod ON localMethod.handler_class_id=localClass.id WHERE localReg.repo_id=appRepo.id AND (localMethod.decorator_value=? OR localMethod.decorator_value=? OR localMethod.method_name=? OR localMethod.decorator_raw_expression LIKE ?)) THEN 1 ELSE 0 END applicationHasLocalRegistrationForOperation,
3431
+ CASE WHEN EXISTS (SELECT 1 FROM handler_registrations localReg JOIN handler_classes localClass ON ((localReg.handler_class_id IS NOT NULL AND localClass.id=localReg.handler_class_id) OR (localReg.handler_class_id IS NULL AND localClass.class_name=localReg.class_name AND localClass.repo_id=localReg.repo_id)) JOIN handler_methods localMethod ON localMethod.handler_class_id=localClass.id WHERE localReg.repo_id=appRepo.id AND (localMethod.decorator_value=? OR localMethod.decorator_value=? OR localMethod.method_name=? OR localMethod.decorator_raw_expression LIKE ?)) THEN 1 ELSE 0 END applicationHasLocalRegistrationForOperation,
3290
3432
  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,
3291
3433
  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,
3292
3434
  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
3293
3435
  FROM handler_methods hm
3294
3436
  JOIN handler_classes hc ON hc.id=hm.handler_class_id
3295
3437
  JOIN repositories handlerRepo ON handlerRepo.id=hc.repo_id
3296
- 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)))
3438
+ JOIN handler_registrations hr ON (
3439
+ (hr.handler_class_id IS NOT NULL AND hr.handler_class_id=hc.id)
3440
+ OR (
3441
+ hr.handler_class_id IS NULL
3442
+ AND (
3443
+ (hr.class_name=hc.class_name AND hr.repo_id=hc.repo_id)
3444
+ OR (
3445
+ instr(hr.import_source,'#')>1
3446
+ AND substr(hr.import_source,1,instr(hr.import_source,'#')-1)
3447
+ =handlerRepo.package_name
3448
+ AND (
3449
+ substr(hr.import_source,instr(hr.import_source,'#')+1)
3450
+ =hc.class_name
3451
+ OR (
3452
+ substr(hr.import_source,instr(hr.import_source,'#')+1)
3453
+ ='default'
3454
+ AND hr.class_name=hc.class_name
3455
+ )
3456
+ )
3457
+ )
3458
+ )
3459
+ )
3460
+ )
3297
3461
  JOIN repositories appRepo ON appRepo.id=hr.repo_id
3298
3462
  WHERE appRepo.workspace_id=?
3299
3463
  AND (hm.decorator_value=? OR hm.decorator_value=? OR hm.method_name=? OR hm.decorator_raw_expression LIKE ?)`).all(
@@ -3395,8 +3559,16 @@ function candidateEvidence(candidate, rank) {
3395
3559
  className: candidate.className,
3396
3560
  sourceFile: candidate.sourceFile,
3397
3561
  sourceLine: candidate.sourceLine,
3398
- registration: { file: candidate.registrationFile, line: candidate.registrationLine, kind: candidate.registrationKind, importSource: candidate.importSource },
3562
+ decoratorResolution: objectJson(candidate.decoratorResolutionJson),
3563
+ registration: registrationEvidence(candidate),
3399
3564
  registrations: candidate.registrations ?? [],
3565
+ registrationPairing: {
3566
+ strategy: registrationPairingStrategy(candidate),
3567
+ registrationId: candidate.registrationId,
3568
+ registrationHandlerClassId: candidate.registrationHandlerClassId,
3569
+ candidateHandlerClassId: candidate.classId,
3570
+ invariantStatus: validRegistrationPair(candidate) ? "valid" : "invalid"
3571
+ },
3400
3572
  applicationPackage: { id: candidate.applicationRepoId, name: candidate.applicationRepo, packageName: candidate.applicationPackage },
3401
3573
  handlerPackage: { id: candidate.handlerRepoId, name: candidate.handlerRepo, packageName: candidate.handlerPackage },
3402
3574
  modelPackage: { id: candidate.modelRepoId, name: candidate.modelRepo, packageName: candidate.modelPackage },
@@ -3436,6 +3608,40 @@ function normalizedOperation(value) {
3436
3608
  return value.startsWith("/") ? value.slice(1) : value;
3437
3609
  }
3438
3610
 
3611
+ // src/trace/selectors.ts
3612
+ function parseVars(values) {
3613
+ const out = {};
3614
+ for (const value of values ?? []) {
3615
+ const [key, ...rest] = value.split("=");
3616
+ if (key && rest.length > 0) out[key] = rest.join("=");
3617
+ }
3618
+ return out;
3619
+ }
3620
+ function ambiguousStartDiagnostic(requested, candidates, message) {
3621
+ const serviceSuggestions = [...new Set(candidates.flatMap((row) => typeof row.servicePath === "string" ? [`--service ${row.servicePath}`] : []))].sort();
3622
+ return {
3623
+ severity: "warning",
3624
+ code: "trace_start_ambiguous",
3625
+ message,
3626
+ normalizedSelectorValue: requested,
3627
+ resolutionStage: "operation",
3628
+ resolutionStatus: "ambiguous_operation",
3629
+ candidates,
3630
+ serviceSuggestions,
3631
+ selectorSuggestions: fullSelectorSuggestions(candidates)
3632
+ };
3633
+ }
3634
+ function fullSelectorSuggestions(candidates) {
3635
+ const includeRepo = new Set(candidates.map((row) => row.repoName)).size > 1;
3636
+ return [...new Set(candidates.flatMap((row) => {
3637
+ if (typeof row.servicePath !== "string" || typeof row.operationPath !== "string") return [];
3638
+ const repoSelector = includeRepo && typeof row.repoName === "string" ? `--repo ${row.repoName} ` : "";
3639
+ return [
3640
+ `${repoSelector}--service ${row.servicePath} --path ${row.operationPath}`
3641
+ ];
3642
+ }))].sort();
3643
+ }
3644
+
3439
3645
  // src/trace/evidence.ts
3440
3646
  function baseTraceEvidence(row, call, persistedEvidence, contextualEvidence) {
3441
3647
  const evidence = { ...persistedEvidence, ...contextualEvidence ?? {} };
@@ -3492,20 +3698,20 @@ function runtimeVariableDiagnostic(edges) {
3492
3698
  }
3493
3699
  function edgeTarget(row, evidence) {
3494
3700
  const effective = parseObject(evidence.effectiveResolution);
3495
- const targetServicePath = stringValue3(effective.targetServicePath ?? evidence.targetServicePath);
3496
- const targetOperationPath = stringValue3(effective.targetOperationPath ?? evidence.targetOperationPath);
3701
+ const targetServicePath = stringValue4(effective.targetServicePath ?? evidence.targetServicePath);
3702
+ const targetOperationPath = stringValue4(effective.targetOperationPath ?? evidence.targetOperationPath);
3497
3703
  if (targetServicePath && targetOperationPath) return `${targetServicePath}${targetOperationPath}`;
3498
3704
  const runtimeCandidate = evidence.runtimeResolvedCandidate;
3499
3705
  if (runtimeCandidate?.servicePath && runtimeCandidate.operationPath) return `${runtimeCandidate.servicePath}${runtimeCandidate.operationPath}`;
3500
- const servicePath = stringValue3(evidence.servicePath);
3501
- const operationPath = stringValue3(evidence.operationPath);
3502
- const targetOperation = stringValue3(evidence.targetOperation);
3503
- const targetRepo = stringValue3(evidence.targetRepo) ?? "";
3706
+ const servicePath = stringValue4(evidence.servicePath);
3707
+ const operationPath = stringValue4(evidence.operationPath);
3708
+ const targetOperation = stringValue4(evidence.targetOperation);
3709
+ const targetRepo = stringValue4(evidence.targetRepo) ?? "";
3504
3710
  if (row.edge_type === "HANDLER_RUNS_DB_QUERY") return `Entity: ${row.to_id || "unknown"}`;
3505
- if (row.edge_type === "HANDLER_RUNS_REMOTE_QUERY") return stringValue3(evidence.remoteQueryTarget) ?? `Remote query: ${row.to_id || "unknown"}`;
3711
+ if (row.edge_type === "HANDLER_RUNS_REMOTE_QUERY") return stringValue4(evidence.remoteQueryTarget) ?? `Remote query: ${row.to_id || "unknown"}`;
3506
3712
  if (row.edge_type === "HANDLER_CALLS_EXTERNAL_HTTP") {
3507
3713
  const target = parseObject(evidence.externalTarget);
3508
- return stringValue3(target.label) ?? `External endpoint: ${row.to_id || "unknown"}`;
3714
+ return stringValue4(target.label) ?? `External endpoint: ${row.to_id || "unknown"}`;
3509
3715
  }
3510
3716
  if (servicePath && operationPath) return `${servicePath}${operationPath}`;
3511
3717
  return targetOperation ? `${targetRepo}:${targetOperation}` : row.to_id;
@@ -3544,12 +3750,12 @@ function withEffectiveResolution(evidence, row, unresolvedReason, resolution) {
3544
3750
  return { ...rest, effectiveResolution: current, linker: { status: current.status, confidence: current.confidence, reason: unresolvedReason, edgeType: current.edgeType } };
3545
3751
  }
3546
3752
  function resolveRuntimeOperation(db, evidence, workspaceId) {
3547
- const servicePath = stringValue3(evidence.servicePath);
3548
- const rawOperationPath = stringValue3(evidence.operationPath);
3753
+ const servicePath = stringValue4(evidence.servicePath);
3754
+ const rawOperationPath = stringValue4(evidence.operationPath);
3549
3755
  const normalized = normalizeODataOperationInvocationPath(rawOperationPath);
3550
- const operationPath = normalized?.wasInvocation ? normalized.normalizedOperationPath : stringValue3(evidence.normalizedOperationPath) ?? rawOperationPath;
3551
- const alias = stringValue3(evidence.serviceAliasExpr ?? evidence.serviceAlias);
3552
- const destination = stringValue3(evidence.destination);
3756
+ const operationPath = normalized?.wasInvocation ? normalized.normalizedOperationPath : stringValue4(evidence.normalizedOperationPath) ?? rawOperationPath;
3757
+ const alias = stringValue4(evidence.serviceAliasExpr ?? evidence.serviceAlias);
3758
+ const destination = stringValue4(evidence.destination);
3553
3759
  return resolveOperation(db, { servicePath, operationPath, alias, destination, hasExplicitOverride: true, isDynamic: true }, workspaceId);
3554
3760
  }
3555
3761
  function evidenceWithRuntimeVariables(evidence, vars) {
@@ -3570,7 +3776,7 @@ function runtimeSubstitutions(evidence, vars) {
3570
3776
  return substitutions;
3571
3777
  }
3572
3778
  function substitutionValue(evidence, key) {
3573
- const value = stringValue3(evidence[key]);
3779
+ const value = stringValue4(evidence[key]);
3574
3780
  if (key !== "operationPath") return value;
3575
3781
  const normalized = normalizeODataOperationInvocationPath(value);
3576
3782
  return normalized?.wasInvocation ? normalized.normalizedOperationPath : value;
@@ -3592,7 +3798,7 @@ function runtimeUnresolvedReason(resolution) {
3592
3798
  function parseObject(value) {
3593
3799
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
3594
3800
  }
3595
- function stringValue3(value) {
3801
+ function stringValue4(value) {
3596
3802
  return typeof value === "string" ? value : void 0;
3597
3803
  }
3598
3804
 
@@ -3641,19 +3847,6 @@ function implementationRejectionReasons(evidence) {
3641
3847
  const reasons = candidates.flatMap((candidate) => Array.isArray(candidate.rejectedReasons) ? candidate.rejectedReasons.filter((reason) => typeof reason === "string") : []);
3642
3848
  return [...new Set(reasons)].sort();
3643
3849
  }
3644
- function ambiguousStartDiagnostic(requested, candidates, message) {
3645
- const serviceSuggestions = [...new Set(candidates.flatMap((row) => typeof row.servicePath === "string" ? [`--service ${row.servicePath}`] : []))].sort();
3646
- return {
3647
- severity: "warning",
3648
- code: "trace_start_ambiguous",
3649
- message,
3650
- normalizedSelectorValue: requested,
3651
- resolutionStage: "operation",
3652
- resolutionStatus: "ambiguous_operation",
3653
- candidates,
3654
- serviceSuggestions
3655
- };
3656
- }
3657
3850
  function sourceFilesForStart(db, repoId, start) {
3658
3851
  const handler = start.handler;
3659
3852
  const operation = normalizeOperation(start.operation ?? start.operationPath);
@@ -4175,6 +4368,7 @@ export {
4175
4368
  parseImplementationHint,
4176
4369
  implementationHintSuggestions,
4177
4370
  linkWorkspace,
4371
+ parseVars,
4178
4372
  trace
4179
4373
  };
4180
- //# sourceMappingURL=chunk-XOROZHW4.js.map
4374
+ //# sourceMappingURL=chunk-52OUS3MO.js.map