@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.
Files changed (46) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/analyzers/flow/csv.d.ts +17 -0
  3. package/dist/analyzers/flow/csv.d.ts.map +1 -0
  4. package/dist/analyzers/flow/csv.js +64 -0
  5. package/dist/analyzers/flow/csv.js.map +1 -0
  6. package/dist/analyzers/flow/extract.d.ts +64 -0
  7. package/dist/analyzers/flow/extract.d.ts.map +1 -0
  8. package/dist/analyzers/flow/extract.js +254 -0
  9. package/dist/analyzers/flow/extract.js.map +1 -0
  10. package/dist/analyzers/flow/gather.d.ts +24 -0
  11. package/dist/analyzers/flow/gather.d.ts.map +1 -0
  12. package/dist/analyzers/flow/gather.js +50 -0
  13. package/dist/analyzers/flow/gather.js.map +1 -0
  14. package/dist/analyzers/flow/model.d.ts +63 -0
  15. package/dist/analyzers/flow/model.d.ts.map +1 -0
  16. package/dist/analyzers/flow/model.js +78 -0
  17. package/dist/analyzers/flow/model.js.map +1 -0
  18. package/dist/analyzers/flow/normalize.d.ts +76 -0
  19. package/dist/analyzers/flow/normalize.d.ts.map +1 -0
  20. package/dist/analyzers/flow/normalize.js +133 -0
  21. package/dist/analyzers/flow/normalize.js.map +1 -0
  22. package/dist/analyzers/flow/spec-source.d.ts +44 -0
  23. package/dist/analyzers/flow/spec-source.d.ts.map +1 -0
  24. package/dist/analyzers/flow/spec-source.js +75 -0
  25. package/dist/analyzers/flow/spec-source.js.map +1 -0
  26. package/dist/ast/parse.d.ts +71 -0
  27. package/dist/ast/parse.d.ts.map +1 -0
  28. package/dist/ast/parse.js +220 -0
  29. package/dist/ast/parse.js.map +1 -0
  30. package/dist/cli.d.ts.map +1 -1
  31. package/dist/cli.js +21 -0
  32. package/dist/cli.js.map +1 -1
  33. package/dist/flow-cli.d.ts +16 -0
  34. package/dist/flow-cli.d.ts.map +1 -0
  35. package/dist/flow-cli.js +98 -0
  36. package/dist/flow-cli.js.map +1 -0
  37. package/dist/languages/index.d.ts +15 -2
  38. package/dist/languages/index.d.ts.map +1 -1
  39. package/dist/languages/index.js +18 -0
  40. package/dist/languages/index.js.map +1 -1
  41. package/dist/languages/types.d.ts +106 -0
  42. package/dist/languages/types.d.ts.map +1 -1
  43. package/dist/languages/typescript.d.ts.map +1 -1
  44. package/dist/languages/typescript.js +32 -0
  45. package/dist/languages/typescript.js.map +1 -1
  46. package/package.json +4 -2
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ /**
3
+ * Flow model + the join — assemble extracted client calls and route
4
+ * declarations into a `FlowModel`, then bind each call to the route it targets
5
+ * on the normalized `(method, path)` key.
6
+ *
7
+ * The join is where consumed meets served. A binding carries a confidence in
8
+ * [0, 1] + a reason, mirroring the git-aware matcher's contract: an exact key
9
+ * match on a path with real static signal is full confidence; a path that is
10
+ * all placeholder (`/{var}`) carries no signal and is low confidence even when
11
+ * it happens to match a catch-all; a dynamic or unrouted call is unresolved.
12
+ * Confidence is what a gate thresholds on — only high-confidence bindings can
13
+ * block, which is what keeps the false-positive budget intact.
14
+ *
15
+ * Pure over its inputs (the file-extraction step that produces `FileFlow`s does
16
+ * the I/O); this module just structures + joins.
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.joinFlow = joinFlow;
20
+ exports.buildFlowModel = buildFlowModel;
21
+ exports.extractFlowModel = extractFlowModel;
22
+ exports.summarize = summarize;
23
+ const extract_1 = require("./extract");
24
+ /** A path made up entirely of `{var}` segments carries no static signal. */
25
+ function isPlaceholderOnly(path) {
26
+ return /^(\/\{var\})+$/.test(path);
27
+ }
28
+ /**
29
+ * Bind each client call to the route it targets, on the normalized
30
+ * `(method, path)` key. Routes are indexed once; each call resolves in O(1).
31
+ */
32
+ function joinFlow(calls, routes) {
33
+ const routeIndex = new Map();
34
+ for (const r of routes)
35
+ routeIndex.set(`${r.method} ${r.path}`, r);
36
+ return calls.map((call) => {
37
+ if (call.path == null)
38
+ return { call, route: null, confidence: 0, reason: 'external' };
39
+ const route = routeIndex.get(`${call.method} ${call.path}`) ?? null;
40
+ if (!route)
41
+ return { call, route: null, confidence: 0, reason: 'no-route' };
42
+ if (isPlaceholderOnly(call.path)) {
43
+ return { call, route, confidence: 0.3, reason: 'placeholder-only' };
44
+ }
45
+ return { call, route, confidence: 1, reason: 'exact' };
46
+ });
47
+ }
48
+ /** Flatten per-file surfaces into one model + its bindings. */
49
+ function buildFlowModel(fileFlows) {
50
+ const calls = fileFlows.flatMap((f) => f.calls);
51
+ const routes = fileFlows.flatMap((f) => f.routes);
52
+ return { calls, routes, bindings: joinFlow(calls, routes) };
53
+ }
54
+ /**
55
+ * Extract + assemble a flow model from a set of files. `null` extractions
56
+ * (unparseable / engine unavailable) are skipped, never fatal.
57
+ */
58
+ async function extractFlowModel(filePaths, config) {
59
+ const flows = [];
60
+ for (const path of filePaths) {
61
+ const flow = await (0, extract_1.extractFileFlow)(path, config);
62
+ if (flow)
63
+ flows.push(flow);
64
+ }
65
+ return buildFlowModel(flows);
66
+ }
67
+ function summarize(model) {
68
+ const resolved = model.bindings.filter((b) => b.route !== null).length;
69
+ const highConfidence = model.bindings.filter((b) => b.confidence >= 1).length;
70
+ return {
71
+ calls: model.calls.length,
72
+ routes: model.routes.length,
73
+ resolved,
74
+ highConfidence,
75
+ unresolved: model.calls.length - resolved,
76
+ };
77
+ }
78
+ //# sourceMappingURL=model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.js","sourceRoot":"","sources":["../../../src/analyzers/flow/model.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * URL / route-path normalization — the canonical, pure transform that lets a
3
+ * frontend client call and a backend route declaration JOIN even though they
4
+ * are written differently. It is the precision-critical core of the flow
5
+ * feature: this algorithm was validated at 96% precision (vs ~84% for a regex
6
+ * approach) on a real axios → LoopBack stack — the win comes from being
7
+ * structural rather than regex-mangled.
8
+ *
9
+ * The contract: turn a raw URL/route literal into a canonical path string in
10
+ * which path parameters are erased to `{var}` and host/prefix noise is gone,
11
+ * so `axios.get(`${Config.apiBase()}/articles/${slug}`)` (client) and
12
+ * `@get('/articles/{id}')` (server) both reduce to `/articles/{var}` and
13
+ * match. Method normalization (`del` → `DELETE`) lives here too.
14
+ *
15
+ * Two inputs are deliberately NOT baked in as language facts (CLAUDE.md
16
+ * Rule 6 boundary):
17
+ * - **Host helpers** (`${Config.apiBase()}`, `${apiUrl}`) are per-APP, not
18
+ * per-language — they arrive via `NormalizeConfig.stripUrlPrefixes`
19
+ * (sourced from `.dxkit/policy.json:flow.stripUrlPrefixes`).
20
+ * - **Param-form canonicalization** (`:id`, `{id}`, `${x}` → `{var}`) is
21
+ * uniform across frameworks, so it lives here rather than in a per-pack
22
+ * descriptor.
23
+ *
24
+ * Pure: no I/O, deterministic over its inputs. Identity-bearing (a binding's
25
+ * Rule 9 fingerprint is computed from the normalized path), so changes here
26
+ * are a normalization-scheme change — treat with the same care as a
27
+ * fingerprint-scheme bump.
28
+ */
29
+ /** Canonical HTTP verbs the flow feature recognizes. */
30
+ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
31
+ /** Per-app normalization inputs (not language facts — see module header). */
32
+ export interface NormalizeConfig {
33
+ /**
34
+ * Host-helper / base-URL prefixes to strip before matching, so a client
35
+ * call's absolute-ish URL reduces to the bare route path. Each entry is a
36
+ * literal substring removed wherever it appears at the head of the URL —
37
+ * e.g. `['${Config.apiBase()}', '${apiUrl}']`. Sourced from
38
+ * `.dxkit/policy.json:flow.stripUrlPrefixes`; `flow init` auto-suggests the
39
+ * dominant host-helper found across client calls.
40
+ */
41
+ stripUrlPrefixes?: readonly string[];
42
+ }
43
+ /**
44
+ * Canonicalize a raw URL / route literal to a comparable path, or `null` for
45
+ * an external absolute URL or empty / non-path input. A path that is entirely
46
+ * dynamic (`${x}`) normalizes faithfully to `/{var}` rather than being
47
+ * dropped here — the join (not this function) treats an all-placeholder path
48
+ * as unresolved (validated: precision held at 96% with this split).
49
+ *
50
+ * `raw` is the literal text as it appears in source, including any
51
+ * surrounding quotes/backticks (the extractor passes the node text verbatim).
52
+ *
53
+ * Steps (order matters):
54
+ * 1. strip surrounding quotes / backticks;
55
+ * 2. strip configured host-helper prefixes;
56
+ * 3. reject external absolute URLs (`http(s)://…`, `${host}://…`) → `null`
57
+ * (a call to a host we don't serve is not an internal route binding);
58
+ * 4. collapse template expressions `${…}` → `{var}`;
59
+ * 5. drop the query string (`?…`) — query params don't distinguish a route;
60
+ * 6. canonicalize path params `:id` and `{id}` → `{var}`;
61
+ * 7. ensure a single leading slash (LoopBack allows `@post('zen/x')`);
62
+ * 8. drop a trailing slash;
63
+ * 9. require a real path head (`/letter` or `/{var}`), else `null`.
64
+ */
65
+ export declare function normalizePath(raw: string | null | undefined, config?: NormalizeConfig): string | null;
66
+ /**
67
+ * Canonicalize a matched method token (a decorator name like `get`, or a
68
+ * client/router member like `post`) to an uppercase {@link HttpMethod}.
69
+ * `aliases` carries pack-declared exceptions (LoopBack's `del` → `DELETE`);
70
+ * everything else upper-cases. Returns `null` for a token that doesn't map to
71
+ * a known verb.
72
+ */
73
+ export declare function normalizeMethod(token: string, aliases?: Readonly<Record<string, string>>): HttpMethod | null;
74
+ /** A normalized binding key `"<METHOD> <path>"` — the join key + identity input. */
75
+ export declare function bindingKey(method: HttpMethod, path: string): string;
76
+ //# sourceMappingURL=normalize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../../../src/analyzers/flow/normalize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,wDAAwD;AACxD,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;AAE1F,6EAA6E;AAC7E,MAAM,WAAW,eAAe;IAC9B;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAID;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAC9B,MAAM,CAAC,EAAE,eAAe,GACvB,MAAM,GAAG,IAAI,CAiDf;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GACzC,UAAU,GAAG,IAAI,CAInB;AAgBD,oFAAoF;AACpF,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnE"}
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ /**
3
+ * URL / route-path normalization — the canonical, pure transform that lets a
4
+ * frontend client call and a backend route declaration JOIN even though they
5
+ * are written differently. It is the precision-critical core of the flow
6
+ * feature: this algorithm was validated at 96% precision (vs ~84% for a regex
7
+ * approach) on a real axios → LoopBack stack — the win comes from being
8
+ * structural rather than regex-mangled.
9
+ *
10
+ * The contract: turn a raw URL/route literal into a canonical path string in
11
+ * which path parameters are erased to `{var}` and host/prefix noise is gone,
12
+ * so `axios.get(`${Config.apiBase()}/articles/${slug}`)` (client) and
13
+ * `@get('/articles/{id}')` (server) both reduce to `/articles/{var}` and
14
+ * match. Method normalization (`del` → `DELETE`) lives here too.
15
+ *
16
+ * Two inputs are deliberately NOT baked in as language facts (CLAUDE.md
17
+ * Rule 6 boundary):
18
+ * - **Host helpers** (`${Config.apiBase()}`, `${apiUrl}`) are per-APP, not
19
+ * per-language — they arrive via `NormalizeConfig.stripUrlPrefixes`
20
+ * (sourced from `.dxkit/policy.json:flow.stripUrlPrefixes`).
21
+ * - **Param-form canonicalization** (`:id`, `{id}`, `${x}` → `{var}`) is
22
+ * uniform across frameworks, so it lives here rather than in a per-pack
23
+ * descriptor.
24
+ *
25
+ * Pure: no I/O, deterministic over its inputs. Identity-bearing (a binding's
26
+ * Rule 9 fingerprint is computed from the normalized path), so changes here
27
+ * are a normalization-scheme change — treat with the same care as a
28
+ * fingerprint-scheme bump.
29
+ */
30
+ Object.defineProperty(exports, "__esModule", { value: true });
31
+ exports.normalizePath = normalizePath;
32
+ exports.normalizeMethod = normalizeMethod;
33
+ exports.bindingKey = bindingKey;
34
+ const PLACEHOLDER = '{var}';
35
+ /**
36
+ * Canonicalize a raw URL / route literal to a comparable path, or `null` for
37
+ * an external absolute URL or empty / non-path input. A path that is entirely
38
+ * dynamic (`${x}`) normalizes faithfully to `/{var}` rather than being
39
+ * dropped here — the join (not this function) treats an all-placeholder path
40
+ * as unresolved (validated: precision held at 96% with this split).
41
+ *
42
+ * `raw` is the literal text as it appears in source, including any
43
+ * surrounding quotes/backticks (the extractor passes the node text verbatim).
44
+ *
45
+ * Steps (order matters):
46
+ * 1. strip surrounding quotes / backticks;
47
+ * 2. strip configured host-helper prefixes;
48
+ * 3. reject external absolute URLs (`http(s)://…`, `${host}://…`) → `null`
49
+ * (a call to a host we don't serve is not an internal route binding);
50
+ * 4. collapse template expressions `${…}` → `{var}`;
51
+ * 5. drop the query string (`?…`) — query params don't distinguish a route;
52
+ * 6. canonicalize path params `:id` and `{id}` → `{var}`;
53
+ * 7. ensure a single leading slash (LoopBack allows `@post('zen/x')`);
54
+ * 8. drop a trailing slash;
55
+ * 9. require a real path head (`/letter` or `/{var}`), else `null`.
56
+ */
57
+ function normalizePath(raw, config) {
58
+ if (raw == null)
59
+ return null;
60
+ let s = raw.trim();
61
+ if (s.length === 0)
62
+ return null;
63
+ // 1. surrounding quotes / backticks
64
+ if (s.length >= 2 && (s[0] === "'" || s[0] === '"' || s[0] === '`') && s[s.length - 1] === s[0]) {
65
+ s = s.slice(1, -1);
66
+ }
67
+ // 2. host-helper prefixes (longest first, so a more specific prefix wins)
68
+ for (const prefix of [...(config?.stripUrlPrefixes ?? [])].sort((a, b) => b.length - a.length)) {
69
+ if (prefix && s.startsWith(prefix)) {
70
+ s = s.slice(prefix.length);
71
+ break;
72
+ }
73
+ // also strip when embedded at the head inside a template, e.g. a leading
74
+ // `${Config.apiBase()}` not at index 0 due to whitespace — rare; handled by
75
+ // a single replace of the first occurrence.
76
+ if (prefix && s.includes(prefix)) {
77
+ s = s.replace(prefix, '');
78
+ break;
79
+ }
80
+ }
81
+ // 3. external absolute URL (not one of our host helpers) → not internal
82
+ if (/^https?:\/\//i.test(s) || /^\$\{[^}]*\}:\/\//.test(s))
83
+ return null;
84
+ // 4. template expressions → placeholder
85
+ s = s.replace(/\$\{[^}]*\}/g, PLACEHOLDER);
86
+ // 5. drop query string
87
+ const q = s.indexOf('?');
88
+ if (q !== -1)
89
+ s = s.slice(0, q);
90
+ // 6. canonicalize path params
91
+ s = s.replace(/:[A-Za-z0-9_]+/g, PLACEHOLDER); // Express/Rails :id
92
+ s = s.replace(/\{[^}]*\}/g, PLACEHOLDER); // OpenAPI/LoopBack {id} (and already-{var})
93
+ // 7. single leading slash
94
+ if (!s.startsWith('/'))
95
+ s = '/' + s;
96
+ s = s.replace(/\/{2,}/g, '/'); // collapse accidental doubles after prefix strip
97
+ // 8. trailing slash
98
+ if (s.length > 1 && s.endsWith('/'))
99
+ s = s.slice(0, -1);
100
+ // 9. require a real path head
101
+ if (!/^\/([A-Za-z]|\{var\})/.test(s))
102
+ return null;
103
+ return s;
104
+ }
105
+ /**
106
+ * Canonicalize a matched method token (a decorator name like `get`, or a
107
+ * client/router member like `post`) to an uppercase {@link HttpMethod}.
108
+ * `aliases` carries pack-declared exceptions (LoopBack's `del` → `DELETE`);
109
+ * everything else upper-cases. Returns `null` for a token that doesn't map to
110
+ * a known verb.
111
+ */
112
+ function normalizeMethod(token, aliases) {
113
+ const lower = token.toLowerCase();
114
+ const mapped = (aliases?.[lower] ?? lower).toUpperCase();
115
+ return isHttpMethod(mapped) ? mapped : null;
116
+ }
117
+ const HTTP_METHODS = new Set([
118
+ 'GET',
119
+ 'POST',
120
+ 'PUT',
121
+ 'PATCH',
122
+ 'DELETE',
123
+ 'HEAD',
124
+ 'OPTIONS',
125
+ ]);
126
+ function isHttpMethod(s) {
127
+ return HTTP_METHODS.has(s);
128
+ }
129
+ /** A normalized binding key `"<METHOD> <path>"` — the join key + identity input. */
130
+ function bindingKey(method, path) {
131
+ return `${method} ${path}`;
132
+ }
133
+ //# sourceMappingURL=normalize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize.js","sourceRoot":"","sources":["../../../src/analyzers/flow/normalize.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * OpenAPI / spec served-side source — consume an existing API spec as the
3
+ * authoritative served contract, rather than statically extracting routes.
4
+ *
5
+ * Build-vs-buy (CLAUDE.md Rule 5 at the served layer): where a backend ships or
6
+ * can emit an OpenAPI document — LoopBack's `exportOpenApiSpec`, NestJS/FastAPI
7
+ * generators, hand-maintained specs — that document is higher-fidelity than
8
+ * decorator scraping: it is the framework's own answer, and it cannot pick up a
9
+ * commented-out or dead route. So a spec, when present, is the PREFERRED served
10
+ * source; static route extraction (`extract.ts`) is the fallback for backends
11
+ * with no spec. The CONSUMED side (client calls) has no spec equivalent and is
12
+ * always AST-extracted — a spec replaces half the problem, never the engine.
13
+ *
14
+ * Output is the same `RouteEndpoint` shape the static extractor produces, so the
15
+ * join (`model.ts`) is indifferent to where a route came from. Identity-bearing
16
+ * via the normalized path (Rule 9), so paths route through the shared normalizer
17
+ * exactly as source-extracted ones do.
18
+ *
19
+ * JSON OpenAPI (2.0/3.x) today; YAML is a fast-follow (a parser dependency
20
+ * decision). Pure over its inputs; the file read is the only I/O.
21
+ */
22
+ import type { RouteEndpoint } from './extract';
23
+ /** A path item holds HTTP-method operations alongside non-method siblings
24
+ * (`parameters`, `summary`, `$ref`), so its values are loosely typed and the
25
+ * method entries are narrowed at read time. */
26
+ type OpenApiPathItem = Record<string, unknown>;
27
+ interface OpenApiDoc {
28
+ paths?: Record<string, OpenApiPathItem | undefined>;
29
+ }
30
+ /**
31
+ * Routes from a parsed OpenAPI document. Each `(path, method)` pair becomes a
32
+ * served `RouteEndpoint` with `via: 'spec'`; the handler is the operationId when
33
+ * present. Paths are canonicalized through the shared normalizer so they join
34
+ * against client calls identically to source-extracted routes.
35
+ */
36
+ export declare function routesFromOpenApi(doc: OpenApiDoc, sourceFile: string): RouteEndpoint[];
37
+ /**
38
+ * Read + parse a JSON OpenAPI document into served routes. Returns `[]` (never
39
+ * throws) when the file is unreadable or not valid JSON OpenAPI — a missing or
40
+ * broken spec degrades to the static fallback, it does not fail the run.
41
+ */
42
+ export declare function loadOpenApiRoutes(filePath: string): RouteEndpoint[];
43
+ export {};
44
+ //# sourceMappingURL=spec-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec-source.d.ts","sourceRoot":"","sources":["../../../src/analyzers/flow/spec-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAK/C;;gDAEgD;AAChD,KAAK,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAC/C,UAAU,UAAU;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CAAC,CAAC;CACrD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,aAAa,EAAE,CAkBtF;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,EAAE,CASnE"}
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ /**
3
+ * OpenAPI / spec served-side source — consume an existing API spec as the
4
+ * authoritative served contract, rather than statically extracting routes.
5
+ *
6
+ * Build-vs-buy (CLAUDE.md Rule 5 at the served layer): where a backend ships or
7
+ * can emit an OpenAPI document — LoopBack's `exportOpenApiSpec`, NestJS/FastAPI
8
+ * generators, hand-maintained specs — that document is higher-fidelity than
9
+ * decorator scraping: it is the framework's own answer, and it cannot pick up a
10
+ * commented-out or dead route. So a spec, when present, is the PREFERRED served
11
+ * source; static route extraction (`extract.ts`) is the fallback for backends
12
+ * with no spec. The CONSUMED side (client calls) has no spec equivalent and is
13
+ * always AST-extracted — a spec replaces half the problem, never the engine.
14
+ *
15
+ * Output is the same `RouteEndpoint` shape the static extractor produces, so the
16
+ * join (`model.ts`) is indifferent to where a route came from. Identity-bearing
17
+ * via the normalized path (Rule 9), so paths route through the shared normalizer
18
+ * exactly as source-extracted ones do.
19
+ *
20
+ * JSON OpenAPI (2.0/3.x) today; YAML is a fast-follow (a parser dependency
21
+ * decision). Pure over its inputs; the file read is the only I/O.
22
+ */
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.routesFromOpenApi = routesFromOpenApi;
25
+ exports.loadOpenApiRoutes = loadOpenApiRoutes;
26
+ const fs_1 = require("fs");
27
+ const normalize_1 = require("./normalize");
28
+ const SPEC_METHODS = new Set(['get', 'post', 'put', 'patch', 'delete', 'head', 'options']);
29
+ /**
30
+ * Routes from a parsed OpenAPI document. Each `(path, method)` pair becomes a
31
+ * served `RouteEndpoint` with `via: 'spec'`; the handler is the operationId when
32
+ * present. Paths are canonicalized through the shared normalizer so they join
33
+ * against client calls identically to source-extracted routes.
34
+ */
35
+ function routesFromOpenApi(doc, sourceFile) {
36
+ const out = [];
37
+ const paths = doc.paths ?? {};
38
+ for (const [rawPath, ops] of Object.entries(paths)) {
39
+ if (!ops || typeof ops !== 'object')
40
+ continue;
41
+ const path = (0, normalize_1.normalizePath)(rawPath);
42
+ if (!path)
43
+ continue;
44
+ for (const [rawMethod, op] of Object.entries(ops)) {
45
+ const lower = rawMethod.toLowerCase();
46
+ if (!SPEC_METHODS.has(lower))
47
+ continue; // skip parameters/summary/$ref siblings
48
+ const method = (0, normalize_1.normalizeMethod)(lower);
49
+ if (!method)
50
+ continue;
51
+ const operationId = op?.operationId;
52
+ const handler = typeof operationId === 'string' ? operationId : null;
53
+ out.push({ method, path, via: 'spec', handler, file: sourceFile, line: 0 });
54
+ }
55
+ }
56
+ return out;
57
+ }
58
+ /**
59
+ * Read + parse a JSON OpenAPI document into served routes. Returns `[]` (never
60
+ * throws) when the file is unreadable or not valid JSON OpenAPI — a missing or
61
+ * broken spec degrades to the static fallback, it does not fail the run.
62
+ */
63
+ function loadOpenApiRoutes(filePath) {
64
+ let doc;
65
+ try {
66
+ doc = JSON.parse((0, fs_1.readFileSync)(filePath, 'utf8'));
67
+ }
68
+ catch {
69
+ return [];
70
+ }
71
+ if (!doc || typeof doc !== 'object' || !doc.paths)
72
+ return [];
73
+ return routesFromOpenApi(doc, filePath);
74
+ }
75
+ //# sourceMappingURL=spec-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec-source.js","sourceRoot":"","sources":["../../../src/analyzers/flow/spec-source.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;;AAsBH,8CAkBC;AAOD,8CASC;AAtDD,2BAAkC;AAElC,2CAA8E;AAE9E,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;AAU3F;;;;;GAKG;AACH,SAAgB,iBAAiB,CAAC,GAAe,EAAE,UAAkB;IACnE,MAAM,GAAG,GAAoB,EAAE,CAAC;IAChC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;IAC9B,KAAK,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACnD,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,SAAS;QAC9C,MAAM,IAAI,GAAG,IAAA,yBAAa,EAAC,OAAO,CAAC,CAAC;QACpC,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,KAAK,MAAM,CAAC,SAAS,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YAClD,MAAM,KAAK,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC;YACtC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,SAAS,CAAC,wCAAwC;YAChF,MAAM,MAAM,GAAsB,IAAA,2BAAe,EAAC,KAAK,CAAC,CAAC;YACzD,IAAI,CAAC,MAAM;gBAAE,SAAS;YACtB,MAAM,WAAW,GAAI,EAAuC,EAAE,WAAW,CAAC;YAC1E,MAAM,OAAO,GAAG,OAAO,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC;YACrE,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;QAC9E,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,SAAgB,iBAAiB,CAAC,QAAgB;IAChD,IAAI,GAAe,CAAC;IACpB,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAA,iBAAY,EAAC,QAAQ,EAAE,MAAM,CAAC,CAAe,CAAC;IACjE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,CAAC,GAAG,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IAC7D,OAAO,iBAAiB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;AAC1C,CAAC"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Canonical AST access — dxkit's in-process, graphify-independent parser.
3
+ *
4
+ * This is the ONE module that touches a tree-sitter engine. Every AST-based
5
+ * feature (flow extraction today; a future graph builder that could replace
6
+ * graphify; any later AST analysis) parses through here, so the engine stays
7
+ * swappable in a single file — the same adapter discipline graphify sits
8
+ * behind, but for raw parsing.
9
+ *
10
+ * Why its own layer (vs graphify): graphify is a high-level *graph builder*
11
+ * (functions/calls/communities) that does not expose raw call/decorator/
12
+ * string-literal nodes, and it runs as an optional Python subprocess. This
13
+ * layer is the low-level *parser*: source → concrete syntax tree, in-process
14
+ * (web-tree-sitter wasm), no Python, no graphify. It is also the foundation a
15
+ * future in-house graph builder would consume to migrate the code graph off
16
+ * graphify.
17
+ *
18
+ * Design:
19
+ * - **Lazy + graceful.** The wasm engine loads on first use (keeps startup
20
+ * fast) and every entry point returns `null` rather than throwing when the
21
+ * engine or a grammar is unavailable — AST features degrade, they don't
22
+ * crash (mirrors graphify's "unavailable" contract).
23
+ * - **Pack-driven grammars (Rule 6).** A file's grammar is resolved from the
24
+ * owning pack's `treeSitterGrammars[ext]`; this module never hardcodes a
25
+ * per-language mapping. Logical grammar names map to wasm artifacts here,
26
+ * so the artifact source (currently `tree-sitter-wasms`, pinned) is
27
+ * swappable / vendorable without touching packs.
28
+ * - **Cached.** Parser.init runs once; each grammar's `Language` and a bound
29
+ * `Parser` are cached for the process.
30
+ */
31
+ import type { Node, Tree } from 'web-tree-sitter';
32
+ import type { LanguageId } from '../languages/types';
33
+ export type { Node, Tree } from 'web-tree-sitter';
34
+ /** A successfully parsed file. */
35
+ export interface ParsedFile {
36
+ readonly tree: Tree;
37
+ readonly source: string;
38
+ readonly grammar: string;
39
+ readonly languageId: LanguageId;
40
+ }
41
+ /** Whether the AST engine can be loaded in this environment (for doctor checks). */
42
+ export declare function astEngineAvailable(): Promise<boolean>;
43
+ /**
44
+ * Parse source text with a named grammar. Returns the tree, or `null` if the
45
+ * engine or grammar is unavailable (never throws).
46
+ */
47
+ export declare function parseSource(source: string, grammar: string): Promise<Tree | null>;
48
+ /**
49
+ * The grammar + owning language for a file extension (with leading dot),
50
+ * resolved from the active pack registry — or `null` if no pack parses it.
51
+ */
52
+ export declare function grammarForExtension(ext: string): {
53
+ grammar: string;
54
+ languageId: LanguageId;
55
+ } | null;
56
+ /**
57
+ * Read + parse a file, resolving its grammar from the extension via the pack
58
+ * registry. Returns `null` if the extension maps to no grammar, the file is
59
+ * unreadable, or the engine is unavailable.
60
+ */
61
+ export declare function parseFile(filePath: string): Promise<ParsedFile | null>;
62
+ /**
63
+ * Depth-first walk of a node and its descendants. The visitor runs on each
64
+ * node; return `false` to skip a node's children. Consumers use this rather
65
+ * than walking `node.children` directly so a future engine swap stays
66
+ * contained in this module.
67
+ */
68
+ export declare function walk(node: Node, visit: (node: Node) => void | boolean): void;
69
+ /** Test seam: drop cached engine/grammars/parsers so a test can re-init. */
70
+ export declare function resetAstCachesForTest(): void;
71
+ //# sourceMappingURL=parse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../src/ast/parse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAIH,OAAO,KAAK,EAAY,IAAI,EAAU,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAEpE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAIrD,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAElD,kCAAkC;AAClC,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;CACjC;AA4ED,oFAAoF;AACpF,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,OAAO,CAAC,CAE3D;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAUvF;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,GACV;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,UAAU,CAAA;CAAE,GAAG,IAAI,CAOpD;AAED;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAY5E;AAED;;;;;GAKG;AACH,wBAAgB,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,GAAG,OAAO,GAAG,IAAI,CAM5E;AAED,4EAA4E;AAC5E,wBAAgB,qBAAqB,IAAI,IAAI,CAK5C"}