@zauso-ai/capstan-router 0.1.2

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.
@@ -0,0 +1,5 @@
1
+ export { scanRoutes } from "./scanner.js";
2
+ export { matchRoute } from "./matcher.js";
3
+ export { generateRouteManifest } from "./manifest.js";
4
+ export type { RouteEntry, RouteManifest, MatchedRoute, RouteType } from "./types.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { scanRoutes } from "./scanner.js";
2
+ export { matchRoute } from "./matcher.js";
3
+ export { generateRouteManifest } from "./manifest.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { RouteManifest } from "./types.js";
2
+ export interface AgentApiRoute {
3
+ method: string;
4
+ path: string;
5
+ filePath: string;
6
+ }
7
+ /**
8
+ * Extract API route information from a full RouteManifest for use by the
9
+ * agent surface layer. Each API route is expanded into one entry per HTTP method.
10
+ */
11
+ export declare function generateRouteManifest(manifest: RouteManifest): {
12
+ apiRoutes: AgentApiRoute[];
13
+ };
14
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,aAAa,GACtB;IAAE,SAAS,EAAE,aAAa,EAAE,CAAA;CAAE,CA0BhC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Extract API route information from a full RouteManifest for use by the
3
+ * agent surface layer. Each API route is expanded into one entry per HTTP method.
4
+ */
5
+ export function generateRouteManifest(manifest) {
6
+ const apiRoutes = [];
7
+ for (const route of manifest.routes) {
8
+ if (route.type !== "api") {
9
+ continue;
10
+ }
11
+ const methods = route.methods ?? ["GET"];
12
+ for (const method of methods) {
13
+ apiRoutes.push({
14
+ method,
15
+ path: route.urlPattern,
16
+ filePath: route.filePath,
17
+ });
18
+ }
19
+ }
20
+ // Sort for deterministic output
21
+ apiRoutes.sort((a, b) => {
22
+ const pathCmp = a.path.localeCompare(b.path);
23
+ if (pathCmp !== 0)
24
+ return pathCmp;
25
+ return a.method.localeCompare(b.method);
26
+ });
27
+ return { apiRoutes };
28
+ }
29
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAQA;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CACnC,QAAuB;IAEvB,MAAM,SAAS,GAAoB,EAAE,CAAC;IAEtC,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;QACpC,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;YACzB,SAAS;QACX,CAAC;QAED,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC;QACzC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,SAAS,CAAC,IAAI,CAAC;gBACb,MAAM;gBACN,IAAI,EAAE,KAAK,CAAC,UAAU;gBACtB,QAAQ,EAAE,KAAK,CAAC,QAAQ;aACzB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,gCAAgC;IAChC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACtB,MAAM,OAAO,GAAG,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC7C,IAAI,OAAO,KAAK,CAAC;YAAE,OAAO,OAAO,CAAC;QAClC,OAAO,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,OAAO,EAAE,SAAS,EAAE,CAAC;AACvB,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { MatchedRoute, RouteManifest } from "./types.js";
2
+ /**
3
+ * Match a URL path and HTTP method against the route manifest.
4
+ *
5
+ * Returns the best matching route (most specific) along with extracted parameters,
6
+ * or null if no route matches.
7
+ *
8
+ * Matching priority:
9
+ * 1. Static segments are preferred over dynamic segments
10
+ * 2. Dynamic segments are preferred over catch-all
11
+ * 3. Page routes match any method (they serve GET by convention)
12
+ * 4. API routes must match the given HTTP method
13
+ */
14
+ export declare function matchRoute(manifest: RouteManifest, method: string, urlPath: string): MatchedRoute | null;
15
+ //# sourceMappingURL=matcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matcher.d.ts","sourceRoot":"","sources":["../src/matcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AA+E9D;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CACxB,QAAQ,EAAE,aAAa,EACvB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,GACd,YAAY,GAAG,IAAI,CA0ErB"}
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Normalize a URL path by removing trailing slashes (except for root "/")
3
+ * and collapsing repeated slashes.
4
+ */
5
+ function normalizePath(urlPath) {
6
+ // Collapse repeated slashes
7
+ let normalized = urlPath.replace(/\/+/g, "/");
8
+ // Remove trailing slash (unless it IS the root)
9
+ if (normalized.length > 1 && normalized.endsWith("/")) {
10
+ normalized = normalized.slice(0, -1);
11
+ }
12
+ // Ensure leading slash
13
+ if (!normalized.startsWith("/")) {
14
+ normalized = "/" + normalized;
15
+ }
16
+ return normalized;
17
+ }
18
+ /**
19
+ * Attempt to match a URL path against a single route pattern.
20
+ * Returns extracted params and a specificity score, or null if no match.
21
+ *
22
+ * Specificity is measured as the number of static segments — more static segments
23
+ * means a more specific match. Catch-all routes get the lowest specificity.
24
+ */
25
+ function tryMatch(pattern, urlPath) {
26
+ const patternParts = pattern === "/" ? [""] : pattern.split("/");
27
+ const urlParts = urlPath === "/" ? [""] : urlPath.split("/");
28
+ const params = {};
29
+ let staticSegments = 0;
30
+ for (let i = 0; i < patternParts.length; i++) {
31
+ const patternPart = patternParts[i];
32
+ // Catch-all: matches the rest of the URL
33
+ if (patternPart === "*") {
34
+ // Collect remaining URL segments (everything from this position onward)
35
+ const rest = urlParts.slice(i).join("/");
36
+ // For catch-all stored in params, we need the param name from the route.
37
+ // We store under a generic key here; the caller maps it to the real name.
38
+ params["*"] = rest;
39
+ // Catch-all gets lowest specificity bonus
40
+ return { params, specificity: staticSegments };
41
+ }
42
+ // If we've run out of URL segments but still have pattern segments, no match
43
+ if (i >= urlParts.length) {
44
+ return null;
45
+ }
46
+ const urlPart = urlParts[i];
47
+ if (patternPart.startsWith(":")) {
48
+ // Dynamic segment
49
+ const paramName = patternPart.slice(1);
50
+ params[paramName] = decodeURIComponent(urlPart);
51
+ }
52
+ else {
53
+ // Static segment — must match exactly
54
+ if (patternPart !== urlPart) {
55
+ return null;
56
+ }
57
+ staticSegments++;
58
+ }
59
+ }
60
+ // If there are remaining URL segments that weren't consumed, no match
61
+ if (urlParts.length > patternParts.length) {
62
+ return null;
63
+ }
64
+ return { params, specificity: staticSegments };
65
+ }
66
+ /**
67
+ * Match a URL path and HTTP method against the route manifest.
68
+ *
69
+ * Returns the best matching route (most specific) along with extracted parameters,
70
+ * or null if no route matches.
71
+ *
72
+ * Matching priority:
73
+ * 1. Static segments are preferred over dynamic segments
74
+ * 2. Dynamic segments are preferred over catch-all
75
+ * 3. Page routes match any method (they serve GET by convention)
76
+ * 4. API routes must match the given HTTP method
77
+ */
78
+ export function matchRoute(manifest, method, urlPath) {
79
+ const normalizedPath = normalizePath(urlPath);
80
+ const upperMethod = method.toUpperCase();
81
+ let best = null;
82
+ for (const route of manifest.routes) {
83
+ // Skip layout and middleware entries — they are not directly routable
84
+ if (route.type === "layout" || route.type === "middleware") {
85
+ continue;
86
+ }
87
+ // For API routes, check that the method is supported
88
+ if (route.type === "api" && route.methods && !route.methods.includes(upperMethod)) {
89
+ continue;
90
+ }
91
+ const result = tryMatch(route.urlPattern, normalizedPath);
92
+ if (result === null) {
93
+ continue;
94
+ }
95
+ // Resolve catch-all param name: replace the generic "*" key with the actual param name
96
+ if ("*" in result.params && route.params.length > 0) {
97
+ const catchAllParamName = route.params[route.params.length - 1];
98
+ result.params[catchAllParamName] = result.params["*"];
99
+ delete result.params["*"];
100
+ }
101
+ const candidate = {
102
+ match: { route, params: result.params },
103
+ specificity: result.specificity,
104
+ isCatchAll: route.isCatchAll,
105
+ };
106
+ if (best === null) {
107
+ best = candidate;
108
+ continue;
109
+ }
110
+ // Determine if this candidate is better than the current best.
111
+ //
112
+ // Priority order:
113
+ // 1. Non-catch-all beats catch-all
114
+ // 2. Higher specificity (more static segments) beats lower
115
+ // 3. At equal specificity and catch-all status, prefer the route type
116
+ // that best matches the HTTP method:
117
+ // - For GET requests, prefer page routes over api routes
118
+ // - For non-GET requests, prefer api routes over page routes
119
+ if (!candidate.isCatchAll && best.isCatchAll) {
120
+ best = candidate;
121
+ }
122
+ else if (candidate.isCatchAll === best.isCatchAll) {
123
+ if (candidate.specificity > best.specificity) {
124
+ best = candidate;
125
+ }
126
+ else if (candidate.specificity === best.specificity) {
127
+ const preferApi = upperMethod !== "GET";
128
+ const candidateIsApi = candidate.match.route.type === "api";
129
+ const bestIsApi = best.match.route.type === "api";
130
+ if (preferApi && candidateIsApi && !bestIsApi) {
131
+ best = candidate;
132
+ }
133
+ else if (!preferApi && !candidateIsApi && bestIsApi) {
134
+ best = candidate;
135
+ }
136
+ }
137
+ }
138
+ }
139
+ return best?.match ?? null;
140
+ }
141
+ //# sourceMappingURL=matcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matcher.js","sourceRoot":"","sources":["../src/matcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { RouteManifest } from "./types.js";
2
+ /**
3
+ * Scan a routes directory and produce a RouteManifest describing every route file found.
4
+ */
5
+ export declare function scanRoutes(routesDir: string): Promise<RouteManifest>;
6
+ //# sourceMappingURL=scanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAc,aAAa,EAAa,MAAM,YAAY,CAAC;AAoKvE;;GAEG;AACH,wBAAsB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA2H1E"}
@@ -0,0 +1,257 @@
1
+ import { readdir, stat } from "node:fs/promises";
2
+ import path from "node:path";
3
+ /**
4
+ * Determine the route type from a filename.
5
+ * Returns null if the file is not a recognized route file.
6
+ */
7
+ function classifyFile(filename) {
8
+ if (filename === "_layout.tsx")
9
+ return "layout";
10
+ if (filename === "_middleware.ts")
11
+ return "middleware";
12
+ if (filename.endsWith(".page.tsx"))
13
+ return "page";
14
+ if (filename.endsWith(".api.ts"))
15
+ return "api";
16
+ return null;
17
+ }
18
+ /**
19
+ * Convert a filename to its URL segment contribution.
20
+ *
21
+ * index.page.tsx -> "" (index routes map to the directory itself)
22
+ * about.page.tsx -> "about"
23
+ * [id].page.tsx -> ":id"
24
+ * [...rest].page.tsx -> "*"
25
+ * index.api.ts -> ""
26
+ * [id].api.ts -> ":id"
27
+ */
28
+ function fileToSegment(filename) {
29
+ // Strip the suffix to get the base name
30
+ let base;
31
+ if (filename.endsWith(".page.tsx")) {
32
+ base = filename.slice(0, -".page.tsx".length);
33
+ }
34
+ else if (filename.endsWith(".api.ts")) {
35
+ base = filename.slice(0, -".api.ts".length);
36
+ }
37
+ else {
38
+ return { segment: "", params: [], isCatchAll: false };
39
+ }
40
+ if (base === "index") {
41
+ return { segment: "", params: [], isCatchAll: false };
42
+ }
43
+ // Catch-all: [...rest]
44
+ const catchAllMatch = base.match(/^\[\.\.\.(\w+)\]$/);
45
+ if (catchAllMatch) {
46
+ return { segment: "*", params: [catchAllMatch[1]], isCatchAll: true };
47
+ }
48
+ // Dynamic segment: [param]
49
+ const dynamicMatch = base.match(/^\[(\w+)\]$/);
50
+ if (dynamicMatch) {
51
+ return { segment: `:${dynamicMatch[1]}`, params: [dynamicMatch[1]], isCatchAll: false };
52
+ }
53
+ // Static segment
54
+ return { segment: base, params: [], isCatchAll: false };
55
+ }
56
+ /**
57
+ * Convert a directory path relative to the routes root into URL segments.
58
+ * Each directory named as a dynamic segment (e.g. `[orgId]`) is converted.
59
+ */
60
+ function dirToSegments(relativeDir) {
61
+ if (relativeDir === "" || relativeDir === ".") {
62
+ return { segments: [], params: [] };
63
+ }
64
+ const parts = relativeDir.split(path.sep);
65
+ const segments = [];
66
+ const params = [];
67
+ for (const part of parts) {
68
+ const catchAllMatch = part.match(/^\[\.\.\.(\w+)\]$/);
69
+ if (catchAllMatch) {
70
+ segments.push("*");
71
+ params.push(catchAllMatch[1]);
72
+ continue;
73
+ }
74
+ const dynamicMatch = part.match(/^\[(\w+)\]$/);
75
+ if (dynamicMatch) {
76
+ segments.push(`:${dynamicMatch[1]}`);
77
+ params.push(dynamicMatch[1]);
78
+ continue;
79
+ }
80
+ segments.push(part);
81
+ }
82
+ return { segments, params };
83
+ }
84
+ /**
85
+ * Collect all _layout.tsx files from the routes root down to the given directory,
86
+ * ordered from outermost to innermost.
87
+ */
88
+ function collectLayouts(routesDir, relativeDir) {
89
+ const layouts = [];
90
+ const parts = relativeDir === "" || relativeDir === "." ? [] : relativeDir.split(path.sep);
91
+ // Check the root directory first
92
+ layouts.push(path.join(routesDir, "_layout.tsx"));
93
+ // Then each nested directory
94
+ let current = routesDir;
95
+ for (const part of parts) {
96
+ current = path.join(current, part);
97
+ layouts.push(path.join(current, "_layout.tsx"));
98
+ }
99
+ return layouts;
100
+ }
101
+ /**
102
+ * Collect all _middleware.ts files from the routes root down to the given directory,
103
+ * ordered from outermost to innermost.
104
+ */
105
+ function collectMiddlewares(routesDir, relativeDir) {
106
+ const middlewares = [];
107
+ const parts = relativeDir === "" || relativeDir === "." ? [] : relativeDir.split(path.sep);
108
+ middlewares.push(path.join(routesDir, "_middleware.ts"));
109
+ let current = routesDir;
110
+ for (const part of parts) {
111
+ current = path.join(current, part);
112
+ middlewares.push(path.join(current, "_middleware.ts"));
113
+ }
114
+ return middlewares;
115
+ }
116
+ /**
117
+ * Recursively walk a directory, returning all files as paths relative to the root.
118
+ */
119
+ async function walkDir(dir, root) {
120
+ const files = [];
121
+ let entries;
122
+ try {
123
+ entries = await readdir(dir, { withFileTypes: true });
124
+ }
125
+ catch {
126
+ // Directory doesn't exist or can't be read
127
+ return files;
128
+ }
129
+ for (const entry of entries) {
130
+ const fullPath = path.join(dir, entry.name);
131
+ if (entry.isDirectory()) {
132
+ const nested = await walkDir(fullPath, root);
133
+ files.push(...nested);
134
+ }
135
+ else if (entry.isFile()) {
136
+ files.push(path.relative(root, fullPath));
137
+ }
138
+ }
139
+ return files;
140
+ }
141
+ /**
142
+ * Filter layout/middleware candidate paths to only those that actually exist as
143
+ * files discovered during the scan.
144
+ */
145
+ function filterExisting(candidates, existingAbsolute) {
146
+ return candidates.filter((p) => existingAbsolute.has(p));
147
+ }
148
+ /**
149
+ * Scan a routes directory and produce a RouteManifest describing every route file found.
150
+ */
151
+ export async function scanRoutes(routesDir) {
152
+ const resolvedRoot = path.resolve(routesDir);
153
+ // Verify the directory exists
154
+ try {
155
+ const s = await stat(resolvedRoot);
156
+ if (!s.isDirectory()) {
157
+ return {
158
+ routes: [],
159
+ scannedAt: new Date().toISOString(),
160
+ rootDir: resolvedRoot,
161
+ };
162
+ }
163
+ }
164
+ catch {
165
+ return {
166
+ routes: [],
167
+ scannedAt: new Date().toISOString(),
168
+ rootDir: resolvedRoot,
169
+ };
170
+ }
171
+ const relativePaths = await walkDir(resolvedRoot, resolvedRoot);
172
+ // Build a set of all absolute paths for existence checks
173
+ const absolutePathSet = new Set(relativePaths.map((rp) => path.join(resolvedRoot, rp)));
174
+ const routes = [];
175
+ for (const relPath of relativePaths) {
176
+ const filename = path.basename(relPath);
177
+ const routeType = classifyFile(filename);
178
+ if (routeType === null) {
179
+ // Not a recognized route file — skip
180
+ continue;
181
+ }
182
+ const relativeDir = path.dirname(relPath);
183
+ const absoluteFilePath = path.join(resolvedRoot, relPath);
184
+ // Build URL pattern
185
+ const dirInfo = dirToSegments(relativeDir);
186
+ if (routeType === "layout" || routeType === "middleware") {
187
+ // Layouts and middlewares don't get their own URL pattern —
188
+ // they are referenced by other routes. But we still include them
189
+ // in the manifest so they can be discovered.
190
+ const urlParts = dirInfo.segments;
191
+ const urlPattern = "/" + urlParts.join("/");
192
+ routes.push({
193
+ filePath: absoluteFilePath,
194
+ type: routeType,
195
+ urlPattern: urlPattern === "/" ? "/" : urlPattern.replace(/\/$/, ""),
196
+ layouts: [],
197
+ middlewares: [],
198
+ params: dirInfo.params,
199
+ isCatchAll: false,
200
+ });
201
+ continue;
202
+ }
203
+ // Page or API route
204
+ const fileInfo = fileToSegment(filename);
205
+ const allParams = [...dirInfo.params, ...fileInfo.params];
206
+ const urlParts = [...dirInfo.segments];
207
+ if (fileInfo.segment !== "") {
208
+ urlParts.push(fileInfo.segment);
209
+ }
210
+ const urlPattern = "/" + urlParts.join("/");
211
+ // Collect parent layouts and middlewares (only those that actually exist on disk)
212
+ const layoutCandidates = collectLayouts(resolvedRoot, relativeDir);
213
+ const middlewareCandidates = collectMiddlewares(resolvedRoot, relativeDir);
214
+ const layouts = filterExisting(layoutCandidates, absolutePathSet);
215
+ const middlewares = filterExisting(middlewareCandidates, absolutePathSet);
216
+ const entry = {
217
+ filePath: absoluteFilePath,
218
+ type: routeType,
219
+ urlPattern: urlPattern === "/" ? "/" : urlPattern.replace(/\/$/, ""),
220
+ layouts,
221
+ middlewares,
222
+ params: allParams,
223
+ isCatchAll: fileInfo.isCatchAll,
224
+ };
225
+ if (routeType === "api") {
226
+ // API routes support all standard HTTP methods by default.
227
+ // The actual exported methods are determined at runtime.
228
+ entry.methods = ["GET", "POST", "PUT", "DELETE", "PATCH"];
229
+ }
230
+ routes.push(entry);
231
+ }
232
+ // Sort for deterministic output:
233
+ // 1. By URL pattern alphabetically
234
+ // 2. By type (layout < middleware < page < api)
235
+ // 3. By file path as tiebreaker
236
+ const typeOrder = {
237
+ layout: 0,
238
+ middleware: 1,
239
+ page: 2,
240
+ api: 3,
241
+ };
242
+ routes.sort((a, b) => {
243
+ const patternCmp = a.urlPattern.localeCompare(b.urlPattern);
244
+ if (patternCmp !== 0)
245
+ return patternCmp;
246
+ const typeCmp = typeOrder[a.type] - typeOrder[b.type];
247
+ if (typeCmp !== 0)
248
+ return typeCmp;
249
+ return a.filePath.localeCompare(b.filePath);
250
+ });
251
+ return {
252
+ routes,
253
+ scannedAt: new Date().toISOString(),
254
+ rootDir: resolvedRoot,
255
+ };
256
+ }
257
+ //# sourceMappingURL=scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.js","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ export type RouteType = "page" | "api" | "layout" | "middleware";
2
+ export interface RouteEntry {
3
+ /** Absolute file path */
4
+ filePath: string;
5
+ /** Route type determined by file suffix */
6
+ type: RouteType;
7
+ /** URL path pattern, e.g. "/tickets/:id" */
8
+ urlPattern: string;
9
+ /** HTTP methods this route handles (for api routes) */
10
+ methods?: string[];
11
+ /** Parent layout file paths (from outermost to innermost) */
12
+ layouts: string[];
13
+ /** Middleware file paths (from outermost to innermost) */
14
+ middlewares: string[];
15
+ /** Dynamic parameter names */
16
+ params: string[];
17
+ /** Whether this is a catch-all route */
18
+ isCatchAll: boolean;
19
+ }
20
+ export interface RouteManifest {
21
+ routes: RouteEntry[];
22
+ /** Timestamp of last scan */
23
+ scannedAt: string;
24
+ /** Root directory that was scanned */
25
+ rootDir: string;
26
+ }
27
+ export interface MatchedRoute {
28
+ route: RouteEntry;
29
+ params: Record<string, string>;
30
+ }
31
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,YAAY,CAAC;AAEjE,MAAM,WAAW,UAAU;IACzB,yBAAyB;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,IAAI,EAAE,SAAS,CAAC;IAChB,4CAA4C;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,6DAA6D;IAC7D,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,0DAA0D;IAC1D,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,8BAA8B;IAC9B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,wCAAwC;IACxC,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,6BAA6B;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,sCAAsC;IACtC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,UAAU,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@zauso-ai/capstan-router",
3
+ "version": "0.1.2",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "types": "./dist/index.d.ts"
11
+ }
12
+ },
13
+ "scripts": {
14
+ "build": "tsc -p tsconfig.json",
15
+ "typecheck": "tsc -p tsconfig.json --noEmit"
16
+ },
17
+ "description": "File-based routing for Capstan — .page.tsx, .api.ts, _layout.tsx, _middleware.ts",
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/barry3406/capstan.git",
24
+ "directory": "packages/router"
25
+ },
26
+ "license": "MIT",
27
+ "author": "barry3406",
28
+ "homepage": "https://github.com/barry3406/capstan",
29
+ "keywords": [
30
+ "capstan",
31
+ "ai-agent",
32
+ "full-stack",
33
+ "framework",
34
+ "mcp",
35
+ "a2a",
36
+ "typescript"
37
+ ],
38
+ "publishConfig": {
39
+ "access": "public"
40
+ }
41
+ }