@flight-framework/core 0.3.2 → 0.4.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.
- package/LICENSE +21 -21
- package/README.md +541 -541
- package/dist/actions/index.js +1 -1
- package/dist/adapters/index.d.ts +138 -1
- package/dist/adapters/index.js +1 -1
- package/dist/cache/index.js +1 -1
- package/dist/{chunk-3UQJE3XZ.js → chunk-2F2QU6RC.js} +2 -2
- package/dist/chunk-2F2QU6RC.js.map +1 -0
- package/dist/{chunk-4U7CJVNQ.js → chunk-3KRBRSRJ.js} +2 -2
- package/dist/chunk-3KRBRSRJ.js.map +1 -0
- package/dist/{chunk-OZ3EXPLE.js → chunk-3QP3E7HS.js} +2 -2
- package/dist/chunk-3QP3E7HS.js.map +1 -0
- package/dist/{chunk-CKJHJPKQ.js → chunk-62C7LX2E.js} +2 -2
- package/dist/chunk-62C7LX2E.js.map +1 -0
- package/dist/{chunk-YNTMYL36.js → chunk-6BDCTUQY.js} +3 -3
- package/dist/chunk-6BDCTUQY.js.map +1 -0
- package/dist/{chunk-KDEA64UX.js → chunk-EGB7C73X.js} +5 -5
- package/dist/chunk-EGB7C73X.js.map +1 -0
- package/dist/{chunk-2JVEH76V.js → chunk-FSJNOPYE.js} +3 -3
- package/dist/chunk-FSJNOPYE.js.map +1 -0
- package/dist/{chunk-ARBKF6VI.js → chunk-GCQZ4FHI.js} +2 -2
- package/dist/{chunk-ARBKF6VI.js.map → chunk-GCQZ4FHI.js.map} +1 -1
- package/dist/{chunk-EHVUAFNH.js → chunk-IXMD5QH2.js} +2 -2
- package/dist/chunk-IXMD5QH2.js.map +1 -0
- package/dist/{chunk-6GI6HFSQ.js → chunk-K2CQZPCG.js} +2 -2
- package/dist/chunk-K2CQZPCG.js.map +1 -0
- package/dist/chunk-K5NYYNFS.js +311 -0
- package/dist/chunk-K5NYYNFS.js.map +1 -0
- package/dist/{chunk-6IG6XIXU.js → chunk-LNV47HGV.js} +2 -2
- package/dist/chunk-LNV47HGV.js.map +1 -0
- package/dist/{chunk-65JYF3DJ.js → chunk-MDQNNIHH.js} +2 -2
- package/dist/chunk-MDQNNIHH.js.map +1 -0
- package/dist/{chunk-GNS2FGPC.js → chunk-MQQLYWZZ.js} +2 -2
- package/dist/chunk-MQQLYWZZ.js.map +1 -0
- package/dist/{chunk-LAKHYTHL.js → chunk-NWMJYTMB.js} +3 -3
- package/dist/chunk-NWMJYTMB.js.map +1 -0
- package/dist/{chunk-R7MEVVA4.js → chunk-OYF2OAKS.js} +2 -2
- package/dist/chunk-OYF2OAKS.js.map +1 -0
- package/dist/{chunk-FRAH5QNY.js → chunk-P6WSBVDT.js} +4 -4
- package/dist/chunk-P6WSBVDT.js.map +1 -0
- package/dist/{chunk-5XHOLZBJ.js → chunk-PDW5WCMW.js} +2 -2
- package/dist/chunk-PDW5WCMW.js.map +1 -0
- package/dist/{chunk-A2QRUBVE.js → chunk-PVUMB632.js} +2 -2
- package/dist/chunk-PVUMB632.js.map +1 -0
- package/dist/{chunk-LKOPJ3GS.js → chunk-R7SQAREQ.js} +2 -2
- package/dist/chunk-R7SQAREQ.js.map +1 -0
- package/dist/{chunk-CNY3ZUVG.js → chunk-RSVA2EYO.js} +2 -2
- package/dist/chunk-RSVA2EYO.js.map +1 -0
- package/dist/{chunk-PAVI5W6M.js → chunk-T4Z4HM4W.js} +3 -3
- package/dist/chunk-T4Z4HM4W.js.map +1 -0
- package/dist/{chunk-HNPO6LFW.js → chunk-TASAT7KB.js} +2 -2
- package/dist/chunk-TASAT7KB.js.map +1 -0
- package/dist/{chunk-3N5ZBVZJ.js → chunk-VPFMHGEV.js} +2 -2
- package/dist/chunk-VPFMHGEV.js.map +1 -0
- package/dist/{chunk-A4TKWQBU.js → chunk-W6D62JCI.js} +2 -2
- package/dist/chunk-W6D62JCI.js.map +1 -0
- package/dist/{chunk-UFWGOJL7.js → chunk-WFAWAHJH.js} +2 -2
- package/dist/chunk-WFAWAHJH.js.map +1 -0
- package/dist/{chunk-NZS2YJ43.js → chunk-WOEIJWGJ.js} +2 -2
- package/dist/chunk-WOEIJWGJ.js.map +1 -0
- package/dist/{chunk-VNO2YUVD.js → chunk-XOIYNY4I.js} +2 -2
- package/dist/chunk-XOIYNY4I.js.map +1 -0
- package/dist/{chunk-PO7IHPFF.js → chunk-XSY5AAXT.js} +2 -2
- package/dist/chunk-XSY5AAXT.js.map +1 -0
- package/dist/{chunk-OZBPR27I.js → chunk-YHEVHRLH.js} +2 -2
- package/dist/chunk-YHEVHRLH.js.map +1 -0
- package/dist/{chunk-XU6MRYG2.js → chunk-ZIE56LCA.js} +3 -3
- package/dist/chunk-ZIE56LCA.js.map +1 -0
- package/dist/{chunk-B2LPSCES.js → chunk-ZVC3ZWLM.js} +2 -2
- package/dist/chunk-ZVC3ZWLM.js.map +1 -0
- package/dist/client.js +8 -8
- package/dist/client.js.map +1 -1
- package/dist/config/index.js +1 -1
- package/dist/errors/index.js +2 -2
- package/dist/file-router/index.js +1 -1
- package/dist/file-router/streaming-hints.js +1 -1
- package/dist/handlers/index.js +1 -1
- package/dist/index.js +30 -30
- package/dist/index.js.map +1 -1
- package/dist/islands/index.js +1 -1
- package/dist/middleware/index.js +1 -1
- package/dist/react/index.js +2 -2
- package/dist/react/index.js.map +1 -1
- package/dist/render/index.js +1 -1
- package/dist/router/index.js +1 -1
- package/dist/rsc/adapters/index.js +4 -4
- package/dist/rsc/adapters/preact.js +1 -1
- package/dist/rsc/adapters/react.js +1 -1
- package/dist/rsc/adapters/solid.js +1 -1
- package/dist/rsc/adapters/vue.js +1 -1
- package/dist/rsc/boundaries.js +1 -1
- package/dist/rsc/context.js +1 -1
- package/dist/rsc/index.js +11 -11
- package/dist/rsc/legacy.js +1 -1
- package/dist/rsc/payload.js +1 -1
- package/dist/rsc/plugins/esbuild.js +2 -2
- package/dist/rsc/plugins/index.js +4 -4
- package/dist/rsc/plugins/rollup.js +2 -2
- package/dist/rsc/renderer.js +3 -3
- package/dist/rsc/stream.js +1 -1
- package/dist/rsc/vite-plugin.js +2 -2
- package/dist/server/index.js +4 -4
- package/dist/streaming/adapters/index.js +1 -1
- package/dist/streaming/conditional.js +1 -1
- package/dist/streaming/index.js +1 -1
- package/dist/streaming/observability.js +2 -2
- package/dist/streaming/priority.js +1 -1
- package/dist/utils/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-2JVEH76V.js.map +0 -1
- package/dist/chunk-3N5ZBVZJ.js.map +0 -1
- package/dist/chunk-3UQJE3XZ.js.map +0 -1
- package/dist/chunk-4U7CJVNQ.js.map +0 -1
- package/dist/chunk-5XHOLZBJ.js.map +0 -1
- package/dist/chunk-65JYF3DJ.js.map +0 -1
- package/dist/chunk-6GI6HFSQ.js.map +0 -1
- package/dist/chunk-6IG6XIXU.js.map +0 -1
- package/dist/chunk-A2QRUBVE.js.map +0 -1
- package/dist/chunk-A4TKWQBU.js.map +0 -1
- package/dist/chunk-B2LPSCES.js.map +0 -1
- package/dist/chunk-CKJHJPKQ.js.map +0 -1
- package/dist/chunk-CNY3ZUVG.js.map +0 -1
- package/dist/chunk-EHVUAFNH.js.map +0 -1
- package/dist/chunk-FRAH5QNY.js.map +0 -1
- package/dist/chunk-GNS2FGPC.js.map +0 -1
- package/dist/chunk-HNPO6LFW.js.map +0 -1
- package/dist/chunk-KDEA64UX.js.map +0 -1
- package/dist/chunk-LAKHYTHL.js.map +0 -1
- package/dist/chunk-LKOPJ3GS.js.map +0 -1
- package/dist/chunk-NZS2YJ43.js.map +0 -1
- package/dist/chunk-OZ3EXPLE.js.map +0 -1
- package/dist/chunk-OZBPR27I.js.map +0 -1
- package/dist/chunk-PAVI5W6M.js.map +0 -1
- package/dist/chunk-PO7IHPFF.js.map +0 -1
- package/dist/chunk-QK6UEQ75.js +0 -13
- package/dist/chunk-QK6UEQ75.js.map +0 -1
- package/dist/chunk-R7MEVVA4.js.map +0 -1
- package/dist/chunk-UFWGOJL7.js.map +0 -1
- package/dist/chunk-VNO2YUVD.js.map +0 -1
- package/dist/chunk-XU6MRYG2.js.map +0 -1
- package/dist/chunk-YNTMYL36.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,541 +1,541 @@
|
|
|
1
|
-
# @flight-framework/core
|
|
2
|
-
|
|
3
|
-
Core primitives for Flight Framework, including configuration, routing, caching, streaming SSR, and server actions.
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- Multi-render mode: SSR, SSG, ISR, and streaming
|
|
8
|
-
- Framework support: React, Vue, Svelte, Solid, HTMX
|
|
9
|
-
- File-based routing with automatic route discovery
|
|
10
|
-
- Type-safe server actions with form support
|
|
11
|
-
- Streaming SSR with priority control
|
|
12
|
-
- Islands architecture for partial hydration
|
|
13
|
-
- Pluggable cache adapters with deduplication
|
|
14
|
-
- Structured error handling with type guards
|
|
15
|
-
|
|
16
|
-
## Installation
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
npm install @flight-framework/core
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
## Quick Start
|
|
23
|
-
|
|
24
|
-
### Configuration
|
|
25
|
-
|
|
26
|
-
```typescript
|
|
27
|
-
// flight.config.ts
|
|
28
|
-
import { defineConfig } from '@flight-framework/core';
|
|
29
|
-
|
|
30
|
-
export default defineConfig({
|
|
31
|
-
server: {
|
|
32
|
-
port: 3000,
|
|
33
|
-
},
|
|
34
|
-
render: {
|
|
35
|
-
defaultMode: 'ssr',
|
|
36
|
-
streaming: true,
|
|
37
|
-
},
|
|
38
|
-
});
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
### Create a Server
|
|
42
|
-
|
|
43
|
-
```typescript
|
|
44
|
-
import { createServer } from '@flight-framework/core';
|
|
45
|
-
|
|
46
|
-
const server = createServer({
|
|
47
|
-
port: 3000,
|
|
48
|
-
routes: [
|
|
49
|
-
{ path: '/', handler: homeHandler },
|
|
50
|
-
{ path: '/api/*', handler: apiHandler },
|
|
51
|
-
],
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
await server.start();
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
### File-Based Routing
|
|
58
|
-
|
|
59
|
-
```typescript
|
|
60
|
-
import { createFileRouter, scanRoutes } from '@flight-framework/core';
|
|
61
|
-
|
|
62
|
-
const routes = await scanRoutes('./src/routes');
|
|
63
|
-
const router = createFileRouter({ routes });
|
|
64
|
-
|
|
65
|
-
// Routes are discovered automatically:
|
|
66
|
-
// src/routes/index.page.tsx -> /
|
|
67
|
-
// src/routes/about.page.tsx -> /about
|
|
68
|
-
// src/routes/blog/[slug].page.tsx -> /blog/:slug
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
## Modules
|
|
72
|
-
|
|
73
|
-
### Router
|
|
74
|
-
|
|
75
|
-
```typescript
|
|
76
|
-
import { createRouter } from '@flight-framework/core/router';
|
|
77
|
-
|
|
78
|
-
const router = createRouter();
|
|
79
|
-
router.add('GET', '/users/:id', userHandler);
|
|
80
|
-
|
|
81
|
-
const match = router.match('GET', '/users/123');
|
|
82
|
-
// { params: { id: '123' }, handler: userHandler }
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
### Cache
|
|
86
|
-
|
|
87
|
-
```typescript
|
|
88
|
-
import { createCache, memory, cached, dedupe } from '@flight-framework/core/cache';
|
|
89
|
-
|
|
90
|
-
// Create a cache with memory adapter
|
|
91
|
-
const cache = createCache({
|
|
92
|
-
adapter: memory(),
|
|
93
|
-
ttl: 60000,
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
// Cache function results
|
|
97
|
-
const getUser = cached(
|
|
98
|
-
async (id: string) => fetchUser(id),
|
|
99
|
-
{ ttl: 5000, key: (id) => `user:${id}` }
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
// Deduplicate concurrent requests
|
|
103
|
-
const getData = dedupe(fetchData);
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
### Server Actions
|
|
107
|
-
|
|
108
|
-
```typescript
|
|
109
|
-
import { registerAction, executeAction, cookies } from '@flight-framework/core/actions';
|
|
110
|
-
|
|
111
|
-
// Register a server action
|
|
112
|
-
registerAction('createUser', async (formData: FormData) => {
|
|
113
|
-
const name = formData.get('name');
|
|
114
|
-
const user = await db.users.create({ name });
|
|
115
|
-
|
|
116
|
-
cookies().set('user_id', user.id);
|
|
117
|
-
|
|
118
|
-
return { success: true, user };
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
// Execute from client
|
|
122
|
-
const result = await executeAction('createUser', formData);
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
### Streaming SSR
|
|
126
|
-
|
|
127
|
-
```typescript
|
|
128
|
-
import {
|
|
129
|
-
createStreamingSSR,
|
|
130
|
-
streamWithPriority
|
|
131
|
-
} from '@flight-framework/core/streaming';
|
|
132
|
-
|
|
133
|
-
// Basic streaming
|
|
134
|
-
const stream = await createStreamingSSR({
|
|
135
|
-
component: App,
|
|
136
|
-
props: { data },
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
// Priority-based streaming (out-of-order)
|
|
140
|
-
const result = await streamWithPriority({
|
|
141
|
-
boundaries: [
|
|
142
|
-
{ id: 'header', priority: 100, render: Header },
|
|
143
|
-
{ id: 'sidebar', priority: 50, render: Sidebar },
|
|
144
|
-
{ id: 'content', priority: 75, render: Content },
|
|
145
|
-
],
|
|
146
|
-
});
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
### Islands Architecture
|
|
150
|
-
|
|
151
|
-
```typescript
|
|
152
|
-
import {
|
|
153
|
-
defineIsland,
|
|
154
|
-
hydrateIslands,
|
|
155
|
-
createReactIslandAdapter
|
|
156
|
-
} from '@flight-framework/core/islands';
|
|
157
|
-
|
|
158
|
-
// Define an island component
|
|
159
|
-
const Counter = defineIsland({
|
|
160
|
-
name: 'counter',
|
|
161
|
-
component: CounterComponent,
|
|
162
|
-
hydrate: 'visible', // 'load' | 'visible' | 'idle' | 'interaction'
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
// Client-side hydration
|
|
166
|
-
hydrateIslands({
|
|
167
|
-
adapter: createReactIslandAdapter(),
|
|
168
|
-
});
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
### Middleware
|
|
172
|
-
|
|
173
|
-
Flight provides a composable middleware system for request/response handling.
|
|
174
|
-
|
|
175
|
-
#### Middleware Chain
|
|
176
|
-
|
|
177
|
-
```typescript
|
|
178
|
-
import {
|
|
179
|
-
createMiddlewareChain,
|
|
180
|
-
cors,
|
|
181
|
-
logger,
|
|
182
|
-
securityHeaders
|
|
183
|
-
} from '@flight-framework/core/middleware';
|
|
184
|
-
|
|
185
|
-
const chain = createMiddlewareChain();
|
|
186
|
-
|
|
187
|
-
chain
|
|
188
|
-
.use(logger())
|
|
189
|
-
.use(cors({ origin: ['https://app.example.com'] }))
|
|
190
|
-
.use(securityHeaders())
|
|
191
|
-
.use(async (ctx, next) => {
|
|
192
|
-
const start = Date.now();
|
|
193
|
-
await next();
|
|
194
|
-
console.log(`Request took ${Date.now() - start}ms`);
|
|
195
|
-
});
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
#### Error Handling
|
|
199
|
-
|
|
200
|
-
Centralized error handling with the `errorHandler` factory:
|
|
201
|
-
|
|
202
|
-
```typescript
|
|
203
|
-
import { createMiddlewareChain, errorHandler } from '@flight-framework/core/middleware';
|
|
204
|
-
|
|
205
|
-
const chain = createMiddlewareChain();
|
|
206
|
-
|
|
207
|
-
// Place error handler first in the chain
|
|
208
|
-
chain.use(errorHandler({
|
|
209
|
-
expose: process.env.NODE_ENV === 'development',
|
|
210
|
-
emit: (error, ctx) => {
|
|
211
|
-
logger.error(`[${ctx.method}] ${ctx.url.pathname}:`, error);
|
|
212
|
-
errorTracker.capture(error);
|
|
213
|
-
},
|
|
214
|
-
}));
|
|
215
|
-
|
|
216
|
-
chain.use(authMiddleware);
|
|
217
|
-
chain.use(routeHandler);
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
The error handler catches all downstream errors, sets appropriate status codes, and supports custom error handlers:
|
|
221
|
-
|
|
222
|
-
```typescript
|
|
223
|
-
chain.use(errorHandler({
|
|
224
|
-
onError: async ({ error, status, ctx, timestamp }) => {
|
|
225
|
-
ctx.status = status;
|
|
226
|
-
ctx.responseBody = JSON.stringify({
|
|
227
|
-
error: error.message,
|
|
228
|
-
timestamp,
|
|
229
|
-
requestId: ctx.locals.requestId,
|
|
230
|
-
});
|
|
231
|
-
},
|
|
232
|
-
}));
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
#### Typed Context
|
|
236
|
-
|
|
237
|
-
Middleware context supports generics for type-safe data sharing:
|
|
238
|
-
|
|
239
|
-
```typescript
|
|
240
|
-
import type { Middleware, MiddlewareContext } from '@flight-framework/core/middleware';
|
|
241
|
-
|
|
242
|
-
interface AppLocals {
|
|
243
|
-
user: { id: string; role: string };
|
|
244
|
-
requestId: string;
|
|
245
|
-
db: DatabaseClient;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const authMiddleware: Middleware<AppLocals> = async (ctx, next) => {
|
|
249
|
-
const token = ctx.headers.get('Authorization');
|
|
250
|
-
const user = await verifyToken(token);
|
|
251
|
-
|
|
252
|
-
ctx.locals.user = user;
|
|
253
|
-
ctx.locals.requestId = crypto.randomUUID();
|
|
254
|
-
|
|
255
|
-
await next();
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
// Type-safe access in subsequent middleware
|
|
259
|
-
const roleGuard: Middleware<AppLocals> = async (ctx, next) => {
|
|
260
|
-
if (ctx.locals.user.role !== 'admin') {
|
|
261
|
-
ctx.status = 403;
|
|
262
|
-
ctx.responseBody = 'Forbidden';
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
await next();
|
|
266
|
-
};
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
#### CORS
|
|
270
|
-
|
|
271
|
-
CORS middleware with dynamic origin validation and CDN compatibility:
|
|
272
|
-
|
|
273
|
-
```typescript
|
|
274
|
-
import { cors } from '@flight-framework/core/middleware';
|
|
275
|
-
|
|
276
|
-
// Static origins
|
|
277
|
-
chain.use(cors({
|
|
278
|
-
origin: ['https://app.example.com', 'https://admin.example.com'],
|
|
279
|
-
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
280
|
-
credentials: true,
|
|
281
|
-
}));
|
|
282
|
-
|
|
283
|
-
// Async validation (database lookup)
|
|
284
|
-
chain.use(cors({
|
|
285
|
-
origin: async (requestOrigin) => {
|
|
286
|
-
return await db.allowedOrigins.exists(requestOrigin);
|
|
287
|
-
},
|
|
288
|
-
exposeHeaders: ['X-Request-Id', 'X-RateLimit-Remaining'],
|
|
289
|
-
}));
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
Dynamic origins automatically set `Vary: Origin` for CDN/cache compatibility.
|
|
293
|
-
|
|
294
|
-
#### Built-in Middleware
|
|
295
|
-
|
|
296
|
-
| Middleware | Purpose |
|
|
297
|
-
|------------|---------|
|
|
298
|
-
| `cors(options?)` | Cross-origin resource sharing |
|
|
299
|
-
| `logger(options?)` | Request logging with configurable levels |
|
|
300
|
-
| `securityHeaders(options?)` | Security headers (CSP, X-Frame-Options, etc.) |
|
|
301
|
-
| `errorHandler(options?)` | Centralized error handling |
|
|
302
|
-
| `compress()` | Mark responses for compression |
|
|
303
|
-
|
|
304
|
-
#### Logger Configuration
|
|
305
|
-
|
|
306
|
-
```typescript
|
|
307
|
-
import { logger } from '@flight-framework/core/middleware';
|
|
308
|
-
|
|
309
|
-
chain.use(logger({
|
|
310
|
-
level: 'info', // 'debug' | 'info' | 'warn' | 'error' | 'silent'
|
|
311
|
-
format: 'json', // 'pretty' | 'json' | 'combined' | 'common' | 'short'
|
|
312
|
-
skip: (ctx) => ctx.url.pathname === '/health',
|
|
313
|
-
writer: (entry, formatted) => externalLogger.log(entry),
|
|
314
|
-
}));
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
The logger captures errors from downstream middleware before re-throwing, ensuring all requests are logged including failures.
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
### Error Handling
|
|
321
|
-
|
|
322
|
-
```typescript
|
|
323
|
-
import {
|
|
324
|
-
FlightError,
|
|
325
|
-
createError,
|
|
326
|
-
isFlightError,
|
|
327
|
-
createNotFound,
|
|
328
|
-
createForbidden,
|
|
329
|
-
} from '@flight-framework/core/errors';
|
|
330
|
-
|
|
331
|
-
// Create typed errors
|
|
332
|
-
throw createNotFound('Page not found');
|
|
333
|
-
throw createForbidden('Access denied');
|
|
334
|
-
|
|
335
|
-
// Create custom errors
|
|
336
|
-
throw createError({
|
|
337
|
-
statusCode: 422,
|
|
338
|
-
message: 'Validation failed',
|
|
339
|
-
data: { field: 'email', error: 'Invalid format' },
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
// Type guards
|
|
343
|
-
if (isFlightError(error)) {
|
|
344
|
-
console.log(error.statusCode, error.digest);
|
|
345
|
-
}
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
### React Integration
|
|
349
|
-
|
|
350
|
-
```typescript
|
|
351
|
-
import { ErrorProvider, useError, useFlightError } from '@flight-framework/core/react';
|
|
352
|
-
|
|
353
|
-
// Wrap your app
|
|
354
|
-
<ErrorProvider onError={console.error}>
|
|
355
|
-
<App />
|
|
356
|
-
</ErrorProvider>
|
|
357
|
-
|
|
358
|
-
// Use in components
|
|
359
|
-
function MyComponent() {
|
|
360
|
-
const { error, clearError } = useFlightError();
|
|
361
|
-
|
|
362
|
-
if (error) {
|
|
363
|
-
return <ErrorDisplay error={error} onRetry={clearError} />;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
return <Content />;
|
|
367
|
-
}
|
|
368
|
-
```
|
|
369
|
-
|
|
370
|
-
### Metadata (SEO)
|
|
371
|
-
|
|
372
|
-
```typescript
|
|
373
|
-
import { renderMetadataToHead, type Metadata } from '@flight-framework/core';
|
|
374
|
-
|
|
375
|
-
const metadata: Metadata = {
|
|
376
|
-
title: 'My Page',
|
|
377
|
-
description: 'Page description',
|
|
378
|
-
openGraph: {
|
|
379
|
-
title: 'OG Title',
|
|
380
|
-
image: '/og-image.png',
|
|
381
|
-
},
|
|
382
|
-
};
|
|
383
|
-
|
|
384
|
-
const headHtml = renderMetadataToHead(metadata);
|
|
385
|
-
```
|
|
386
|
-
|
|
387
|
-
### Route Rules (ISR/SSG)
|
|
388
|
-
|
|
389
|
-
```typescript
|
|
390
|
-
import { defineConfig } from '@flight-framework/core';
|
|
391
|
-
|
|
392
|
-
export default defineConfig({
|
|
393
|
-
routeRules: {
|
|
394
|
-
'/': { prerender: true },
|
|
395
|
-
'/blog/**': { isr: 3600 },
|
|
396
|
-
'/api/**': { ssr: true },
|
|
397
|
-
'/static/**': { static: true },
|
|
398
|
-
},
|
|
399
|
-
});
|
|
400
|
-
```
|
|
401
|
-
|
|
402
|
-
### Revalidation
|
|
403
|
-
|
|
404
|
-
```typescript
|
|
405
|
-
import {
|
|
406
|
-
revalidatePath,
|
|
407
|
-
revalidateTag,
|
|
408
|
-
createRevalidateHandler
|
|
409
|
-
} from '@flight-framework/core';
|
|
410
|
-
|
|
411
|
-
// On-demand revalidation
|
|
412
|
-
await revalidatePath('/blog/my-post');
|
|
413
|
-
await revalidateTag('blog-posts');
|
|
414
|
-
|
|
415
|
-
// Create revalidation API handler
|
|
416
|
-
const handler = createRevalidateHandler({
|
|
417
|
-
secret: process.env.REVALIDATE_SECRET,
|
|
418
|
-
});
|
|
419
|
-
```
|
|
420
|
-
|
|
421
|
-
## Environment Utilities
|
|
422
|
-
|
|
423
|
-
```typescript
|
|
424
|
-
import {
|
|
425
|
-
isServer,
|
|
426
|
-
isBrowser,
|
|
427
|
-
isProduction,
|
|
428
|
-
isDevelopment
|
|
429
|
-
} from '@flight-framework/core/utils';
|
|
430
|
-
|
|
431
|
-
if (isServer()) {
|
|
432
|
-
// Server-only code
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
if (isDevelopment()) {
|
|
436
|
-
console.log('Debug info');
|
|
437
|
-
}
|
|
438
|
-
```
|
|
439
|
-
|
|
440
|
-
## Module Exports
|
|
441
|
-
|
|
442
|
-
| Export | Description |
|
|
443
|
-
|--------|-------------|
|
|
444
|
-
| `@flight-framework/core` | Main entry with all primitives |
|
|
445
|
-
| `@flight-framework/core/router` | Routing utilities |
|
|
446
|
-
| `@flight-framework/core/cache` | Caching system |
|
|
447
|
-
| `@flight-framework/core/server` | HTTP server |
|
|
448
|
-
| `@flight-framework/core/render` | Rendering modes |
|
|
449
|
-
| `@flight-framework/core/middleware` | Middleware chain |
|
|
450
|
-
| `@flight-framework/core/actions` | Server actions |
|
|
451
|
-
| `@flight-framework/core/streaming` | Streaming SSR |
|
|
452
|
-
| `@flight-framework/core/islands` | Islands architecture |
|
|
453
|
-
| `@flight-framework/core/errors` | Error handling |
|
|
454
|
-
| `@flight-framework/core/react` | React integration |
|
|
455
|
-
| `@flight-framework/core/rsc` | React Server Components |
|
|
456
|
-
| `@flight-framework/core/config` | Configuration |
|
|
457
|
-
| `@flight-framework/core/utils` | Environment utilities |
|
|
458
|
-
|
|
459
|
-
## TypeScript
|
|
460
|
-
|
|
461
|
-
Full TypeScript support with exported types for all APIs:
|
|
462
|
-
|
|
463
|
-
```typescript
|
|
464
|
-
import type {
|
|
465
|
-
FlightConfig,
|
|
466
|
-
Route,
|
|
467
|
-
RouteMatch,
|
|
468
|
-
Cache,
|
|
469
|
-
CacheAdapter,
|
|
470
|
-
Middleware,
|
|
471
|
-
FlightError,
|
|
472
|
-
Metadata,
|
|
473
|
-
StreamingHints,
|
|
474
|
-
} from '@flight-framework/core';
|
|
475
|
-
```
|
|
476
|
-
|
|
477
|
-
## Build Plugins
|
|
478
|
-
|
|
479
|
-
### Critical CSS Extraction
|
|
480
|
-
|
|
481
|
-
Extract and inline critical CSS for improved LCP:
|
|
482
|
-
|
|
483
|
-
```bash
|
|
484
|
-
npm install critters
|
|
485
|
-
```
|
|
486
|
-
|
|
487
|
-
```typescript
|
|
488
|
-
// vite.config.ts
|
|
489
|
-
import { criticalCSS } from '@flight-framework/core/plugins';
|
|
490
|
-
|
|
491
|
-
export default defineConfig({
|
|
492
|
-
plugins: [
|
|
493
|
-
criticalCSS({
|
|
494
|
-
// Strategy for loading non-critical CSS
|
|
495
|
-
preload: 'swap', // 'body' | 'media' | 'swap' | 'swap-high' | 'js' | 'js-lazy'
|
|
496
|
-
|
|
497
|
-
// Remove inlined CSS from source
|
|
498
|
-
pruneSource: false,
|
|
499
|
-
|
|
500
|
-
// Routes to process
|
|
501
|
-
include: ['**/*.html'],
|
|
502
|
-
exclude: ['/api/**'],
|
|
503
|
-
}),
|
|
504
|
-
],
|
|
505
|
-
});
|
|
506
|
-
```
|
|
507
|
-
|
|
508
|
-
#### Preload Strategies
|
|
509
|
-
|
|
510
|
-
| Strategy | Description |
|
|
511
|
-
|----------|-------------|
|
|
512
|
-
| `swap` | Use rel="preload" and swap on load |
|
|
513
|
-
| `swap-high` | Like swap with fetchpriority="high" |
|
|
514
|
-
| `media` | Use media="print" and swap |
|
|
515
|
-
| `js` | Load via JavaScript |
|
|
516
|
-
| `js-lazy` | Load via JavaScript when idle |
|
|
517
|
-
| `body` | Move stylesheets to end of body |
|
|
518
|
-
|
|
519
|
-
#### CSS Utilities
|
|
520
|
-
|
|
521
|
-
```typescript
|
|
522
|
-
import {
|
|
523
|
-
extractInlineStyles,
|
|
524
|
-
mergeCSS,
|
|
525
|
-
generatePreloadLink,
|
|
526
|
-
generateNoscriptFallback,
|
|
527
|
-
} from '@flight-framework/core/plugins/critical-css';
|
|
528
|
-
|
|
529
|
-
// Extract styles from HTML
|
|
530
|
-
const { html, styles } = extractInlineStyles(htmlString);
|
|
531
|
-
|
|
532
|
-
// Generate preload link
|
|
533
|
-
const preload = generatePreloadLink('/styles.css', 'swap');
|
|
534
|
-
|
|
535
|
-
// Generate noscript fallback
|
|
536
|
-
const fallback = generateNoscriptFallback('/styles.css');
|
|
537
|
-
```
|
|
538
|
-
|
|
539
|
-
## License
|
|
540
|
-
|
|
541
|
-
MIT
|
|
1
|
+
# @flight-framework/core
|
|
2
|
+
|
|
3
|
+
Core primitives for Flight Framework, including configuration, routing, caching, streaming SSR, and server actions.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Multi-render mode: SSR, SSG, ISR, and streaming
|
|
8
|
+
- Framework support: React, Vue, Svelte, Solid, HTMX
|
|
9
|
+
- File-based routing with automatic route discovery
|
|
10
|
+
- Type-safe server actions with form support
|
|
11
|
+
- Streaming SSR with priority control
|
|
12
|
+
- Islands architecture for partial hydration
|
|
13
|
+
- Pluggable cache adapters with deduplication
|
|
14
|
+
- Structured error handling with type guards
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @flight-framework/core
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
### Configuration
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
// flight.config.ts
|
|
28
|
+
import { defineConfig } from '@flight-framework/core';
|
|
29
|
+
|
|
30
|
+
export default defineConfig({
|
|
31
|
+
server: {
|
|
32
|
+
port: 3000,
|
|
33
|
+
},
|
|
34
|
+
render: {
|
|
35
|
+
defaultMode: 'ssr',
|
|
36
|
+
streaming: true,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Create a Server
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { createServer } from '@flight-framework/core';
|
|
45
|
+
|
|
46
|
+
const server = createServer({
|
|
47
|
+
port: 3000,
|
|
48
|
+
routes: [
|
|
49
|
+
{ path: '/', handler: homeHandler },
|
|
50
|
+
{ path: '/api/*', handler: apiHandler },
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
await server.start();
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### File-Based Routing
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { createFileRouter, scanRoutes } from '@flight-framework/core';
|
|
61
|
+
|
|
62
|
+
const routes = await scanRoutes('./src/routes');
|
|
63
|
+
const router = createFileRouter({ routes });
|
|
64
|
+
|
|
65
|
+
// Routes are discovered automatically:
|
|
66
|
+
// src/routes/index.page.tsx -> /
|
|
67
|
+
// src/routes/about.page.tsx -> /about
|
|
68
|
+
// src/routes/blog/[slug].page.tsx -> /blog/:slug
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Modules
|
|
72
|
+
|
|
73
|
+
### Router
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { createRouter } from '@flight-framework/core/router';
|
|
77
|
+
|
|
78
|
+
const router = createRouter();
|
|
79
|
+
router.add('GET', '/users/:id', userHandler);
|
|
80
|
+
|
|
81
|
+
const match = router.match('GET', '/users/123');
|
|
82
|
+
// { params: { id: '123' }, handler: userHandler }
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Cache
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import { createCache, memory, cached, dedupe } from '@flight-framework/core/cache';
|
|
89
|
+
|
|
90
|
+
// Create a cache with memory adapter
|
|
91
|
+
const cache = createCache({
|
|
92
|
+
adapter: memory(),
|
|
93
|
+
ttl: 60000,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Cache function results
|
|
97
|
+
const getUser = cached(
|
|
98
|
+
async (id: string) => fetchUser(id),
|
|
99
|
+
{ ttl: 5000, key: (id) => `user:${id}` }
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// Deduplicate concurrent requests
|
|
103
|
+
const getData = dedupe(fetchData);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Server Actions
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import { registerAction, executeAction, cookies } from '@flight-framework/core/actions';
|
|
110
|
+
|
|
111
|
+
// Register a server action
|
|
112
|
+
registerAction('createUser', async (formData: FormData) => {
|
|
113
|
+
const name = formData.get('name');
|
|
114
|
+
const user = await db.users.create({ name });
|
|
115
|
+
|
|
116
|
+
cookies().set('user_id', user.id);
|
|
117
|
+
|
|
118
|
+
return { success: true, user };
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Execute from client
|
|
122
|
+
const result = await executeAction('createUser', formData);
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Streaming SSR
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import {
|
|
129
|
+
createStreamingSSR,
|
|
130
|
+
streamWithPriority
|
|
131
|
+
} from '@flight-framework/core/streaming';
|
|
132
|
+
|
|
133
|
+
// Basic streaming
|
|
134
|
+
const stream = await createStreamingSSR({
|
|
135
|
+
component: App,
|
|
136
|
+
props: { data },
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Priority-based streaming (out-of-order)
|
|
140
|
+
const result = await streamWithPriority({
|
|
141
|
+
boundaries: [
|
|
142
|
+
{ id: 'header', priority: 100, render: Header },
|
|
143
|
+
{ id: 'sidebar', priority: 50, render: Sidebar },
|
|
144
|
+
{ id: 'content', priority: 75, render: Content },
|
|
145
|
+
],
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Islands Architecture
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
import {
|
|
153
|
+
defineIsland,
|
|
154
|
+
hydrateIslands,
|
|
155
|
+
createReactIslandAdapter
|
|
156
|
+
} from '@flight-framework/core/islands';
|
|
157
|
+
|
|
158
|
+
// Define an island component
|
|
159
|
+
const Counter = defineIsland({
|
|
160
|
+
name: 'counter',
|
|
161
|
+
component: CounterComponent,
|
|
162
|
+
hydrate: 'visible', // 'load' | 'visible' | 'idle' | 'interaction'
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Client-side hydration
|
|
166
|
+
hydrateIslands({
|
|
167
|
+
adapter: createReactIslandAdapter(),
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Middleware
|
|
172
|
+
|
|
173
|
+
Flight provides a composable middleware system for request/response handling.
|
|
174
|
+
|
|
175
|
+
#### Middleware Chain
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
import {
|
|
179
|
+
createMiddlewareChain,
|
|
180
|
+
cors,
|
|
181
|
+
logger,
|
|
182
|
+
securityHeaders
|
|
183
|
+
} from '@flight-framework/core/middleware';
|
|
184
|
+
|
|
185
|
+
const chain = createMiddlewareChain();
|
|
186
|
+
|
|
187
|
+
chain
|
|
188
|
+
.use(logger())
|
|
189
|
+
.use(cors({ origin: ['https://app.example.com'] }))
|
|
190
|
+
.use(securityHeaders())
|
|
191
|
+
.use(async (ctx, next) => {
|
|
192
|
+
const start = Date.now();
|
|
193
|
+
await next();
|
|
194
|
+
console.log(`Request took ${Date.now() - start}ms`);
|
|
195
|
+
});
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
#### Error Handling
|
|
199
|
+
|
|
200
|
+
Centralized error handling with the `errorHandler` factory:
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
import { createMiddlewareChain, errorHandler } from '@flight-framework/core/middleware';
|
|
204
|
+
|
|
205
|
+
const chain = createMiddlewareChain();
|
|
206
|
+
|
|
207
|
+
// Place error handler first in the chain
|
|
208
|
+
chain.use(errorHandler({
|
|
209
|
+
expose: process.env.NODE_ENV === 'development',
|
|
210
|
+
emit: (error, ctx) => {
|
|
211
|
+
logger.error(`[${ctx.method}] ${ctx.url.pathname}:`, error);
|
|
212
|
+
errorTracker.capture(error);
|
|
213
|
+
},
|
|
214
|
+
}));
|
|
215
|
+
|
|
216
|
+
chain.use(authMiddleware);
|
|
217
|
+
chain.use(routeHandler);
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
The error handler catches all downstream errors, sets appropriate status codes, and supports custom error handlers:
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
chain.use(errorHandler({
|
|
224
|
+
onError: async ({ error, status, ctx, timestamp }) => {
|
|
225
|
+
ctx.status = status;
|
|
226
|
+
ctx.responseBody = JSON.stringify({
|
|
227
|
+
error: error.message,
|
|
228
|
+
timestamp,
|
|
229
|
+
requestId: ctx.locals.requestId,
|
|
230
|
+
});
|
|
231
|
+
},
|
|
232
|
+
}));
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
#### Typed Context
|
|
236
|
+
|
|
237
|
+
Middleware context supports generics for type-safe data sharing:
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
import type { Middleware, MiddlewareContext } from '@flight-framework/core/middleware';
|
|
241
|
+
|
|
242
|
+
interface AppLocals {
|
|
243
|
+
user: { id: string; role: string };
|
|
244
|
+
requestId: string;
|
|
245
|
+
db: DatabaseClient;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const authMiddleware: Middleware<AppLocals> = async (ctx, next) => {
|
|
249
|
+
const token = ctx.headers.get('Authorization');
|
|
250
|
+
const user = await verifyToken(token);
|
|
251
|
+
|
|
252
|
+
ctx.locals.user = user;
|
|
253
|
+
ctx.locals.requestId = crypto.randomUUID();
|
|
254
|
+
|
|
255
|
+
await next();
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// Type-safe access in subsequent middleware
|
|
259
|
+
const roleGuard: Middleware<AppLocals> = async (ctx, next) => {
|
|
260
|
+
if (ctx.locals.user.role !== 'admin') {
|
|
261
|
+
ctx.status = 403;
|
|
262
|
+
ctx.responseBody = 'Forbidden';
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
await next();
|
|
266
|
+
};
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
#### CORS
|
|
270
|
+
|
|
271
|
+
CORS middleware with dynamic origin validation and CDN compatibility:
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
import { cors } from '@flight-framework/core/middleware';
|
|
275
|
+
|
|
276
|
+
// Static origins
|
|
277
|
+
chain.use(cors({
|
|
278
|
+
origin: ['https://app.example.com', 'https://admin.example.com'],
|
|
279
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
280
|
+
credentials: true,
|
|
281
|
+
}));
|
|
282
|
+
|
|
283
|
+
// Async validation (database lookup)
|
|
284
|
+
chain.use(cors({
|
|
285
|
+
origin: async (requestOrigin) => {
|
|
286
|
+
return await db.allowedOrigins.exists(requestOrigin);
|
|
287
|
+
},
|
|
288
|
+
exposeHeaders: ['X-Request-Id', 'X-RateLimit-Remaining'],
|
|
289
|
+
}));
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Dynamic origins automatically set `Vary: Origin` for CDN/cache compatibility.
|
|
293
|
+
|
|
294
|
+
#### Built-in Middleware
|
|
295
|
+
|
|
296
|
+
| Middleware | Purpose |
|
|
297
|
+
|------------|---------|
|
|
298
|
+
| `cors(options?)` | Cross-origin resource sharing |
|
|
299
|
+
| `logger(options?)` | Request logging with configurable levels |
|
|
300
|
+
| `securityHeaders(options?)` | Security headers (CSP, X-Frame-Options, etc.) |
|
|
301
|
+
| `errorHandler(options?)` | Centralized error handling |
|
|
302
|
+
| `compress()` | Mark responses for compression |
|
|
303
|
+
|
|
304
|
+
#### Logger Configuration
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
import { logger } from '@flight-framework/core/middleware';
|
|
308
|
+
|
|
309
|
+
chain.use(logger({
|
|
310
|
+
level: 'info', // 'debug' | 'info' | 'warn' | 'error' | 'silent'
|
|
311
|
+
format: 'json', // 'pretty' | 'json' | 'combined' | 'common' | 'short'
|
|
312
|
+
skip: (ctx) => ctx.url.pathname === '/health',
|
|
313
|
+
writer: (entry, formatted) => externalLogger.log(entry),
|
|
314
|
+
}));
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
The logger captures errors from downstream middleware before re-throwing, ensuring all requests are logged including failures.
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
### Error Handling
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
import {
|
|
324
|
+
FlightError,
|
|
325
|
+
createError,
|
|
326
|
+
isFlightError,
|
|
327
|
+
createNotFound,
|
|
328
|
+
createForbidden,
|
|
329
|
+
} from '@flight-framework/core/errors';
|
|
330
|
+
|
|
331
|
+
// Create typed errors
|
|
332
|
+
throw createNotFound('Page not found');
|
|
333
|
+
throw createForbidden('Access denied');
|
|
334
|
+
|
|
335
|
+
// Create custom errors
|
|
336
|
+
throw createError({
|
|
337
|
+
statusCode: 422,
|
|
338
|
+
message: 'Validation failed',
|
|
339
|
+
data: { field: 'email', error: 'Invalid format' },
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// Type guards
|
|
343
|
+
if (isFlightError(error)) {
|
|
344
|
+
console.log(error.statusCode, error.digest);
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### React Integration
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
import { ErrorProvider, useError, useFlightError } from '@flight-framework/core/react';
|
|
352
|
+
|
|
353
|
+
// Wrap your app
|
|
354
|
+
<ErrorProvider onError={console.error}>
|
|
355
|
+
<App />
|
|
356
|
+
</ErrorProvider>
|
|
357
|
+
|
|
358
|
+
// Use in components
|
|
359
|
+
function MyComponent() {
|
|
360
|
+
const { error, clearError } = useFlightError();
|
|
361
|
+
|
|
362
|
+
if (error) {
|
|
363
|
+
return <ErrorDisplay error={error} onRetry={clearError} />;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return <Content />;
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Metadata (SEO)
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
import { renderMetadataToHead, type Metadata } from '@flight-framework/core';
|
|
374
|
+
|
|
375
|
+
const metadata: Metadata = {
|
|
376
|
+
title: 'My Page',
|
|
377
|
+
description: 'Page description',
|
|
378
|
+
openGraph: {
|
|
379
|
+
title: 'OG Title',
|
|
380
|
+
image: '/og-image.png',
|
|
381
|
+
},
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
const headHtml = renderMetadataToHead(metadata);
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### Route Rules (ISR/SSG)
|
|
388
|
+
|
|
389
|
+
```typescript
|
|
390
|
+
import { defineConfig } from '@flight-framework/core';
|
|
391
|
+
|
|
392
|
+
export default defineConfig({
|
|
393
|
+
routeRules: {
|
|
394
|
+
'/': { prerender: true },
|
|
395
|
+
'/blog/**': { isr: 3600 },
|
|
396
|
+
'/api/**': { ssr: true },
|
|
397
|
+
'/static/**': { static: true },
|
|
398
|
+
},
|
|
399
|
+
});
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### Revalidation
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
import {
|
|
406
|
+
revalidatePath,
|
|
407
|
+
revalidateTag,
|
|
408
|
+
createRevalidateHandler
|
|
409
|
+
} from '@flight-framework/core';
|
|
410
|
+
|
|
411
|
+
// On-demand revalidation
|
|
412
|
+
await revalidatePath('/blog/my-post');
|
|
413
|
+
await revalidateTag('blog-posts');
|
|
414
|
+
|
|
415
|
+
// Create revalidation API handler
|
|
416
|
+
const handler = createRevalidateHandler({
|
|
417
|
+
secret: process.env.REVALIDATE_SECRET,
|
|
418
|
+
});
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
## Environment Utilities
|
|
422
|
+
|
|
423
|
+
```typescript
|
|
424
|
+
import {
|
|
425
|
+
isServer,
|
|
426
|
+
isBrowser,
|
|
427
|
+
isProduction,
|
|
428
|
+
isDevelopment
|
|
429
|
+
} from '@flight-framework/core/utils';
|
|
430
|
+
|
|
431
|
+
if (isServer()) {
|
|
432
|
+
// Server-only code
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (isDevelopment()) {
|
|
436
|
+
console.log('Debug info');
|
|
437
|
+
}
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
## Module Exports
|
|
441
|
+
|
|
442
|
+
| Export | Description |
|
|
443
|
+
|--------|-------------|
|
|
444
|
+
| `@flight-framework/core` | Main entry with all primitives |
|
|
445
|
+
| `@flight-framework/core/router` | Routing utilities |
|
|
446
|
+
| `@flight-framework/core/cache` | Caching system |
|
|
447
|
+
| `@flight-framework/core/server` | HTTP server |
|
|
448
|
+
| `@flight-framework/core/render` | Rendering modes |
|
|
449
|
+
| `@flight-framework/core/middleware` | Middleware chain |
|
|
450
|
+
| `@flight-framework/core/actions` | Server actions |
|
|
451
|
+
| `@flight-framework/core/streaming` | Streaming SSR |
|
|
452
|
+
| `@flight-framework/core/islands` | Islands architecture |
|
|
453
|
+
| `@flight-framework/core/errors` | Error handling |
|
|
454
|
+
| `@flight-framework/core/react` | React integration |
|
|
455
|
+
| `@flight-framework/core/rsc` | React Server Components |
|
|
456
|
+
| `@flight-framework/core/config` | Configuration |
|
|
457
|
+
| `@flight-framework/core/utils` | Environment utilities |
|
|
458
|
+
|
|
459
|
+
## TypeScript
|
|
460
|
+
|
|
461
|
+
Full TypeScript support with exported types for all APIs:
|
|
462
|
+
|
|
463
|
+
```typescript
|
|
464
|
+
import type {
|
|
465
|
+
FlightConfig,
|
|
466
|
+
Route,
|
|
467
|
+
RouteMatch,
|
|
468
|
+
Cache,
|
|
469
|
+
CacheAdapter,
|
|
470
|
+
Middleware,
|
|
471
|
+
FlightError,
|
|
472
|
+
Metadata,
|
|
473
|
+
StreamingHints,
|
|
474
|
+
} from '@flight-framework/core';
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
## Build Plugins
|
|
478
|
+
|
|
479
|
+
### Critical CSS Extraction
|
|
480
|
+
|
|
481
|
+
Extract and inline critical CSS for improved LCP:
|
|
482
|
+
|
|
483
|
+
```bash
|
|
484
|
+
npm install critters
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
```typescript
|
|
488
|
+
// vite.config.ts
|
|
489
|
+
import { criticalCSS } from '@flight-framework/core/plugins';
|
|
490
|
+
|
|
491
|
+
export default defineConfig({
|
|
492
|
+
plugins: [
|
|
493
|
+
criticalCSS({
|
|
494
|
+
// Strategy for loading non-critical CSS
|
|
495
|
+
preload: 'swap', // 'body' | 'media' | 'swap' | 'swap-high' | 'js' | 'js-lazy'
|
|
496
|
+
|
|
497
|
+
// Remove inlined CSS from source
|
|
498
|
+
pruneSource: false,
|
|
499
|
+
|
|
500
|
+
// Routes to process
|
|
501
|
+
include: ['**/*.html'],
|
|
502
|
+
exclude: ['/api/**'],
|
|
503
|
+
}),
|
|
504
|
+
],
|
|
505
|
+
});
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
#### Preload Strategies
|
|
509
|
+
|
|
510
|
+
| Strategy | Description |
|
|
511
|
+
|----------|-------------|
|
|
512
|
+
| `swap` | Use rel="preload" and swap on load |
|
|
513
|
+
| `swap-high` | Like swap with fetchpriority="high" |
|
|
514
|
+
| `media` | Use media="print" and swap |
|
|
515
|
+
| `js` | Load via JavaScript |
|
|
516
|
+
| `js-lazy` | Load via JavaScript when idle |
|
|
517
|
+
| `body` | Move stylesheets to end of body |
|
|
518
|
+
|
|
519
|
+
#### CSS Utilities
|
|
520
|
+
|
|
521
|
+
```typescript
|
|
522
|
+
import {
|
|
523
|
+
extractInlineStyles,
|
|
524
|
+
mergeCSS,
|
|
525
|
+
generatePreloadLink,
|
|
526
|
+
generateNoscriptFallback,
|
|
527
|
+
} from '@flight-framework/core/plugins/critical-css';
|
|
528
|
+
|
|
529
|
+
// Extract styles from HTML
|
|
530
|
+
const { html, styles } = extractInlineStyles(htmlString);
|
|
531
|
+
|
|
532
|
+
// Generate preload link
|
|
533
|
+
const preload = generatePreloadLink('/styles.css', 'swap');
|
|
534
|
+
|
|
535
|
+
// Generate noscript fallback
|
|
536
|
+
const fallback = generateNoscriptFallback('/styles.css');
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
## License
|
|
540
|
+
|
|
541
|
+
MIT
|