@flexireact/core 1.0.2 → 2.0.1

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 (37) hide show
  1. package/README.md +112 -112
  2. package/bin/flexireact.js +23 -0
  3. package/cli/index.ts +9 -21
  4. package/core/cli/{logger.js → logger.ts} +8 -2
  5. package/core/client/{hydration.js → hydration.ts} +10 -0
  6. package/core/client/{islands.js → islands.ts} +6 -1
  7. package/core/client/{navigation.js → navigation.ts} +10 -2
  8. package/core/client/{runtime.js → runtime.ts} +16 -0
  9. package/core/{config.js → config.ts} +7 -4
  10. package/core/{index.js → index.ts} +6 -2
  11. package/core/islands/{index.js → index.ts} +16 -4
  12. package/core/{logger.js → logger.ts} +1 -1
  13. package/core/middleware/{index.js → index.ts} +32 -9
  14. package/core/plugins/{index.js → index.ts} +9 -6
  15. package/core/render/index.ts +1069 -0
  16. package/core/{render.js → render.ts} +7 -5
  17. package/core/router/index.ts +543 -0
  18. package/core/rsc/{index.js → index.ts} +6 -5
  19. package/core/server/{index.js → index.ts} +25 -6
  20. package/core/{server.js → server.ts} +8 -2
  21. package/core/ssg/{index.js → index.ts} +30 -5
  22. package/core/start-dev.ts +6 -0
  23. package/core/start-prod.ts +6 -0
  24. package/core/tsconfig.json +28 -0
  25. package/core/types.ts +239 -0
  26. package/package.json +20 -15
  27. package/cli/index.js +0 -992
  28. package/core/render/index.js +0 -773
  29. package/core/router/index.js +0 -296
  30. /package/core/{api.js → api.ts} +0 -0
  31. /package/core/build/{index.js → index.ts} +0 -0
  32. /package/core/client/{index.js → index.ts} +0 -0
  33. /package/core/{context.js → context.ts} +0 -0
  34. /package/core/{dev.js → dev.ts} +0 -0
  35. /package/core/{loader.js → loader.ts} +0 -0
  36. /package/core/{router.js → router.ts} +0 -0
  37. /package/core/{utils.js → utils.ts} +0 -0
@@ -3,12 +3,14 @@ import React from 'react';
3
3
 
4
4
  /**
5
5
  * Renders a React component to a full HTML page
6
- * @param {React.Component} Component - The React component to render
7
- * @param {Object} props - Props to pass to the component
8
- * @param {Object} options - Rendering options
9
- * @returns {string} Complete HTML string
10
6
  */
