@saptools/service-flow 0.1.11 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.12
4
+
5
+ - Resolved same-repository local CAP service calls by qualified CDS name, simple service name, and service path, with explicit local transport and alias-chain evidence.
6
+ - Made implementation matching decorator-aware so generated `Func*`/`Action*` constants outrank method-name fallback and contradictory decorators are rejected without making edges ambiguous.
7
+ - Cleared stale unresolved reasons from resolved symbol calls and suppressed false trace unresolved reasons for symbol edges with concrete callee ids.
8
+ - Made local symbol-call collection conservative, added named export-list support, and indexed one-level object-literal helper methods as executable symbols so traces can reach helper database queries.
9
+ - Added first-class symbol nodes and readable symbol labels/locations to JSON, table, and Mermaid trace output.
10
+ - Deduplicated implementation candidates by method identity while retaining multiple registration rows as nested evidence, and kept default doctor output from failing on explainable source-ownership gaps.
11
+ - Documented the generated-constant decision: this patch uses deterministic decorator normalization for linking while `parseGeneratedConstants` remains a low-level parser export rather than a persisted graph fact.
12
+
3
13
  ## 0.1.11
4
14
 
5
15
  - Added repository-owned executable symbols, source-symbol ownership for outbound calls, and local symbol-call facts so traces can follow reachable same-file/imported helpers without including unrelated calls from the same file.
package/README.md CHANGED
@@ -59,7 +59,7 @@ npm install @saptools/service-flow
59
59
  - Repository selectors on list, trace, graph, and inspect commands narrow scope. Unknown selectors return empty machine-readable diagnostics instead of falling back to the whole workspace.
60
60
  - Helper-package dependency edges prefer exact indexed package names. Duplicate package-name candidates are persisted as ambiguous evidence rather than silently selecting one repository.
61
61
  - Handler registration parsing is AST-based for common `createCombinedHandler({ handler: ... })` forms: direct arrays, arrays assembled with spreads, non-`handlers` array names, aliased class imports, default-imported arrays, named exported arrays, and safe relative re-exports. Class-level rows keep registration file/line and import evidence.
62
- - Implementation edges require both operation compatibility and registration evidence. Same-repository registrations do not need a self-dependency edge; cross-package matches use registration or handler-package dependencies on the model package. Duplicate strong candidates are stored as ambiguous implementation edges.
62
+ - Implementation edges require both operation compatibility and registration evidence. Decorator operation signals are stronger than method-name fallback; common generated names such as `FuncGetConfiguration` and `ActionGetConfiguration` are normalized before comparison, and a contradictory decorator rejects the candidate even when the TypeScript method name collides. Same-repository registrations do not need a self-dependency edge; cross-package matches use registration or handler-package dependencies on the model package. Duplicate strong candidates are stored as ambiguous implementation edges.
63
63
  - Traces render persisted `OPERATION_IMPLEMENTED_BY_HANDLER` hops after static or runtime remote operation resolution, including terminal handler nodes and ambiguous or unresolved implementation evidence when traversal cannot continue.
64
64
  - Repository fingerprints include source content, package name/version, dependencies and devDependencies, scripts, normalized `cds.requires` (including nested credentials), package file content, and the analyzer version. Metadata-only changes therefore trigger reindexing.
65
65
  - Index publication is designed around the last-good snapshot: failed parse or persistence attempts are recorded as diagnostics and must not be mixed with older graph facts. After indexing changes, relink before relying on graph/trace output; doctor reports stale or inconsistent stores where detectable.
@@ -152,7 +152,9 @@ target handler up to `--depth` instead of showing only calls in the first file.
152
152
 
153
153
  `service-flow trace` starts from the selected handler method symbol, renders outbound calls owned by that symbol, and follows conservative local helper-call facts. Supported helper edges include same-file functions, `this.method()` calls, and exactly mapped relative imports/exports that resolve to an indexed executable symbol. Calls from unrelated functions in the same source file are not included merely because the file path matches.
154
154
 
