@vyuhlabs/dxkit 2.18.1 → 2.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/dist/analyzers/flow/csv.d.ts +17 -0
- package/dist/analyzers/flow/csv.d.ts.map +1 -0
- package/dist/analyzers/flow/csv.js +64 -0
- package/dist/analyzers/flow/csv.js.map +1 -0
- package/dist/analyzers/flow/extract.d.ts +64 -0
- package/dist/analyzers/flow/extract.d.ts.map +1 -0
- package/dist/analyzers/flow/extract.js +254 -0
- package/dist/analyzers/flow/extract.js.map +1 -0
- package/dist/analyzers/flow/gather.d.ts +24 -0
- package/dist/analyzers/flow/gather.d.ts.map +1 -0
- package/dist/analyzers/flow/gather.js +50 -0
- package/dist/analyzers/flow/gather.js.map +1 -0
- package/dist/analyzers/flow/model.d.ts +63 -0
- package/dist/analyzers/flow/model.d.ts.map +1 -0
- package/dist/analyzers/flow/model.js +78 -0
- package/dist/analyzers/flow/model.js.map +1 -0
- package/dist/analyzers/flow/normalize.d.ts +76 -0
- package/dist/analyzers/flow/normalize.d.ts.map +1 -0
- package/dist/analyzers/flow/normalize.js +133 -0
- package/dist/analyzers/flow/normalize.js.map +1 -0
- package/dist/analyzers/flow/spec-source.d.ts +44 -0
- package/dist/analyzers/flow/spec-source.d.ts.map +1 -0
- package/dist/analyzers/flow/spec-source.js +75 -0
- package/dist/analyzers/flow/spec-source.js.map +1 -0
- package/dist/ast/parse.d.ts +71 -0
- package/dist/ast/parse.d.ts.map +1 -0
- package/dist/ast/parse.js +220 -0
- package/dist/ast/parse.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +21 -0
- package/dist/cli.js.map +1 -1
- package/dist/flow-cli.d.ts +16 -0
- package/dist/flow-cli.d.ts.map +1 -0
- package/dist/flow-cli.js +98 -0
- package/dist/flow-cli.js.map +1 -0
- package/dist/languages/index.d.ts +15 -2
- package/dist/languages/index.d.ts.map +1 -1
- package/dist/languages/index.js +18 -0
- package/dist/languages/index.js.map +1 -1
- package/dist/languages/types.d.ts +106 -0
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/languages/typescript.d.ts.map +1 -1
- package/dist/languages/typescript.js +32 -0
- package/dist/languages/typescript.js.map +1 -1
- package/package.json +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.19.0] - 2026-06-30
|
|
11
|
+
|
|
12
|
+
### Added — application-flow extraction (`flow extract`)
|
|
13
|
+
|
|
14
|
+
`vyuh-dxkit flow extract` statically maps a frontend's HTTP calls to a backend's
|
|
15
|
+
routes and writes the result as CSVs. It is the first slice of the Flow feature
|
|
16
|
+
(UI to API traceability).
|
|
17
|
+
|
|
18
|
+
- **AST-based, not regex.** A new in-process, graphify-independent tree-sitter
|
|
19
|
+
layer (`src/ast/`, web-tree-sitter/wasm) parses source; a per-language
|
|
20
|
+
`httpFlow` descriptor declares which constructs are HTTP clients and routes,
|
|
21
|
+
so no framework literal is hardcoded in the analyzer.
|
|
22
|
+
- **Both sides plus the join.** It extracts outbound client calls
|
|
23
|
+
(fetch/axios/wrappers) and inbound routes (LoopBack/NestJS decorators, Express
|
|
24
|
+
app/router), canonicalizes URLs and route paths to a common shape, and binds
|
|
25
|
+
each call to the route it targets with a confidence score.
|
|
26
|
+
- **OpenAPI when present.** An existing OpenAPI spec is consumed as the
|
|
27
|
+
authoritative served side (`--specs`), unioned with static extraction (spec
|
|
28
|
+
for authority, static for recall, since generated specs are often incomplete).
|
|
29
|
+
- **Usage:** `flow extract [--frontend <dir>] [--backend <dir>] [--specs a.json,b.json] [--out <dir>]`;
|
|
30
|
+
writes `api_calls.csv`, `routes.csv`, `api_route_mapping.csv`. TypeScript and
|
|
31
|
+
JavaScript in this release; more languages follow.
|
|
32
|
+
|
|
10
33
|
## [2.18.1] - 2026-06-23
|
|
11
34
|
|
|
12
35
|
### Fixed — next-step hints now use a resolvable invocation
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSV renderers over a `FlowModel` — the parity output surface (mirrors the
|
|
3
|
+
* reference tool's `api_data` / `controller_data` / `api_controller_mapping`
|
|
4
|
+
* CSVs). Pure: a model in, CSV text out. CSV is one renderer among several
|
|
5
|
+
* (the graph + the gate consume the same model), so nothing analytical lives
|
|
6
|
+
* here — only serialization.
|
|
7
|
+
*/
|
|
8
|
+
import type { FlowModel } from './model';
|
|
9
|
+
/** Outbound HTTP calls (the consumed side). */
|
|
10
|
+
export declare function callsCsv(model: FlowModel): string;
|
|
11
|
+
/** Served routes (the served side), from source extraction and/or a spec. */
|
|
12
|
+
export declare function routesCsv(model: FlowModel): string;
|
|
13
|
+
/** The join: each client call mapped to the route it targets (or not). */
|
|
14
|
+
export declare function mappingCsv(model: FlowModel): string;
|
|
15
|
+
/** The full parity CSV set, keyed by output filename. */
|
|
16
|
+
export declare function flowCsvFiles(model: FlowModel): Record<string, string>;
|
|
17
|
+
//# sourceMappingURL=csv.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csv.d.ts","sourceRoot":"","sources":["../../../src/analyzers/flow/csv.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAczC,+CAA+C;AAC/C,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAKjD;AAED,6EAA6E;AAC7E,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAKlD;AAED,0EAA0E;AAC1E,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CA2BnD;AAED,yDAAyD;AACzD,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMrE"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CSV renderers over a `FlowModel` — the parity output surface (mirrors the
|
|
4
|
+
* reference tool's `api_data` / `controller_data` / `api_controller_mapping`
|
|
5
|
+
* CSVs). Pure: a model in, CSV text out. CSV is one renderer among several
|
|
6
|
+
* (the graph + the gate consume the same model), so nothing analytical lives
|
|
7
|
+
* here — only serialization.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.callsCsv = callsCsv;
|
|
11
|
+
exports.routesCsv = routesCsv;
|
|
12
|
+
exports.mappingCsv = mappingCsv;
|
|
13
|
+
exports.flowCsvFiles = flowCsvFiles;
|
|
14
|
+
/** Quote every field (CSV-safe: doubles embedded quotes), join a row. */
|
|
15
|
+
function row(fields) {
|
|
16
|
+
return fields.map((f) => `"${String(f ?? '').replace(/"/g, '""')}"`).join(',');
|
|
17
|
+
}
|
|
18
|
+
function toCsv(header, rows) {
|
|
19
|
+
return [row(header), ...rows.map(row)].join('\n') + '\n';
|
|
20
|
+
}
|
|
21
|
+
/** Outbound HTTP calls (the consumed side). */
|
|
22
|
+
function callsCsv(model) {
|
|
23
|
+
return toCsv(['method', 'path', 'raw_url', 'receiver', 'file', 'line'], model.calls.map((c) => [c.method, c.path, c.rawUrl, c.receiver, c.file, c.line]));
|
|
24
|
+
}
|
|
25
|
+
/** Served routes (the served side), from source extraction and/or a spec. */
|
|
26
|
+
function routesCsv(model) {
|
|
27
|
+
return toCsv(['method', 'path', 'via', 'handler', 'file', 'line'], // arch-shape-ok: CSV column name, not a role label
|
|
28
|
+
model.routes.map((r) => [r.method, r.path, r.via, r.handler, r.file, r.line]));
|
|
29
|
+
}
|
|
30
|
+
/** The join: each client call mapped to the route it targets (or not). */
|
|
31
|
+
function mappingCsv(model) {
|
|
32
|
+
return toCsv([
|
|
33
|
+
'call_method',
|
|
34
|
+
'call_path',
|
|
35
|
+
'call_file',
|
|
36
|
+
'call_line',
|
|
37
|
+
'reason',
|
|
38
|
+
'confidence',
|
|
39
|
+
'route_path',
|
|
40
|
+
'route_handler',
|
|
41
|
+
'route_via',
|
|
42
|
+
'route_file',
|
|
43
|
+
], model.bindings.map((b) => [
|
|
44
|
+
b.call.method,
|
|
45
|
+
b.call.path,
|
|
46
|
+
b.call.file,
|
|
47
|
+
b.call.line,
|
|
48
|
+
b.reason,
|
|
49
|
+
b.confidence,
|
|
50
|
+
b.route?.path ?? '',
|
|
51
|
+
b.route?.handler ?? '',
|
|
52
|
+
b.route?.via ?? '',
|
|
53
|
+
b.route?.file ?? '',
|
|
54
|
+
]));
|
|
55
|
+
}
|
|
56
|
+
/** The full parity CSV set, keyed by output filename. */
|
|
57
|
+
function flowCsvFiles(model) {
|
|
58
|
+
return {
|
|
59
|
+
'api_calls.csv': callsCsv(model),
|
|
60
|
+
'routes.csv': routesCsv(model),
|
|
61
|
+
'api_route_mapping.csv': mappingCsv(model),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=csv.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csv.js","sourceRoot":"","sources":["../../../src/analyzers/flow/csv.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;AAiBH,4BAKC;AAGD,8BAKC;AAGD,gCA2BC;AAGD,oCAMC;AAjED,yEAAyE;AACzE,SAAS,GAAG,CAAC,MAA6C;IACxD,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACjF,CAAC;AAED,SAAS,KAAK,CACZ,MAAyB,EACzB,IAA0D;IAE1D,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAC3D,CAAC;AAED,+CAA+C;AAC/C,SAAgB,QAAQ,CAAC,KAAgB;IACvC,OAAO,KAAK,CACV,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,EACzD,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CACjF,CAAC;AACJ,CAAC;AAED,6EAA6E;AAC7E,SAAgB,SAAS,CAAC,KAAgB;IACxC,OAAO,KAAK,CACV,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,mDAAmD;IACzG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAC9E,CAAC;AACJ,CAAC;AAED,0EAA0E;AAC1E,SAAgB,UAAU,CAAC,KAAgB;IACzC,OAAO,KAAK,CACV;QACE,aAAa;QACb,WAAW;QACX,WAAW;QACX,WAAW;QACX,QAAQ;QACR,YAAY;QACZ,YAAY;QACZ,eAAe;QACf,WAAW;QACX,YAAY;KACb,EACD,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QACxB,CAAC,CAAC,IAAI,CAAC,MAAM;QACb,CAAC,CAAC,IAAI,CAAC,IAAI;QACX,CAAC,CAAC,IAAI,CAAC,IAAI;QACX,CAAC,CAAC,IAAI,CAAC,IAAI;QACX,CAAC,CAAC,MAAM;QACR,CAAC,CAAC,UAAU;QACZ,CAAC,CAAC,KAAK,EAAE,IAAI,IAAI,EAAE;QACnB,CAAC,CAAC,KAAK,EAAE,OAAO,IAAI,EAAE;QACtB,CAAC,CAAC,KAAK,EAAE,GAAG,IAAI,EAAE;QAClB,CAAC,CAAC,KAAK,EAAE,IAAI,IAAI,EAAE;KACpB,CAAC,CACH,CAAC;AACJ,CAAC;AAED,yDAAyD;AACzD,SAAgB,YAAY,CAAC,KAAgB;IAC3C,OAAO;QACL,eAAe,EAAE,QAAQ,CAAC,KAAK,CAAC;QAChC,YAAY,EAAE,SAAS,CAAC,KAAK,CAAC;QAC9B,uBAAuB,EAAE,UAAU,CAAC,KAAK,CAAC;KAC3C,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flow extraction — the AST pass that turns source into the two sides of an
|
|
3
|
+
* HTTP integration: outbound client calls (CONSUMED) and inbound route
|
|
4
|
+
* declarations (SERVED).
|
|
5
|
+
*
|
|
6
|
+
* It is the cross-cutting consumer of three seams and hardcodes none of them:
|
|
7
|
+
* - the canonical AST layer (`src/ast/`) for parsing — graphify-independent,
|
|
8
|
+
* and registered as its own concern, never folded into graphify's pass;
|
|
9
|
+
* - each pack's `httpFlow` descriptor (Rule 6) for WHICH constructs are HTTP
|
|
10
|
+
* — no `fetch`/`axios`/`@get` literal lives here;
|
|
11
|
+
* - the shared normalizer for canonical paths + verbs.
|
|
12
|
+
*
|
|
13
|
+
* Every source file is scanned for BOTH surfaces: a backend that serves routes
|
|
14
|
+
* AND calls other services contributes to both lists (the consumer/provider
|
|
15
|
+
* model — a participant is whatever its served/consumed sets make it). Role
|
|
16
|
+
* assignment happens above this module, never inside it.
|
|
17
|
+
*
|
|
18
|
+
* Precision guard (validated): a member call with no declared receiver allowlist
|
|
19
|
+
* (`axios.get`, `requests.get`, `agent.X.del`) only counts as a client call when
|
|
20
|
+
* its first argument is a path-like literal — that filter keeps non-HTTP `.get`/
|
|
21
|
+
* `.delete` (lodash, Maps) out while still catching app-specific wrappers.
|
|
22
|
+
*/
|
|
23
|
+
import type { HttpFlowSupport } from '../../languages/types';
|
|
24
|
+
import { type Node } from '../../ast/parse';
|
|
25
|
+
import { type HttpMethod, type NormalizeConfig } from './normalize';
|
|
26
|
+
/** An outbound HTTP call found in source (the consumed side). */
|
|
27
|
+
export interface ClientCall {
|
|
28
|
+
readonly method: HttpMethod;
|
|
29
|
+
readonly rawUrl: string;
|
|
30
|
+
/** Normalized path, or `null` when the URL is dynamic/external (unresolved). */
|
|
31
|
+
readonly path: string | null;
|
|
32
|
+
readonly receiver: string;
|
|
33
|
+
readonly file: string;
|
|
34
|
+
readonly line: number;
|
|
35
|
+
}
|
|
36
|
+
/** An inbound route a service serves (the served side). `via` records how it
|
|
37
|
+
* was discovered: a source decorator, an Express-style route call, or an
|
|
38
|
+
* ingested OpenAPI/spec document (preferred when available). */
|
|
39
|
+
export interface RouteEndpoint {
|
|
40
|
+
readonly method: HttpMethod;
|
|
41
|
+
readonly path: string;
|
|
42
|
+
readonly via: 'decorator' | 'router-call' | 'spec';
|
|
43
|
+
readonly handler: string | null;
|
|
44
|
+
readonly file: string;
|
|
45
|
+
readonly line: number;
|
|
46
|
+
}
|
|
47
|
+
/** Both HTTP surfaces extracted from one file. */
|
|
48
|
+
export interface FileFlow {
|
|
49
|
+
readonly calls: ClientCall[];
|
|
50
|
+
readonly routes: RouteEndpoint[];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Extract both HTTP surfaces from one already-parsed tree using a pack's
|
|
54
|
+
* httpFlow descriptor. Pure over its inputs.
|
|
55
|
+
*/
|
|
56
|
+
export declare function extractFromTree(root: Node, hf: HttpFlowSupport, file: string, config?: NormalizeConfig): FileFlow;
|
|
57
|
+
/**
|
|
58
|
+
* Extract both HTTP surfaces from one file on disk. Returns empty surfaces when
|
|
59
|
+
* the language has no httpFlow descriptor, and `null` when the file can't be
|
|
60
|
+
* parsed (engine/grammar unavailable or unreadable) — callers treat `null` as
|
|
61
|
+
* "skip this file", never an error.
|
|
62
|
+
*/
|
|
63
|
+
export declare function extractFileFlow(filePath: string, config?: NormalizeConfig): Promise<FileFlow | null>;
|
|
64
|
+
//# sourceMappingURL=extract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../../../src/analyzers/flow/extract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAc,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAAmB,KAAK,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAkC,KAAK,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAEpG,iEAAiE;AACjE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;iEAEiE;AACjE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,WAAW,GAAG,aAAa,GAAG,MAAM,CAAC;IACnD,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,kDAAkD;AAClD,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,KAAK,EAAE,UAAU,EAAE,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,aAAa,EAAE,CAAC;CAClC;AAiGD;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,IAAI,EACV,EAAE,EAAE,eAAe,EACnB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,eAAe,GACvB,QAAQ,CA0GV;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,eAAe,GACvB,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAM1B"}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Flow extraction — the AST pass that turns source into the two sides of an
|
|
4
|
+
* HTTP integration: outbound client calls (CONSUMED) and inbound route
|
|
5
|
+
* declarations (SERVED).
|
|
6
|
+
*
|
|
7
|
+
* It is the cross-cutting consumer of three seams and hardcodes none of them:
|
|
8
|
+
* - the canonical AST layer (`src/ast/`) for parsing — graphify-independent,
|
|
9
|
+
* and registered as its own concern, never folded into graphify's pass;
|
|
10
|
+
* - each pack's `httpFlow` descriptor (Rule 6) for WHICH constructs are HTTP
|
|
11
|
+
* — no `fetch`/`axios`/`@get` literal lives here;
|
|
12
|
+
* - the shared normalizer for canonical paths + verbs.
|
|
13
|
+
*
|
|
14
|
+
* Every source file is scanned for BOTH surfaces: a backend that serves routes
|
|
15
|
+
* AND calls other services contributes to both lists (the consumer/provider
|
|
16
|
+
* model — a participant is whatever its served/consumed sets make it). Role
|
|
17
|
+
* assignment happens above this module, never inside it.
|
|
18
|
+
*
|
|
19
|
+
* Precision guard (validated): a member call with no declared receiver allowlist
|
|
20
|
+
* (`axios.get`, `requests.get`, `agent.X.del`) only counts as a client call when
|
|
21
|
+
* its first argument is a path-like literal — that filter keeps non-HTTP `.get`/
|
|
22
|
+
* `.delete` (lodash, Maps) out while still catching app-specific wrappers.
|
|
23
|
+
*/
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.extractFromTree = extractFromTree;
|
|
26
|
+
exports.extractFileFlow = extractFileFlow;
|
|
27
|
+
const languages_1 = require("../../languages");
|
|
28
|
+
const parse_1 = require("../../ast/parse");
|
|
29
|
+
const normalize_1 = require("./normalize");
|
|
30
|
+
const HTTP_VERB_METHODS = new Set([
|
|
31
|
+
'get',
|
|
32
|
+
'post',
|
|
33
|
+
'put',
|
|
34
|
+
'patch',
|
|
35
|
+
'delete',
|
|
36
|
+
'del',
|
|
37
|
+
'head',
|
|
38
|
+
'options',
|
|
39
|
+
]);
|
|
40
|
+
function line(node) {
|
|
41
|
+
return node.startPosition.row + 1;
|
|
42
|
+
}
|
|
43
|
+
/** Text of a string/template-string node, else null (a dynamic argument). */
|
|
44
|
+
function literalText(node) {
|
|
45
|
+
if (!node)
|
|
46
|
+
return null;
|
|
47
|
+
return node.type === 'string' || node.type === 'template_string' ? node.text : null;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Does a raw literal look like a URL/path at the source level — i.e. could this
|
|
51
|
+
* `.get(...)`/`.post(...)` plausibly be an HTTP call rather than a Map/cache/
|
|
52
|
+
* lodash accessor? Used only for member calls with NO declared receiver
|
|
53
|
+
* allowlist, where it is the precision guard: a leading `/`, a leading template
|
|
54
|
+
* (`${host}/…`), an explicit scheme, or any embedded `/` qualifies; a bare token
|
|
55
|
+
* like `'config-key'` does not. (Route decorators are exempt — a framework route
|
|
56
|
+
* string is known to be a path even without a leading slash.)
|
|
57
|
+
*/
|
|
58
|
+
function looksLikeUrlLiteral(raw) {
|
|
59
|
+
if (raw == null)
|
|
60
|
+
return false;
|
|
61
|
+
let s = raw.trim();
|
|
62
|
+
if (s.length >= 2 && /^['"`]/.test(s) && s.endsWith(s[0]))
|
|
63
|
+
s = s.slice(1, -1);
|
|
64
|
+
return s.startsWith('/') || s.startsWith('${') || s.includes('://') || s.includes('/');
|
|
65
|
+
}
|
|
66
|
+
function firstNamedArg(callOrArgs) {
|
|
67
|
+
const args = callOrArgs?.childForFieldName('arguments') ?? null;
|
|
68
|
+
if (!args)
|
|
69
|
+
return null;
|
|
70
|
+
for (const c of args.namedChildren)
|
|
71
|
+
if (c)
|
|
72
|
+
return c;
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
/** Does a member-call receiver match one of the declared bases (e.g. `app`, or
|
|
76
|
+
* `this.app`)? */
|
|
77
|
+
function receiverMatchesBase(receiver, bases) {
|
|
78
|
+
return bases.some((b) => receiver === b || receiver.endsWith(`.${b}`));
|
|
79
|
+
}
|
|
80
|
+
/** Pull `method: 'X'` out of a fetch options object (2nd arg), default GET. */
|
|
81
|
+
function fetchMethod(call, hf) {
|
|
82
|
+
const args = call.childForFieldName('arguments');
|
|
83
|
+
const opts = args?.namedChildren?.[1] ?? null;
|
|
84
|
+
if (opts && opts.type === 'object') {
|
|
85
|
+
let verb = null;
|
|
86
|
+
(0, parse_1.walk)(opts, (n) => {
|
|
87
|
+
if (verb)
|
|
88
|
+
return false;
|
|
89
|
+
if (n.type === 'pair') {
|
|
90
|
+
const key = n.childForFieldName('key');
|
|
91
|
+
const val = n.childForFieldName('value');
|
|
92
|
+
const keyName = key ? key.text.replace(/['"`]/g, '') : '';
|
|
93
|
+
if (keyName === 'method') {
|
|
94
|
+
const raw = literalText(val);
|
|
95
|
+
if (raw)
|
|
96
|
+
verb = (0, normalize_1.normalizeMethod)(raw.replace(/['"`]/g, ''), hf.methodAliases);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
if (verb)
|
|
101
|
+
return verb;
|
|
102
|
+
}
|
|
103
|
+
return 'GET';
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The handler name a route decorator is attached to (best-effort). Decorators
|
|
107
|
+
* are siblings preceding the member in the class body, so the handler is the
|
|
108
|
+
* decorator's next named sibling (skipping any stacked decorators); a nested
|
|
109
|
+
* grammar shape is handled by an ancestor fallback.
|
|
110
|
+
*/
|
|
111
|
+
function decoratedHandlerName(decorator) {
|
|
112
|
+
let sib = decorator.nextNamedSibling;
|
|
113
|
+
while (sib && sib.type === 'decorator')
|
|
114
|
+
sib = sib.nextNamedSibling;
|
|
115
|
+
const sibName = sib?.childForFieldName('name');
|
|
116
|
+
if (sibName)
|
|
117
|
+
return sibName.text;
|
|
118
|
+
let cur = decorator.parent;
|
|
119
|
+
for (let i = 0; cur && i < 3; i++) {
|
|
120
|
+
if (cur.type === 'method_definition' || cur.type === 'function_declaration') {
|
|
121
|
+
const name = cur.childForFieldName('name');
|
|
122
|
+
if (name)
|
|
123
|
+
return name.text;
|
|
124
|
+
}
|
|
125
|
+
cur = cur.parent;
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Extract both HTTP surfaces from one already-parsed tree using a pack's
|
|
131
|
+
* httpFlow descriptor. Pure over its inputs.
|
|
132
|
+
*/
|
|
133
|
+
function extractFromTree(root, hf, file, config) {
|
|
134
|
+
const calls = [];
|
|
135
|
+
const routes = [];
|
|
136
|
+
const clientCallees = new Set(hf.clientCallees ?? []);
|
|
137
|
+
const methodCallMethods = new Set(hf.clientMethodCallees?.methods ?? []);
|
|
138
|
+
const methodCallBases = hf.clientMethodCallees?.bases;
|
|
139
|
+
const routeDecorators = new Set(hf.routeDecorators ?? []);
|
|
140
|
+
const routerMethods = new Set(hf.routeRouterCallees?.methods ?? []);
|
|
141
|
+
const routerBases = hf.routeRouterCallees?.bases ?? [];
|
|
142
|
+
(0, parse_1.walk)(root, (node) => {
|
|
143
|
+
// ── decorator routes: @get('/x') ──
|
|
144
|
+
if (node.type === 'decorator' && routeDecorators.size) {
|
|
145
|
+
const call = node.namedChildren.find((c) => c?.type === 'call_expression') ?? null;
|
|
146
|
+
const callee = call?.childForFieldName('function');
|
|
147
|
+
const name = callee && callee.type === 'identifier' ? callee.text : '';
|
|
148
|
+
if (call && routeDecorators.has(name)) {
|
|
149
|
+
const path = (0, normalize_1.normalizePath)(literalText(firstNamedArg(call)), config);
|
|
150
|
+
const method = (0, normalize_1.normalizeMethod)(name, hf.methodAliases);
|
|
151
|
+
if (path && method) {
|
|
152
|
+
routes.push({
|
|
153
|
+
method,
|
|
154
|
+
path,
|
|
155
|
+
via: 'decorator',
|
|
156
|
+
handler: decoratedHandlerName(node),
|
|
157
|
+
file,
|
|
158
|
+
line: line(node),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return; // a decorator's call is bookkeeping, not a client call
|
|
163
|
+
}
|
|
164
|
+
if (node.type !== 'call_expression')
|
|
165
|
+
return;
|
|
166
|
+
const callee = node.childForFieldName('function');
|
|
167
|
+
if (!callee)
|
|
168
|
+
return;
|
|
169
|
+
// ── bare client call: fetch(url, opts) ──
|
|
170
|
+
if (callee.type === 'identifier' && clientCallees.has(callee.text)) {
|
|
171
|
+
const raw = literalText(firstNamedArg(node));
|
|
172
|
+
if (raw != null) {
|
|
173
|
+
calls.push({
|
|
174
|
+
method: fetchMethod(node, hf),
|
|
175
|
+
rawUrl: raw,
|
|
176
|
+
path: (0, normalize_1.normalizePath)(raw, config),
|
|
177
|
+
receiver: callee.text,
|
|
178
|
+
file,
|
|
179
|
+
line: line(node),
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
// ── member call: <recv>.<verb>(url|path, ...) ──
|
|
185
|
+
if (callee.type === 'member_expression') {
|
|
186
|
+
const prop = callee.childForFieldName('property');
|
|
187
|
+
const obj = callee.childForFieldName('object');
|
|
188
|
+
const verb = prop ? prop.text : '';
|
|
189
|
+
const receiver = obj ? obj.text : '';
|
|
190
|
+
if (!HTTP_VERB_METHODS.has(verb))
|
|
191
|
+
return;
|
|
192
|
+
// router/app route declaration
|
|
193
|
+
if (routerMethods.has(verb) && receiverMatchesBase(receiver, routerBases)) {
|
|
194
|
+
const path = (0, normalize_1.normalizePath)(literalText(firstNamedArg(node)), config);
|
|
195
|
+
const method = (0, normalize_1.normalizeMethod)(verb, hf.methodAliases);
|
|
196
|
+
if (path && method) {
|
|
197
|
+
routes.push({
|
|
198
|
+
method,
|
|
199
|
+
path,
|
|
200
|
+
via: 'router-call',
|
|
201
|
+
handler: receiver,
|
|
202
|
+
file,
|
|
203
|
+
line: line(node),
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
// client call (bases optional). With no allowlist, require a path-like
|
|
209
|
+
// literal first arg — the precision guard against non-HTTP .get/.delete.
|
|
210
|
+
if (methodCallMethods.has(verb)) {
|
|
211
|
+
const baseOk = !methodCallBases ||
|
|
212
|
+
receiverMatchesBase(receiver, methodCallBases) ||
|
|
213
|
+
methodCallBases.includes(receiver);
|
|
214
|
+
if (!baseOk)
|
|
215
|
+
return;
|
|
216
|
+
const raw = literalText(firstNamedArg(node));
|
|
217
|
+
// Unallowlisted receiver → require a URL-looking literal (precision guard).
|
|
218
|
+
if (!methodCallBases && !looksLikeUrlLiteral(raw))
|
|
219
|
+
return;
|
|
220
|
+
const method = (0, normalize_1.normalizeMethod)(verb, hf.methodAliases);
|
|
221
|
+
if (raw != null && method) {
|
|
222
|
+
calls.push({
|
|
223
|
+
method,
|
|
224
|
+
rawUrl: raw,
|
|
225
|
+
path: (0, normalize_1.normalizePath)(raw, config),
|
|
226
|
+
receiver,
|
|
227
|
+
file,
|
|
228
|
+
line: line(node),
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
return { calls, routes };
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Extract both HTTP surfaces from one file on disk. Returns empty surfaces when
|
|
238
|
+
* the language has no httpFlow descriptor, and `null` when the file can't be
|
|
239
|
+
* parsed (engine/grammar unavailable or unreadable) — callers treat `null` as
|
|
240
|
+
* "skip this file", never an error.
|
|
241
|
+
*/
|
|
242
|
+
async function extractFileFlow(filePath, config) {
|
|
243
|
+
const parsed = await (0, parse_1.parseFile)(filePath);
|
|
244
|
+
if (!parsed)
|
|
245
|
+
return null;
|
|
246
|
+
const hf = httpFlowFor(parsed.languageId);
|
|
247
|
+
if (!hf)
|
|
248
|
+
return { calls: [], routes: [] };
|
|
249
|
+
return extractFromTree(parsed.tree.rootNode, hf, filePath, config);
|
|
250
|
+
}
|
|
251
|
+
function httpFlowFor(languageId) {
|
|
252
|
+
return (0, languages_1.getLanguage)(languageId)?.httpFlow;
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=extract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract.js","sourceRoot":"","sources":["../../../src/analyzers/flow/extract.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flow gather — walk a set of roots, extract both HTTP surfaces from every
|
|
3
|
+
* source file, and assemble a `FlowModel`. The served side is the UNION of what
|
|
4
|
+
* source extraction finds and what any configured OpenAPI spec declares (a spec
|
|
5
|
+
* is authoritative but often incomplete, so static extraction adds recall).
|
|
6
|
+
*
|
|
7
|
+
* File discovery goes through the canonical `walkSourceFiles` (Rule 4
|
|
8
|
+
* exclusions + test-file skipping built in); the scanned extensions are exactly
|
|
9
|
+
* those of packs that declare BOTH an httpFlow descriptor and a tree-sitter
|
|
10
|
+
* grammar, so a non-flow language's files are never parsed and adding a flow
|
|
11
|
+
* pack auto-extends the scan (Rule 6).
|
|
12
|
+
*/
|
|
13
|
+
import { type FlowModel } from './model';
|
|
14
|
+
export interface GatherFlowOptions {
|
|
15
|
+
/** Directories to scan for source (frontend, backend, services…). */
|
|
16
|
+
readonly roots: readonly string[];
|
|
17
|
+
/** OpenAPI spec files whose served routes union with the extracted ones. */
|
|
18
|
+
readonly specs?: readonly string[];
|
|
19
|
+
/** Host-helper prefixes to strip during URL normalization (per-app config). */
|
|
20
|
+
readonly stripUrlPrefixes?: readonly string[];
|
|
21
|
+
}
|
|
22
|
+
/** Walk + extract + assemble. Files that don't parse are skipped, never fatal. */
|
|
23
|
+
export declare function gatherFlowModel(opts: GatherFlowOptions): Promise<FlowModel>;
|
|
24
|
+
//# sourceMappingURL=gather.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gather.d.ts","sourceRoot":"","sources":["../../../src/analyzers/flow/gather.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,EAAkB,KAAK,SAAS,EAAE,MAAM,SAAS,CAAC;AAIzD,MAAM,WAAW,iBAAiB;IAChC,qEAAqE;IACrE,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,4EAA4E;IAC5E,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,+EAA+E;IAC/E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC/C;AAaD,kFAAkF;AAClF,wBAAsB,eAAe,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,SAAS,CAAC,CAgBjF"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Flow gather — walk a set of roots, extract both HTTP surfaces from every
|
|
4
|
+
* source file, and assemble a `FlowModel`. The served side is the UNION of what
|
|
5
|
+
* source extraction finds and what any configured OpenAPI spec declares (a spec
|
|
6
|
+
* is authoritative but often incomplete, so static extraction adds recall).
|
|
7
|
+
*
|
|
8
|
+
* File discovery goes through the canonical `walkSourceFiles` (Rule 4
|
|
9
|
+
* exclusions + test-file skipping built in); the scanned extensions are exactly
|
|
10
|
+
* those of packs that declare BOTH an httpFlow descriptor and a tree-sitter
|
|
11
|
+
* grammar, so a non-flow language's files are never parsed and adding a flow
|
|
12
|
+
* pack auto-extends the scan (Rule 6).
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.gatherFlowModel = gatherFlowModel;
|
|
16
|
+
const path_1 = require("path");
|
|
17
|
+
const languages_1 = require("../../languages");
|
|
18
|
+
const walk_source_files_1 = require("../tools/walk-source-files");
|
|
19
|
+
const extract_1 = require("./extract");
|
|
20
|
+
const model_1 = require("./model");
|
|
21
|
+
const spec_source_1 = require("./spec-source");
|
|
22
|
+
/** Extensions of packs that can contribute flow (httpFlow + a grammar). */
|
|
23
|
+
function flowExtensions() {
|
|
24
|
+
const exts = new Set();
|
|
25
|
+
for (const pack of languages_1.LANGUAGES) {
|
|
26
|
+
if (pack.httpFlow && pack.treeSitterGrammars) {
|
|
27
|
+
for (const ext of Object.keys(pack.treeSitterGrammars))
|
|
28
|
+
exts.add(ext);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return [...exts];
|
|
32
|
+
}
|
|
33
|
+
/** Walk + extract + assemble. Files that don't parse are skipped, never fatal. */
|
|
34
|
+
async function gatherFlowModel(opts) {
|
|
35
|
+
const config = { stripUrlPrefixes: opts.stripUrlPrefixes };
|
|
36
|
+
const extensions = flowExtensions();
|
|
37
|
+
const fileFlows = [];
|
|
38
|
+
for (const root of opts.roots) {
|
|
39
|
+
for (const rel of (0, walk_source_files_1.walkSourceFiles)(root, { extensions })) {
|
|
40
|
+
const flow = await (0, extract_1.extractFileFlow)((0, path_1.join)(root, rel), config);
|
|
41
|
+
if (flow)
|
|
42
|
+
fileFlows.push(flow);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Served side = extracted routes UNION spec routes (dedup is implicit: the
|
|
46
|
+
// join indexes routes by (method, path), so a duplicate collapses).
|
|
47
|
+
const specRoutes = (opts.specs ?? []).flatMap((spec) => (0, spec_source_1.loadOpenApiRoutes)(spec));
|
|
48
|
+
return (0, model_1.buildFlowModel)([...fileFlows, { calls: [], routes: specRoutes }]);
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=gather.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gather.js","sourceRoot":"","sources":["../../../src/analyzers/flow/gather.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;AA+BH,0CAgBC;AA7CD,+BAA4B;AAC5B,+CAA4C;AAC5C,kEAA6D;AAC7D,uCAA2D;AAC3D,mCAAyD;AACzD,+CAAkD;AAYlD,2EAA2E;AAC3E,SAAS,cAAc;IACrB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,qBAAS,EAAE,CAAC;QAC7B,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC7C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC;gBAAE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACxE,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;AACnB,CAAC;AAED,kFAAkF;AAC3E,KAAK,UAAU,eAAe,CAAC,IAAuB;IAC3D,MAAM,MAAM,GAAoB,EAAE,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,EAAE,CAAC;IAC5E,MAAM,UAAU,GAAG,cAAc,EAAE,CAAC;IACpC,MAAM,SAAS,GAAe,EAAE,CAAC;IAEjC,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QAC9B,KAAK,MAAM,GAAG,IAAI,IAAA,mCAAe,EAAC,IAAI,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;YACxD,MAAM,IAAI,GAAG,MAAM,IAAA,yBAAe,EAAC,IAAA,WAAI,EAAC,IAAI,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;YAC5D,IAAI,IAAI;gBAAE,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IAED,2EAA2E;IAC3E,oEAAoE;IACpE,MAAM,UAAU,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAA,+BAAiB,EAAC,IAAI,CAAC,CAAC,CAAC;IACjF,OAAO,IAAA,sBAAc,EAAC,CAAC,GAAG,SAAS,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC;AAC3E,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flow model + the join — assemble extracted client calls and route
|
|
3
|
+
* declarations into a `FlowModel`, then bind each call to the route it targets
|
|
4
|
+
* on the normalized `(method, path)` key.
|
|
5
|
+
*
|
|
6
|
+
* The join is where consumed meets served. A binding carries a confidence in
|
|
7
|
+
* [0, 1] + a reason, mirroring the git-aware matcher's contract: an exact key
|
|
8
|
+
* match on a path with real static signal is full confidence; a path that is
|
|
9
|
+
* all placeholder (`/{var}`) carries no signal and is low confidence even when
|
|
10
|
+
* it happens to match a catch-all; a dynamic or unrouted call is unresolved.
|
|
11
|
+
* Confidence is what a gate thresholds on — only high-confidence bindings can
|
|
12
|
+
* block, which is what keeps the false-positive budget intact.
|
|
13
|
+
*
|
|
14
|
+
* Pure over its inputs (the file-extraction step that produces `FileFlow`s does
|
|
15
|
+
* the I/O); this module just structures + joins.
|
|
16
|
+
*/
|
|
17
|
+
import { type ClientCall, type FileFlow, type RouteEndpoint } from './extract';
|
|
18
|
+
import type { NormalizeConfig } from './normalize';
|
|
19
|
+
/**
|
|
20
|
+
* Why a call did or didn't bind to a route:
|
|
21
|
+
* - `exact` — matched a route on a path with real static signal;
|
|
22
|
+
* - `placeholder-only` — matched, but the path is all `{var}` (no signal);
|
|
23
|
+
* - `no-route` — path resolved to an internal route shape, but no
|
|
24
|
+
* served route matches it;
|
|
25
|
+
* - `external` — the URL is an external/absolute address we don't
|
|
26
|
+
* serve (path is null), so it is not an internal binding.
|
|
27
|
+
*/
|
|
28
|
+
export type BindingReason = 'exact' | 'placeholder-only' | 'no-route' | 'external';
|
|
29
|
+
/** One client call resolved (or not) against the served routes. */
|
|
30
|
+
export interface FlowBinding {
|
|
31
|
+
readonly call: ClientCall;
|
|
32
|
+
readonly route: RouteEndpoint | null;
|
|
33
|
+
readonly confidence: number;
|
|
34
|
+
readonly reason: BindingReason;
|
|
35
|
+
}
|
|
36
|
+
/** The assembled flow: both surfaces plus the bindings between them. */
|
|
37
|
+
export interface FlowModel {
|
|
38
|
+
readonly calls: readonly ClientCall[];
|
|
39
|
+
readonly routes: readonly RouteEndpoint[];
|
|
40
|
+
readonly bindings: readonly FlowBinding[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Bind each client call to the route it targets, on the normalized
|
|
44
|
+
* `(method, path)` key. Routes are indexed once; each call resolves in O(1).
|
|
45
|
+
*/
|
|
46
|
+
export declare function joinFlow(calls: readonly ClientCall[], routes: readonly RouteEndpoint[]): FlowBinding[];
|
|
47
|
+
/** Flatten per-file surfaces into one model + its bindings. */
|
|
48
|
+
export declare function buildFlowModel(fileFlows: readonly FileFlow[]): FlowModel;
|
|
49
|
+
/**
|
|
50
|
+
* Extract + assemble a flow model from a set of files. `null` extractions
|
|
51
|
+
* (unparseable / engine unavailable) are skipped, never fatal.
|
|
52
|
+
*/
|
|
53
|
+
export declare function extractFlowModel(filePaths: readonly string[], config?: NormalizeConfig): Promise<FlowModel>;
|
|
54
|
+
/** Summary counts for a model (drives the `flow` preview + acceptance checks). */
|
|
55
|
+
export interface FlowSummary {
|
|
56
|
+
readonly calls: number;
|
|
57
|
+
readonly routes: number;
|
|
58
|
+
readonly resolved: number;
|
|
59
|
+
readonly highConfidence: number;
|
|
60
|
+
readonly unresolved: number;
|
|
61
|
+
}
|
|
62
|
+
export declare function summarize(model: FlowModel): FlowSummary;
|
|
63
|
+
//# sourceMappingURL=model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../../src/analyzers/flow/model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAmB,KAAK,UAAU,EAAE,KAAK,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAChG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD;;;;;;;;GAQG;AACH,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,kBAAkB,GAAG,UAAU,GAAG,UAAU,CAAC;AAEnF,mEAAmE;AACnE,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;CAChC;AAED,wEAAwE;AACxE,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,SAAS,aAAa,EAAE,CAAC;IAC1C,QAAQ,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAC;CAC3C;AAOD;;;GAGG;AACH,wBAAgB,QAAQ,CACtB,KAAK,EAAE,SAAS,UAAU,EAAE,EAC5B,MAAM,EAAE,SAAS,aAAa,EAAE,GAC/B,WAAW,EAAE,CAaf;AAED,+DAA+D;AAC/D,wBAAgB,cAAc,CAAC,SAAS,EAAE,SAAS,QAAQ,EAAE,GAAG,SAAS,CAIxE;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,MAAM,CAAC,EAAE,eAAe,GACvB,OAAO,CAAC,SAAS,CAAC,CAOpB;AAED,kFAAkF;AAClF,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,GAAG,WAAW,CAUvD"}
|