@flexireact/core 3.0.1 → 3.0.2
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/dist/cli/index.js +1514 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/client/index.js +373 -0
- package/dist/core/client/index.js.map +1 -0
- package/dist/core/index.js +6415 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/server/index.js +3094 -0
- package/dist/core/server/index.js.map +1 -0
- package/package.json +80 -80
- package/bin/flexireact.js +0 -23
- package/cli/generators.ts +0 -616
- package/cli/index.ts +0 -1182
- package/core/actions/index.ts +0 -364
- package/core/api.ts +0 -143
- package/core/build/index.ts +0 -425
- package/core/cli/logger.ts +0 -353
- package/core/client/Link.tsx +0 -345
- package/core/client/hydration.ts +0 -147
- package/core/client/index.ts +0 -12
- package/core/client/islands.ts +0 -143
- package/core/client/navigation.ts +0 -212
- package/core/client/runtime.ts +0 -52
- package/core/config.ts +0 -116
- package/core/context.ts +0 -83
- package/core/dev.ts +0 -47
- package/core/devtools/index.ts +0 -644
- package/core/edge/cache.ts +0 -344
- package/core/edge/fetch-polyfill.ts +0 -247
- package/core/edge/handler.ts +0 -248
- package/core/edge/index.ts +0 -81
- package/core/edge/ppr.ts +0 -264
- package/core/edge/runtime.ts +0 -161
- package/core/font/index.ts +0 -306
- package/core/helpers.ts +0 -494
- package/core/image/index.ts +0 -413
- package/core/index.ts +0 -218
- package/core/islands/index.ts +0 -293
- package/core/loader.ts +0 -111
- package/core/logger.ts +0 -242
- package/core/metadata/index.ts +0 -622
- package/core/middleware/index.ts +0 -416
- package/core/plugins/index.ts +0 -373
- package/core/render/index.ts +0 -1243
- package/core/render.ts +0 -136
- package/core/router/index.ts +0 -551
- package/core/router.ts +0 -141
- package/core/rsc/index.ts +0 -199
- package/core/server/index.ts +0 -779
- package/core/server.ts +0 -203
- package/core/ssg/index.ts +0 -346
- package/core/start-dev.ts +0 -6
- package/core/start-prod.ts +0 -6
- package/core/tsconfig.json +0 -30
- package/core/types.ts +0 -239
- package/core/utils.ts +0 -176
package/core/helpers.ts
DELETED
|
@@ -1,494 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FlexiReact Server Helpers
|
|
3
|
-
* Utility functions for server-side operations
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// ============================================================================
|
|
7
|
-
// Response Helpers
|
|
8
|
-
// ============================================================================
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Custom error classes for control flow
|
|
12
|
-
*/
|
|
13
|
-
export class RedirectError extends Error {
|
|
14
|
-
public readonly url: string;
|
|
15
|
-
public readonly statusCode: number;
|
|
16
|
-
public readonly type = 'redirect' as const;
|
|
17
|
-
|
|
18
|
-
constructor(url: string, statusCode: number = 307) {
|
|
19
|
-
super(`Redirect to ${url}`);
|
|
20
|
-
this.name = 'RedirectError';
|
|
21
|
-
this.url = url;
|
|
22
|
-
this.statusCode = statusCode;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export class NotFoundError extends Error {
|
|
27
|
-
public readonly type = 'notFound' as const;
|
|
28
|
-
|
|
29
|
-
constructor(message: string = 'Page not found') {
|
|
30
|
-
super(message);
|
|
31
|
-
this.name = 'NotFoundError';
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Redirect to a different URL
|
|
37
|
-
* @param url - The URL to redirect to
|
|
38
|
-
* @param type - 'replace' (307) or 'push' (308) for permanent
|
|
39
|
-
*
|
|
40
|
-
* @example
|
|
41
|
-
* ```tsx
|
|
42
|
-
* import { redirect } from '@flexireact/core';
|
|
43
|
-
*
|
|
44
|
-
* export default function ProtectedPage() {
|
|
45
|
-
* const user = getUser();
|
|
46
|
-
* if (!user) {
|
|
47
|
-
* redirect('/login');
|
|
48
|
-
* }
|
|
49
|
-
* return <Dashboard user={user} />;
|
|
50
|
-
* }
|
|
51
|
-
* ```
|
|
52
|
-
*/
|
|
53
|
-
export function redirect(url: string, type: 'replace' | 'permanent' = 'replace'): never {
|
|
54
|
-
const statusCode = type === 'permanent' ? 308 : 307;
|
|
55
|
-
throw new RedirectError(url, statusCode);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Trigger a 404 Not Found response
|
|
60
|
-
*
|
|
61
|
-
* @example
|
|
62
|
-
* ```tsx
|
|
63
|
-
* import { notFound } from '@flexireact/core';
|
|
64
|
-
*
|
|
65
|
-
* export default function ProductPage({ params }) {
|
|
66
|
-
* const product = getProduct(params.id);
|
|
67
|
-
* if (!product) {
|
|
68
|
-
* notFound();
|
|
69
|
-
* }
|
|
70
|
-
* return <Product data={product} />;
|
|
71
|
-
* }
|
|
72
|
-
* ```
|
|
73
|
-
*/
|
|
74
|
-
export function notFound(message?: string): never {
|
|
75
|
-
throw new NotFoundError(message);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Create a JSON response
|
|
80
|
-
*
|
|
81
|
-
* @example
|
|
82
|
-
* ```ts
|
|
83
|
-
* // In API route
|
|
84
|
-
* export function GET() {
|
|
85
|
-
* return json({ message: 'Hello' });
|
|
86
|
-
* }
|
|
87
|
-
*
|
|
88
|
-
* export function POST() {
|
|
89
|
-
* return json({ error: 'Bad request' }, { status: 400 });
|
|
90
|
-
* }
|
|
91
|
-
* ```
|
|
92
|
-
*/
|
|
93
|
-
export function json<T>(
|
|
94
|
-
data: T,
|
|
95
|
-
options: {
|
|
96
|
-
status?: number;
|
|
97
|
-
headers?: Record<string, string>;
|
|
98
|
-
} = {}
|
|
99
|
-
): Response {
|
|
100
|
-
const { status = 200, headers = {} } = options;
|
|
101
|
-
|
|
102
|
-
return new Response(JSON.stringify(data), {
|
|
103
|
-
status,
|
|
104
|
-
headers: {
|
|
105
|
-
'Content-Type': 'application/json',
|
|
106
|
-
...headers
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Create an HTML response
|
|
113
|
-
*/
|
|
114
|
-
export function html(
|
|
115
|
-
content: string,
|
|
116
|
-
options: {
|
|
117
|
-
status?: number;
|
|
118
|
-
headers?: Record<string, string>;
|
|
119
|
-
} = {}
|
|
120
|
-
): Response {
|
|
121
|
-
const { status = 200, headers = {} } = options;
|
|
122
|
-
|
|
123
|
-
return new Response(content, {
|
|
124
|
-
status,
|
|
125
|
-
headers: {
|
|
126
|
-
'Content-Type': 'text/html; charset=utf-8',
|
|
127
|
-
...headers
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Create a text response
|
|
134
|
-
*/
|
|
135
|
-
export function text(
|
|
136
|
-
content: string,
|
|
137
|
-
options: {
|
|
138
|
-
status?: number;
|
|
139
|
-
headers?: Record<string, string>;
|
|
140
|
-
} = {}
|
|
141
|
-
): Response {
|
|
142
|
-
const { status = 200, headers = {} } = options;
|
|
143
|
-
|
|
144
|
-
return new Response(content, {
|
|
145
|
-
status,
|
|
146
|
-
headers: {
|
|
147
|
-
'Content-Type': 'text/plain; charset=utf-8',
|
|
148
|
-
...headers
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// ============================================================================
|
|
154
|
-
// Cookies API
|
|
155
|
-
// ============================================================================
|
|
156
|
-
|
|
157
|
-
export interface CookieOptions {
|
|
158
|
-
/** Max age in seconds */
|
|
159
|
-
maxAge?: number;
|
|
160
|
-
/** Expiration date */
|
|
161
|
-
expires?: Date;
|
|
162
|
-
/** Cookie path */
|
|
163
|
-
path?: string;
|
|
164
|
-
/** Cookie domain */
|
|
165
|
-
domain?: string;
|
|
166
|
-
/** Secure flag (HTTPS only) */
|
|
167
|
-
secure?: boolean;
|
|
168
|
-
/** HttpOnly flag */
|
|
169
|
-
httpOnly?: boolean;
|
|
170
|
-
/** SameSite policy */
|
|
171
|
-
sameSite?: 'strict' | 'lax' | 'none';
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Cookie utilities for server-side operations
|
|
176
|
-
*/
|
|
177
|
-
export const cookies = {
|
|
178
|
-
/**
|
|
179
|
-
* Parse cookies from a cookie header string
|
|
180
|
-
*/
|
|
181
|
-
parse(cookieHeader: string): Record<string, string> {
|
|
182
|
-
const cookies: Record<string, string> = {};
|
|
183
|
-
if (!cookieHeader) return cookies;
|
|
184
|
-
|
|
185
|
-
cookieHeader.split(';').forEach(cookie => {
|
|
186
|
-
const [name, ...rest] = cookie.split('=');
|
|
187
|
-
if (name) {
|
|
188
|
-
const value = rest.join('=');
|
|
189
|
-
cookies[name.trim()] = decodeURIComponent(value.trim());
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
return cookies;
|
|
194
|
-
},
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Get a cookie value from request headers
|
|
198
|
-
*/
|
|
199
|
-
get(request: Request, name: string): string | undefined {
|
|
200
|
-
const cookieHeader = request.headers.get('cookie') || '';
|
|
201
|
-
const parsed = this.parse(cookieHeader);
|
|
202
|
-
return parsed[name];
|
|
203
|
-
},
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Get all cookies from request
|
|
207
|
-
*/
|
|
208
|
-
getAll(request: Request): Record<string, string> {
|
|
209
|
-
const cookieHeader = request.headers.get('cookie') || '';
|
|
210
|
-
return this.parse(cookieHeader);
|
|
211
|
-
},
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Serialize a cookie for Set-Cookie header
|
|
215
|
-
*/
|
|
216
|
-
serialize(name: string, value: string, options: CookieOptions = {}): string {
|
|
217
|
-
let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
|
|
218
|
-
|
|
219
|
-
if (options.maxAge !== undefined) {
|
|
220
|
-
cookie += `; Max-Age=${options.maxAge}`;
|
|
221
|
-
}
|
|
222
|
-
if (options.expires) {
|
|
223
|
-
cookie += `; Expires=${options.expires.toUTCString()}`;
|
|
224
|
-
}
|
|
225
|
-
if (options.path) {
|
|
226
|
-
cookie += `; Path=${options.path}`;
|
|
227
|
-
}
|
|
228
|
-
if (options.domain) {
|
|
229
|
-
cookie += `; Domain=${options.domain}`;
|
|
230
|
-
}
|
|
231
|
-
if (options.secure) {
|
|
232
|
-
cookie += '; Secure';
|
|
233
|
-
}
|
|
234
|
-
if (options.httpOnly) {
|
|
235
|
-
cookie += '; HttpOnly';
|
|
236
|
-
}
|
|
237
|
-
if (options.sameSite) {
|
|
238
|
-
cookie += `; SameSite=${options.sameSite.charAt(0).toUpperCase() + options.sameSite.slice(1)}`;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
return cookie;
|
|
242
|
-
},
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Create a Set-Cookie header value
|
|
246
|
-
*/
|
|
247
|
-
set(name: string, value: string, options: CookieOptions = {}): string {
|
|
248
|
-
return this.serialize(name, value, {
|
|
249
|
-
path: '/',
|
|
250
|
-
httpOnly: true,
|
|
251
|
-
secure: process.env.NODE_ENV === 'production',
|
|
252
|
-
sameSite: 'lax',
|
|
253
|
-
...options
|
|
254
|
-
});
|
|
255
|
-
},
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Create a cookie deletion header
|
|
259
|
-
*/
|
|
260
|
-
delete(name: string, options: Omit<CookieOptions, 'maxAge' | 'expires'> = {}): string {
|
|
261
|
-
return this.serialize(name, '', {
|
|
262
|
-
...options,
|
|
263
|
-
path: '/',
|
|
264
|
-
maxAge: 0
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
// ============================================================================
|
|
270
|
-
// Headers API
|
|
271
|
-
// ============================================================================
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Headers utilities for server-side operations
|
|
275
|
-
*/
|
|
276
|
-
export const headers = {
|
|
277
|
-
/**
|
|
278
|
-
* Create a new Headers object with common defaults
|
|
279
|
-
*/
|
|
280
|
-
create(init?: HeadersInit): Headers {
|
|
281
|
-
return new Headers(init);
|
|
282
|
-
},
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* Get a header value from request
|
|
286
|
-
*/
|
|
287
|
-
get(request: Request, name: string): string | null {
|
|
288
|
-
return request.headers.get(name);
|
|
289
|
-
},
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* Get all headers as an object
|
|
293
|
-
*/
|
|
294
|
-
getAll(request: Request): Record<string, string> {
|
|
295
|
-
const result: Record<string, string> = {};
|
|
296
|
-
request.headers.forEach((value, key) => {
|
|
297
|
-
result[key] = value;
|
|
298
|
-
});
|
|
299
|
-
return result;
|
|
300
|
-
},
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Check if request has a specific header
|
|
304
|
-
*/
|
|
305
|
-
has(request: Request, name: string): boolean {
|
|
306
|
-
return request.headers.has(name);
|
|
307
|
-
},
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Get content type from request
|
|
311
|
-
*/
|
|
312
|
-
contentType(request: Request): string | null {
|
|
313
|
-
return request.headers.get('content-type');
|
|
314
|
-
},
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
* Check if request accepts JSON
|
|
318
|
-
*/
|
|
319
|
-
acceptsJson(request: Request): boolean {
|
|
320
|
-
const accept = request.headers.get('accept') || '';
|
|
321
|
-
return accept.includes('application/json') || accept.includes('*/*');
|
|
322
|
-
},
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Check if request is AJAX/fetch
|
|
326
|
-
*/
|
|
327
|
-
isAjax(request: Request): boolean {
|
|
328
|
-
return request.headers.get('x-requested-with') === 'XMLHttpRequest' ||
|
|
329
|
-
this.acceptsJson(request);
|
|
330
|
-
},
|
|
331
|
-
|
|
332
|
-
/**
|
|
333
|
-
* Get authorization header
|
|
334
|
-
*/
|
|
335
|
-
authorization(request: Request): { type: string; credentials: string } | null {
|
|
336
|
-
const auth = request.headers.get('authorization');
|
|
337
|
-
if (!auth) return null;
|
|
338
|
-
|
|
339
|
-
const [type, ...rest] = auth.split(' ');
|
|
340
|
-
return {
|
|
341
|
-
type: type.toLowerCase(),
|
|
342
|
-
credentials: rest.join(' ')
|
|
343
|
-
};
|
|
344
|
-
},
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* Get bearer token from authorization header
|
|
348
|
-
*/
|
|
349
|
-
bearerToken(request: Request): string | null {
|
|
350
|
-
const auth = this.authorization(request);
|
|
351
|
-
if (auth?.type === 'bearer') {
|
|
352
|
-
return auth.credentials;
|
|
353
|
-
}
|
|
354
|
-
return null;
|
|
355
|
-
},
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
* Common security headers
|
|
359
|
-
*/
|
|
360
|
-
security(): Record<string, string> {
|
|
361
|
-
return {
|
|
362
|
-
'X-Content-Type-Options': 'nosniff',
|
|
363
|
-
'X-Frame-Options': 'DENY',
|
|
364
|
-
'X-XSS-Protection': '1; mode=block',
|
|
365
|
-
'Referrer-Policy': 'strict-origin-when-cross-origin',
|
|
366
|
-
'Permissions-Policy': 'camera=(), microphone=(), geolocation=()'
|
|
367
|
-
};
|
|
368
|
-
},
|
|
369
|
-
|
|
370
|
-
/**
|
|
371
|
-
* CORS headers
|
|
372
|
-
*/
|
|
373
|
-
cors(options: {
|
|
374
|
-
origin?: string;
|
|
375
|
-
methods?: string[];
|
|
376
|
-
headers?: string[];
|
|
377
|
-
credentials?: boolean;
|
|
378
|
-
maxAge?: number;
|
|
379
|
-
} = {}): Record<string, string> {
|
|
380
|
-
const {
|
|
381
|
-
origin = '*',
|
|
382
|
-
methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
|
|
383
|
-
headers: allowHeaders = ['Content-Type', 'Authorization'],
|
|
384
|
-
credentials = false,
|
|
385
|
-
maxAge = 86400
|
|
386
|
-
} = options;
|
|
387
|
-
|
|
388
|
-
const corsHeaders: Record<string, string> = {
|
|
389
|
-
'Access-Control-Allow-Origin': origin,
|
|
390
|
-
'Access-Control-Allow-Methods': methods.join(', '),
|
|
391
|
-
'Access-Control-Allow-Headers': allowHeaders.join(', '),
|
|
392
|
-
'Access-Control-Max-Age': String(maxAge)
|
|
393
|
-
};
|
|
394
|
-
|
|
395
|
-
if (credentials) {
|
|
396
|
-
corsHeaders['Access-Control-Allow-Credentials'] = 'true';
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
return corsHeaders;
|
|
400
|
-
},
|
|
401
|
-
|
|
402
|
-
/**
|
|
403
|
-
* Cache control headers
|
|
404
|
-
*/
|
|
405
|
-
cache(options: {
|
|
406
|
-
maxAge?: number;
|
|
407
|
-
sMaxAge?: number;
|
|
408
|
-
staleWhileRevalidate?: number;
|
|
409
|
-
private?: boolean;
|
|
410
|
-
noStore?: boolean;
|
|
411
|
-
} = {}): Record<string, string> {
|
|
412
|
-
if (options.noStore) {
|
|
413
|
-
return { 'Cache-Control': 'no-store, no-cache, must-revalidate' };
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
const directives: string[] = [];
|
|
417
|
-
|
|
418
|
-
if (options.private) {
|
|
419
|
-
directives.push('private');
|
|
420
|
-
} else {
|
|
421
|
-
directives.push('public');
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
if (options.maxAge !== undefined) {
|
|
425
|
-
directives.push(`max-age=${options.maxAge}`);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
if (options.sMaxAge !== undefined) {
|
|
429
|
-
directives.push(`s-maxage=${options.sMaxAge}`);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
if (options.staleWhileRevalidate !== undefined) {
|
|
433
|
-
directives.push(`stale-while-revalidate=${options.staleWhileRevalidate}`);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
return { 'Cache-Control': directives.join(', ') };
|
|
437
|
-
}
|
|
438
|
-
};
|
|
439
|
-
|
|
440
|
-
// ============================================================================
|
|
441
|
-
// Request Helpers
|
|
442
|
-
// ============================================================================
|
|
443
|
-
|
|
444
|
-
/**
|
|
445
|
-
* Parse JSON body from request
|
|
446
|
-
*/
|
|
447
|
-
export async function parseJson<T = unknown>(request: Request): Promise<T> {
|
|
448
|
-
try {
|
|
449
|
-
return await request.json();
|
|
450
|
-
} catch {
|
|
451
|
-
throw new Error('Invalid JSON body');
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
/**
|
|
456
|
-
* Parse form data from request
|
|
457
|
-
*/
|
|
458
|
-
export async function parseFormData(request: Request): Promise<FormData> {
|
|
459
|
-
return await request.formData();
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
/**
|
|
463
|
-
* Parse URL search params
|
|
464
|
-
*/
|
|
465
|
-
export function parseSearchParams(request: Request): URLSearchParams {
|
|
466
|
-
const url = new URL(request.url);
|
|
467
|
-
return url.searchParams;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
/**
|
|
471
|
-
* Get request method
|
|
472
|
-
*/
|
|
473
|
-
export function getMethod(request: Request): string {
|
|
474
|
-
return request.method.toUpperCase();
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
/**
|
|
478
|
-
* Get request pathname
|
|
479
|
-
*/
|
|
480
|
-
export function getPathname(request: Request): string {
|
|
481
|
-
const url = new URL(request.url);
|
|
482
|
-
return url.pathname;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
/**
|
|
486
|
-
* Check if request method matches
|
|
487
|
-
*/
|
|
488
|
-
export function isMethod(request: Request, method: string | string[]): boolean {
|
|
489
|
-
const reqMethod = getMethod(request);
|
|
490
|
-
if (Array.isArray(method)) {
|
|
491
|
-
return method.map(m => m.toUpperCase()).includes(reqMethod);
|
|
492
|
-
}
|
|
493
|
-
return reqMethod === method.toUpperCase();
|
|
494
|
-
}
|