11
- export function render(Component, props = {}, options = {}) {
7
+ interface RenderOptions {
8
+ title?: string;
9
+ scripts?: string[];
10
+ styles?: string[];
11
+ }
12
+
13
+ export function render(Component: React.ComponentType<any>, props: Record<string, any> = {}, options: RenderOptions = {}) {
12
14
  const {
13
15
  title = 'FlexiReact App',
14
16
  scripts = [],
@@ -0,0 +1,543 @@
1
+ /**
2
+ * FlexiReact Router v2
3
+ * Advanced file-based routing with nested routes, loading, and error boundaries
4
+ *
5
+ * Supports multiple routing conventions:
6
+ * - pages/ : Traditional file-based routing (index.tsx, about.tsx)
7
+ * - app/ : Next.js style App Router (page.tsx, layout.tsx)
8
+ * - routes/ : FlexiReact v2 routes directory (home.tsx → /, [slug].tsx → /:slug)
9
+ */
10
+
11
+ import fs from 'fs';
12
+ import path from 'path';
13
+ import { isServerComponent, isClientComponent, isIsland } from '../utils.js';
14
+
15
+ /**
16
+ * Route types
17
+ */
18
+ export const RouteType = {
19
+ PAGE: 'page',
20
+ API: 'api',
21
+ LAYOUT: 'layout',
22
+ LOADING: 'loading',
23
+ ERROR: 'error',
24
+ NOT_FOUND: 'not-found'
25
+ };
26
+
27
+ /**
28
+ * Builds the complete route tree from all routing directories
29
+ */
30
+ export function buildRouteTree(pagesDir, layoutsDir, appDir = null, routesDir = null) {
31
+ const projectRoot = path.dirname(pagesDir);
32
+
33
+ const routes: {
34
+ pages: any[];
35
+ api: any[];
36
+ layouts: Map<any, any>;
37
+ tree: Record<string, any>;
38
+ appRoutes: any[];
39
+ flexiRoutes: any[];
40
+ rootLayout?: string;
41
+ } = {
42
+ pages: [],
43
+ api: [],
44
+ layouts: new Map(),
45
+ tree: {},
46
+ appRoutes: [], // Next.js style app router routes
47
+ flexiRoutes: [] // FlexiReact v2 routes/ directory
48
+ };
49
+
50
+ // 1. Scan routes/ directory (FlexiReact v2 - priority)
51
+ const routesDirPath = routesDir || path.join(projectRoot, 'routes');
52
+ if (fs.existsSync(routesDirPath)) {
53
+ scanRoutesDirectory(routesDirPath, routesDirPath, routes);
54
+ }
55
+
56
+ // 2. Scan app/ directory (Next.js style App Router)
57
+ const appDirPath = appDir || path.join(projectRoot, 'app');
58
+ if (fs.existsSync(appDirPath)) {
59
+ scanAppDirectory(appDirPath, appDirPath, routes);
60
+ }
61
+
62
+ // 3. Scan pages/ directory (traditional routing - fallback)
63
+ if (fs.existsSync(pagesDir)) {
64
+ scanDirectory(pagesDir, pagesDir, routes);
65
+ }
66
+
67
+ // 4. Scan layouts/ directory
68
+ if (fs.existsSync(layoutsDir)) {
69
+ scanLayouts(layoutsDir, routes.layouts);
70
+ }
71
+
72
+ // 5. Check for root layout in app/ directory
73
+ const rootLayoutPath = path.join(appDirPath, 'layout.tsx');
74
+ const rootLayoutPathJs = path.join(appDirPath, 'layout.jsx');
75
+ if (fs.existsSync(rootLayoutPath)) {
76
+ routes.rootLayout = rootLayoutPath;
77
+ } else if (fs.existsSync(rootLayoutPathJs)) {
78
+ routes.rootLayout = rootLayoutPathJs;
79
+ }
80
+
81
+ // Build route tree for nested routes
82
+ routes.tree = buildTree([...routes.flexiRoutes, ...routes.appRoutes, ...routes.pages]);
83
+
84
+ return routes;
85
+ }
86
+
87
+ /**
88
+ * Scans routes/ directory for FlexiReact v2 style routing
89
+ *
90
+ * Convention:
91
+ * - home.tsx → /
92
+ * - about.tsx → /about
93
+ * - blog/index.tsx → /blog
94
+ * - blog/[slug].tsx → /blog/:slug
95
+ * - (public)/home.tsx → / (route group, not in URL)
96
+ * - api/hello.ts → /api/hello (API route)
97
+ * - dashboard/layout.tsx → layout for /dashboard/*
98
+ */
99
+ function scanRoutesDirectory(baseDir, currentDir, routes, parentSegments = [], parentLayout = null) {
100
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
101
+
102
+ // Find special files in current directory
103
+ let layoutFile = null;
104
+ let loadingFile = null;
105
+ let errorFile = null;
106
+
107
+ for (const entry of entries) {
108
+ if (entry.isFile()) {
109
+ const name = entry.name.replace(/\.(jsx|js|tsx|ts)$/, '');
110
+ const fullPath = path.join(currentDir, entry.name);
111
+ const ext = path.extname(entry.name);
112
+
113
+ // Special files
114
+ if (name === 'layout') layoutFile = fullPath;
115
+ if (name === 'loading') loadingFile = fullPath;
116
+ if (name === 'error') errorFile = fullPath;
117
+
118
+ // Skip special files and non-route files
119
+ if (['layout', 'loading', 'error', 'not-found'].includes(name)) continue;
120
+ if (!['.tsx', '.jsx', '.ts', '.js'].includes(ext)) continue;
121
+
122
+ // API routes (in api/ folder or .ts/.js files in api/)
123
+ const relativePath = path.relative(baseDir, currentDir);
124
+ const isApiRoute = relativePath.startsWith('api') || relativePath.startsWith('api/');
125
+
126
+ if (isApiRoute && ['.ts', '.js'].includes(ext)) {
127
+ const apiPath = '/' + [...parentSegments, name === 'index' ? '' : name].filter(Boolean).join('/');
128
+ routes.api.push({
129
+ type: RouteType.API,
130
+ path: apiPath.replace(/\/+/g, '/') || '/',
131
+ filePath: fullPath,
132
+ pattern: createRoutePattern(apiPath),
133
+ segments: [...parentSegments, name === 'index' ? '' : name].filter(Boolean)
134
+ });
135
+ continue;
136
+ }
137
+
138
+ // Page routes
139
+ if (['.tsx', '.jsx'].includes(ext)) {
140
+ let routePath;
141
+
142
+ // home.tsx → /
143
+ if (name === 'home' && parentSegments.length === 0) {
144
+ routePath = '/';
145
+ }
146
+ // index.tsx → parent path
147
+ else if (name === 'index') {
148
+ routePath = '/' + parentSegments.join('/') || '/';
149
+ }
150
+ // [param].tsx → /:param
151
+ else if (name.startsWith('[') && name.endsWith(']')) {
152
+ const paramName = name.slice(1, -1);
153
+ // Handle catch-all [...slug]
154
+ if (paramName.startsWith('...')) {
155
+ routePath = '/' + [...parentSegments, '*' + paramName.slice(3)].join('/');
156
+ } else {
157
+ routePath = '/' + [...parentSegments, ':' + paramName].join('/');
158
+ }
159
+ }
160
+ // regular.tsx → /regular
161
+ else {
162
+ routePath = '/' + [...parentSegments, name].join('/');
163
+ }
164
+
165
+ routes.flexiRoutes.push({
166
+ type: RouteType.PAGE,
167
+ path: routePath.replace(/\/+/g, '/'),
168
+ filePath: fullPath,
169
+ pattern: createRoutePattern(routePath),
170
+ segments: routePath.split('/').filter(Boolean),
171
+ layout: layoutFile || parentLayout,
172
+ loading: loadingFile,
173
+ error: errorFile,
174
+ isFlexiRouter: true,
175
+ isServerComponent: isServerComponent(fullPath),
176
+ isClientComponent: isClientComponent(fullPath),
177
+ isIsland: isIsland(fullPath)
178
+ });
179
+ }
180
+ }
181
+ }
182
+
183
+ // Recursively scan subdirectories
184
+ for (const entry of entries) {
185
+ if (entry.isDirectory()) {
186
+ const fullPath = path.join(currentDir, entry.name);
187
+ const dirName = entry.name;
188
+
189
+ // Skip special directories
190
+ if (dirName.startsWith('_') || dirName.startsWith('.')) continue;
191
+
192
+ // Handle route groups (parentheses) - don't add to URL
193
+ const isGroup = dirName.startsWith('(') && dirName.endsWith(')');
194
+
195
+ // Handle dynamic segments [param]
196
+ let segmentName = dirName;
197
+ if (dirName.startsWith('[') && dirName.endsWith(']')) {
198
+ const paramName = dirName.slice(1, -1);
199
+ if (paramName.startsWith('...')) {
200
+ segmentName = '*' + paramName.slice(3);
201
+ } else {
202
+ segmentName = ':' + paramName;
203
+ }
204
+ }
205
+
206
+ const newSegments = isGroup ? parentSegments : [...parentSegments, segmentName];
207
+ const newLayout = layoutFile || parentLayout;
208
+
209
+ scanRoutesDirectory(baseDir, fullPath, routes, newSegments, newLayout);
210
+ }
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Scans app directory for Next.js style routing
216
+ * Supports: page.tsx, layout.tsx, loading.tsx, error.tsx, not-found.tsx
217
+ */
218
+ function scanAppDirectory(baseDir, currentDir, routes, parentSegments = [], parentLayout = null) {
219
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
220
+
221
+ // Find special files in current directory
222
+ const specialFiles = {
223
+ page: null,
224
+ layout: null,
225
+ loading: null,
226
+ error: null,
227
+ notFound: null,
228
+ template: null
229
+ };
230
+
231
+ for (const entry of entries) {
232
+ if (entry.isFile()) {
233
+ const name = entry.name.replace(/\.(jsx|js|tsx|ts)$/, '');
234
+ const fullPath = path.join(currentDir, entry.name);
235
+
236
+ if (name === 'page') specialFiles.page = fullPath;
237
+ if (name === 'layout') specialFiles.layout = fullPath;
238
+ if (name === 'loading') specialFiles.loading = fullPath;
239
+ if (name === 'error') specialFiles.error = fullPath;
240
+ if (name === 'not-found') specialFiles.notFound = fullPath;
241
+ if (name === 'template') specialFiles.template = fullPath;
242
+ }
243
+ }
244
+
245
+ // If there's a page.tsx, create a route
246
+ if (specialFiles.page) {
247
+ const routePath = '/' + parentSegments.join('/') || '/';
248
+
249
+ routes.appRoutes.push({
250
+ type: RouteType.PAGE,
251
+ path: routePath.replace(/\/+/g, '/'),
252
+ filePath: specialFiles.page,
253
+ pattern: createRoutePattern(routePath),
254
+ segments: parentSegments,
255
+ layout: specialFiles.layout || parentLayout,
256
+ loading: specialFiles.loading,
257
+ error: specialFiles.error,
258
+ notFound: specialFiles.notFound,
259
+ template: specialFiles.template,
260
+ isAppRouter: true,
261
+ isServerComponent: isServerComponent(specialFiles.page),
262
+ isClientComponent: isClientComponent(specialFiles.page),
263
+ isIsland: isIsland(specialFiles.page)
264
+ });
265
+ }
266
+
267
+ // Recursively scan subdirectories
268
+ for (const entry of entries) {
269
+ if (entry.isDirectory()) {
270
+ const fullPath = path.join(currentDir, entry.name);
271
+
272
+ // Handle route groups (parentheses) - don't add to URL
273
+ const isGroup = entry.name.startsWith('(') && entry.name.endsWith(')');
274
+
275
+ // Handle dynamic segments [param]
276
+ let segmentName = entry.name;
277
+ if (entry.name.startsWith('[') && entry.name.endsWith(']')) {
278
+ // Convert [param] to :param
279
+ segmentName = ':' + entry.name.slice(1, -1);
280
+ // Handle catch-all [...param]
281
+ if (entry.name.startsWith('[...')) {
282
+ segmentName = '*' + entry.name.slice(4, -1);
283
+ }
284
+ // Handle optional catch-all [[...param]]
285
+ if (entry.name.startsWith('[[...')) {
286
+ segmentName = '*' + entry.name.slice(5, -2);
287
+ }
288
+ }
289
+
290
+ const newSegments = isGroup ? parentSegments : [...parentSegments, segmentName];
291
+ const newLayout = specialFiles.layout || parentLayout;
292
+
293
+ scanAppDirectory(baseDir, fullPath, routes, newSegments, newLayout);
294
+ }
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Scans directory recursively for route files
300
+ */
301
+ function scanDirectory(baseDir, currentDir, routes, parentSegments = []) {
302
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
303
+
304
+ // First, find special files in current directory
305
+ const specialFiles = {
306
+ layout: null,
307
+ loading: null,
308
+ error: null,
309
+ notFound: null
310
+ };
311
+
312
+ for (const entry of entries) {
313
+ if (entry.isFile()) {
314
+ const name = entry.name.replace(/\.(jsx|js|tsx|ts)$/, '');
315
+ const fullPath = path.join(currentDir, entry.name);
316
+
317
+ if (name === 'layout') specialFiles.layout = fullPath;
318
+ if (name === 'loading') specialFiles.loading = fullPath;
319
+ if (name === 'error') specialFiles.error = fullPath;
320
+ if (name === 'not-found' || name === '404') specialFiles.notFound = fullPath;
321
+ }
322
+ }
323
+
324
+ for (const entry of entries) {
325
+ const fullPath = path.join(currentDir, entry.name);
326
+ const relativePath = path.relative(baseDir, fullPath);
327
+
328
+ if (entry.isDirectory()) {
329
+ // Handle route groups (parentheses)
330
+ const isGroup = entry.name.startsWith('(') && entry.name.endsWith(')');
331
+ const newSegments = isGroup ? parentSegments : [...parentSegments, entry.name];
332
+
333
+ scanDirectory(baseDir, fullPath, routes, newSegments);
334
+ } else if (entry.isFile()) {
335
+ const ext = path.extname(entry.name);
336
+ const baseName = path.basename(entry.name, ext);
337
+
338
+ // Skip special files (already processed)
339
+ if (['layout', 'loading', 'error', 'not-found', '404'].includes(baseName)) {
340
+ continue;
341
+ }
342
+
343
+ if (['.jsx', '.js', '.tsx', '.ts'].includes(ext)) {
344
+ const isApi = relativePath.startsWith('api' + path.sep) || relativePath.startsWith('api/');
345
+
346
+ if (isApi && ['.js', '.ts'].includes(ext)) {
347
+ routes.api.push(createRoute(fullPath, baseDir, specialFiles, RouteType.API));
348
+ } else if (!isApi && ['.jsx', '.tsx'].includes(ext)) {
349
+ routes.pages.push(createRoute(fullPath, baseDir, specialFiles, RouteType.PAGE));
350
+ }
351
+ }
352
+ }
353
+ }
354
+ }
355
+
356
+ /**
357
+ * Creates a route object from file path
358
+ */
359
+ function createRoute(filePath, baseDir, specialFiles, type) {
360
+ const relativePath = path.relative(baseDir, filePath);
361
+ const routePath = filePathToRoute(relativePath);
362
+
363
+ return {
364
+ type,
365
+ path: routePath,
366
+ filePath,
367
+ pattern: createRoutePattern(routePath),
368
+ segments: routePath.split('/').filter(Boolean),
369
+ layout: specialFiles.layout,
370
+ loading: specialFiles.loading,
371
+ error: specialFiles.error,
372
+ notFound: specialFiles.notFound,
373
+ isServerComponent: isServerComponent(filePath),
374
+ isClientComponent: isClientComponent(filePath),
375
+ isIsland: isIsland(filePath)
376
+ };
377
+ }
378
+
379
+ /**
380
+ * Scans layouts directory
381
+ */
382
+ function scanLayouts(layoutsDir, layoutsMap) {
383
+ const entries = fs.readdirSync(layoutsDir, { withFileTypes: true });
384
+
385
+ for (const entry of entries) {
386
+ if (entry.isFile() && /\.(jsx|tsx)$/.test(entry.name)) {
387
+ const name = entry.name.replace(/\.(jsx|tsx)$/, '');
388
+ layoutsMap.set(name, path.join(layoutsDir, entry.name));
389
+ }
390
+ }
391
+ }
392
+
393
+ /**
394
+ * Converts file path to route path
395
+ */
396
+ function filePathToRoute(filePath) {
397
+ let route = filePath.replace(/\\/g, '/');
398
+
399
+ // Remove extension
400
+ route = route.replace(/\.(jsx|js|tsx|ts)$/, '');
401
+
402
+ // Convert [param] to :param
403
+ route = route.replace(/\[\.\.\.([^\]]+)\]/g, '*$1'); // Catch-all [...slug]
404
+ route = route.replace(/\[([^\]]+)\]/g, ':$1');
405
+
406
+ // Handle index files
407
+ if (route.endsWith('/index')) {
408
+ route = route.slice(0, -6) || '/';
409
+ } else if (route === 'index') {
410
+ route = '/';
411
+ }
412
+
413
+ // Handle route groups - remove (groupName) from path
414
+ route = route.replace(/\/?\([^)]+\)\/?/g, '/');
415
+
416
+ // Ensure leading slash and clean up
417
+ if (!route.startsWith('/')) {
418
+ route = '/' + route;
419
+ }
420
+ route = route.replace(/\/+/g, '/');
421
+
422
+ return route;
423
+ }
424
+
425
+ /**
426
+ * Creates regex pattern for route matching
427
+ */
428
+ function createRoutePattern(routePath) {
429
+ let pattern = routePath
430
+ .replace(/\*[^/]*/g, '(.*)') // Catch-all
431
+ .replace(/:[^/]+/g, '([^/]+)') // Dynamic segments
432
+ .replace(/\//g, '\\/');
433
+
434
+ return new RegExp(`^${pattern}$`);
435
+ }
436
+
437
+ /**
438
+ * Builds a tree structure for nested routes
439
+ */
440
+ function buildTree(routes) {
441
+ const tree = { children: {}, routes: [] };
442
+
443
+ for (const route of routes) {
444
+ let current = tree;
445
+
446
+ for (const segment of route.segments) {
447
+ if (!current.children[segment]) {
448
+ current.children[segment] = { children: {}, routes: [] };
449
+ }
450
+ current = current.children[segment];
451
+ }
452
+
453
+ current.routes.push(route);
454
+ }
455
+
456
+ return tree;
457
+ }
458
+
459
+ /**
460
+ * Matches URL path against routes
461
+ */
462
+ export function matchRoute(urlPath, routes) {
463
+ const normalizedPath = urlPath === '' ? '/' : urlPath.split('?')[0];
464
+
465
+ for (const route of routes) {
466
+ const match = normalizedPath.match(route.pattern);
467
+
468
+ if (match) {
469
+ const params = extractParams(route.path, match);
470
+ return { ...route, params };
471
+ }
472
+ }
473
+
474
+ return null;
475
+ }
476
+
477
+ /**
478
+ * Extracts parameters from route match
479
+ */
480
+ function extractParams(routePath, match) {
481
+ const params = {};
482
+ const paramNames = [];
483
+
484
+ // Extract param names from route path
485
+ const paramRegex = /:([^/]+)|\*([^/]*)/g;
486
+ let paramMatch;
487
+
488
+ while ((paramMatch = paramRegex.exec(routePath)) !== null) {
489
+ paramNames.push(paramMatch[1] || paramMatch[2] || 'splat');
490
+ }
491
+
492
+ paramNames.forEach((name, index) => {
493
+ params[name] = match[index + 1];
494
+ });
495
+
496
+ return params;
497
+ }
498
+
499
+ /**
500
+ * Finds all layouts that apply to a route
501
+ */
502
+ export function findRouteLayouts(route, layoutsMap) {
503
+ const layouts = [];
504
+
505
+ // Check for segment-based layouts
506
+ let currentPath = '';
507
+ for (const segment of route.segments) {
508
+ currentPath += '/' + segment;
509
+ const layoutName = segment;
510
+
511
+ if (layoutsMap.has(layoutName)) {
512
+ layouts.push({
513
+ name: layoutName,
514
+ filePath: layoutsMap.get(layoutName)
515
+ });
516
+ }
517
+ }
518
+
519
+ // Check for route-specific layout
520
+ if (route.layout) {
521
+ layouts.push({
522
+ name: 'route',
523
+ filePath: route.layout
524
+ });
525
+ }
526
+
527
+ // Check for root layout
528
+ if (layoutsMap.has('root')) {
529
+ layouts.unshift({
530
+ name: 'root',
531
+ filePath: layoutsMap.get('root')
532
+ });
533
+ }
534
+
535
+ return layouts;
536
+ }
537
+
538
+ export default {
539
+ buildRouteTree,
540
+ matchRoute,
541
+ findRouteLayouts,
542
+ RouteType
543
+ };
@@ -94,16 +94,17 @@ function serializeNode(node) {
94
94
  const { type, props } = node;
95
95
 
96
96
  // Handle client component references
97
- if (type.$$typeof === Symbol.for('react.client.reference')) {
97
+ const typeAny = type as any;
98
+ if (typeAny.$$typeof === Symbol.for('react.client.reference')) {
98
99
  return {
99
100
  $$type: 'client',
100
- $$id: type.$$id,
101
+ $$id: typeAny.$$id,
101
102
  props: serializeProps(props)
102
103
  };
103
104
  }
104
105
 
105
106
  // Handle regular elements
106
- const typeName = typeof type === 'string' ? type : type.displayName || type.name || 'Unknown';
107
+ const typeName = typeof type === 'string' ? type : (typeAny.displayName || typeAny.name || 'Unknown');
107
108
 
108
109
  return {
109
110
  $$type: 'element',
@@ -118,8 +119,8 @@ function serializeNode(node) {
118
119
  /**
119
120
  * Serializes props, handling special cases
120
121
  */
121
- function serializeProps(props) {
122
- const serialized = {};
122
+ function serializeProps(props: Record<string, any>) {
123
+ const serialized: Record<string, any> = {};
123
124
 
124
125
  for (const [key, value] of Object.entries(props)) {
125
126
  if (key === 'children') {
@@ -44,7 +44,14 @@ const MIME_TYPES = {
44
44
  /**
45
45
  * Creates the FlexiReact server
46
46
  */
47
- export async function createServer(options = {}) {
47
+ interface CreateServerOptions {
48
+ projectRoot?: string;
49
+ mode?: 'development' | 'production';
50
+ port?: number;
51
+ host?: string;
52
+ }
53
+
54
+ export async function createServer(options: CreateServerOptions = {}) {
48
55
  const serverStartTime = Date.now();
49
56
  const projectRoot = options.projectRoot || process.cwd();
50
57
  const isDev = options.mode === 'development';
@@ -128,7 +135,19 @@ export async function createServer(options = {}) {
128
135
  return await handleApiRoute(req, res, apiRoute, loadModule);
129
136
  }
130
137
 
131
- // Match page routes
138
+ // Match FlexiReact v2 routes (routes/ directory - priority)
139
+ const flexiRoute = matchRoute(effectivePath, routes.flexiRoutes || []);
140
+ if (flexiRoute) {
141
+ return await handlePageRoute(req, res, flexiRoute, routes, config, loadModule, url);
142
+ }
143
+
144
+ // Match app routes (app/ directory - Next.js style)
145
+ const appRoute = matchRoute(effectivePath, routes.appRoutes || []);
146
+ if (appRoute) {
147
+ return await handlePageRoute(req, res, appRoute, routes, config, loadModule, url);
148
+ }
149
+
150
+ // Match page routes (pages/ directory - legacy fallback)
132
151
  const pageRoute = matchRoute(effectivePath, routes.pages);
133
152
  if (pageRoute) {
134
153
  return await handlePageRoute(req, res, pageRoute, routes, config, loadModule, url);
@@ -166,7 +185,7 @@ export async function createServer(options = {}) {
166
185
 
167
186
  return new Promise((resolve, reject) => {
168
187
  // Handle port in use error
169
- server.on('error', (err) => {
188
+ server.on('error', (err: NodeJS.ErrnoException) => {
170
189
  if (err.code === 'EADDRINUSE') {
171
190
  logger.portInUse(port);
172
191
  process.exit(1);
@@ -264,8 +283,8 @@ async function handleApiRoute(req, res, route, loadModule) {
264
283
  // Enhanced response
265
284
  const enhancedRes = createApiResponse(res);
266
285
 
267
- // Find handler
268
- const handler = module[method] || module.default;
286
+ // Find handler (check both lowercase and uppercase method names)
287
+ const handler = module[method] || module[method.toUpperCase()] || module.default;
269
288
 
270
289
  if (!handler) {
271
290
  enhancedRes.status(405).json({ error: 'Method not allowed' });
@@ -457,7 +476,7 @@ async function handlePageRoute(req, res, route, routes, config, loadModule, url)
457
476
  favicon: config.favicon || null,
458
477
  needsHydration: isClientComponent,
459
478
  componentPath: route.filePath,
460
- route: route.path || pathname,
479
+ route: route.path || url.pathname,
461
480
  isSSG: !!pageModule.getStaticProps
462
481
  });
463
482
 
@@ -39,7 +39,13 @@ const MIME_TYPES = {
39
39
  /**
40
40
  * Creates and starts the FlexiReact dev server
41
41
  */
42
- export function createServer(options = {}) {
42
+ interface ServerOptions {
43
+ port?: number;
44
+ host?: string;
45
+ pagesDir?: string;
46
+ }
47
+
48
+ export function createServer(options: ServerOptions = {}) {
43
49
  const {
44
50
  port = PORT,
45
51
  host = HOST,
@@ -97,7 +103,7 @@ export function createServer(options = {}) {
97
103
  }
98
104
  });
99
105
 
100
- server.listen(port, host, () => {
106
+ server.listen(port as number, host as string, () => {
101
107
  console.log('');
102
108
  console.log(' ⚡ FlexiReact Dev Server');
103
109
  console.log(' ─────────────────────────');