@flexireact/core 1.0.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +117 -116
  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/{index.js → index.ts} +2 -1
  10. package/core/islands/{index.js → index.ts} +16 -4
  11. package/core/{logger.js → logger.ts} +1 -1
  12. package/core/middleware/{index.js → index.ts} +32 -9
  13. package/core/plugins/{index.js → index.ts} +9 -6
  14. package/core/render/index.ts +1069 -0
  15. package/core/{render.js → render.ts} +7 -5
  16. package/core/router/index.ts +543 -0
  17. package/core/rsc/{index.js → index.ts} +6 -5
  18. package/core/server/{index.js → index.ts} +25 -6
  19. package/core/{server.js → server.ts} +8 -2
  20. package/core/ssg/{index.js → index.ts} +30 -5
  21. package/core/start-dev.ts +6 -0
  22. package/core/start-prod.ts +6 -0
  23. package/core/tsconfig.json +28 -0
  24. package/core/types.ts +239 -0
  25. package/package.json +19 -14
  26. package/cli/index.js +0 -992
  27. package/core/render/index.js +0 -765
  28. package/core/router/index.js +0 -296
  29. /package/core/{api.js → api.ts} +0 -0
  30. /package/core/build/{index.js → index.ts} +0 -0
  31. /package/core/client/{index.js → index.ts} +0 -0
  32. /package/core/{config.js → config.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
package/README.md CHANGED
@@ -1,25 +1,34 @@
1
1
  <p align="center">
2
- <img src="./assets/logo.png" alt="FlexiReact Logo" width="400" />
2
+ <img src="./assets/flexireact.webp" alt="FlexiReact Logo" width="400" />
3
3
  </p>
4
4
 
5
- <h1 align="center">FlexiReact</h1>
5
+ <h1 align="center">FlexiReact v2</h1>
6
6
 
7
7
  <p align="center">
8
8
  <strong>The Modern React Framework</strong>
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
- A blazing-fast React framework with TypeScript, Tailwind CSS, SSR, SSG, Islands architecture, and file-based routing.<br/>
12
+ A blazing-fast React framework with TypeScript, Tailwind CSS v4, SSR, SSG, Islands architecture, and file-based routing.<br/>
13
13
  Inspired by Next.js, Remix, Astro, and TanStack Start — but simpler and lighter.
14
14
  </p>
15
15
 
16
16
  <p align="center">
17
- <a href="https://www.npmjs.com/package/flexireact"><img src="https://img.shields.io/npm/v/flexireact.svg" alt="npm version" /></a>
17
+ <a href="https://www.npmjs.com/package/@flexireact/core"><img src="https://img.shields.io/npm/v/@flexireact/core.svg" alt="npm version" /></a>
18
18
  <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT" /></a>
19
- <a href="#"><img src="https://img.shields.io/badge/TypeScript-Ready-blue.svg" alt="TypeScript Ready" /></a>
20
- <a href="#"><img src="https://img.shields.io/badge/Tailwind-CSS-38B2AC.svg" alt="Tailwind CSS" /></a>
19
+ <a href="#"><img src="https://img.shields.io/badge/TypeScript-Native-blue.svg" alt="TypeScript Native" /></a>
20
+ <a href="#"><img src="https://img.shields.io/badge/Tailwind-v4-38B2AC.svg" alt="Tailwind CSS v4" /></a>
21
21
  </p>
22
22
 
23
+ ## 🆕 What's New in v2
24
+
25
+ - **TypeScript Native** — Core rewritten in TypeScript for better DX
26
+ - **Tailwind CSS v4** — New `@import "tailwindcss"` and `@theme` syntax
27
+ - **Routes Directory** — New `routes/` directory with route groups, dynamic segments
28
+ - **Modern 404 Page** — Beautiful, interactive error pages
29
+ - **Enhanced DevTools** — Precise error messages with color-coded render times
30
+ - **Improved CLI** — TypeScript-based CLI with better templates
31
+
23
32
  ## ✨ Features
24
33
 
25
34
  ### 🏗️ Core Framework
@@ -140,8 +149,9 @@
140
149
 
141
150
  ```bash
142
151
  # Create a new project
143
- npx flexireact create myapp
144
- cd myapp
152
+ npx create-flexireact@latest my-app
153
+ cd my-app
154
+ npm install
145
155
 
146
156
  # Start development server
147
157
  npm run dev
@@ -155,51 +165,73 @@ npm run start
155
165
 
156
166
  Open http://localhost:3000
157
167
 
158
- ## 📁 Project Structure
168
+ ## 📁 Project Structure (v2)
169
+
170
+ FlexiReact v2 introduces a new `routes/` directory with enhanced routing capabilities:
159
171
 
160
172
  ```
161
173
  myapp/
162
- ├── app/ # App directory
163
- │ ├── components/ # Reusable components
164
- │ │ ├── Button.tsx # Button component
165
- │ │ ├── Card.tsx # Card component
166
- │ │ ├── Navbar.tsx # Navigation bar
167
- │ │ └── index.ts # Component exports
174
+ ├── app/ # App directory (layout, components, styles)
175
+ │ ├── components/
176
+ │ │ ├── ui/ # UI components (Button, Card, etc.)
177
+ │ │ └── layout/ # Layout components (Navbar, Footer)
168
178
  │ ├── styles/
169
- │ │ └── globals.css # Global styles + Tailwind
170
- └── layout.tsx # Root layout
171
- ├── pages/ # Routes (file-based)
172
- ├── index.tsx # /
173
- │ ├── about.tsx # /about
179
+ │ │ └── globals.css # Global styles + Tailwind v4
180
+ ├── providers/ # React context providers
181
+ │ └── layout.tsx # Root layout
182
+ ├── routes/ # FlexiReact v2 file-based routing
183
+ │ ├── (public)/ # Route groups (don't affect URL)
184
+ │ │ ├── home.tsx # → /
185
+ │ │ └── about.tsx # → /about
174
186
  │ ├── blog/
175
- │ │ ├── index.tsx # → /blog
176
- │ │ └── [slug].tsx # → /blog/:slug
187
+ │ │ ├── index.tsx # → /blog
188
+ │ │ └── [slug].tsx # → /blog/:slug
177
189
  │ └── api/
178
- │ └── hello.ts # → /api/hello
179
- ├── public/ # Static assets
180
- ├── tailwind.config.js # Tailwind configuration
181
- ├── tsconfig.json # TypeScript configuration
182
- ├── flexireact.config.ts # FlexiReact configuration
190
+ │ └── hello.ts # → /api/hello
191
+ ├── lib/ # Utilities
192
+ │ └── utils.ts
193
+ ├── public/ # Static assets
194
+ ├── tsconfig.json # TypeScript configuration
195
+ ├── flexireact.config.ts # FlexiReact configuration
183
196
  └── package.json
184
197
  ```
185
198
 
186
- ## 🛣️ Routing
199
+ ## 🛣️ Routing (v2)
200
+
201
+ FlexiReact v2 supports three routing conventions (in priority order):
202
+
203
+ ### 1. Routes Directory (Recommended)
204
+
205
+ | File | Route |
206
+ |------|-------|
207
+ | `routes/(public)/home.tsx` | `/` |
208
+ | `routes/(public)/about.tsx` | `/about` |
209
+ | `routes/blog/index.tsx` | `/blog` |
210
+ | `routes/blog/[slug].tsx` | `/blog/:slug` |
211
+ | `routes/[...path].tsx` | Catch-all route |
212
+ | `routes/api/hello.ts` | `/api/hello` |
213
+
214
+ ### 2. App Directory (Next.js style)
215
+
216
+ | File | Route |
217
+ |------|-------|
218
+ | `app/page.tsx` | `/` |
219
+ | `app/about/page.tsx` | `/about` |
220
+ | `app/blog/[slug]/page.tsx` | `/blog/:slug` |
187
221
 
188
- ### Page Routes
222
+ ### 3. Pages Directory (Legacy)
189
223
 
190
224
  | File | Route |
191
225
  |------|-------|
192
- | `pages/index.jsx` | `/` |
193
- | `pages/about.jsx` | `/about` |
194
- | `pages/blog/[slug].jsx` | `/blog/:slug` |
195
- | `pages/[...path].jsx` | Catch-all route |
226
+ | `pages/index.tsx` | `/` |
227
+ | `pages/about.tsx` | `/about` |
196
228
 
197
229
  ### Dynamic Routes
198
230
 
199
- ```jsx
200
- // pages/users/[id].jsx
201
- export default function User({ params }) {
202
- return <h1>User: {params.id}</h1>;
231
+ ```tsx
232
+ // routes/blog/[slug].tsx
233
+ export default function BlogPost({ params }: { params: { slug: string } }) {
234
+ return <h1>Post: {params.slug}</h1>;
203
235
  }
204
236
  ```
205
237
 
@@ -208,42 +240,62 @@ export default function User({ params }) {
208
240
  Use parentheses to group routes without affecting the URL:
209
241
 
210
242
  ```
211
- pages/
212
- (marketing)/
213
- about.jsx # → /about
214
- contact.jsx # → /contact
215
- (app)/
216
- dashboard.jsx # → /dashboard
243
+ routes/
244
+ (public)/
245
+ home.tsx # → /
246
+ about.tsx # → /about
247
+ (dashboard)/
248
+ settings.tsx # → /settings
217
249
  ```
218
250
 
219
251
  ## 📐 Layouts
220
252
 
221
- Create persistent layouts in `layouts/`:
253
+ Create layouts in `app/layout.tsx` or within route directories:
222
254
 
223
- ```jsx
224
- // layouts/root.jsx
225
- export default function RootLayout({ children }) {
255
+ ```tsx
256
+ // app/layout.tsx
257
+ import { Navbar } from './components/layout/Navbar';
258
+ import { Footer } from './components/layout/Footer';
259
+
260
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
226
261
  return (
227
- <div>
228
- <header>My App</header>
229
- <main>{children}</main>
230
- <footer>© 2024</footer>
231
- </div>
262
+ <html lang="en" className="dark">
263
+ <head>
264
+ <link rel="stylesheet" href="/styles.css" />
265
+ </head>
266
+ <body className="bg-background text-foreground">
267
+ <Navbar />
268
+ <main>{children}</main>
269
+ <Footer />
270
+ </body>
271
+ </html>
232
272
  );
233
273
  }
234
274
  ```
235
275
 
236
276
  ## ⏳ Loading & Error States
237
277
 
238
- ```jsx
239
- // pages/loading.jsx
278
+ ```tsx
279
+ // routes/loading.tsx
240
280
  export default function Loading() {
241
- return <div>Loading...</div>;
281
+ return (
282
+ <div className="flex items-center justify-center min-h-screen">
283
+ <div className="w-8 h-8 border-2 border-primary border-t-transparent rounded-full animate-spin" />
284
+ </div>
285
+ );
242
286
  }
243
287
 
244
- // pages/error.jsx
245
- export default function Error({ error }) {
246
- return <div>Error: {error.message}</div>;
288
+ // routes/error.tsx
289
+ export default function Error({ error, reset }: { error: Error; reset: () => void }) {
290
+ return (
291
+ <div className="flex flex-col items-center justify-center min-h-screen">
292
+ <h1 className="text-4xl font-bold text-red-500">Something went wrong</h1>
293
+ <p className="text-gray-400 mt-4">{error.message}</p>
294
+ <button onClick={reset} className="mt-8 px-6 py-3 bg-primary text-black rounded-lg">
295
+ Try again
296
+ </button>
297
+ </div>
298
+ );
247
299
  }
248
300
  ```
249
301
 
@@ -479,63 +531,6 @@ Islands provide partial hydration:
479
531
  - **Better performance** — less code to parse/execute
480
532
  - **Selective loading** — hydrate on visibility, interaction, etc.
481
533
 
482
- ## 🎨 FlexiUI - Official UI Library
483
-
484
- FlexiReact comes with an official UI component library: **@flexireact/flexi-ui**
485
-
486
- ```bash
487
- npm install @flexireact/flexi-ui
488
- ```
489
-
490
- ### Features
491
- - 🌙 **Dark-first design** with neon emerald accents
492
- - ♿ **Fully accessible** (ARIA-compliant, Radix UI primitives)
493
- - 🎯 **TypeScript native** with full type safety
494
- - 🌳 **Tree-shakeable** — import only what you need
495
- - ⚡ **SSR ready** — works with FlexiReact SSR
496
-
497
- ### Quick Setup
498
-
499
- ```js
500
- // tailwind.config.js
501
- const { flexiUIPlugin } = require('@flexireact/flexi-ui/tailwind');
502
-
503
- module.exports = {
504
- darkMode: 'class',
505
- content: [
506
- './pages/**/*.{js,ts,jsx,tsx}',
507
- './node_modules/@flexireact/flexi-ui/dist/**/*.js',
508
- ],
509
- plugins: [flexiUIPlugin],
510
- };
511
- ```
512
-
513
- ### Usage
514
-
515
- ```jsx
516
- import { Button, Card, Badge, Input } from '@flexireact/flexi-ui';
517
-
518
- export default function MyPage() {
519
- return (
520
- <Card>
521
- <Badge variant="success">New</Badge>
522
- <h2>Welcome!</h2>
523
- <Input placeholder="Enter your email" />
524
- <Button>Get Started</Button>
525
- </Card>
526
- );
527
- }
528
- ```
529
-
530
- ### Available Components
531
- - **Core**: Button, Input, Textarea, Checkbox, Switch, Select
532
- - **Display**: Card, Badge, Avatar, Tooltip
533
- - **Feedback**: Alert, Toast, Spinner, Skeleton, Progress
534
- - **Overlay**: Modal, Drawer, Dropdown
535
- - **Layout**: Separator, Tabs
536
-
537
- 📖 [FlexiUI Documentation](https://github.com/flexireact/flexi-ui)
538
-
539
534
  ---
540
535
 
541
536
  ## 📋 Requirements
@@ -543,7 +538,13 @@ export default function MyPage() {
543
538
  - Node.js 18+
544
539
  - React 18+
545
540
 
541
+ ## 🔗 Links
542
+
543
+ - [GitHub Repository](https://github.com/flexireact/flexireact)
544
+ - [npm Package](https://www.npmjs.com/package/@flexireact/core)
545
+ - [Issues](https://github.com/flexireact/flexireact/issues)
546
+
546
547
  ## 📄 License
547
548
 
548
- MIT
549
+ MIT © [FlexiReact Team](https://github.com/flexireact)
549
550
 
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * FlexiReact CLI Entry Point
5
+ * Uses tsx to run TypeScript CLI directly
6
+ */
7
+
8
+ import { spawnSync } from 'child_process';
9
+ import { fileURLToPath } from 'url';
10
+ import { dirname, join } from 'path';
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = dirname(__filename);
14
+
15
+ const cliPath = join(__dirname, '..', 'cli', 'index.ts');
16
+
17
+ // Run the CLI with tsx
18
+ const result = spawnSync('npx', ['tsx', cliPath, ...process.argv.slice(2)], {
19
+ stdio: 'inherit',
20
+ shell: true,
21
+ });
22
+
23
+ process.exit(result.status ?? 0);
package/cli/index.ts CHANGED
@@ -112,7 +112,7 @@ async function createProject(projectName?: string): Promise<void> {
112
112
  name: 'projectName',
113
113
  message: 'Project name:',
114
114
  initial: 'my-flexi-app',
115
- validate: (value) => value.length > 0 || 'Project name is required'
115
+ validate: (value: string) => value.length > 0 || 'Project name is required'
116
116
  });
117
117
  name = response.projectName;
118
118
  if (!name) process.exit(1);
@@ -830,21 +830,15 @@ ${pc.cyan(' │')} ${pc.cyan('│')}
830
830
  ${pc.cyan(' ╰─────────────────────────────────────────╯')}
831
831
  `);
832
832
 
833
- const loaderPath = path.join(__dirname, '..', 'core', 'loader.js');
834
- const serverPath = path.join(__dirname, '..', 'core', 'server', 'index.js');
835
- const loaderUrl = pathToFileURL(loaderPath).href;
833
+ const startDevPath = path.join(__dirname, '..', 'core', 'start-dev.ts');
836
834
 
837
835
  const child = spawn(
838
- process.execPath,
839
- [
840
- '--import',
841
- `data:text/javascript,import { register } from 'node:module'; register('${loaderUrl}', import.meta.url);`,
842
- '-e',
843
- `import('${pathToFileURL(serverPath).href}').then(m => m.createServer({ mode: 'development' }))`
844
- ],
836
+ 'npx',
837
+ ['tsx', startDevPath],
845
838
  {
846
839
  stdio: 'inherit',
847
840
  cwd: process.cwd(),
841
+ shell: true,
848
842
  env: { ...process.env, NODE_ENV: 'development', FORCE_COLOR: '1' }
849
843
  }
850
844
  );
@@ -909,21 +903,15 @@ async function runStart(): Promise<void> {
909
903
  log.info('Starting production server...');
910
904
  log.blank();
911
905
 
912
- const loaderPath = path.join(__dirname, '..', 'core', 'loader.js');
913
- const serverPath = path.join(__dirname, '..', 'core', 'server', 'index.js');
914
- const loaderUrl = pathToFileURL(loaderPath).href;
906
+ const startProdPath = path.join(__dirname, '..', 'core', 'start-prod.ts');
915
907
 
916
908
  const child = spawn(
917
- process.execPath,
918
- [
919
- '--import',
920
- `data:text/javascript,import { register } from 'node:module'; register('${loaderUrl}', import.meta.url);`,
921
- '-e',
922
- `import('${pathToFileURL(serverPath).href}').then(m => m.createServer({ mode: 'production' }))`
923
- ],
909
+ 'npx',
910
+ ['tsx', startProdPath],
924
911
  {
925
912
  stdio: 'inherit',
926
913
  cwd: process.cwd(),
914
+ shell: true,
927
915
  env: { ...process.env, NODE_ENV: 'production' }
928
916
  }
929
917
  );
@@ -65,7 +65,13 @@ const box = {
65
65
  verticalLeft: '┤',
66
66
  };
67
67
 
68
- function createBox(content, options = {}) {
68
+ interface BoxOptions {
69
+ padding?: number;
70
+ borderColor?: (s: string) => string;
71
+ width?: number;
72
+ }
73
+
74
+ function createBox(content: string, options: BoxOptions = {}) {
69
75
  const {
70
76
  padding = 1,
71
77
  borderColor = colors.primary,
@@ -180,7 +186,7 @@ function pluginLoader(plugins = []) {
180
186
  // HTTP Request Logger
181
187
  // ============================================================================
182
188
 
183
- function request(method, path, statusCode, duration, options = {}) {
189
+ function request(method: string, path: string, statusCode: number, duration: number, options: { type?: string } = {}) {
184
190
  const { type = 'dynamic' } = options;
185
191
 
186
192
  // Method colors
@@ -6,6 +6,16 @@
6
6
  import React from 'react';
7
7
  import { hydrateRoot, createRoot } from 'react-dom/client';
8
8
 
9
+ // Extend Window interface for __FLEXI_DATA__
10
+ declare global {
11
+ interface Window {
12
+ __FLEXI_DATA__?: {
13
+ islands?: any[];
14
+ props?: Record<string, any>;
15
+ };
16
+ }
17
+ }
18
+
9
19
  /**
10
20
  * Hydrates a specific island component
11
21
  */
@@ -40,7 +40,12 @@ export function IslandBoundary({ children, fallback = null, name = 'island' }) {
40
40
  /**
41
41
  * Creates a lazy-loaded island
42
42
  */
43
- export function createClientIsland(loader, options = {}) {
43
+ interface LazyIslandOptions {
44
+ fallback?: React.ReactNode;
45
+ name?: string;
46
+ }
47
+
48
+ export function createClientIsland(loader: () => Promise<any>, options: LazyIslandOptions = {}) {
44
49
  const { fallback = null, name = 'lazy-island' } = options;
45
50
 
46
51
  return function LazyIsland(props) {
@@ -6,7 +6,10 @@
6
6
  import React from 'react';
7
7
 
8
8
  // Navigation state
9
- const navigationState = {
9
+ const navigationState: {
10
+ listeners: Set<(url: string) => void>;
11
+ prefetched: Set<string>;
12
+ } = {
10
13
  listeners: new Set(),
11
14
  prefetched: new Set()
12
15
  };
@@ -14,7 +17,12 @@ const navigationState = {
14
17
  /**
15
18
  * Navigates to a new URL
16
19
  */
17
- export function navigate(url, options = {}) {
20
+ interface NavigateOptions {
21
+ replace?: boolean;
22
+ scroll?: boolean;
23
+ }
24
+
25
+ export function navigate(url: string, options: NavigateOptions = {}) {
18
26
  const { replace = false, scroll = true } = options;
19
27
 
20
28
  if (replace) {
@@ -6,6 +6,22 @@
6
6
  import { hydrateAllIslands, setupProgressiveHydration } from './hydration.js';
7
7
  import { navigate, prefetch } from './navigation.js';
8
8
 
9
+ // Extend Window interface
10
+ declare global {
11
+ interface Window {
12
+ FlexiReact: {
13
+ navigate: typeof navigate;
14
+ prefetch: typeof prefetch;
15
+ hydrateAllIslands: typeof hydrateAllIslands;
16
+ setupProgressiveHydration: typeof setupProgressiveHydration;
17
+ };
18
+ __FLEXI_DATA__?: {
19
+ islands?: any[];
20
+ props?: Record<string, any>;
21
+ };
22
+ }
23
+ }
24
+
9
25
  // Expose to global scope
10
26
  window.FlexiReact = {
11
27
  navigate,
@@ -15,7 +15,8 @@ export { buildRouteTree, matchRoute, findRouteLayouts, RouteType } from './route
15
15
  export { renderPage, renderError, renderLoading } from './render/index.js';
16
16
 
17
17
  // Server
18
- export { createServer } from './server/index.js';
18
+ import { createServer } from './server/index.js';
19
+ export { createServer };
19
20
 
20
21
  // Build
21
22
  export { build, buildDev, BuildMode } from './build/index.js';
@@ -63,8 +63,13 @@ export function getRegisteredIslands() {
63
63
  /**
64
64
  * Creates an island component wrapper
65
65
  */
66
- export function createIsland(Component, options = {}) {
67
- const { name = Component.name || 'Island', clientPath } = options;
66
+ interface IslandOptions {
67
+ name?: string;
68
+ clientPath?: string;
69
+ }
70
+
71
+ export function createIsland(Component: React.ComponentType<any>, options: IslandOptions = {}) {
72
+ const { name = (Component as any).name || 'Island', clientPath } = options;
68
73
 
69
74
  function IslandWrapper(props) {
70
75
  return Island({
@@ -148,9 +153,16 @@ export const LoadStrategy = {
148
153
  /**
149
154
  * Creates a lazy island that hydrates based on strategy
150
155
  */
151
- export function createLazyIsland(Component, options = {}) {
156
+ interface LazyIslandOptions {
157
+ name?: string;
158
+ clientPath?: string;
159
+ strategy?: string;
160
+ media?: string | null;
161
+ }
162
+
163
+ export function createLazyIsland(Component: React.ComponentType<any>, options: LazyIslandOptions = {}) {
152
164
  const {
153
- name = Component.name || 'LazyIsland',
165
+ name = (Component as any).name || 'LazyIsland',
154
166
  clientPath,
155
167
  strategy = LoadStrategy.VISIBLE,
156
168
  media = null
@@ -122,7 +122,7 @@ export const logger = {
122
122
  },
123
123
 
124
124
  // HTTP request log - Compact single line like Next.js
125
- request(method, path, status, time, extra = {}) {
125
+ request(method: string, path: string, status: number, time: number, extra: { type?: string } = {}) {
126
126
  const methodColor = getMethodColor(method);
127
127
  const statusColor = getStatusColor(status);
128
128
  const timeStr = formatTime(time);
@@ -29,8 +29,22 @@ import { pathToFileURL } from 'url';
29
29
  /**
30
30
  * Middleware response helpers
31
31
  */
32
+ interface MiddlewareResponseOptions {
33
+ type?: string;
34
+ status?: number;
35
+ headers?: Record<string, string>;
36
+ body?: any;
37
+ url?: string | null;
38
+ }
39
+
32
40
  export class MiddlewareResponse {
33
- constructor(options = {}) {
41
+ type: string;
42
+ status: number;
43
+ headers: Map<string, string>;
44
+ body: any;
45
+ url: string | null;
46
+
47
+ constructor(options: MiddlewareResponseOptions = {}) {
34
48
  this.type = options.type || 'next';
35
49
  this.status = options.status || 200;
36
50
  this.headers = new Map(Object.entries(options.headers || {}));
@@ -69,7 +83,7 @@ export class MiddlewareResponse {
69
83
  /**
70
84
  * Return a JSON response
71
85
  */
72
- static json(data, options = {}) {
86
+ static json(data: any, options: { status?: number; headers?: Record<string, string> } = {}) {
73
87
  return new MiddlewareResponse({
74
88
  type: 'response',
75
89
  status: options.status || 200,
@@ -81,7 +95,7 @@ export class MiddlewareResponse {
81
95
  /**
82
96
  * Return an HTML response
83
97
  */
84
- static html(content, options = {}) {
98
+ static html(content: string, options: { status?: number; headers?: Record<string, string> } = {}) {
85
99
  return new MiddlewareResponse({
86
100
  type: 'response',
87
101
  status: options.status || 200,
@@ -95,7 +109,16 @@ export class MiddlewareResponse {
95
109
  * Middleware request wrapper
96
110
  */
97
111
  export class MiddlewareRequest {
98
- constructor(req) {
112
+ raw: any;
113
+ method: string;
114
+ url: string;
115
+ headers: Map<string, string>;
116
+ pathname: string;
117
+ searchParams: URLSearchParams;
118
+ query: Record<string, string>;
119
+ cookies: Map<string, string>;
120
+
121
+ constructor(req: any) {
99
122
  this.raw = req;
100
123
  this.method = req.method;
101
124
  this.url = req.url;
@@ -105,7 +128,7 @@ export class MiddlewareRequest {
105
128
  const parsedUrl = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
106
129
  this.pathname = parsedUrl.pathname;
107
130
  this.searchParams = parsedUrl.searchParams;
108
- this.query = Object.fromEntries(parsedUrl.searchParams);
131
+ this.query = Object.fromEntries(parsedUrl.searchParams) as Record<string, string>;
109
132
 
110
133
  // Parse cookies
111
134
  this.cookies = this._parseCookies(req.headers.cookie || '');
@@ -274,7 +297,7 @@ export const middlewares = {
274
297
  /**
275
298
  * CORS middleware
276
299
  */
277
- cors(options = {}) {
300
+ cors(options: { origin?: string; methods?: string; headers?: string; credentials?: boolean } = {}) {
278
301
  const {
279
302
  origin = '*',
280
303
  methods = 'GET,HEAD,PUT,PATCH,POST,DELETE',
@@ -294,7 +317,7 @@ export const middlewares = {
294
317
 
295
318
  // Handle preflight
296
319
  if (request.method === 'OPTIONS') {
297
- return MiddlewareResponse.json({}, { status: 204, headers: response.headers });
320
+ return MiddlewareResponse.json({}, { status: 204, headers: Object.fromEntries(response.headers) });
298
321
  }
299
322
 
300
323
  return response;
@@ -330,7 +353,7 @@ export const middlewares = {
330
353
  /**
331
354
  * Rate limiting middleware
332
355
  */
333
- rateLimit(options = {}) {
356
+ rateLimit(options: { windowMs?: number; max?: number } = {}) {
334
357
  const { windowMs = 60000, max = 100 } = options;
335
358
  const requests = new Map();
336
359
 
@@ -369,7 +392,7 @@ export const middlewares = {
369
392
  /**
370
393
  * Logging middleware
371
394
  */
372
- logger(options = {}) {
395
+ logger(options: { format?: string } = {}) {
373
396
  const { format = 'combined' } = options;
374
397
 
375
398
  return (request) => {