155
- Local CAP calls through `cds.services.<Service>.<operation>()`, bracket service lookups, and simple aliases are indexed as local operation calls. Entity accessors such as `cds.services.db.entities(...)` are treated as entity metadata access, not operation calls.
155
+ Local CAP calls through `cds.services.<Service>.<operation>()`, bracket service lookups, and simple aliases are indexed as local operation calls. Linking stays within the same repository by default and matches the target operation by exact qualified CDS service name, exact simple service name, exact service path, or an unambiguous service-path suffix. Entity accessors such as `cds.services.db.entities(...)` are treated as entity metadata access, not operation calls.
156
+
157
+ Conservative local symbol traversal intentionally excludes decorators, built-ins such as `JSON.parse`, collection methods, third-party APIs, and arbitrary property chains unless the callee can plausibly resolve to an indexed local symbol. Named export lists such as `export { loadTemplate as publicLoadTemplate }` are indexed with the public exported name so relative imports can resolve. One-level object-literal helpers are indexed as symbols named like `cacheHelper.getConfiguration`; nested object literals are not yet expanded beyond the first helper level. `parseGeneratedConstants` remains a public low-level parser export for callers that need it, but generated constants are not persisted as graph facts in this patch; linking uses the deterministic decorator normalizer described above.
156
158
  JSON output includes typed nodes for calls, operations, database entities,
157
159
  external destinations, and unresolved/dynamic candidates when edges exist.
158
160
 
@@ -1074,7 +1074,7 @@ function substituteVariables(template, vars) {
1074
1074
  // src/linker/service-resolver.ts
1075
1075
  function rows(db, operationPath, workspaceId) {
1076
1076
  return db.prepare(
1077
- `SELECT o.id operationId,r.name repoName,s.service_path servicePath,o.operation_path operationPath,o.operation_name operationName,o.source_file sourceFile,o.source_line sourceLine,0 score,'' reasons
1077
+ `SELECT o.id operationId,r.name repoName,s.service_name serviceName,s.qualified_name qualifiedName,s.service_path servicePath,o.operation_path operationPath,o.operation_name operationName,o.source_file sourceFile,o.source_line sourceLine,0 score,'' reasons
1078
1078
  FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id
1079
1079
  WHERE (? IS NULL OR r.workspace_id=?) AND (o.operation_path=? OR o.operation_name=?) ORDER BY r.name,s.service_path,o.operation_name`
1080
1080
  ).all(
@@ -1110,7 +1110,7 @@ function resolveOperation(db, signals, workspaceId) {
1110
1110
  reasons: ["no_operation_candidates"]
1111
1111
  };
1112
1112
  const hasStrongSignal = Boolean(
1113
- signals.servicePath || signals.alias || signals.destination || signals.hasExplicitOverride
1113
+ signals.servicePath || signals.serviceName || signals.alias || signals.destination || signals.hasExplicitOverride
1114
1114
  );
1115
1115
  for (const c of candidates) {
1116
1116
  if (signals.servicePath && c.servicePath === signals.servicePath) {
@@ -1121,14 +1121,25 @@ function resolveOperation(db, signals, workspaceId) {
1121
1121
  c.score -= 0.1;
1122
1122
  c.reasons.push("service_path_mismatch");
1123
1123
  }
1124
- if (signals.serviceName && (c.servicePath === `/${signals.serviceName}` || c.servicePath.endsWith(`/${signals.serviceName}`))) {
1125
- c.score += 0.7;
1126
- c.reasons.push("exact_local_service_name");
1124
+ if (signals.serviceName) {
1125
+ const simple = signals.serviceName.split(".").at(-1) ?? signals.serviceName;
1126
+ if (c.qualifiedName === signals.serviceName) {
1127
+ c.score += 0.8;
1128
+ c.reasons.push("exact_local_qualified_service_name");
1129
+ } else if (c.serviceName === signals.serviceName || c.serviceName === simple) {
1130
+ c.score += 0.75;
1131
+ c.reasons.push("exact_local_simple_service_name");
1132
+ } else if (c.servicePath === signals.serviceName || c.servicePath === `/${signals.serviceName}` || c.servicePath === `/${simple}`) {
1133
+ c.score += 0.7;
1134
+ c.reasons.push("exact_local_service_path");
1135
+ } else if (c.servicePath.endsWith(`/${simple}`)) {
1136
+ c.score += candidates.filter((candidate) => candidate.servicePath.endsWith(`/${simple}`)).length === 1 ? 0.65 : 0.2;
1137
+ c.reasons.push("suffix_local_service_path");
1138
+ } else c.reasons.push("local_service_name_mismatch");
1127
1139
  }
1128
- if (signals.serviceName && c.servicePath !== `/${signals.serviceName}` && !c.servicePath.endsWith(`/${signals.serviceName}`)) c.reasons.push("local_service_name_mismatch");
1129
1140
  if (signals.hasExplicitOverride) {
1130
1141
  c.score += 0.2;
1131
- c.reasons.push("explicit_dynamic_override");
1142
+ c.reasons.push(signals.repoId !== void 0 ? "explicit_local_service_call" : "explicit_dynamic_override");
1132
1143
  }
1133
1144
  }
1134
1145
  for (const c of candidates) c.score = Math.max(0, Math.min(1, c.score));
@@ -1149,7 +1160,7 @@ function resolveOperation(db, signals, workspaceId) {
1149
1160
  candidates,
1150
1161
  reasons: ["operation_path_only_has_no_strong_target_signal"]
1151
1162
  };
1152
- if (best && best.score >= 0.9 && (best.servicePath === signals.servicePath || Boolean(signals.serviceName && (best.servicePath === `/${signals.serviceName}` || best.servicePath.endsWith(`/${signals.serviceName}`)))) && (best.operationPath === signals.operationPath || best.operationName === signals.operationPath.replace(/^\//, "")) && (!second || best.score - second.score >= 0.25))
1163
+ if (best && best.score >= 0.9 && (best.servicePath === signals.servicePath || Boolean(signals.serviceName && !best.reasons.includes("local_service_name_mismatch"))) && (best.operationPath === signals.operationPath || best.operationName === signals.operationPath.replace(/^\//, "")) && (!second || best.score - second.score >= 0.25))
1153
1164
  return {
1154
1165
  status: "resolved",
1155
1166
  target: best,
@@ -1307,7 +1318,7 @@ function rankedImplementationCandidates(db, workspaceId, operation) {
1307
1318
  function deduplicateCandidates(rows2) {
1308
1319
  const merged = /* @__PURE__ */ new Map();
1309
1320
  for (const row of rows2) {
1310
- const key = [row.methodId, row.classId, row.applicationRepoId, row.importSource ?? "", row.registrationKind ?? ""].join(":");
1321
+ const key = [row.methodId, row.classId, row.handlerRepoId].join(":");
1311
1322
  const registration = { file: row.registrationFile, line: row.registrationLine, kind: row.registrationKind, importSource: row.importSource };
1312
1323
  const existing = merged.get(key);
1313
1324
  if (!existing) {
@@ -1335,6 +1346,9 @@ function implementationCandidates(db, workspaceId, operation) {
1335
1346
  const modelRepoGraphId = graphId(operation.modelRepoId);
1336
1347
  return db.prepare(`SELECT DISTINCT
1337
1348
  hm.id methodId,
1349
+ hm.method_name methodName,
1350
+ hm.decorator_value decoratorValue,
1351
+ hm.decorator_raw_expression decoratorRawExpression,
1338
1352
  hc.id classId,
1339
1353
  hc.class_name className,
1340
1354
  hc.source_file sourceFile,
@@ -1361,7 +1375,7 @@ function implementationCandidates(db, workspaceId, operation) {
1361
1375
  CASE WHEN appRepo.id=handlerRepo.id THEN 1 ELSE 0 END sameRepoRegistration,
1362
1376
  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,
1363
1377
  CASE WHEN EXISTS (SELECT 1 FROM cds_services localService WHERE localService.repo_id=appRepo.id) THEN 1 ELSE 0 END applicationHasLocalServices,
1364
- 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=?)) THEN 1 ELSE 0 END applicationHasLocalRegistrationForOperation,
1378
+ 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,
1365
1379
  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,
1366
1380
  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,
1367
1381
  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
@@ -1371,7 +1385,7 @@ function implementationCandidates(db, workspaceId, operation) {
1371
1385
  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)))
