@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.
Files changed (115) hide show
  1. package/README.md +310 -24
  2. package/bin/zap +0 -0
  3. package/bin/zap-codegen +0 -0
  4. package/dist/cli/commands/build.d.ts +11 -0
  5. package/dist/cli/commands/build.js +282 -0
  6. package/dist/cli/commands/codegen.d.ts +8 -0
  7. package/dist/cli/commands/codegen.js +95 -0
  8. package/dist/cli/commands/dev.d.ts +20 -0
  9. package/dist/cli/commands/dev.js +78 -0
  10. package/dist/cli/commands/new.d.ts +9 -0
  11. package/dist/cli/commands/new.js +307 -0
  12. package/dist/cli/commands/routes-old.d.ts +9 -0
  13. package/dist/cli/commands/routes-old.js +106 -0
  14. package/dist/cli/commands/routes.d.ts +11 -0
  15. package/dist/cli/commands/routes.js +280 -0
  16. package/dist/cli/commands/serve.d.ts +17 -0
  17. package/dist/cli/commands/serve.js +386 -0
  18. package/dist/cli/index.d.ts +2 -0
  19. package/dist/cli/index.js +76 -0
  20. package/dist/cli/utils/index.d.ts +2 -0
  21. package/dist/cli/utils/index.js +2 -0
  22. package/dist/cli/utils/logger.d.ts +84 -0
  23. package/dist/cli/utils/logger.js +181 -0
  24. package/dist/cli/utils/port-finder.d.ts +8 -0
  25. package/dist/cli/utils/port-finder.js +48 -0
  26. package/dist/dev-server/codegen-runner.d.ts +41 -0
  27. package/dist/dev-server/codegen-runner.js +172 -0
  28. package/dist/dev-server/hot-reload.d.ts +72 -0
  29. package/dist/dev-server/hot-reload.js +280 -0
  30. package/dist/dev-server/index.d.ts +8 -0
  31. package/dist/dev-server/index.js +8 -0
  32. package/dist/dev-server/route-scanner.d.ts +71 -0
  33. package/dist/dev-server/route-scanner.js +114 -0
  34. package/dist/dev-server/rust-builder.d.ts +66 -0
  35. package/dist/dev-server/rust-builder.js +286 -0
  36. package/dist/dev-server/server.d.ts +147 -0
  37. package/dist/dev-server/server.js +658 -0
  38. package/dist/dev-server/vite-proxy.d.ts +56 -0
  39. package/dist/dev-server/vite-proxy.js +212 -0
  40. package/dist/dev-server/watcher.d.ts +48 -0
  41. package/dist/dev-server/watcher.js +127 -0
  42. package/dist/router/codegen-enhanced.d.ts +5 -0
  43. package/dist/router/codegen-enhanced.js +275 -0
  44. package/dist/router/codegen.d.ts +17 -0
  45. package/dist/router/codegen.js +654 -0
  46. package/dist/router/index.d.ts +16 -0
  47. package/dist/router/index.js +19 -0
  48. package/dist/router/scanner.d.ts +86 -0
  49. package/dist/router/scanner.js +689 -0
  50. package/dist/router/ssg.d.ts +115 -0
  51. package/dist/router/ssg.js +202 -0
  52. package/dist/router/types.d.ts +124 -0
  53. package/dist/router/types.js +9 -0
  54. package/dist/router/watch.d.ts +38 -0
  55. package/dist/router/watch.js +135 -0
  56. package/dist/runtime/csrf.d.ts +146 -0
  57. package/dist/runtime/csrf.js +166 -0
  58. package/dist/runtime/error-boundary.d.ts +129 -0
  59. package/dist/runtime/error-boundary.js +287 -0
  60. package/dist/runtime/hooks.d.ts +83 -0
  61. package/dist/runtime/hooks.js +96 -0
  62. package/dist/runtime/index.d.ts +229 -0
  63. package/dist/runtime/index.js +449 -0
  64. package/dist/runtime/ipc-client.d.ts +144 -0
  65. package/dist/runtime/ipc-client.js +621 -0
  66. package/dist/runtime/logger.d.ts +71 -0
  67. package/dist/runtime/logger.js +164 -0
  68. package/dist/runtime/middleware.d.ts +66 -0
  69. package/dist/runtime/middleware.js +114 -0
  70. package/dist/runtime/process-manager.d.ts +51 -0
  71. package/dist/runtime/process-manager.js +207 -0
  72. package/dist/runtime/router-simple.d.ts +98 -0
  73. package/dist/runtime/router-simple.js +330 -0
  74. package/dist/runtime/router.d.ts +103 -0
  75. package/dist/runtime/router.js +435 -0
  76. package/dist/runtime/rpc-client.d.ts +35 -0
  77. package/dist/runtime/rpc-client.js +140 -0
  78. package/dist/runtime/streaming-utils.d.ts +86 -0
  79. package/dist/runtime/streaming-utils.js +150 -0
  80. package/dist/runtime/types.d.ts +465 -0
  81. package/dist/runtime/types.js +60 -0
  82. package/dist/runtime/websockets-utils.d.ts +50 -0
  83. package/dist/runtime/websockets-utils.js +92 -0
  84. package/package.json +30 -20
  85. package/index.js +0 -29
  86. package/internal/cli/package.json +0 -46
  87. package/internal/cli/tsconfig.tsbuildinfo +0 -1
  88. package/internal/dev-server/node_modules/ora/index.d.ts +0 -332
  89. package/internal/dev-server/node_modules/ora/index.js +0 -416
  90. package/internal/dev-server/node_modules/ora/license +0 -9
  91. package/internal/dev-server/node_modules/ora/node_modules/string-width/index.d.ts +0 -36
  92. package/internal/dev-server/node_modules/ora/node_modules/string-width/index.js +0 -65
  93. package/internal/dev-server/node_modules/ora/node_modules/string-width/license +0 -9
  94. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/LICENSE-MIT.txt +0 -20
  95. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/README.md +0 -107
  96. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/index.d.ts +0 -3
  97. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/index.js +0 -4
  98. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/index.mjs +0 -4
  99. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/package.json +0 -46
  100. package/internal/dev-server/node_modules/ora/node_modules/string-width/package.json +0 -60
  101. package/internal/dev-server/node_modules/ora/node_modules/string-width/readme.md +0 -62
  102. package/internal/dev-server/node_modules/ora/package.json +0 -66
  103. package/internal/dev-server/node_modules/ora/readme.md +0 -325
  104. package/internal/dev-server/package.json +0 -41
  105. package/internal/router/package.json +0 -28
  106. package/internal/runtime/package.json +0 -41
  107. package/internal/runtime/src/error-boundary.tsx +0 -476
  108. package/internal/runtime/src/router-simple.tsx +0 -640
  109. package/internal/runtime/src/router.tsx +0 -771
  110. package/internal/runtime/tsconfig.tsbuildinfo +0 -1
  111. package/src/errors.js +0 -33
  112. package/src/logger.js +0 -10
  113. package/src/middleware.js +0 -32
  114. package/src/router.js +0 -41
  115. 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,9 @@
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 {};
@@ -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
+ }