@zap-js/client 0.0.2 → 0.0.4
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/README.md +310 -24
- package/bin/zap +0 -0
- package/bin/zap-codegen +0 -0
- package/dist/cli/commands/build.d.ts +11 -0
- package/dist/cli/commands/build.js +282 -0
- package/dist/cli/commands/codegen.d.ts +8 -0
- package/dist/cli/commands/codegen.js +95 -0
- package/dist/cli/commands/dev.d.ts +20 -0
- package/dist/cli/commands/dev.js +78 -0
- package/dist/cli/commands/new.d.ts +9 -0
- package/dist/cli/commands/new.js +307 -0
- package/dist/cli/commands/routes-old.d.ts +9 -0
- package/dist/cli/commands/routes-old.js +106 -0
- package/dist/cli/commands/routes.d.ts +11 -0
- package/dist/cli/commands/routes.js +280 -0
- package/dist/cli/commands/serve.d.ts +17 -0
- package/dist/cli/commands/serve.js +386 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +76 -0
- package/dist/cli/utils/index.d.ts +2 -0
- package/dist/cli/utils/index.js +2 -0
- package/dist/cli/utils/logger.d.ts +84 -0
- package/dist/cli/utils/logger.js +181 -0
- package/dist/cli/utils/port-finder.d.ts +8 -0
- package/dist/cli/utils/port-finder.js +48 -0
- package/dist/dev-server/codegen-runner.d.ts +41 -0
- package/dist/dev-server/codegen-runner.js +172 -0
- package/dist/dev-server/hot-reload.d.ts +72 -0
- package/dist/dev-server/hot-reload.js +280 -0
- package/dist/dev-server/index.d.ts +8 -0
- package/dist/dev-server/index.js +8 -0
- package/dist/dev-server/route-scanner.d.ts +71 -0
- package/dist/dev-server/route-scanner.js +114 -0
- package/dist/dev-server/rust-builder.d.ts +66 -0
- package/dist/dev-server/rust-builder.js +286 -0
- package/dist/dev-server/server.d.ts +147 -0
- package/dist/dev-server/server.js +658 -0
- package/dist/dev-server/vite-proxy.d.ts +56 -0
- package/dist/dev-server/vite-proxy.js +212 -0
- package/dist/dev-server/watcher.d.ts +48 -0
- package/dist/dev-server/watcher.js +127 -0
- package/dist/router/codegen-enhanced.d.ts +5 -0
- package/dist/router/codegen-enhanced.js +275 -0
- package/dist/router/codegen.d.ts +17 -0
- package/dist/router/codegen.js +654 -0
- package/dist/router/index.d.ts +16 -0
- package/dist/router/index.js +19 -0
- package/dist/router/scanner.d.ts +86 -0
- package/dist/router/scanner.js +689 -0
- package/dist/router/ssg.d.ts +115 -0
- package/dist/router/ssg.js +202 -0
- package/dist/router/types.d.ts +124 -0
- package/dist/router/types.js +9 -0
- package/dist/router/watch.d.ts +38 -0
- package/dist/router/watch.js +135 -0
- package/dist/runtime/csrf.d.ts +146 -0
- package/dist/runtime/csrf.js +166 -0
- package/dist/runtime/error-boundary.d.ts +129 -0
- package/dist/runtime/error-boundary.js +287 -0
- package/dist/runtime/hooks.d.ts +83 -0
- package/dist/runtime/hooks.js +96 -0
- package/dist/runtime/index.d.ts +229 -0
- package/dist/runtime/index.js +449 -0
- package/dist/runtime/ipc-client.d.ts +144 -0
- package/dist/runtime/ipc-client.js +621 -0
- package/dist/runtime/logger.d.ts +71 -0
- package/dist/runtime/logger.js +164 -0
- package/dist/runtime/middleware.d.ts +66 -0
- package/dist/runtime/middleware.js +114 -0
- package/dist/runtime/process-manager.d.ts +51 -0
- package/dist/runtime/process-manager.js +207 -0
- package/dist/runtime/router-simple.d.ts +98 -0
- package/dist/runtime/router-simple.js +330 -0
- package/dist/runtime/router.d.ts +103 -0
- package/dist/runtime/router.js +435 -0
- package/dist/runtime/rpc-client.d.ts +35 -0
- package/dist/runtime/rpc-client.js +140 -0
- package/dist/runtime/streaming-utils.d.ts +86 -0
- package/dist/runtime/streaming-utils.js +150 -0
- package/dist/runtime/types.d.ts +465 -0
- package/dist/runtime/types.js +60 -0
- package/dist/runtime/websockets-utils.d.ts +50 -0
- package/dist/runtime/websockets-utils.js +92 -0
- package/package.json +30 -20
- package/index.js +0 -29
- package/internal/cli/package.json +0 -46
- package/internal/cli/tsconfig.tsbuildinfo +0 -1
- package/internal/dev-server/node_modules/ora/index.d.ts +0 -332
- package/internal/dev-server/node_modules/ora/index.js +0 -416
- package/internal/dev-server/node_modules/ora/license +0 -9
- package/internal/dev-server/node_modules/ora/node_modules/string-width/index.d.ts +0 -36
- package/internal/dev-server/node_modules/ora/node_modules/string-width/index.js +0 -65
- package/internal/dev-server/node_modules/ora/node_modules/string-width/license +0 -9
- package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/LICENSE-MIT.txt +0 -20
- package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/README.md +0 -107
- package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/index.d.ts +0 -3
- package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/index.js +0 -4
- package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/index.mjs +0 -4
- package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/package.json +0 -46
- package/internal/dev-server/node_modules/ora/node_modules/string-width/package.json +0 -60
- package/internal/dev-server/node_modules/ora/node_modules/string-width/readme.md +0 -62
- package/internal/dev-server/node_modules/ora/package.json +0 -66
- package/internal/dev-server/node_modules/ora/readme.md +0 -325
- package/internal/dev-server/package.json +0 -41
- package/internal/router/package.json +0 -28
- package/internal/runtime/package.json +0 -41
- package/internal/runtime/src/error-boundary.tsx +0 -476
- package/internal/runtime/src/router-simple.tsx +0 -640
- package/internal/runtime/src/router.tsx +0 -771
- package/internal/runtime/tsconfig.tsbuildinfo +0 -1
- package/src/errors.js +0 -33
- package/src/logger.js +0 -10
- package/src/middleware.js +0 -32
- package/src/router.js +0 -41
- package/src/types.js +0 -39
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static Site Generation (SSG) for ZapJS
|
|
3
|
+
*
|
|
4
|
+
* Supports generateStaticParams export for pre-rendering dynamic routes at build time.
|
|
5
|
+
*
|
|
6
|
+
* Usage in route files:
|
|
7
|
+
* ```tsx
|
|
8
|
+
* // routes/posts/[id].tsx
|
|
9
|
+
* export async function generateStaticParams() {
|
|
10
|
+
* const posts = await getPosts();
|
|
11
|
+
* return posts.map(post => ({ id: post.id }));
|
|
12
|
+
* }
|
|
13
|
+
*
|
|
14
|
+
* export default function PostPage({ params }: { params: { id: string } }) {
|
|
15
|
+
* // ...
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
import type { RouteTree, ScannedRoute } from './types.js';
|
|
20
|
+
/**
|
|
21
|
+
* Static params returned by generateStaticParams
|
|
22
|
+
*/
|
|
23
|
+
export type StaticParams = Record<string, string>;
|
|
24
|
+
/**
|
|
25
|
+
* Function signature for generateStaticParams export
|
|
26
|
+
*/
|
|
27
|
+
export type GenerateStaticParamsFn = () => Promise<StaticParams[]> | StaticParams[];
|
|
28
|
+
/**
|
|
29
|
+
* Pre-rendered route information
|
|
30
|
+
*/
|
|
31
|
+
export interface PrerenderedRoute {
|
|
32
|
+
/** Original route path pattern (e.g., /posts/:id) */
|
|
33
|
+
pattern: string;
|
|
34
|
+
/** Concrete path (e.g., /posts/123) */
|
|
35
|
+
path: string;
|
|
36
|
+
/** Route params */
|
|
37
|
+
params: StaticParams;
|
|
38
|
+
/** File path for the static HTML */
|
|
39
|
+
outputPath: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* SSG manifest written at build time
|
|
43
|
+
*/
|
|
44
|
+
export interface SsgManifest {
|
|
45
|
+
version: string;
|
|
46
|
+
generatedAt: string;
|
|
47
|
+
routes: PrerenderedRoute[];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Options for SSG generation
|
|
51
|
+
*/
|
|
52
|
+
export interface SsgOptions {
|
|
53
|
+
/** Output directory for static files */
|
|
54
|
+
outputDir: string;
|
|
55
|
+
/** Routes directory */
|
|
56
|
+
routesDir: string;
|
|
57
|
+
/** Route tree from scanner */
|
|
58
|
+
routeTree: RouteTree;
|
|
59
|
+
/** Whether to generate HTML files (requires renderer) */
|
|
60
|
+
generateHtml?: boolean;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Find all routes that have generateStaticParams export
|
|
64
|
+
*/
|
|
65
|
+
export declare function findSsgRoutes(routeTree: RouteTree): ScannedRoute[];
|
|
66
|
+
/**
|
|
67
|
+
* Convert route pattern to concrete path with params
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* buildPath('/posts/:id', { id: '123' }) => '/posts/123'
|
|
71
|
+
* buildPath('/blog/:slug/*rest', { slug: 'hello', rest: 'a/b' }) => '/blog/hello/a/b'
|
|
72
|
+
*/
|
|
73
|
+
export declare function buildPath(pattern: string, params: StaticParams): string;
|
|
74
|
+
/**
|
|
75
|
+
* Get output file path for a static route
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* getOutputPath('/posts/123', 'dist') => 'dist/posts/123/index.html'
|
|
79
|
+
* getOutputPath('/', 'dist') => 'dist/index.html'
|
|
80
|
+
*/
|
|
81
|
+
export declare function getOutputPath(path: string, outputDir: string): string;
|
|
82
|
+
/**
|
|
83
|
+
* Load generateStaticParams from a route file
|
|
84
|
+
*/
|
|
85
|
+
export declare function loadGenerateStaticParams(filePath: string): Promise<GenerateStaticParamsFn | null>;
|
|
86
|
+
/**
|
|
87
|
+
* Generate static params for all SSG routes
|
|
88
|
+
*/
|
|
89
|
+
export declare function collectStaticParams(ssgRoutes: ScannedRoute[], routesDir: string): Promise<Map<ScannedRoute, StaticParams[]>>;
|
|
90
|
+
/**
|
|
91
|
+
* Build all pre-rendered routes
|
|
92
|
+
*/
|
|
93
|
+
export declare function buildPrerenderedRoutes(options: SsgOptions): Promise<PrerenderedRoute[]>;
|
|
94
|
+
/**
|
|
95
|
+
* Write SSG manifest
|
|
96
|
+
*/
|
|
97
|
+
export declare function writeSsgManifest(routes: PrerenderedRoute[], outputDir: string): void;
|
|
98
|
+
/**
|
|
99
|
+
* Read SSG manifest
|
|
100
|
+
*/
|
|
101
|
+
export declare function readSsgManifest(outputDir: string): SsgManifest | null;
|
|
102
|
+
/**
|
|
103
|
+
* Main SSG build function
|
|
104
|
+
*
|
|
105
|
+
* Called during `zap build` to generate static paths
|
|
106
|
+
*/
|
|
107
|
+
export declare function buildSsg(options: SsgOptions): Promise<SsgManifest>;
|
|
108
|
+
/**
|
|
109
|
+
* Check if a path is statically generated
|
|
110
|
+
*/
|
|
111
|
+
export declare function isStaticPath(path: string, manifest: SsgManifest): boolean;
|
|
112
|
+
/**
|
|
113
|
+
* Get static route info for a path
|
|
114
|
+
*/
|
|
115
|
+
export declare function getStaticRoute(path: string, manifest: SsgManifest): PrerenderedRoute | null;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static Site Generation (SSG) for ZapJS
|
|
3
|
+
*
|
|
4
|
+
* Supports generateStaticParams export for pre-rendering dynamic routes at build time.
|
|
5
|
+
*
|
|
6
|
+
* Usage in route files:
|
|
7
|
+
* ```tsx
|
|
8
|
+
* // routes/posts/[id].tsx
|
|
9
|
+
* export async function generateStaticParams() {
|
|
10
|
+
* const posts = await getPosts();
|
|
11
|
+
* return posts.map(post => ({ id: post.id }));
|
|
12
|
+
* }
|
|
13
|
+
*
|
|
14
|
+
* export default function PostPage({ params }: { params: { id: string } }) {
|
|
15
|
+
* // ...
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|
20
|
+
import { join, dirname } from 'path';
|
|
21
|
+
/**
|
|
22
|
+
* Find all routes that have generateStaticParams export
|
|
23
|
+
*/
|
|
24
|
+
export function findSsgRoutes(routeTree) {
|
|
25
|
+
return routeTree.routes.filter((route) => route.hasGenerateStaticParams && route.params.length > 0);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Convert route pattern to concrete path with params
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* buildPath('/posts/:id', { id: '123' }) => '/posts/123'
|
|
32
|
+
* buildPath('/blog/:slug/*rest', { slug: 'hello', rest: 'a/b' }) => '/blog/hello/a/b'
|
|
33
|
+
*/
|
|
34
|
+
export function buildPath(pattern, params) {
|
|
35
|
+
let path = pattern;
|
|
36
|
+
// Replace catch-all params (*param or *param?)
|
|
37
|
+
path = path.replace(/\*(\w+)\??/g, (_, key) => {
|
|
38
|
+
return params[key] ?? '';
|
|
39
|
+
});
|
|
40
|
+
// Replace regular params (:param or :param?)
|
|
41
|
+
path = path.replace(/:(\w+)\??/g, (_, key) => {
|
|
42
|
+
return params[key] ?? '';
|
|
43
|
+
});
|
|
44
|
+
// Clean up double slashes and trailing slashes
|
|
45
|
+
path = path.replace(/\/+/g, '/');
|
|
46
|
+
if (path !== '/' && path.endsWith('/')) {
|
|
47
|
+
path = path.slice(0, -1);
|
|
48
|
+
}
|
|
49
|
+
return path || '/';
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get output file path for a static route
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* getOutputPath('/posts/123', 'dist') => 'dist/posts/123/index.html'
|
|
56
|
+
* getOutputPath('/', 'dist') => 'dist/index.html'
|
|
57
|
+
*/
|
|
58
|
+
export function getOutputPath(path, outputDir) {
|
|
59
|
+
if (path === '/') {
|
|
60
|
+
return join(outputDir, 'index.html');
|
|
61
|
+
}
|
|
62
|
+
// Remove leading slash and add index.html
|
|
63
|
+
const cleanPath = path.replace(/^\//, '');
|
|
64
|
+
return join(outputDir, cleanPath, 'index.html');
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Load generateStaticParams from a route file
|
|
68
|
+
*/
|
|
69
|
+
export async function loadGenerateStaticParams(filePath) {
|
|
70
|
+
try {
|
|
71
|
+
const module = await import(filePath);
|
|
72
|
+
if (typeof module.generateStaticParams === 'function') {
|
|
73
|
+
return module.generateStaticParams;
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
console.error(`[SSG] Failed to load generateStaticParams from ${filePath}:`, error);
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Generate static params for all SSG routes
|
|
84
|
+
*/
|
|
85
|
+
export async function collectStaticParams(ssgRoutes, routesDir) {
|
|
86
|
+
const results = new Map();
|
|
87
|
+
for (const route of ssgRoutes) {
|
|
88
|
+
const generateFn = await loadGenerateStaticParams(route.filePath);
|
|
89
|
+
if (generateFn) {
|
|
90
|
+
try {
|
|
91
|
+
const params = await generateFn();
|
|
92
|
+
results.set(route, params);
|
|
93
|
+
console.log(`[SSG] ${route.urlPath}: ${params.length} static paths`);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
console.error(`[SSG] Error generating params for ${route.urlPath}:`, error);
|
|
97
|
+
results.set(route, []);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return results;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Build all pre-rendered routes
|
|
105
|
+
*/
|
|
106
|
+
export async function buildPrerenderedRoutes(options) {
|
|
107
|
+
const { outputDir, routeTree } = options;
|
|
108
|
+
// Find SSG routes
|
|
109
|
+
const ssgRoutes = findSsgRoutes(routeTree);
|
|
110
|
+
if (ssgRoutes.length === 0) {
|
|
111
|
+
console.log('[SSG] No routes with generateStaticParams found');
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
console.log(`[SSG] Found ${ssgRoutes.length} routes with generateStaticParams`);
|
|
115
|
+
// Collect static params
|
|
116
|
+
const paramsMap = await collectStaticParams(ssgRoutes, options.routesDir);
|
|
117
|
+
// Build pre-rendered routes
|
|
118
|
+
const prerenderedRoutes = [];
|
|
119
|
+
for (const [route, paramsList] of paramsMap) {
|
|
120
|
+
for (const params of paramsList) {
|
|
121
|
+
const path = buildPath(route.urlPath, params);
|
|
122
|
+
const outputPath = getOutputPath(path, outputDir);
|
|
123
|
+
prerenderedRoutes.push({
|
|
124
|
+
pattern: route.urlPath,
|
|
125
|
+
path,
|
|
126
|
+
params,
|
|
127
|
+
outputPath,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
console.log(`[SSG] Generated ${prerenderedRoutes.length} static paths`);
|
|
132
|
+
return prerenderedRoutes;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Write SSG manifest
|
|
136
|
+
*/
|
|
137
|
+
export function writeSsgManifest(routes, outputDir) {
|
|
138
|
+
const manifest = {
|
|
139
|
+
version: '1.0.0',
|
|
140
|
+
generatedAt: new Date().toISOString(),
|
|
141
|
+
routes,
|
|
142
|
+
};
|
|
143
|
+
const manifestPath = join(outputDir, 'ssg-manifest.json');
|
|
144
|
+
ensureDir(dirname(manifestPath));
|
|
145
|
+
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
146
|
+
console.log(`[SSG] Wrote manifest to ${manifestPath}`);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Read SSG manifest
|
|
150
|
+
*/
|
|
151
|
+
export function readSsgManifest(outputDir) {
|
|
152
|
+
const manifestPath = join(outputDir, 'ssg-manifest.json');
|
|
153
|
+
if (!existsSync(manifestPath)) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
const content = readFileSync(manifestPath, 'utf-8');
|
|
158
|
+
return JSON.parse(content);
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Ensure directory exists
|
|
166
|
+
*/
|
|
167
|
+
function ensureDir(dir) {
|
|
168
|
+
if (!existsSync(dir)) {
|
|
169
|
+
mkdirSync(dir, { recursive: true });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Main SSG build function
|
|
174
|
+
*
|
|
175
|
+
* Called during `zap build` to generate static paths
|
|
176
|
+
*/
|
|
177
|
+
export async function buildSsg(options) {
|
|
178
|
+
console.log('[SSG] Starting static generation...');
|
|
179
|
+
// Build pre-rendered routes
|
|
180
|
+
const routes = await buildPrerenderedRoutes(options);
|
|
181
|
+
// Write manifest
|
|
182
|
+
writeSsgManifest(routes, options.outputDir);
|
|
183
|
+
const manifest = {
|
|
184
|
+
version: '1.0.0',
|
|
185
|
+
generatedAt: new Date().toISOString(),
|
|
186
|
+
routes,
|
|
187
|
+
};
|
|
188
|
+
console.log('[SSG] Static generation complete');
|
|
189
|
+
return manifest;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Check if a path is statically generated
|
|
193
|
+
*/
|
|
194
|
+
export function isStaticPath(path, manifest) {
|
|
195
|
+
return manifest.routes.some((route) => route.path === path);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Get static route info for a path
|
|
199
|
+
*/
|
|
200
|
+
export function getStaticRoute(path, manifest) {
|
|
201
|
+
return manifest.routes.find((route) => route.path === path) ?? null;
|
|
202
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route types for ZapJS file-based routing (Next.js style conventions)
|
|
3
|
+
*
|
|
4
|
+
* File naming:
|
|
5
|
+
* - [param].tsx → /:param (dynamic segment)
|
|
6
|
+
* - [...slug].tsx → /*slug (catch-all)
|
|
7
|
+
* - [[...slug]].tsx → /*slug? (optional catch-all)
|
|
8
|
+
*/
|
|
9
|
+
export type RouteType = 'page' | 'api' | 'layout' | 'root';
|
|
10
|
+
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
|
|
11
|
+
export interface RouteParam {
|
|
12
|
+
name: string;
|
|
13
|
+
/** Position in the URL path segments */
|
|
14
|
+
index: number;
|
|
15
|
+
/** Whether this is a catch-all param (e.g., [...slug] or [[...slug]]) */
|
|
16
|
+
catchAll: boolean;
|
|
17
|
+
/** Whether this param is optional (e.g., [[...slug]]) */
|
|
18
|
+
optional: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface ScannedRoute {
|
|
21
|
+
/** Absolute file path */
|
|
22
|
+
filePath: string;
|
|
23
|
+
/** Relative path from routes directory */
|
|
24
|
+
relativePath: string;
|
|
25
|
+
/** Generated URL path (e.g., /posts/:id) */
|
|
26
|
+
urlPath: string;
|
|
27
|
+
/** Route type */
|
|
28
|
+
type: RouteType;
|
|
29
|
+
/** Extracted route parameters */
|
|
30
|
+
params: RouteParam[];
|
|
31
|
+
/** For API routes, the HTTP methods exported */
|
|
32
|
+
methods?: HttpMethod[];
|
|
33
|
+
/** Parent layout file path (if any) */
|
|
34
|
+
layoutPath?: string;
|
|
35
|
+
/** Route group name (from (group) folders) */
|
|
36
|
+
group?: string;
|
|
37
|
+
/** Whether this is an index route */
|
|
38
|
+
isIndex: boolean;
|
|
39
|
+
/** Whether this route exports an errorComponent */
|
|
40
|
+
hasErrorComponent?: boolean;
|
|
41
|
+
/** Export name for the error component (defaults to 'errorComponent') */
|
|
42
|
+
errorComponentExport?: string;
|
|
43
|
+
/** Whether this route exports a pendingComponent */
|
|
44
|
+
hasPendingComponent?: boolean;
|
|
45
|
+
/** Export name for the pending component */
|
|
46
|
+
pendingComponentExport?: string;
|
|
47
|
+
/** Whether this route exports a meta function for head management */
|
|
48
|
+
hasMeta?: boolean;
|
|
49
|
+
/** Whether this route exports middleware */
|
|
50
|
+
hasMiddleware?: boolean;
|
|
51
|
+
/** Whether this route exports generateStaticParams for SSG pre-rendering */
|
|
52
|
+
hasGenerateStaticParams?: boolean;
|
|
53
|
+
/** Route priority score (higher = more specific) */
|
|
54
|
+
priority?: number;
|
|
55
|
+
}
|
|
56
|
+
export interface LayoutRoute {
|
|
57
|
+
/** Absolute file path */
|
|
58
|
+
filePath: string;
|
|
59
|
+
/** Relative path from routes directory */
|
|
60
|
+
relativePath: string;
|
|
61
|
+
/** URL path segment this layout applies to */
|
|
62
|
+
urlPath: string;
|
|
63
|
+
/** Child routes */
|
|
64
|
+
children: (ScannedRoute | LayoutRoute)[];
|
|
65
|
+
/** Parent layout path (for nested layouts) */
|
|
66
|
+
parentLayout?: string;
|
|
67
|
+
/** Directory path this layout is scoped to */
|
|
68
|
+
scopePath: string;
|
|
69
|
+
}
|
|
70
|
+
export interface RootRoute extends LayoutRoute {
|
|
71
|
+
type: 'root';
|
|
72
|
+
}
|
|
73
|
+
export interface WebSocketRoute {
|
|
74
|
+
/** Absolute file path */
|
|
75
|
+
filePath: string;
|
|
76
|
+
/** Relative path from routes directory */
|
|
77
|
+
relativePath: string;
|
|
78
|
+
/** WebSocket URL path */
|
|
79
|
+
urlPath: string;
|
|
80
|
+
/** Route parameters */
|
|
81
|
+
params: RouteParam[];
|
|
82
|
+
}
|
|
83
|
+
export interface RouteTree {
|
|
84
|
+
root: RootRoute | null;
|
|
85
|
+
routes: ScannedRoute[];
|
|
86
|
+
layouts: LayoutRoute[];
|
|
87
|
+
apiRoutes: ScannedRoute[];
|
|
88
|
+
/** WebSocket routes from ws/ folder or WEBSOCKET exports */
|
|
89
|
+
wsRoutes: WebSocketRoute[];
|
|
90
|
+
}
|
|
91
|
+
export interface ScanOptions {
|
|
92
|
+
/** Routes directory path */
|
|
93
|
+
routesDir: string;
|
|
94
|
+
/** File extensions to consider as routes */
|
|
95
|
+
extensions?: string[];
|
|
96
|
+
/** Whether to include API routes */
|
|
97
|
+
includeApi?: boolean;
|
|
98
|
+
}
|
|
99
|
+
export interface CodegenOptions {
|
|
100
|
+
/** Output directory for generated files */
|
|
101
|
+
outputDir: string;
|
|
102
|
+
/** Route tree to generate from */
|
|
103
|
+
routeTree: RouteTree;
|
|
104
|
+
/** Whether to generate React Router compatible output */
|
|
105
|
+
reactRouter?: boolean;
|
|
106
|
+
}
|
|
107
|
+
export interface WatchOptions extends ScanOptions {
|
|
108
|
+
/** Callback when routes change */
|
|
109
|
+
onChange: (tree: RouteTree) => void | Promise<void>;
|
|
110
|
+
/** Debounce delay in ms */
|
|
111
|
+
debounce?: number;
|
|
112
|
+
/** Skip the initial callback on start (useful when routes are already scanned) */
|
|
113
|
+
skipInitial?: boolean;
|
|
114
|
+
}
|
|
115
|
+
export interface RouteMatch {
|
|
116
|
+
route: ScannedRoute;
|
|
117
|
+
params: Record<string, string>;
|
|
118
|
+
}
|
|
119
|
+
export interface RouteManifest {
|
|
120
|
+
version: string;
|
|
121
|
+
generatedAt: string;
|
|
122
|
+
routes: ScannedRoute[];
|
|
123
|
+
apiRoutes: ScannedRoute[];
|
|
124
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File watcher for route changes
|
|
3
|
+
*
|
|
4
|
+
* Uses chokidar to watch for file changes in the routes directory
|
|
5
|
+
* and triggers route tree regeneration
|
|
6
|
+
*/
|
|
7
|
+
import type { WatchOptions, RouteTree } from './types.js';
|
|
8
|
+
export declare class RouteWatcher {
|
|
9
|
+
private watcher;
|
|
10
|
+
private scanner;
|
|
11
|
+
private options;
|
|
12
|
+
private debounceTimer;
|
|
13
|
+
private pendingChange;
|
|
14
|
+
constructor(options: WatchOptions);
|
|
15
|
+
/**
|
|
16
|
+
* Start watching the routes directory
|
|
17
|
+
*/
|
|
18
|
+
start(): void;
|
|
19
|
+
/**
|
|
20
|
+
* Stop watching
|
|
21
|
+
*/
|
|
22
|
+
stop(): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Get current route tree without triggering callback
|
|
25
|
+
*/
|
|
26
|
+
scan(): RouteTree;
|
|
27
|
+
private handleChange;
|
|
28
|
+
private scheduleCallback;
|
|
29
|
+
private triggerCallback;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Convenience function to create and start a watcher
|
|
33
|
+
*/
|
|
34
|
+
export declare function watchRoutes(options: WatchOptions): RouteWatcher;
|
|
35
|
+
/**
|
|
36
|
+
* Watch routes and regenerate files on changes
|
|
37
|
+
*/
|
|
38
|
+
export declare function watchAndRegenerate(routesDir: string, outputDir: string, options?: Partial<Omit<WatchOptions, 'routesDir' | 'onChange'>>): RouteWatcher;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File watcher for route changes
|
|
3
|
+
*
|
|
4
|
+
* Uses chokidar to watch for file changes in the routes directory
|
|
5
|
+
* and triggers route tree regeneration
|
|
6
|
+
*/
|
|
7
|
+
import chokidar from 'chokidar';
|
|
8
|
+
import { extname } from 'path';
|
|
9
|
+
import { RouteScanner } from './scanner.js';
|
|
10
|
+
const DEFAULT_EXTENSIONS = ['.tsx', '.ts', '.jsx', '.js'];
|
|
11
|
+
const DEFAULT_DEBOUNCE = 100;
|
|
12
|
+
export class RouteWatcher {
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.watcher = null;
|
|
15
|
+
this.debounceTimer = null;
|
|
16
|
+
this.pendingChange = false;
|
|
17
|
+
this.options = {
|
|
18
|
+
debounce: DEFAULT_DEBOUNCE,
|
|
19
|
+
...options,
|
|
20
|
+
};
|
|
21
|
+
this.scanner = new RouteScanner({
|
|
22
|
+
routesDir: options.routesDir,
|
|
23
|
+
extensions: options.extensions,
|
|
24
|
+
includeApi: options.includeApi,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Start watching the routes directory
|
|
29
|
+
*/
|
|
30
|
+
start() {
|
|
31
|
+
if (this.watcher) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const extensions = this.options.extensions ?? DEFAULT_EXTENSIONS;
|
|
35
|
+
const patterns = extensions.map((ext) => `**/*${ext}`);
|
|
36
|
+
this.watcher = chokidar.watch(patterns, {
|
|
37
|
+
cwd: this.options.routesDir,
|
|
38
|
+
ignoreInitial: true,
|
|
39
|
+
persistent: true,
|
|
40
|
+
awaitWriteFinish: {
|
|
41
|
+
stabilityThreshold: 50,
|
|
42
|
+
pollInterval: 10,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
this.watcher.on('add', (path) => this.handleChange('add', path));
|
|
46
|
+
this.watcher.on('change', (path) => this.handleChange('change', path));
|
|
47
|
+
this.watcher.on('unlink', (path) => this.handleChange('unlink', path));
|
|
48
|
+
this.watcher.on('addDir', (path) => this.handleChange('addDir', path));
|
|
49
|
+
this.watcher.on('unlinkDir', (path) => this.handleChange('unlinkDir', path));
|
|
50
|
+
this.watcher.on('error', (error) => {
|
|
51
|
+
console.error('[RouteWatcher] Error:', error);
|
|
52
|
+
});
|
|
53
|
+
// Initial scan (skip if routes are already known)
|
|
54
|
+
if (!this.options.skipInitial) {
|
|
55
|
+
this.triggerCallback();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Stop watching
|
|
60
|
+
*/
|
|
61
|
+
async stop() {
|
|
62
|
+
if (this.debounceTimer) {
|
|
63
|
+
clearTimeout(this.debounceTimer);
|
|
64
|
+
this.debounceTimer = null;
|
|
65
|
+
}
|
|
66
|
+
if (this.watcher) {
|
|
67
|
+
await this.watcher.close();
|
|
68
|
+
this.watcher = null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Get current route tree without triggering callback
|
|
73
|
+
*/
|
|
74
|
+
scan() {
|
|
75
|
+
return this.scanner.scan();
|
|
76
|
+
}
|
|
77
|
+
handleChange(event, path) {
|
|
78
|
+
// Skip non-route files
|
|
79
|
+
const ext = extname(path);
|
|
80
|
+
const extensions = this.options.extensions ?? DEFAULT_EXTENSIONS;
|
|
81
|
+
if (event !== 'addDir' && event !== 'unlinkDir' && !extensions.includes(ext)) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
// Skip excluded files/directories
|
|
85
|
+
if (path.includes('/-') || path.startsWith('-')) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
this.pendingChange = true;
|
|
89
|
+
this.scheduleCallback();
|
|
90
|
+
}
|
|
91
|
+
scheduleCallback() {
|
|
92
|
+
if (this.debounceTimer) {
|
|
93
|
+
clearTimeout(this.debounceTimer);
|
|
94
|
+
}
|
|
95
|
+
this.debounceTimer = setTimeout(() => {
|
|
96
|
+
this.debounceTimer = null;
|
|
97
|
+
if (this.pendingChange) {
|
|
98
|
+
this.pendingChange = false;
|
|
99
|
+
this.triggerCallback();
|
|
100
|
+
}
|
|
101
|
+
}, this.options.debounce);
|
|
102
|
+
}
|
|
103
|
+
async triggerCallback() {
|
|
104
|
+
try {
|
|
105
|
+
const tree = this.scanner.scan();
|
|
106
|
+
await this.options.onChange(tree);
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
console.error('[RouteWatcher] Callback error:', error);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Convenience function to create and start a watcher
|
|
115
|
+
*/
|
|
116
|
+
export function watchRoutes(options) {
|
|
117
|
+
const watcher = new RouteWatcher(options);
|
|
118
|
+
watcher.start();
|
|
119
|
+
return watcher;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Watch routes and regenerate files on changes
|
|
123
|
+
*/
|
|
124
|
+
export function watchAndRegenerate(routesDir, outputDir, options) {
|
|
125
|
+
// Import dynamically to avoid circular dependency
|
|
126
|
+
const { generateRouteTree } = require('./codegen.js');
|
|
127
|
+
return watchRoutes({
|
|
128
|
+
routesDir,
|
|
129
|
+
...options,
|
|
130
|
+
onChange: (tree) => {
|
|
131
|
+
generateRouteTree({ outputDir, routeTree: tree });
|
|
132
|
+
console.log(`[RouteWatcher] Regenerated route tree (${tree.routes.length} routes, ${tree.apiRoutes.length} API routes)`);
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|