1372
1386
  JOIN repositories appRepo ON appRepo.id=hr.repo_id
1373
1387
  WHERE appRepo.workspace_id=?
1374
- AND (hm.decorator_value=? OR hm.decorator_value=? OR hm.method_name=?)`).all(
1388
+ AND (hm.decorator_value=? OR hm.decorator_value=? OR hm.method_name=? OR hm.decorator_raw_expression LIKE ?)`).all(
1375
1389
  operation.modelRepoId,
1376
1390
  operation.modelRepo,
1377
1391
  operation.modelPackage,
@@ -1385,12 +1399,14 @@ function implementationCandidates(db, workspaceId, operation) {
1385
1399
  normalizedOperation(String(operation.operationPath ?? "")),
1386
1400
  operation.operationName,
1387
1401
  operation.operationName,
1402
+ `%${upperFirst(normalizedOperation(String(operation.operationPath ?? operation.operationName ?? "")))}%`,
1388
1403
  modelRepoGraphId,
1389
1404
  modelRepoGraphId,
1390
1405
  workspaceId,
1391
1406
  normalizedOperation(String(operation.operationPath ?? "")),
1392
1407
  operation.operationName,
1393
- operation.operationName
1408
+ operation.operationName,
1409
+ `%${upperFirst(normalizedOperation(String(operation.operationPath ?? operation.operationName ?? "")))}%`
1394
1410
  );
1395
1411
  }
