@patchstack/connect 0.5.15 → 0.5.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -3119,6 +3119,16 @@ function rootIdentifier(node, ts) {
3119
3119
  }
3120
3120
  return void 0;
3121
3121
  }
3122
+ function rootIdentifierNode(node, ts) {
3123
+ let cur = node;
3124
+ while (cur) {
3125
+ if (ts.isIdentifier(cur)) return cur;
3126
+ if (ts.isPropertyAccessExpression(cur) || ts.isElementAccessExpression(cur) || ts.isCallExpression(cur) || ts.isNewExpression(cur) || ts.isNonNullExpression(cur) || ts.isParenthesizedExpression(cur) || ts.isAwaitExpression(cur)) {
3127
+ cur = cur.expression;
3128
+ } else return void 0;
3129
+ }
3130
+ return void 0;
3131
+ }
3122
3132
  function spanOf(node) {
3123
3133
  const out = { line: lineOf(node) };
3124
3134
  try {
@@ -3223,6 +3233,15 @@ function opCallOf(propAccess, ts) {
3223
3233
  const p = propAccess?.parent;
3224
3234
  return p && ts.isCallExpression(p) && p.expression === propAccess ? p : propAccess;
3225
3235
  }
3236
+ function localCallIdentifiers(node, ts) {
3237
+ const out = [];
3238
+ const visit = (n) => {
3239
+ if (ts.isCallExpression(n) && ts.isIdentifier(n.expression)) out.push(n.expression);
3240
+ ts.forEachChild(n, visit);
3241
+ };
3242
+ visit(node);
3243
+ return out;
3244
+ }
3226
3245
  function localCalls(node, ts) {
3227
3246
  const names = [];
3228
3247
  const visit = (n) => {
@@ -3259,8 +3278,9 @@ function routeFromFilePath(relFile) {
3259
3278
  segs = dirs.slice(at("routes") + 1);
3260
3279
  } else if (at("pages") !== -1) {
3261
3280
  segs = [...dirs.slice(at("pages") + 1), ...base === "index" ? [] : [base]];
3262
- } else if (at("server") !== -1) {
3263
- segs = [...dirs.slice(at("server") + 1), ...base === "index" ? [] : [base.replace(/\.(get|post|put|patch|delete|head|options)$/i, "")]];
3281
+ } else if (nuxtRoot(dirs) !== void 0) {
3282
+ const root = nuxtRoot(dirs);
3283
+ segs = [...root, ...base === "index" ? [] : [base.replace(/\.(get|post|put|patch|delete|head|options)$/i, "")]];
3264
3284
  }
3265
3285
  if (!segs) return {};
3266
3286
  segs = segs.filter((s) => !(s.startsWith("(") && s.endsWith(")")) && !s.startsWith("@") && !s.startsWith("_"));
@@ -3276,6 +3296,21 @@ function routeFromFilePath(relFile) {
3276
3296
  const route = "/" + mapped.join("/");
3277
3297
  return { route: route.length > 1 ? route.replace(/\/+$/, "") : "/", dynamic };
3278
3298
  }
3299
+ function isPagesApiFile(relFile) {
3300
+ const dirs = relFile.split(/[\\/]/).filter(Boolean).slice(0, -1);
3301
+ const i = dirs.lastIndexOf("pages");
3302
+ return i !== -1 && dirs[i + 1] === "api";
3303
+ }
3304
+ function nuxtRoot(dirs) {
3305
+ const i = dirs.lastIndexOf("server");
3306
+ if (i === -1) return void 0;
3307
+ if (dirs[i + 1] === "api") return dirs.slice(i + 1);
3308
+ if (dirs[i + 1] === "routes") return dirs.slice(i + 2);
3309
+ return void 0;
3310
+ }
3311
+ function isRoutePath(route) {
3312
+ return route.startsWith("/") || route === "*";
3313
+ }
3279
3314
  function routeFromChain(expr, ts) {
3280
3315
  let cur = expr;
3281
3316
  while (cur && ts.isCallExpression(cur) && ts.isPropertyAccessExpression(cur.expression)) {
@@ -3336,7 +3371,35 @@ function detectFramework(cwd) {
3336
3371
  }
3337
3372
  return "unknown";
3338
3373
  }
3339
- var isSourceFile = (name) => /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/.test(name) && !name.endsWith(".d.ts");
3374
+ var isSourceFile = (name) => /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/.test(name) && !name.endsWith(".d.ts") || isComponentFile(name);
3375
+ var isComponentFile = (name) => /\.(vue|svelte|astro)$/.test(name);
3376
+ function componentScript(text, file) {
3377
+ const keep = [];
3378
+ if (file.endsWith(".astro")) {
3379
+ const open2 = /^?\s*---[^\S\r\n]*\r?\n/.exec(text);
3380
+ if (open2) {
3381
+ const start = open2[0].length;
3382
+ const close = /\r?\n---[^\S\r\n]*(\r?\n|$)/g;
3383
+ close.lastIndex = start - 1;
3384
+ const end = close.exec(text);
3385
+ if (!end) return null;
3386
+ if (end.index > start) keep.push([start, end.index]);
3387
+ }
3388
+ }
3389
+ const opening = /<script\b[^>]*>/gi;
3390
+ for (let m = opening.exec(text); m; m = opening.exec(text)) {
3391
+ const start = m.index + m[0].length;
3392
+ const closing = /<\/script\s*>/gi;
3393
+ closing.lastIndex = start;
3394
+ const end = closing.exec(text);
3395
+ if (!end) return null;
3396
+ keep.push([start, end.index]);
3397
+ opening.lastIndex = end.index + end[0].length;
3398
+ }
3399
+ let out = text.replace(/[^\r\n]/g, " ");
3400
+ for (const [start, end] of keep) out = out.slice(0, start) + text.slice(start, end) + out.slice(end);
3401
+ return out;
3402
+ }
3340
3403
  var SKIP_DIRS = /* @__PURE__ */ new Set([
3341
3404
  "node_modules",
3342
3405
  "dist",
@@ -7017,6 +7080,97 @@ function npmPackageOf(spec) {
7017
7080
 
7018
7081
  // src/map/sinks.ts
7019
7082
  import { createHash as createHash4 } from "crypto";
7083
+
7084
+ // src/map/scope.ts
7085
+ var scopeCache = /* @__PURE__ */ new WeakMap();
7086
+ function declarationOf(id, ts) {
7087
+ if (!id || !ts.isIdentifier(id)) return void 0;
7088
+ const name = id.text;
7089
+ for (let cur = id.parent; cur; cur = cur.parent) {
7090
+ const declared = declaredIn(cur, ts);
7091
+ if (declared?.has(name)) return declared.get(name);
7092
+ }
7093
+ return void 0;
7094
+ }
7095
+ function isGlobal(node, name, ts) {
7096
+ if (ts.isIdentifier(node)) return node.text === name && declarationOf(node, ts) === void 0;
7097
+ return ts.isPropertyAccessExpression(node) && node.name.text === name && ts.isIdentifier(node.expression) && node.expression.text === "globalThis" && declarationOf(node.expression, ts) === void 0;
7098
+ }
7099
+ function boundIdentifiers(name, ts) {
7100
+ if (!name) return [];
7101
+ if (ts.isIdentifier(name)) return [name];
7102
+ if (ts.isObjectBindingPattern(name) || ts.isArrayBindingPattern(name)) {
7103
+ return name.elements.flatMap((el) => ts.isBindingElement(el) ? boundIdentifiers(el.name, ts) : []);
7104
+ }
7105
+ return [];
7106
+ }
7107
+ function isFunctionScope(n, ts) {
7108
+ return ts.isFunctionDeclaration(n) || ts.isFunctionExpression(n) || ts.isArrowFunction(n) || ts.isMethodDeclaration(n) || ts.isConstructorDeclaration(n) || ts.isGetAccessorDeclaration(n) || ts.isSetAccessorDeclaration(n) || ts.isSourceFile(n);
7109
+ }
7110
+ function declaredIn(n, ts) {
7111
+ const cached2 = scopeCache.get(n);
7112
+ if (cached2) return cached2;
7113
+ const out = /* @__PURE__ */ new Map();
7114
+ const add = (ids) => {
7115
+ for (const i of ids) if (!out.has(i.text)) out.set(i.text, i);
7116
+ };
7117
+ if (isFunctionScope(n, ts)) {
7118
+ if ((ts.isFunctionExpression(n) || ts.isClassExpression?.(n)) && n.name) add([n.name]);
7119
+ for (const p of n.parameters ?? []) add(boundIdentifiers(p.name, ts));
7120
+ collectVarScoped(ts.isSourceFile(n) ? n : n.body, ts, add);
7121
+ if (ts.isSourceFile(n)) collectLexical(n.statements, ts, add, true);
7122
+ } else if (ts.isBlock(n) || ts.isModuleBlock(n) || ts.isCaseBlock(n)) {
7123
+ const statements = ts.isCaseBlock(n) ? n.clauses.flatMap((c) => [...c.statements]) : n.statements;
7124
+ collectLexical(statements, ts, add, false);
7125
+ } else if (ts.isForStatement(n) || ts.isForInStatement(n) || ts.isForOfStatement(n)) {
7126
+ const init = n.initializer;
7127
+ if (init && ts.isVariableDeclarationList(init) && isLexicalList(init, ts)) {
7128
+ for (const d of init.declarations) add(boundIdentifiers(d.name, ts));
7129
+ }
7130
+ } else if (ts.isCatchClause(n)) {
7131
+ if (n.variableDeclaration) add(boundIdentifiers(n.variableDeclaration.name, ts));
7132
+ } else if (ts.isClassExpression?.(n) && n.name) {
7133
+ add([n.name]);
7134
+ } else {
7135
+ return void 0;
7136
+ }
7137
+ scopeCache.set(n, out);
7138
+ return out;
7139
+ }
7140
+ function isLexicalList(list, ts) {
7141
+ return (list.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0;
7142
+ }
7143
+ function collectLexical(statements, ts, add, topLevel) {
7144
+ for (const s of statements ?? []) {
7145
+ if (ts.isVariableStatement(s) && isLexicalList(s.declarationList, ts)) {
7146
+ for (const d of s.declarationList.declarations) add(boundIdentifiers(d.name, ts));
7147
+ } else if ((ts.isClassDeclaration(s) || ts.isFunctionDeclaration(s) || ts.isEnumDeclaration(s)) && s.name) {
7148
+ add([s.name]);
7149
+ } else if (topLevel && ts.isImportDeclaration(s) && s.importClause) {
7150
+ const clause = s.importClause;
7151
+ if (clause.name) add([clause.name]);
7152
+ const bindings = clause.namedBindings;
7153
+ if (bindings && ts.isNamespaceImport(bindings)) add([bindings.name]);
7154
+ if (bindings && ts.isNamedImports(bindings)) add(bindings.elements.map((e) => e.name));
7155
+ } else if (topLevel && ts.isImportEqualsDeclaration(s)) {
7156
+ add([s.name]);
7157
+ }
7158
+ }
7159
+ }
7160
+ function collectVarScoped(node, ts, add) {
7161
+ const visit = (n) => {
7162
+ if (!n) return;
7163
+ if (ts.isVariableDeclarationList(n) && !isLexicalList(n, ts)) {
7164
+ for (const d of n.declarations) add(boundIdentifiers(d.name, ts));
7165
+ }
7166
+ if (n !== node && isFunctionScope(n, ts)) return;
7167
+ if (ts.isClassDeclaration(n) || ts.isClassExpression?.(n)) return;
7168
+ ts.forEachChild(n, visit);
7169
+ };
7170
+ visit(node);
7171
+ }
7172
+
7173
+ // src/map/sinks.ts
7020
7174
  var DB_OPS = /* @__PURE__ */ new Set(["insert", "update", "delete", "select", "upsert", "rpc"]);
7021
7175
  var PRISMA_OPS = /* @__PURE__ */ new Set(["create", "createMany", "update", "updateMany", "delete", "deleteMany", "upsert", "findFirst", "findUnique", "findMany", "count", "aggregate"]);
7022
7176
  var FS_CALLS = /^(readFile|writeFile|readFileSync|writeFileSync|appendFile|createReadStream|createWriteStream|unlink|rm|rmSync|mkdir|readdir|stat|open)$/;
@@ -7059,29 +7213,39 @@ function recognizedSinkKinds(pkg) {
7059
7213
  return kinds;
7060
7214
  }
7061
7215
  function collectLocalSinks(sf, ts, bindings, ctx) {
7062
- const map = /* @__PURE__ */ new Map();
7063
- const visit = (node) => {
7064
- if (ts.isFunctionDeclaration(node) && node.name && node.body) map.set(node.name.text, directSinks(node.body, ts, bindings, ctx));
7216
+ const byDeclaration = /* @__PURE__ */ new Map();
7217
+ const byName = /* @__PURE__ */ new Map();
7218
+ const record = (name, body, topLevel) => {
7219
+ const sinks = directSinks(body, ts, bindings, ctx);
7220
+ byDeclaration.set(name, sinks);
7221
+ if (topLevel || !byName.has(name.text)) byName.set(name.text, sinks);
7222
+ };
7223
+ const visit = (node, depth) => {
7224
+ const topLevel = depth === 0;
7225
+ if (ts.isFunctionDeclaration(node) && node.name && node.body) record(node.name, node.body, topLevel);
7065
7226
  else if (ts.isVariableStatement(node)) {
7066
7227
  for (const decl of node.declarationList.declarations) {
7067
7228
  if (ts.isIdentifier(decl.name) && decl.initializer && isFnLike(decl.initializer, ts)) {
7068
- map.set(decl.name.text, directSinks(decl.initializer.body, ts, bindings, ctx));
7229
+ record(decl.name, decl.initializer.body, topLevel);
7069
7230
  }
7070
7231
  }
7071
7232
  }
7072
- ts.forEachChild(node, visit);
7233
+ ts.forEachChild(node, (child) => visit(child, depth + (ts.isSourceFile(node) ? 0 : 1)));
7073
7234
  };
7074
- visit(sf);
7075
- return map;
7235
+ visit(sf, 0);
7236
+ return { byDeclaration, byName };
7076
7237
  }
7077
7238
  function sinksFrom(arrowOrNode, ts, localSinks, bindings, ctx) {
7078
7239
  if (!arrowOrNode) return [];
7079
7240
  const body = arrowOrNode.isSyntheticBody ? arrowOrNode.body : isFnLike(arrowOrNode, ts) ? arrowOrNode.body : arrowOrNode;
7080
7241
  if (!body) return [];
7081
7242
  const sinks = directSinks(body, ts, bindings, ctx);
7082
- for (const called of localCalls(body, ts)) {
7083
- for (const s of localSinks.get(called) ?? []) sinks.push(s);
7084
- if (ctx) {
7243
+ for (const callee of localCallIdentifiers(body, ts)) {
7244
+ const called = callee.text;
7245
+ const declaration = declarationOf(callee, ts);
7246
+ const helper = declaration === void 0 ? void 0 : localSinks.byDeclaration.get(declaration);
7247
+ for (const s of helper ?? []) sinks.push(s);
7248
+ if (ctx && (declaration === void 0 || isModuleLevel(declaration, ts))) {
7085
7249
  const spec = bindings.resolve(called);
7086
7250
  if (spec && spec.startsWith(".")) {
7087
7251
  for (const s of ctx.graph.importedSinks(ctx.file, spec, bindings.exportNameOf(called) ?? called)) sinks.push(s);
@@ -7098,6 +7262,22 @@ function sinksFrom(arrowOrNode, ts, localSinks, bindings, ctx) {
7098
7262
  }
7099
7263
  return dedupeSinks(sinks, ctx?.owner ?? "");
7100
7264
  }
7265
+ function isContextRequest(node, ts) {
7266
+ if (!ts.isPropertyAccessExpression(node) || node.name.text !== "req" || !ts.isIdentifier(node.expression)) return false;
7267
+ const declaration = declarationOf(node.expression, ts);
7268
+ for (let cur = declaration?.parent; cur; cur = cur.parent) {
7269
+ if (ts.isParameter(cur)) return true;
7270
+ if (!ts.isBindingElement(cur) && !ts.isObjectBindingPattern(cur) && !ts.isArrayBindingPattern(cur)) return false;
7271
+ }
7272
+ return false;
7273
+ }
7274
+ function isModuleLevel(declaration, ts) {
7275
+ for (let cur = declaration?.parent; cur; cur = cur.parent) {
7276
+ if (ts.isSourceFile(cur)) return true;
7277
+ if (ts.isFunctionLike(cur) || ts.isBlock(cur) || ts.isClassLike(cur)) return false;
7278
+ }
7279
+ return false;
7280
+ }
7101
7281
  function namespaceMemberCalls(node, ts) {
7102
7282
  const out = [];
7103
7283
  const visit = (n) => {
@@ -7158,7 +7338,7 @@ function directSinks(node, ts, bindings, ctx) {
7158
7338
  const prismaLikely = b.pkg === "@prisma/client" || !b.pkg && (bindings.imports.has("@prisma/client") || /prisma/i.test(b.root ?? ""));
7159
7339
  if (prismaLikely) push({ kind: "db", ...b.pkg ? { provider: "prisma" } : {}, package: "@prisma/client", table: callee.expression.name.text, op: method, attribution: b.pkg ? "import" : "inferred", ...spanOf(n) });
7160
7340
  }
7161
- if (method === "query" || method === "execute") {
7341
+ if ((method === "query" || method === "execute") && !isContextRequest(callee.expression, ts)) {
7162
7342
  const pkg = b.pkg ?? infer("db");
7163
7343
  const attribution = attributionOf(b, pkg);
7164
7344
  push({ kind: "db", ...dbApi(pkg, attribution), package: pkg, op: method, attribution, ...spanOf(n) });
@@ -7298,114 +7478,6 @@ function dedupeSinks(sinks, owner) {
7298
7478
  return out;
7299
7479
  }
7300
7480
 
7301
- // src/map/module-graph.ts
7302
- import { readFileSync as readFileSync14, realpathSync as realpathSync5, statSync as statSync7 } from "fs";
7303
- import { dirname as dirname8, join as join16, relative as relative5, resolve as resolvePath } from "path";
7304
- var RESOLVE_EXTS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
7305
- function createModuleGraph(ts, opts) {
7306
- const cache = /* @__PURE__ */ new Map();
7307
- const load = (file) => {
7308
- if (cache.has(file)) return cache.get(file) ?? null;
7309
- let entry = null;
7310
- try {
7311
- const text = readFileSync14(file, "utf8");
7312
- const sf = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, guessScriptKind(ts, file));
7313
- const bindings = buildModuleBindings(sf, ts);
7314
- entry = { fnSinks: collectLocalSinks(sf, ts, bindings), calleesOf: collectCallees(sf, ts), bindings };
7315
- } catch {
7316
- entry = null;
7317
- }
7318
- cache.set(file, entry);
7319
- return entry;
7320
- };
7321
- const resolveInProject = (fromFile, specifier) => {
7322
- const target = resolveRelativeModule(fromFile, specifier);
7323
- if (!target) return void 0;
7324
- if (!opts.followOutside) {
7325
- let real = target;
7326
- try {
7327
- real = realpathSync5(target);
7328
- } catch {
7329
- }
7330
- if (!isInside(real, opts.boundary)) return void 0;
7331
- }
7332
- return target;
7333
- };
7334
- return {
7335
- resolveLocal: resolveInProject,
7336
- importedSinks(fromFile, specifier, exportName) {
7337
- const target = resolveInProject(fromFile, specifier);
7338
- if (!target) return [];
7339
- const mod = load(target);
7340
- if (!mod) return [];
7341
- const collected = [...mod.fnSinks.get(exportName) ?? []];
7342
- for (const callee of mod.calleesOf.get(exportName) ?? []) {
7343
- for (const s of mod.fnSinks.get(callee) ?? []) collected.push(s);
7344
- }
7345
- const rel = relative5(opts.cwd, target);
7346
- return collected.map((s) => ({ ...s, file: rel }));
7347
- },
7348
- // A different question about the same module: not "what sinks are in there" but "what does this
7349
- // export TRACE TO". The common AI-built layout puts the client in a lib file —
7350
- // `export const db = createClient(...)` in `lib/db.ts`, imported everywhere — so the receiver of
7351
- // `db.from('orders').insert(...)` resolves to a relative specifier and nothing else. Refusing it (as
7352
- // an unattributable receiver) is right for app code but wrong here: one hop away it is a real
7353
- // dependency, and that chain is import-to-import, fully static — evidence, not inference.
7354
- importedBinding(fromFile, specifier, exportName) {
7355
- const target = resolveInProject(fromFile, specifier);
7356
- if (!target) return void 0;
7357
- const mod = load(target);
7358
- if (!mod) return void 0;
7359
- const pkg = npmPackageOf(mod.bindings.resolve(exportName));
7360
- if (pkg === void 0) return void 0;
7361
- return {
7362
- package: pkg,
7363
- origin: mod.bindings.originOf(exportName) ?? "direct",
7364
- name: mod.bindings.exportNameOf(exportName) ?? exportName
7365
- };
7366
- }
7367
- };
7368
- }
7369
- function resolveRelativeModule(fromFile, spec) {
7370
- if (!spec.startsWith(".")) return void 0;
7371
- const dir = dirname8(fromFile);
7372
- const base = resolvePath(dir, spec);
7373
- const candidates = [];
7374
- const jsLike = /\.(js|jsx|mjs|cjs)$/.exec(base);
7375
- if (jsLike) {
7376
- const stem = base.slice(0, -jsLike[0].length);
7377
- for (const e of RESOLVE_EXTS) candidates.push(stem + e);
7378
- }
7379
- candidates.push(base);
7380
- for (const e of RESOLVE_EXTS) candidates.push(base + e);
7381
- for (const e of RESOLVE_EXTS) candidates.push(join16(base, "index" + e));
7382
- for (const c of candidates) {
7383
- try {
7384
- if (statSync7(c).isFile()) return c;
7385
- } catch {
7386
- }
7387
- }
7388
- return void 0;
7389
- }
7390
- function collectCallees(sf, ts) {
7391
- const map = /* @__PURE__ */ new Map();
7392
- const visit = (node) => {
7393
- let name;
7394
- let body;
7395
- if (ts.isFunctionDeclaration(node) && node.name && node.body) {
7396
- name = node.name.text;
7397
- body = node.body;
7398
- } else if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && isFnLike(node.initializer, ts)) {
7399
- name = node.name.text;
7400
- body = node.initializer.body;
7401
- }
7402
- if (name && body) map.set(name, localCalls(body, ts));
7403
- ts.forEachChild(node, visit);
7404
- };
7405
- visit(sf);
7406
- return map;
7407
- }
7408
-
7409
7481
  // src/map/coordinates.ts
7410
7482
  function runtimeCoordinate(source, path17) {
7411
7483
  if (/\[\d*\]/.test(path17)) {
@@ -7477,7 +7549,9 @@ function inputsFromValidator(validatorCall, ts, bindings) {
7477
7549
  }
7478
7550
  function inputsFromHandler(params, body, ts, bindings, opts = {}) {
7479
7551
  const schemaSource = opts.validatorSource ?? "json-body";
7480
- const schemaFields = zodObjectFields(body, ts, bindings);
7552
+ const inline = zodObjectFields(body, ts, bindings);
7553
+ const referenced = inline.length > 0 ? { fields: [], unresolved: false } : referencedSchemaFields(params, body, ts, bindings);
7554
+ const schemaFields = inline.length > 0 ? inline : referenced.fields;
7481
7555
  const reads = requestMemberAccesses(params, body, ts, opts);
7482
7556
  const byId = /* @__PURE__ */ new Map();
7483
7557
  const put = (name, source, extra = {}) => {
@@ -7490,7 +7564,58 @@ function inputsFromHandler(params, body, ts, bindings, opts = {}) {
7490
7564
  put(name, source ?? schemaSource, shape);
7491
7565
  }
7492
7566
  for (const { name, sources } of reads) for (const source of sources) put(name, source);
7493
- return [...byId.values()];
7567
+ return { inputs: [...byId.values()], schemaUnresolved: referenced.unresolved };
7568
+ }
7569
+ var SCHEMA_METHODS = /* @__PURE__ */ new Set(["parse", "safeParse", "parseAsync", "safeParseAsync"]);
7570
+ function referencedSchemaFields(params, body, ts, bindings) {
7571
+ if (!body) return { fields: [], unresolved: false };
7572
+ const paramDecls = /* @__PURE__ */ new Set();
7573
+ for (const p of params ?? []) {
7574
+ if (!p?.name) continue;
7575
+ if (ts.isIdentifier(p.name)) paramDecls.add(p.name);
7576
+ else if (ts.isObjectBindingPattern(p.name)) {
7577
+ for (const el of p.name.elements) if (ts.isIdentifier(el.name)) paramDecls.add(el.name);
7578
+ }
7579
+ }
7580
+ const fromParams = (e) => {
7581
+ const root = rootIdentifierNode(e, ts);
7582
+ const declaration = root ? declarationOf(root, ts) : void 0;
7583
+ if (declaration === void 0) return false;
7584
+ if (paramDecls.has(declaration)) return true;
7585
+ const owner = declaration.parent;
7586
+ if (owner && ts.isVariableDeclaration(owner) && owner.name === declaration && owner.initializer) {
7587
+ const inner = rootIdentifierNode(owner.initializer, ts);
7588
+ const innerDeclaration = inner ? declarationOf(inner, ts) : void 0;
7589
+ return innerDeclaration !== void 0 && paramDecls.has(innerDeclaration);
7590
+ }
7591
+ return false;
7592
+ };
7593
+ let fields = [];
7594
+ let unresolved = false;
7595
+ const visit = (n) => {
7596
+ if (fields.length > 0) return;
7597
+ if (ts.isCallExpression(n) && ts.isPropertyAccessExpression(n.expression) && SCHEMA_METHODS.has(n.expression.name.text) && ts.isIdentifier(n.expression.expression) && n.arguments.some((a) => fromParams(a))) {
7598
+ const declaration = declarationOf(n.expression.expression, ts);
7599
+ const owner = declaration?.parent;
7600
+ if (owner && ts.isVariableDeclaration(owner) && owner.name === declaration && owner.initializer) {
7601
+ const literal = findValidatorObject(owner.initializer, ts, bindings);
7602
+ if (literal) fields = fieldsOfObject(literal, ts, bindings, "");
7603
+ else unresolved = true;
7604
+ } else if (declaration !== void 0 && isImported(declaration, ts)) {
7605
+ unresolved = true;
7606
+ }
7607
+ }
7608
+ ts.forEachChild(n, visit);
7609
+ };
7610
+ visit(body);
7611
+ return { fields, unresolved: fields.length === 0 && unresolved };
7612
+ }
7613
+ function isImported(declaration, ts) {
7614
+ for (let cur = declaration?.parent; cur; cur = cur.parent) {
7615
+ if (ts.isImportDeclaration(cur)) return true;
7616
+ if (ts.isSourceFile(cur) || ts.isStatement(cur)) return false;
7617
+ }
7618
+ return false;
7494
7619
  }
7495
7620
  function findValidatorObject(node, ts, bindings) {
7496
7621
  let found = null;
@@ -7569,16 +7694,29 @@ function requestMemberAccesses(params, body, ts, opts = {}) {
7569
7694
  out.set(name, list);
7570
7695
  };
7571
7696
  const p0 = params?.[0];
7572
- const reqName = p0 && ts.isIdentifier(p0.name) ? p0.name.text : void 0;
7697
+ const reqDecl = p0 && ts.isIdentifier(p0.name) ? p0.name : void 0;
7698
+ const requestDecls = new Set(reqDecl ? [reqDecl] : []);
7699
+ const isRequest = (e) => ts.isIdentifier(e) && requestDecls.has(declarationOf(e, ts));
7700
+ const isRequestObject = (e) => isRequest(e) || ts.isPropertyAccessExpression(e) && e.name.text === "req" && isRequest(e.expression);
7701
+ const urlDecls = /* @__PURE__ */ new Set();
7702
+ const searchParamDecls = /* @__PURE__ */ new Set();
7573
7703
  const sourceNames = /* @__PURE__ */ new Map();
7574
7704
  const payloadNames = /* @__PURE__ */ new Set();
7575
- if (opts.payloadParam && p0 && ts.isIdentifier(p0.name)) payloadNames.add(p0.name.text);
7576
- if (p0 && !reqName && ts.isObjectBindingPattern(p0.name)) {
7705
+ const sourceOf = (e) => ts.isIdentifier(e) ? sourceNames.get(declarationOf(e, ts)) : void 0;
7706
+ if (opts.payloadParam && p0 && ts.isIdentifier(p0.name)) payloadNames.add(p0.name);
7707
+ if (p0 && !reqDecl && ts.isObjectBindingPattern(p0.name)) {
7577
7708
  for (const el of p0.name.elements) {
7578
7709
  const key = bindingKey(el, ts);
7579
- if (key && REQ_SOURCES.includes(key) && ts.isIdentifier(el.name)) {
7580
- sourceNames.set(el.name.text, namespaceSource(key));
7581
- }
7710
+ if (!key || !ts.isIdentifier(el.name)) continue;
7711
+ if (REQ_SOURCES.includes(key)) sourceNames.set(el.name, namespaceSource(key));
7712
+ else if (key === "request") requestDecls.add(el.name);
7713
+ else if (key === "url") urlDecls.add(el.name);
7714
+ }
7715
+ }
7716
+ for (const p of (params ?? []).slice(1)) {
7717
+ if (!p?.name || !ts.isObjectBindingPattern(p.name)) continue;
7718
+ for (const el of p.name.elements) {
7719
+ if (bindingKey(el, ts) === "params" && ts.isIdentifier(el.name)) sourceNames.set(el.name, "route-param");
7582
7720
  }
7583
7721
  }
7584
7722
  const unwrap = (e) => {
@@ -7586,14 +7724,36 @@ function requestMemberAccesses(params, body, ts, opts = {}) {
7586
7724
  while (cur && (ts.isAwaitExpression(cur) || ts.isAsExpression(cur) || ts.isParenthesizedExpression(cur) || ts.isNonNullExpression(cur))) cur = cur.expression;
7587
7725
  return cur;
7588
7726
  };
7589
- const isPayloadExpr = (e) => ts.isIdentifier(e) && payloadNames.has(e.text);
7590
- const isReqSourceExpr = (e) => isPayloadExpr(e) || ts.isPropertyAccessExpression(e) && ts.isIdentifier(e.expression) && e.expression.text === reqName && REQ_SOURCES.includes(e.name.text) || ts.isIdentifier(e) && sourceNames.has(e.text);
7727
+ const isPayloadExpr = (e) => ts.isIdentifier(e) && payloadNames.has(declarationOf(e, ts));
7728
+ const isReqSourceExpr = (e) => isPayloadExpr(e) || ts.isPropertyAccessExpression(e) && isRequest(e.expression) && REQ_SOURCES.includes(e.name.text) || sourceOf(e) !== void 0;
7591
7729
  const isBodyReadCall = (e) => {
7592
7730
  const inner = unwrap(e);
7593
- return Boolean(inner && ts.isCallExpression(inner) && ts.isPropertyAccessExpression(inner.expression) && ["json", "formData"].includes(inner.expression.name.text) && ts.isIdentifier(inner.expression.expression) && inner.expression.expression.text === reqName);
7731
+ return Boolean(inner && ts.isCallExpression(inner) && ts.isPropertyAccessExpression(inner.expression) && ["json", "formData"].includes(inner.expression.name.text) && isRequestObject(inner.expression.expression));
7732
+ };
7733
+ const isRequestUrl = (e) => {
7734
+ const cur = unwrap(e);
7735
+ if (!cur) return false;
7736
+ if (ts.isIdentifier(cur)) return urlDecls.has(declarationOf(cur, ts));
7737
+ if (ts.isPropertyAccessExpression(cur) && cur.name.text === "nextUrl") return isRequest(cur.expression);
7738
+ if (ts.isNewExpression(cur) && isGlobal(cur.expression, "URL", ts)) {
7739
+ const [href] = cur.arguments ?? [];
7740
+ return Boolean(href && ts.isPropertyAccessExpression(href) && href.name.text === "url" && isRequestObject(href.expression));
7741
+ }
7742
+ return false;
7743
+ };
7744
+ const isSearchParams = (e) => {
7745
+ const cur = unwrap(e);
7746
+ if (!cur) return false;
7747
+ if (ts.isIdentifier(cur)) return searchParamDecls.has(declarationOf(cur, ts));
7748
+ return ts.isPropertyAccessExpression(cur) && cur.name.text === "searchParams" && isRequestUrl(cur.expression);
7749
+ };
7750
+ const literalArgument = (call) => {
7751
+ const [arg] = call.arguments ?? [];
7752
+ return call.arguments?.length === 1 && arg && ts.isStringLiteral(arg) ? arg.text : void 0;
7594
7753
  };
7595
7754
  const isCallee = (n) => Boolean(n.parent && (ts.isCallExpression(n.parent) || ts.isNewExpression(n.parent)) && n.parent.expression === n);
7596
7755
  const ACCESSOR_SOURCES = /* @__PURE__ */ new Set(["header", "cookie", "form-body"]);
7756
+ const HONO_ACCESSORS = { query: "query", param: "route-param", header: "header" };
7597
7757
  const visit = (n) => {
7598
7758
  if (ts.isPropertyAccessExpression(n) && isReqSourceExpr(n.expression) && !isCallee(n)) {
7599
7759
  record(n.name.text, sourceOfExpr(n.expression));
@@ -7604,13 +7764,34 @@ function requestMemberAccesses(params, body, ts, opts = {}) {
7604
7764
  if (ts.isCallExpression(n) && ts.isPropertyAccessExpression(n.expression) && n.expression.name.text === "get" && isReqSourceExpr(n.expression.expression) && n.arguments.length === 1 && n.arguments[0] && ts.isStringLiteral(n.arguments[0]) && ACCESSOR_SOURCES.has(sourceOfExpr(n.expression.expression))) {
7605
7765
  record(n.arguments[0].text, sourceOfExpr(n.expression.expression));
7606
7766
  }
7767
+ if (ts.isCallExpression(n) && ts.isPropertyAccessExpression(n.expression) && n.expression.name.text === "get" && isSearchParams(n.expression.expression)) {
7768
+ const name = literalArgument(n);
7769
+ if (name !== void 0) record(name, "query");
7770
+ }
7771
+ if (ts.isCallExpression(n) && ts.isPropertyAccessExpression(n.expression) && ts.isPropertyAccessExpression(n.expression.expression) && n.expression.expression.name.text === "req" && isRequest(n.expression.expression.expression)) {
7772
+ const source = HONO_ACCESSORS[n.expression.name.text];
7773
+ const name = literalArgument(n);
7774
+ if (source && name !== void 0) record(name, source);
7775
+ }
7607
7776
  if (ts.isVariableDeclaration(n) && n.initializer) {
7608
7777
  const init = unwrap(n.initializer);
7609
- if (ts.isIdentifier(n.name) && isBodyReadCall(n.initializer)) sourceNames.set(n.name.text, bodyReadSource(n.initializer));
7610
- if (ts.isObjectBindingPattern(n.name) && ts.isIdentifier(init) && reqName && init.text === reqName) {
7778
+ if (ts.isIdentifier(n.name)) {
7779
+ if (isRequest(init)) requestDecls.add(n.name);
7780
+ else if (ts.isPropertyAccessExpression(init) && isRequest(init.expression) && REQ_SOURCES.includes(init.name.text)) {
7781
+ sourceNames.set(n.name, namespaceSource(init.name.text));
7782
+ } else if (isRequestUrl(init)) urlDecls.add(n.name);
7783
+ else if (isSearchParams(init)) searchParamDecls.add(n.name);
7784
+ }
7785
+ if (ts.isObjectBindingPattern(n.name) && isRequestUrl(init)) {
7786
+ for (const el of n.name.elements) {
7787
+ if (bindingKey(el, ts) === "searchParams" && ts.isIdentifier(el.name)) searchParamDecls.add(el.name);
7788
+ }
7789
+ }
7790
+ if (ts.isIdentifier(n.name) && isBodyReadCall(n.initializer)) sourceNames.set(n.name, bodyReadSource(n.initializer));
7791
+ if (ts.isObjectBindingPattern(n.name) && isRequest(init)) {
7611
7792
  for (const el of n.name.elements) {
7612
7793
  const key = bindingKey(el, ts);
7613
- if (key && REQ_SOURCES.includes(key) && ts.isIdentifier(el.name)) sourceNames.set(el.name.text, namespaceSource(key));
7794
+ if (key && REQ_SOURCES.includes(key) && ts.isIdentifier(el.name)) sourceNames.set(el.name, namespaceSource(key));
7614
7795
  }
7615
7796
  }
7616
7797
  if (ts.isObjectBindingPattern(n.name) && (isReqSourceExpr(init) || isBodyReadCall(n.initializer))) {
@@ -7632,7 +7813,7 @@ function requestMemberAccesses(params, body, ts, opts = {}) {
7632
7813
  if (REQ_SOURCES.includes(e.name.text)) return named;
7633
7814
  }
7634
7815
  if (ts.isIdentifier(e)) {
7635
- const recorded = sourceNames.get(e.text);
7816
+ const recorded = sourceOf(e);
7636
7817
  if (recorded) return recorded;
7637
7818
  }
7638
7819
  return "body";
@@ -7653,6 +7834,74 @@ function requestMemberAccesses(params, body, ts, opts = {}) {
7653
7834
 
7654
7835
  // src/map/flows.ts
7655
7836
  var MAX_DEPENDENCY_INPUT_FLOWS_PER_ENDPOINT = 100;
7837
+ var MAX_UNPROVEN_FLOWS_PER_ENDPOINT = 200;
7838
+ function boundUnprovenFlows(flows, limit) {
7839
+ let unproven = 0;
7840
+ let truncated = false;
7841
+ const kept = flows.filter((f) => {
7842
+ if (isProvenFlow(f.confidence)) return true;
7843
+ if (unproven >= limit) {
7844
+ truncated = true;
7845
+ return false;
7846
+ }
7847
+ unproven++;
7848
+ return true;
7849
+ });
7850
+ return { flows: kept, truncated };
7851
+ }
7852
+ var Roots = class {
7853
+ constructor(ts) {
7854
+ this.ts = ts;
7855
+ }
7856
+ ts;
7857
+ byDeclaration = /* @__PURE__ */ new Map();
7858
+ add(declaration, root) {
7859
+ if (!this.byDeclaration.has(declaration)) this.byDeclaration.set(declaration, root);
7860
+ }
7861
+ /** The root an identifier occurrence refers to, when its binding is tainted. */
7862
+ get(id) {
7863
+ const declaration = declarationOf(id, this.ts);
7864
+ return declaration === void 0 ? void 0 : this.byDeclaration.get(declaration);
7865
+ }
7866
+ };
7867
+ function reassignedDeclarations(body, ts) {
7868
+ const out = /* @__PURE__ */ new Set();
7869
+ const target = (n) => {
7870
+ if (!n) return;
7871
+ if (ts.isParenthesizedExpression(n)) return target(n.expression);
7872
+ if (ts.isIdentifier(n)) {
7873
+ const declaration = declarationOf(n, ts);
7874
+ if (declaration !== void 0) out.add(declaration);
7875
+ return;
7876
+ }
7877
+ if (ts.isObjectLiteralExpression(n)) {
7878
+ for (const p of n.properties) {
7879
+ if (ts.isShorthandPropertyAssignment(p)) target(p.name);
7880
+ else if (ts.isPropertyAssignment(p)) target(p.initializer);
7881
+ else if (ts.isSpreadAssignment(p)) target(p.expression);
7882
+ }
7883
+ return;
7884
+ }
7885
+ if (ts.isArrayLiteralExpression(n)) {
7886
+ for (const e of n.elements) target(ts.isSpreadElement(e) ? e.expression : e);
7887
+ return;
7888
+ }
7889
+ if (ts.isBinaryExpression(n) && n.operatorToken.kind === ts.SyntaxKind.EqualsToken) target(n.left);
7890
+ };
7891
+ const visit = (n) => {
7892
+ if (!n) return;
7893
+ if (ts.isBinaryExpression(n) && n.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && n.operatorToken.kind <= ts.SyntaxKind.LastAssignment) {
7894
+ target(n.left);
7895
+ }
7896
+ if ((ts.isPrefixUnaryExpression(n) || ts.isPostfixUnaryExpression(n)) && (n.operator === ts.SyntaxKind.PlusPlusToken || n.operator === ts.SyntaxKind.MinusMinusToken)) {
7897
+ target(n.operand);
7898
+ }
7899
+ if ((ts.isForOfStatement(n) || ts.isForInStatement(n)) && !ts.isVariableDeclarationList(n.initializer)) target(n.initializer);
7900
+ ts.forEachChild(n, visit);
7901
+ };
7902
+ visit(body);
7903
+ return out;
7904
+ }
7656
7905
  var ACCESSOR_NAMESPACES = /* @__PURE__ */ new Set(["headers", "cookies"]);
7657
7906
  function spaceOfKey(key) {
7658
7907
  if (key === "query") return "get";
@@ -7665,9 +7914,11 @@ function spaceOfKey(key) {
7665
7914
  return void 0;
7666
7915
  }
7667
7916
  function linkedFlows(body, params, inputs, sinks, ts, invocations = []) {
7668
- const { flows, limitations, dependencyInputFlows, dependencyInputFlowsTruncated } = linkFlows(body, params, inputs, sinks, ts, invocations);
7917
+ const { flows: linked, limitations, dependencyInputFlows, dependencyInputFlowsTruncated } = linkFlows(body, params, inputs, sinks, ts, invocations);
7918
+ const { flows, truncated } = boundUnprovenFlows(linked, MAX_UNPROVEN_FLOWS_PER_ENDPOINT);
7669
7919
  return {
7670
7920
  flows,
7921
+ ...truncated ? { flowsTruncated: true } : {},
7671
7922
  ...limitations.length > 0 ? { limitations } : {},
7672
7923
  ...dependencyInputFlows.length > 0 ? { dependencyInputFlows } : {},
7673
7924
  ...dependencyInputFlowsTruncated ? { dependencyInputFlowsTruncated: true } : {}
@@ -7678,19 +7929,29 @@ function linkFlows(bodyNode, params, inputs, sinks, ts, invocations) {
7678
7929
  return { flows: [], limitations: [], dependencyInputFlows: [], dependencyInputFlowsTruncated: false };
7679
7930
  }
7680
7931
  const CONTAINER_KEYS = /* @__PURE__ */ new Set([...REQ_SOURCES, "data", "input", "payload"]);
7681
- const rootPath = /* @__PURE__ */ new Map();
7682
- const addRoot = (name, path17, space, accessor = false, request = false) => {
7683
- if (!rootPath.has(name)) rootPath.set(name, { path: path17, space, ...accessor ? { accessor } : {}, ...request ? { request } : {} });
7932
+ const rootPath = new Roots(ts);
7933
+ const reassigned = reassignedDeclarations(bodyNode, ts);
7934
+ const addRoot = (declaration, path17, space, accessor = false, request = false, inherited = false, url = false) => {
7935
+ const changed = inherited || reassigned.has(declaration);
7936
+ rootPath.add(declaration, { path: path17, space, ...accessor ? { accessor } : {}, ...request ? { request } : {}, ...changed ? { reassigned: true } : {}, ...url ? { url } : {} });
7684
7937
  };
7685
7938
  for (const [index, p] of (params ?? []).entries()) {
7686
7939
  if (!p?.name) continue;
7687
- if (ts.isIdentifier(p.name)) addRoot(p.name.text, "", void 0, false, index === 0);
7940
+ if (ts.isIdentifier(p.name)) addRoot(p.name, "", void 0, false, index === 0);
7688
7941
  else if (ts.isObjectBindingPattern(p.name)) {
7689
7942
  for (const el of p.name.elements) {
7690
7943
  if (!ts.isBindingElement(el) || !ts.isIdentifier(el.name)) continue;
7691
7944
  const key = bindingKey(el, ts);
7945
+ if (index === 0 && key === "request") {
7946
+ addRoot(el.name, "", void 0, false, true);
7947
+ continue;
7948
+ }
7949
+ if (index === 0 && key === "url") {
7950
+ addRoot(el.name, "", void 0, false, false, false, true);
7951
+ continue;
7952
+ }
7692
7953
  const container = key !== void 0 && CONTAINER_KEYS.has(key);
7693
- addRoot(el.name.text, container ? "" : key ?? el.name.text, container ? spaceOfKey(key) : void 0, container && ACCESSOR_NAMESPACES.has(key));
7954
+ addRoot(el.name, container ? "" : key ?? el.name.text, container ? spaceOfKey(key) : void 0, container && ACCESSOR_NAMESPACES.has(key));
7694
7955
  }
7695
7956
  }
7696
7957
  }
@@ -7698,12 +7959,17 @@ function linkFlows(bodyNode, params, inputs, sinks, ts, invocations) {
7698
7959
  let cur = init;
7699
7960
  while (cur && (ts.isAwaitExpression(cur) || ts.isParenthesizedExpression(cur) || ts.isAsExpression(cur) || ts.isNonNullExpression(cur))) cur = cur.expression;
7700
7961
  if (!cur) return void 0;
7962
+ const params2 = searchParamsOrigin(cur, ts, rootPath);
7963
+ if (params2) return { path: "", space: "get", accessor: true, ...carried(params2) };
7964
+ const url = requestUrlOrigin(cur, ts, rootPath);
7965
+ if (url) return { path: "", url: true, ...carried(url) };
7701
7966
  if (ts.isCallExpression(cur) && ts.isPropertyAccessExpression(cur.expression)) {
7702
7967
  const m = cur.expression.name.text;
7703
7968
  if (["json", "formData", "text"].includes(m)) {
7704
- const root = rootIdentifier(cur.expression.expression, ts);
7705
- if (!root || !rootPath.has(root)) return void 0;
7706
- return m === "formData" ? { path: "", space: "post", accessor: true } : { path: "", space: "post" };
7969
+ const root = rootIdentifierNode(cur.expression.expression, ts);
7970
+ const owner = root ? rootPath.get(root) : void 0;
7971
+ if (!owner) return void 0;
7972
+ return m === "formData" ? { path: "", space: "post", accessor: true, ...carried(owner) } : { path: "", space: "post", ...carried(owner) };
7707
7973
  }
7708
7974
  if (["parse", "safeParse", "validate", "cast"].includes(m)) {
7709
7975
  for (const a of cur.arguments) {
@@ -7719,15 +7985,19 @@ function linkFlows(bodyNode, params, inputs, sinks, ts, invocations) {
7719
7985
  if (ts.isVariableDeclaration(n) && n.initializer) {
7720
7986
  const base = requestReadPath(n.initializer);
7721
7987
  if (base !== void 0) {
7722
- if (ts.isIdentifier(n.name)) addRoot(n.name.text, base.path, base.space, base.accessor === true);
7988
+ if (ts.isIdentifier(n.name)) addRoot(n.name, base.path, base.space, base.accessor === true, base.request === true, base.reassigned === true, base.url === true);
7723
7989
  else if (ts.isObjectBindingPattern(n.name)) {
7724
7990
  for (const el of n.name.elements) {
7725
7991
  if (!ts.isBindingElement(el) || !ts.isIdentifier(el.name)) continue;
7726
7992
  const key = bindingKey(el, ts);
7993
+ if (base.url === true) {
7994
+ if (key === "searchParams") addRoot(el.name, "", "get", true, false, base.reassigned === true);
7995
+ continue;
7996
+ }
7727
7997
  const namespace = base.request === true && key !== void 0 && REQ_SOURCES.includes(key);
7728
7998
  const container = base.path === "" && key !== void 0 && CONTAINER_KEYS.has(key) && (namespace || !REQ_SOURCES.includes(key));
7729
7999
  const space = base.space ?? (namespace ? spaceOfKey(key) : void 0);
7730
- addRoot(el.name.text, container ? "" : join22(base.path, key ?? el.name.text), space, namespace && ACCESSOR_NAMESPACES.has(key));
8000
+ addRoot(el.name, container ? "" : join22(base.path, key ?? el.name.text), space, namespace && ACCESSOR_NAMESPACES.has(key), false, base.reassigned === true);
7731
8001
  }
7732
8002
  }
7733
8003
  }
@@ -7762,7 +8032,7 @@ function linkFlows(bodyNode, params, inputs, sinks, ts, invocations) {
7762
8032
  const whole = pathFromTainted(args[i], ts, rootPath) ?? accessorRead(args[i], ts, rootPath);
7763
8033
  for (const read2 of taintedReadPaths(args[i], ts, rootPath)) {
7764
8034
  const key = `${read2.space ?? "*"}:${read2.path}`;
7765
- const exact = whole !== void 0 && whole.path === read2.path && whole.space === read2.space;
8035
+ const exact = whole !== void 0 && whole.path === read2.path && whole.space === read2.space && whole.reassigned !== true;
7766
8036
  const entry = reads.get(key) ?? { read: read2, roles: /* @__PURE__ */ new Set(), exact: false };
7767
8037
  entry.roles.add(role);
7768
8038
  entry.exact = entry.exact || exact;
@@ -7852,7 +8122,7 @@ function linkFlows(bodyNode, params, inputs, sinks, ts, invocations) {
7852
8122
  kind: invocation.kind,
7853
8123
  resolution: invocation.resolution,
7854
8124
  argumentIndex,
7855
- argumentUse: whole?.path === read2.path && whole.space === read2.space ? "direct" : "within-expression",
8125
+ argumentUse: whole?.path === read2.path && whole.space === read2.space && whole.reassigned !== true ? "direct" : "within-expression",
7856
8126
  line: site.line,
7857
8127
  start: site.start,
7858
8128
  end: site.end
@@ -7940,9 +8210,9 @@ function taintedReadPaths(node, ts, rootPath, includeDeferredBodies = true) {
7940
8210
  return;
7941
8211
  }
7942
8212
  }
7943
- if (ts.isIdentifier(n) && rootPath.has(n.text) && isValueRead(n, ts)) {
7944
- const r = rootPath.get(n.text);
7945
- if (r.path) add({ path: normalizePath(r.path), space: r.space });
8213
+ const bound = ts.isIdentifier(n) && isValueRead(n, ts) ? rootPath.get(n) : void 0;
8214
+ if (bound) {
8215
+ if (bound.path) add({ path: normalizePath(bound.path), space: bound.space });
7946
8216
  }
7947
8217
  ts.forEachChild(n, visit);
7948
8218
  };
@@ -7968,15 +8238,15 @@ function sinkArgumentLimitations(node, ts, rootPath) {
7968
8238
  const visit = (n) => {
7969
8239
  if (!n) return;
7970
8240
  if (ts.isElementAccessExpression(n)) {
7971
- const root = rootIdentifier(n.expression, ts);
8241
+ const root = rootIdentifierNode(n.expression, ts);
7972
8242
  const arg = n.argumentExpression;
7973
- if (root && rootPath.has(root) && arg && !ts.isStringLiteralLike(arg) && !ts.isNumericLiteral(arg)) {
8243
+ if (root && rootPath.get(root) && arg && !ts.isStringLiteralLike(arg) && !ts.isNumericLiteral(arg)) {
7974
8244
  add("dynamic-key", text(n), n);
7975
8245
  }
7976
8246
  }
7977
8247
  if ((ts.isSpreadAssignment?.(n) || ts.isSpreadElement(n)) && n.expression) {
7978
- const root = rootIdentifier(n.expression, ts);
7979
- if (root && rootPath.has(root)) add("spread-into-sink", text(n.parent ?? n), n);
8248
+ const root = rootIdentifierNode(n.expression, ts);
8249
+ if (root && rootPath.get(root)) add("spread-into-sink", text(n.parent ?? n), n);
7980
8250
  }
7981
8251
  ts.forEachChild(n, visit);
7982
8252
  };
@@ -7988,16 +8258,66 @@ function accessorRead(node, ts, rootPath) {
7988
8258
  while (cur && (ts.isAwaitExpression(cur) || ts.isParenthesizedExpression(cur) || ts.isNonNullExpression(cur))) cur = cur.expression;
7989
8259
  if (!cur || !ts.isCallExpression(cur) || !ts.isPropertyAccessExpression(cur.expression)) return void 0;
7990
8260
  const [name] = cur.arguments;
7991
- if (cur.expression.name.text !== "get" || cur.arguments.length !== 1 || !name || !ts.isStringLiteralLike(name)) return void 0;
7992
- if (!isAccessor(cur.expression.expression, ts, rootPath)) return void 0;
7993
- return pathFromTainted(cur.expression.expression, ts, rootPath, [name.text]);
8261
+ if (cur.arguments.length !== 1 || !name || !ts.isStringLiteralLike(name)) return void 0;
8262
+ const method = cur.expression.name.text;
8263
+ const receiver = cur.expression.expression;
8264
+ const hono = HONO_ACCESSOR_SPACES[method];
8265
+ if (hono && ts.isPropertyAccessExpression(receiver) && receiver.name.text === "req" && ts.isIdentifier(receiver.expression)) {
8266
+ const context = rootPath.get(receiver.expression);
8267
+ if (context?.request === true) return { path: normalizePath(name.text), space: hono, ...carried(context) };
8268
+ }
8269
+ if (method !== "get") return void 0;
8270
+ const params = ts.isIdentifier(receiver) ? void 0 : searchParamsOrigin(receiver, ts, rootPath);
8271
+ if (params) return { path: normalizePath(name.text), space: "get", ...carried(params) };
8272
+ if (!isAccessor(receiver, ts, rootPath)) return void 0;
8273
+ return pathFromTainted(receiver, ts, rootPath, [name.text]);
8274
+ }
8275
+ var HONO_ACCESSOR_SPACES = { query: "get", param: "route-param", header: "server" };
8276
+ function carried(origin) {
8277
+ return origin.reassigned === true ? { reassigned: true } : {};
8278
+ }
8279
+ function requestUrlOrigin(node, ts, rootPath) {
8280
+ let cur = node;
8281
+ while (cur && (ts.isParenthesizedExpression(cur) || ts.isNonNullExpression(cur) || ts.isAwaitExpression(cur) || ts.isAsExpression(cur))) cur = cur.expression;
8282
+ if (!cur) return void 0;
8283
+ if (ts.isIdentifier(cur)) {
8284
+ const root = rootPath.get(cur);
8285
+ return root?.url === true ? root : void 0;
8286
+ }
8287
+ const request = (e) => {
8288
+ let inner = e;
8289
+ while (inner && (ts.isParenthesizedExpression(inner) || ts.isNonNullExpression(inner) || ts.isAsExpression(inner))) inner = inner.expression;
8290
+ if (!inner || !ts.isIdentifier(inner)) return void 0;
8291
+ const root = rootPath.get(inner);
8292
+ return root?.request === true ? root : void 0;
8293
+ };
8294
+ if (ts.isPropertyAccessExpression(cur) && cur.name.text === "nextUrl") return request(cur.expression);
8295
+ if (ts.isNewExpression(cur) && isGlobal(cur.expression, "URL", ts)) {
8296
+ const [href] = cur.arguments ?? [];
8297
+ if (!href || !ts.isPropertyAccessExpression(href) || href.name.text !== "url") return void 0;
8298
+ const owner = href.expression;
8299
+ if (ts.isPropertyAccessExpression(owner) && owner.name.text === "req") return request(owner.expression) ?? request(owner);
8300
+ return request(owner);
8301
+ }
8302
+ return void 0;
8303
+ }
8304
+ function searchParamsOrigin(node, ts, rootPath) {
8305
+ let cur = node;
8306
+ while (cur && (ts.isParenthesizedExpression(cur) || ts.isNonNullExpression(cur) || ts.isAsExpression(cur))) cur = cur.expression;
8307
+ if (!cur) return void 0;
8308
+ if (ts.isIdentifier(cur)) {
8309
+ const root = rootPath.get(cur);
8310
+ return root?.accessor === true && root.space === "get" ? root : void 0;
8311
+ }
8312
+ if (!ts.isPropertyAccessExpression(cur) || cur.name.text !== "searchParams") return void 0;
8313
+ return requestUrlOrigin(cur.expression, ts, rootPath);
7994
8314
  }
7995
8315
  function isAccessor(node, ts, rootPath) {
7996
8316
  let cur = node;
7997
8317
  while (cur && (ts.isParenthesizedExpression(cur) || ts.isNonNullExpression(cur))) cur = cur.expression;
7998
- if (ts.isIdentifier(cur)) return rootPath.get(cur.text)?.accessor === true;
8318
+ if (ts.isIdentifier(cur)) return rootPath.get(cur)?.accessor === true;
7999
8319
  if (ts.isPropertyAccessExpression(cur) && ACCESSOR_NAMESPACES.has(cur.name.text) && ts.isIdentifier(cur.expression)) {
8000
- return rootPath.get(cur.expression.text)?.request === true;
8320
+ return rootPath.get(cur.expression)?.request === true;
8001
8321
  }
8002
8322
  return false;
8003
8323
  }
@@ -8023,16 +8343,128 @@ function pathFromTainted(node, ts, rootPath, trailing = []) {
8023
8343
  break;
8024
8344
  }
8025
8345
  if (!cur || !ts.isIdentifier(cur)) return void 0;
8026
- const base = rootPath.get(cur.text);
8027
- if (base === void 0) return void 0;
8346
+ const base = rootPath.get(cur);
8347
+ if (base === void 0 || base.url === true) return void 0;
8028
8348
  segs.push(...trailing);
8029
8349
  let space = base.space;
8030
- if (base.request === true && base.path === "" && segs.length > 1 && REQ_SOURCES.includes(segs[0])) {
8031
- space = spaceOfKey(segs[0]) ?? space;
8350
+ let namespace;
8351
+ if (base.request === true && base.path === "" && segs.length > 0 && REQ_SOURCES.includes(segs[0])) {
8352
+ namespace = segs[0];
8353
+ space = spaceOfKey(namespace) ?? space;
8032
8354
  segs.shift();
8033
8355
  }
8034
- const request = base.request === true && segs.length === 0 ? { request: true } : {};
8035
- return { path: normalizePath([base.path, ...segs].filter(Boolean).join(".")), space, ...request };
8356
+ const request = base.request === true && namespace === void 0 && segs.length === 0 ? { request: true } : {};
8357
+ const accessor = namespace !== void 0 && segs.length === 0 && ACCESSOR_NAMESPACES.has(namespace) ? { accessor: true } : {};
8358
+ const reassigned = base.reassigned === true ? { reassigned: true } : {};
8359
+ return { path: normalizePath([base.path, ...segs].filter(Boolean).join(".")), space, ...request, ...accessor, ...reassigned };
8360
+ }
8361
+
8362
+ // src/map/module-graph.ts
8363
+ import { readFileSync as readFileSync14, realpathSync as realpathSync5, statSync as statSync7 } from "fs";
8364
+ import { dirname as dirname8, join as join16, relative as relative5, resolve as resolvePath } from "path";
8365
+ var RESOLVE_EXTS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
8366
+ function createModuleGraph(ts, opts) {
8367
+ const cache = /* @__PURE__ */ new Map();
8368
+ const load = (file) => {
8369
+ if (cache.has(file)) return cache.get(file) ?? null;
8370
+ let entry = null;
8371
+ try {
8372
+ const text = readFileSync14(file, "utf8");
8373
+ const sf = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, guessScriptKind(ts, file));
8374
+ const bindings = buildModuleBindings(sf, ts);
8375
+ entry = { fnSinks: collectLocalSinks(sf, ts, bindings).byName, calleesOf: collectCallees(sf, ts), bindings };
8376
+ } catch {
8377
+ entry = null;
8378
+ }
8379
+ cache.set(file, entry);
8380
+ return entry;
8381
+ };
8382
+ const resolveInProject = (fromFile, specifier) => {
8383
+ const target = resolveRelativeModule(fromFile, specifier);
8384
+ if (!target) return void 0;
8385
+ if (!opts.followOutside) {
8386
+ let real = target;
8387
+ try {
8388
+ real = realpathSync5(target);
8389
+ } catch {
8390
+ }
8391
+ if (!isInside(real, opts.boundary)) return void 0;
8392
+ }
8393
+ return target;
8394
+ };
8395
+ return {
8396
+ resolveLocal: resolveInProject,
8397
+ importedSinks(fromFile, specifier, exportName) {
8398
+ const target = resolveInProject(fromFile, specifier);
8399
+ if (!target) return [];
8400
+ const mod = load(target);
8401
+ if (!mod) return [];
8402
+ const collected = [...mod.fnSinks.get(exportName) ?? []];
8403
+ for (const callee of mod.calleesOf.get(exportName) ?? []) {
8404
+ for (const s of mod.fnSinks.get(callee) ?? []) collected.push(s);
8405
+ }
8406
+ const rel = relative5(opts.cwd, target);
8407
+ return collected.map((s) => ({ ...s, file: rel }));
8408
+ },
8409
+ // A different question about the same module: not "what sinks are in there" but "what does this
8410
+ // export TRACE TO". The common AI-built layout puts the client in a lib file —
8411
+ // `export const db = createClient(...)` in `lib/db.ts`, imported everywhere — so the receiver of
8412
+ // `db.from('orders').insert(...)` resolves to a relative specifier and nothing else. Refusing it (as
8413
+ // an unattributable receiver) is right for app code but wrong here: one hop away it is a real
8414
+ // dependency, and that chain is import-to-import, fully static — evidence, not inference.
8415
+ importedBinding(fromFile, specifier, exportName) {
8416
+ const target = resolveInProject(fromFile, specifier);
8417
+ if (!target) return void 0;
8418
+ const mod = load(target);
8419
+ if (!mod) return void 0;
8420
+ const pkg = npmPackageOf(mod.bindings.resolve(exportName));
8421
+ if (pkg === void 0) return void 0;
8422
+ return {
8423
+ package: pkg,
8424
+ origin: mod.bindings.originOf(exportName) ?? "direct",
8425
+ name: mod.bindings.exportNameOf(exportName) ?? exportName
8426
+ };
8427
+ }
8428
+ };
8429
+ }
8430
+ function resolveRelativeModule(fromFile, spec) {
8431
+ if (!spec.startsWith(".")) return void 0;
8432
+ const dir = dirname8(fromFile);
8433
+ const base = resolvePath(dir, spec);
8434
+ const candidates = [];
8435
+ const jsLike = /\.(js|jsx|mjs|cjs)$/.exec(base);
8436
+ if (jsLike) {
8437
+ const stem = base.slice(0, -jsLike[0].length);
8438
+ for (const e of RESOLVE_EXTS) candidates.push(stem + e);
8439
+ }
8440
+ candidates.push(base);
8441
+ for (const e of RESOLVE_EXTS) candidates.push(base + e);
8442
+ for (const e of RESOLVE_EXTS) candidates.push(join16(base, "index" + e));
8443
+ for (const c of candidates) {
8444
+ try {
8445
+ if (statSync7(c).isFile()) return c;
8446
+ } catch {
8447
+ }
8448
+ }
8449
+ return void 0;
8450
+ }
8451
+ function collectCallees(sf, ts) {
8452
+ const map = /* @__PURE__ */ new Map();
8453
+ const visit = (node) => {
8454
+ let name;
8455
+ let body;
8456
+ if (ts.isFunctionDeclaration(node) && node.name && node.body) {
8457
+ name = node.name.text;
8458
+ body = node.body;
8459
+ } else if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && isFnLike(node.initializer, ts)) {
8460
+ name = node.name.text;
8461
+ body = node.initializer.body;
8462
+ }
8463
+ if (name && body) map.set(name, localCalls(body, ts));
8464
+ ts.forEachChild(node, visit);
8465
+ };
8466
+ visit(sf);
8467
+ return map;
8036
8468
  }
8037
8469
 
8038
8470
  // src/map/invocations.ts
@@ -8154,6 +8586,7 @@ var HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELE
8154
8586
  function extractFromFile(sf, ts, localSinks, bindings, ctx) {
8155
8587
  const out = [];
8156
8588
  const isServerActionsFile = fileHasUseServer(sf, ts);
8589
+ const pagesApi = isPagesApiFile(ctx.owner);
8157
8590
  const visit = (node) => {
8158
8591
  if (ts.isVariableStatement(node) && hasExport(node, ts)) {
8159
8592
  for (const decl of node.declarationList.declarations) {
@@ -8195,6 +8628,12 @@ function extractFromFile(sf, ts, localSinks, bindings, ctx) {
8195
8628
  out.push(handlerEntry(node.name.text, "server-action", node.parameters, node.body, ts, localSinks, bindings, ctx, spanOf(node)));
8196
8629
  }
8197
8630
  }
8631
+ if (pagesApi) {
8632
+ const handler = defaultExportedHandler(node, ts);
8633
+ if (handler) {
8634
+ out.push(handlerEntry(handler.name ?? "default", "default-export", handler.fn.parameters, handler.fn.body, ts, localSinks, bindings, ctx, spanOf(handler.fn)));
8635
+ }
8636
+ }
8198
8637
  if (ts.isCallExpression(node)) {
8199
8638
  const c = node.expression;
8200
8639
  const denoServe = ts.isPropertyAccessExpression(c) && c.name.text === "serve" && ts.isIdentifier(c.expression) && c.expression.text === "Deno";
@@ -8213,7 +8652,7 @@ function extractFromFile(sf, ts, localSinks, bindings, ctx) {
8213
8652
  const first = args[0];
8214
8653
  const route = first && ts.isStringLiteralLike(first) ? first.text : routeFromChain(node.expression.expression, ts);
8215
8654
  const handler = args[args.length - 1];
8216
- if (route !== void 0 && handler && isFnLike(handler, ts)) {
8655
+ if (route !== void 0 && isRoutePath(route) && handler && isFnLike(handler, ts)) {
8217
8656
  out.push(handlerEntry(route, "route-registration", handler.parameters, handler.body, ts, localSinks, bindings, ctx, {
8218
8657
  // `use`/`all` register handlers but are not HTTP methods — leave method undefined.
8219
8658
  method: HTTP_METHODS.has(mname.toUpperCase()) ? mname.toUpperCase() : void 0,
@@ -8226,7 +8665,7 @@ function extractFromFile(sf, ts, localSinks, bindings, ctx) {
8226
8665
  const arg = node.arguments[0];
8227
8666
  if (arg && ts.isObjectLiteralExpression(arg)) {
8228
8667
  const reg = routeObject(arg, ts);
8229
- if (reg.url && reg.handler) {
8668
+ if (reg.url && isRoutePath(reg.url) && reg.handler) {
8230
8669
  for (const m of reg.methods.length ? reg.methods : [void 0]) {
8231
8670
  out.push(handlerEntry(reg.url, "route-registration", reg.handler.parameters, reg.handler.body, ts, localSinks, bindings, ctx, { method: m, route: reg.url, ...spanOf(node) }));
8232
8671
  }
@@ -8239,6 +8678,19 @@ function extractFromFile(sf, ts, localSinks, bindings, ctx) {
8239
8678
  visit(sf);
8240
8679
  return out;
8241
8680
  }
8681
+ function defaultExportedHandler(node, ts) {
8682
+ const isDefault = (n) => Boolean(n.modifiers?.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword));
8683
+ if (ts.isFunctionDeclaration(node) && hasExport(node, ts) && isDefault(node) && node.body) return { name: node.name?.text, fn: node };
8684
+ if (!ts.isExportAssignment(node) || node.isExportEquals) return void 0;
8685
+ const target = node.expression;
8686
+ if (isFnLike(target, ts)) return { fn: target };
8687
+ if (!ts.isIdentifier(target)) return void 0;
8688
+ const declaration = declarationOf(target, ts);
8689
+ const owner = declaration?.parent;
8690
+ if (owner && ts.isFunctionDeclaration(owner) && owner.body) return { name: target.text, fn: owner };
8691
+ if (owner && ts.isVariableDeclaration(owner) && owner.initializer && isFnLike(owner.initializer, ts)) return { name: target.text, fn: owner.initializer };
8692
+ return void 0;
8693
+ }
8242
8694
  function fileHasUseServer(sf, ts) {
8243
8695
  const first = sf.statements?.[0];
8244
8696
  return Boolean(first && ts.isExpressionStatement(first) && ts.isStringLiteralLike(first.expression) && first.expression.text === "use server");
@@ -8254,7 +8706,7 @@ function handlerInvocations(body, ts, bindings, ctx) {
8254
8706
  function handlerEntry(name, kindLabel, params, body, ts, localSinks, bindings, ctx, extra = {}) {
8255
8707
  const entryKind = kindLabel === "route-registration" || kindLabel === "server-action" || kindLabel === "edge-function" ? kindLabel : "route-handler";
8256
8708
  const payloadStyle = kindLabel === "server-action";
8257
- const inputs = inputsFromHandler(params, body, ts, bindings, {
8709
+ const { inputs, schemaUnresolved } = inputsFromHandler(params, body, ts, bindings, {
8258
8710
  payloadParam: payloadStyle,
8259
8711
  validatorSource: payloadStyle ? "server-fn-data" : "json-body"
8260
8712
  });
@@ -8269,7 +8721,9 @@ function handlerEntry(name, kindLabel, params, body, ts, localSinks, bindings, c
8269
8721
  end: extra.end,
8270
8722
  inputs,
8271
8723
  sinks,
8272
- ...linkedFlows(body, params, inputs, sinks, ts, handlerInvocations(body, ts, bindings, ctx))
8724
+ ...linkedFlows(body, params, inputs, sinks, ts, handlerInvocations(body, ts, bindings, ctx)),
8725
+ // The handler validates its request with a schema declared in another module: its fields are unknown.
8726
+ ...schemaUnresolved ? { inputsResolved: false } : {}
8273
8727
  };
8274
8728
  }
8275
8729
 
@@ -8528,6 +8982,7 @@ function lineAt(starts, pos) {
8528
8982
 
8529
8983
  // src/map/extract.ts
8530
8984
  var MAX_DEPENDENCY_INPUT_FLOWS_PER_MAP = 500;
8985
+ var MAX_UNPROVEN_FLOWS_PER_MAP = 1e3;
8531
8986
  async function extractInputMap(cwd, ts, options = {}) {
8532
8987
  const notes = [];
8533
8988
  const endpoints = [];
@@ -8543,9 +8998,11 @@ async function extractInputMap(cwd, ts, options = {}) {
8543
8998
  const imports = createImportInventory(readPathAliases(cwd));
8544
8999
  const invocations = createInvocationInventory();
8545
9000
  let dependencyInputFlowCount = 0;
9001
+ let unprovenFlowCount = 0;
8546
9002
  let parsed = 0;
8547
9003
  let preFiltered = 0;
8548
9004
  let importScanFailures = 0;
9005
+ let componentFiles = 0;
8549
9006
  let unresolvableImports = 0;
8550
9007
  let sourceBytes = 0;
8551
9008
  const calls = { total: 0, dependency: 0, local: 0, ambiguous: 0 };
@@ -8557,7 +9014,17 @@ async function extractInputMap(cwd, ts, options = {}) {
8557
9014
  const text = readFileSync16(file, "utf8");
8558
9015
  const relFile = relative6(cwd, file);
8559
9016
  sourceBytes += text.length;
8560
- if (!hasEntrySignal(text)) {
9017
+ if (isComponentFile(file)) {
9018
+ componentFiles++;
9019
+ preFiltered++;
9020
+ const script = componentScript(text, file);
9021
+ const scanned = script === null ? null : scanFileImports(script, ts);
9022
+ if (scanned === null) importScanFailures++;
9023
+ else imports.add(relFile, scanned, false);
9024
+ if (script !== null) unresolvableImports += countUnresolvableImports(script, ts);
9025
+ continue;
9026
+ }
9027
+ if (!hasEntrySignal(text) && !isPagesApiFile(relFile)) {
8561
9028
  preFiltered++;
8562
9029
  const scanned = scanFileImports(text, ts);
8563
9030
  if (scanned === null) importScanFailures++;
@@ -8593,11 +9060,17 @@ async function extractInputMap(cwd, ts, options = {}) {
8593
9060
  ep.dependencyInputFlowsTruncated = true;
8594
9061
  }
8595
9062
  dependencyInputFlowCount += ep.dependencyInputFlows?.length ?? 0;
9063
+ const bounded = boundUnprovenFlows(ep.flows, Math.max(0, MAX_UNPROVEN_FLOWS_PER_MAP - unprovenFlowCount));
9064
+ if (bounded.truncated) {
9065
+ ep.flows = bounded.flows;
9066
+ ep.flowsTruncated = true;
9067
+ }
9068
+ unprovenFlowCount += ep.flows.filter((f) => !isProvenFlow(f.confidence)).length;
8596
9069
  if (ep.route === void 0 && ep.entryKind === "edge-function") {
8597
9070
  const fn = functionNameFromPath(relFile);
8598
9071
  if (fn) ep.route = "/" + fn;
8599
9072
  }
8600
- if (ep.route === void 0 && (ep.entryKind === "route-handler" || ep.entryKind === "server-action")) {
9073
+ if (ep.route === void 0 && ep.entryKind === "route-handler") {
8601
9074
  const derived = routeFromFilePath(relFile);
8602
9075
  if (derived.route) {
8603
9076
  ep.route = derived.route;
@@ -8648,6 +9121,10 @@ async function extractInputMap(cwd, ts, options = {}) {
8648
9121
  if (truncatedDependencyFlows > 0) {
8649
9122
  notes.push(`${truncatedDependencyFlows} endpoint(s) had more dependency-input links than the bounded map carries; those endpoint records are marked dependencyInputFlowsTruncated.`);
8650
9123
  }
9124
+ const truncatedFlows = endpoints.filter((e) => e.flowsTruncated).length;
9125
+ if (truncatedFlows > 0) {
9126
+ notes.push(`${truncatedFlows} endpoint(s) had more unproven flows than the bounded map carries; those endpoint records are marked flowsTruncated. Every proven flow is kept.`);
9127
+ }
8651
9128
  if (unresolved > 0) {
8652
9129
  notes.push(`${unresolved} endpoint(s) declare an input validator that could not be statically parsed \u2014 their inputs are UNKNOWN, not empty (marked inputsResolved: false).`);
8653
9130
  }
@@ -8655,6 +9132,9 @@ async function extractInputMap(cwd, ts, options = {}) {
8655
9132
  if (heuristicOnly > 0) {
8656
9133
  notes.push(`${heuristicOnly} endpoint(s) have inputs and sinks but no proven data link \u2014 their flows say "may reach", not "does reach" (see each flow's confidence).`);
8657
9134
  }
9135
+ if (componentFiles > 0) {
9136
+ notes.push(`${componentFiles} single-file component(s) (.vue, .svelte, .astro) were scanned for imports only. Code in their script blocks and Astro frontmatter can run on the server, but it is not analyzed for endpoints, inputs or sinks.`);
9137
+ }
8658
9138
  if (endpoints.length === 0) notes.push("No recognized server-side entry points found under the analyzed roots.");
8659
9139
  const importList = imports.list();
8660
9140
  const unmodelled = importList.filter((d) => d.recognizedSinkKinds.length === 0).length;