1396
1412
  function scoreImplementationCandidate(row, operation) {
@@ -1408,7 +1424,10 @@ function scoreImplementationCandidate(row, operation) {
1408
1424
  const importSource = typeof row.importSource === "string" && row.importSource.length > 0;
1409
1425
  const sameRepoRegistration = flag(row.sameRepoRegistration);
1410
1426
  const modelOriented = row.modelKind === "cap-db-model" || !applicationHasLocalRegistrationForOperation;
1411
- const methodMatches = true;
1427
+ const methodSignal = implementationMethodSignal(row, operation);
1428
+ const methodMatches = methodSignal.matches;
1429
+ acceptedReasons.push(...methodSignal.acceptedReasons);
1430
+ rejectedReasons.push(...methodSignal.rejectedReasons);
1412
1431
  const registeredAndLinked = sameRepoRegistration && importSource;
1413
1432
  const helperOwned = modelOriented && methodMatches && registeredAndLinked && sameRepoRegistration && !applicationHasLocalServices && !modelIsApplicationRepo && !modelIsHandlerRepo && !localServicePathMatch && !appDependsOnModel && !appDependsOnHandler && !handlerDependsOnModel;
1414
1433
  if (modelIsApplicationRepo) {
@@ -1449,7 +1468,7 @@ function scoreImplementationCandidate(row, operation) {
1449
1468
  const hasCrossPackage = appDependsOnModel && (modelIsHandlerRepo || appDependsOnHandler || !importSource);
1450
1469
  const contradicted = applicationHasLocalServices && !localServicePathMatch && !appDependsOnModel && !hasOwnership;
1451
1470
  if (!hasOwnership && !localServicePathMatch && !hasCrossPackage && !helperOwned) rejectedReasons.push("missing direct ownership, exact local service path, or validated cross-package dependency evidence");
1452
- const accepted = !contradicted && (hasOwnership || localServicePathMatch || hasCrossPackage || handlerDependsOnModel || helperOwned);
1471
+ const accepted = methodMatches && !methodSignal.contradicted && !contradicted && (hasOwnership || localServicePathMatch || hasCrossPackage || handlerDependsOnModel || helperOwned);
1453
1472
  if (!accepted && rejectedReasons.length === 0) rejectedReasons.push("candidate did not meet implementation ownership policy");
1454
1473
  return { ...row, methodId: Number(row.methodId), score, accepted, acceptedReasons, rejectedReasons };
1455
1474
  }
@@ -1485,6 +1504,29 @@ function candidateEvidence(candidate, rank) {
1485
1504
  }
1486
1505
  };
1487
1506
  }
1507
+ function implementationMethodSignal(row, operation) {
1508
+ const op = normalizedOperation(String(operation.operationPath ?? operation.operationName ?? ""));
1509
+ const decorator = normalizeDecoratorOperation(typeof row.decoratorValue === "string" ? row.decoratorValue : void 0, typeof row.decoratorRawExpression === "string" ? row.decoratorRawExpression : void 0);
1510
+ if (decorator && decorator === op) return { matches: true, contradicted: false, acceptedReasons: ["decorator targets operation"], rejectedReasons: [] };
1511
+ if (decorator && decorator !== op) return { matches: false, contradicted: true, acceptedReasons: [], rejectedReasons: ["method_name_matches_but_decorator_targets_different_operation"] };
1512
+ if (String(row.methodName ?? "") === op) return { matches: true, contradicted: false, acceptedReasons: ["method name fallback matched operation"], rejectedReasons: [] };
1513
+ return { matches: false, contradicted: false, acceptedReasons: [], rejectedReasons: ["method name does not match operation"] };
1514
+ }
1515
+ function normalizeDecoratorOperation(value, raw) {
1516
+ const candidate = value ?? raw?.split(".").filter(Boolean).at(-2);
1517
+ if (!candidate) return void 0;
1518
+ const cleaned = candidate.replace(/^['"`]|['"`]$/g, "");
1519
+ for (const prefix of ["Func", "Action"]) {
1520
+ if (cleaned.startsWith(prefix) && cleaned.length > prefix.length) return lowerFirst(cleaned.slice(prefix.length));
1521
+ }
1522
+ return normalizedOperation(cleaned);
1523
+ }
1524
+ function upperFirst(value) {
1525
+ return value ? `${value[0]?.toUpperCase() ?? ""}${value.slice(1)}` : value;
1526
+ }
1527
+ function lowerFirst(value) {
1528
+ return value ? `${value[0]?.toLowerCase() ?? ""}${value.slice(1)}` : value;
1529
+ }
1488
1530
  function flag(value) {
1489
1531
  return Boolean(Number(value ?? 0));
1490
1532
  }
@@ -1634,6 +1676,12 @@ function evidenceWithRuntimeVariables(evidence, vars) {
1634
1676
  if (missing.length > 0) next.missingRuntimeVariables = [...new Set(missing)];
1635
1677
  return next;
1636
1678
  }
1679
+ function symbolNode(db, symbolId) {
1680
+ const row = db.prepare(`SELECT s.id symbolId,s.name symbolName,s.qualified_name qualifiedName,s.source_file sourceFile,s.start_line startLine,s.end_line endLine,r.name repoName,r.id repoId FROM symbols s JOIN repositories r ON r.id=s.repo_id WHERE s.id=?`).get(symbolId);
1681
+ if (!row) return void 0;
1682
+ const fileName = String(row.sourceFile ?? "").split("/").at(-1) ?? String(row.sourceFile ?? "");
1683
+ return { id: `symbol:${symbolId}`, kind: "symbol", label: `${fileName}:${String(row.qualifiedName ?? row.symbolName)}`, ...row };
1684
+ }
1637
1685
  function operationNode(db, operationId) {
1638
1686
  const row = db.prepare(`SELECT o.id operationId,o.operation_name operationName,o.operation_type operationType,o.operation_path operationPath,o.source_file sourceFile,o.source_line sourceLine,s.id serviceId,s.service_name serviceName,s.qualified_name qualifiedName,s.service_path servicePath,r.id repoId,r.name repoName FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE o.id=?`).get(operationId);
1639
1687
  if (!row) return void 0;
@@ -1728,7 +1776,10 @@ function trace(db, start, options) {
1728
1776
  const nextFiles = /* @__PURE__ */ new Set([String(symbolCall.calleeFile)]);
1729
1777
  const nextRepoId = Number(symbolCall.calleeRepoId);
1730
1778
  const nextKey = `${nextRepoId}:${[...nextSymbols].join(",")}:${[...nextFiles].join(",")}`;
1731
- edges.push({ step: current.depth, type: "local_symbol_call", from: String(symbolCall.callee_expression), to: `symbol:${String(symbolCall.callee_symbol_id)}`, evidence: JSON.parse(String(symbolCall.evidence_json || "{}")), confidence: Number(symbolCall.confidence ?? 0.8), unresolvedReason: symbolCall.unresolved_reason ? String(symbolCall.unresolved_reason) : void 0 });
1779
+ const calleeNode = symbolNode(db, Number(symbolCall.callee_symbol_id));
1780
+ if (calleeNode) nodes.set(String(calleeNode.id), calleeNode);
1781
+ const evidence = { ...JSON.parse(String(symbolCall.evidence_json || "{}")), sourceFile: symbolCall.source_file, sourceLine: symbolCall.source_line, calleeSymbolId: symbolCall.callee_symbol_id, calleeSymbolName: calleeNode?.symbolName, calleeSymbolFile: calleeNode?.sourceFile, resolutionStatus: symbolCall.status };
1782
+ edges.push({ step: current.depth, type: "local_symbol_call", from: String(symbolCall.callee_expression), to: calleeNode?.label ? String(calleeNode.label) : `symbol:${String(symbolCall.callee_symbol_id)}`, evidence, confidence: Number(symbolCall.confidence ?? 0.8), unresolvedReason: String(symbolCall.status) === "resolved" ? void 0 : symbolCall.unresolved_reason ? String(symbolCall.unresolved_reason) : void 0 });
1732
1783
  if (seenScopes.has(nextKey)) edges.push({ step: current.depth, type: "cycle", from: String(symbolCall.callee_expression), to: nextKey, evidence: { cycle: true, symbolCallId: symbolCall.id }, confidence: 1, unresolvedReason: "Cycle detected; downstream symbol already visited" });
1733
1784
  else queue.push({ repoId: nextRepoId, files: nextFiles, symbolIds: nextSymbols, depth: current.depth + 1 });
1734
1785
  }
@@ -1842,4 +1893,4 @@ export {
1842
1893
  linkWorkspace,
1843
1894
  trace
1844
1895
  };
1845
- //# sourceMappingURL=chunk-GG4XJGES.js.map
1896
+ //# sourceMappingURL=chunk-MXYYARUP.js.map