@lolyjs/core 0.2.0-alpha.2 → 0.2.0-alpha.21
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/LICENCE.md +9 -0
- package/README.md +1074 -761
- package/dist/{bootstrap-BiCQmSkx.d.mts → bootstrap-BfGTMUkj.d.mts} +19 -0
- package/dist/{bootstrap-BiCQmSkx.d.ts → bootstrap-BfGTMUkj.d.ts} +19 -0
- package/dist/cli.cjs +16997 -4416
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +17007 -4416
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +14731 -1652
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +295 -57
- package/dist/index.d.ts +295 -57
- package/dist/index.js +17190 -4102
- package/dist/index.js.map +1 -1
- package/dist/index.types-DMOO-uvF.d.mts +221 -0
- package/dist/index.types-DMOO-uvF.d.ts +221 -0
- package/dist/react/cache.cjs +107 -32
- package/dist/react/cache.cjs.map +1 -1
- package/dist/react/cache.d.mts +27 -21
- package/dist/react/cache.d.ts +27 -21
- package/dist/react/cache.js +107 -32
- package/dist/react/cache.js.map +1 -1
- package/dist/react/components.cjs +10 -8
- package/dist/react/components.cjs.map +1 -1
- package/dist/react/components.js +10 -8
- package/dist/react/components.js.map +1 -1
- package/dist/react/hooks.cjs +208 -26
- package/dist/react/hooks.cjs.map +1 -1
- package/dist/react/hooks.d.mts +75 -15
- package/dist/react/hooks.d.ts +75 -15
- package/dist/react/hooks.js +208 -26
- package/dist/react/hooks.js.map +1 -1
- package/dist/react/sockets.cjs +13 -6
- package/dist/react/sockets.cjs.map +1 -1
- package/dist/react/sockets.js +13 -6
- package/dist/react/sockets.js.map +1 -1
- package/dist/react/themes.cjs +61 -18
- package/dist/react/themes.cjs.map +1 -1
- package/dist/react/themes.js +63 -20
- package/dist/react/themes.js.map +1 -1
- package/dist/runtime.cjs +544 -111
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.d.mts +2 -2
- package/dist/runtime.d.ts +2 -2
- package/dist/runtime.js +540 -107
- package/dist/runtime.js.map +1 -1
- package/package.json +49 -4
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { Request, Response } from 'express';
|
|
2
|
+
|
|
3
|
+
type GenerateStaticParams = () => Array<Record<string, string>> | Promise<Array<Record<string, string>>>;
|
|
4
|
+
interface ServerContext {
|
|
5
|
+
req: Request;
|
|
6
|
+
res: Response;
|
|
7
|
+
params: Record<string, string>;
|
|
8
|
+
pathname: string;
|
|
9
|
+
locals: Record<string, any>;
|
|
10
|
+
}
|
|
11
|
+
interface WssActions {
|
|
12
|
+
/**
|
|
13
|
+
* Emit to current socket only (reply)
|
|
14
|
+
*/
|
|
15
|
+
reply(event: string, payload?: any): void;
|
|
16
|
+
/**
|
|
17
|
+
* Emit an event to all clients in the namespace
|
|
18
|
+
*/
|
|
19
|
+
emit: (event: string, ...args: any[]) => void;
|
|
20
|
+
/**
|
|
21
|
+
* Emit to everyone except current socket
|
|
22
|
+
*/
|
|
23
|
+
broadcast(event: string, payload?: any, opts?: {
|
|
24
|
+
excludeSelf?: boolean;
|
|
25
|
+
}): void;
|
|
26
|
+
/**
|
|
27
|
+
* Join a room
|
|
28
|
+
*/
|
|
29
|
+
join(room: string): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Leave a room
|
|
32
|
+
*/
|
|
33
|
+
leave(room: string): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Emit to a specific room
|
|
36
|
+
*/
|
|
37
|
+
toRoom(room: string): {
|
|
38
|
+
emit(event: string, payload?: any): void;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Emit to a specific user (by userId)
|
|
42
|
+
* Uses presence mapping to find user's sockets
|
|
43
|
+
*/
|
|
44
|
+
toUser(userId: string): {
|
|
45
|
+
emit(event: string, payload?: any): void;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Emit error event (reserved event: __loly:error)
|
|
49
|
+
*/
|
|
50
|
+
error(code: string, message: string, details?: any): void;
|
|
51
|
+
/**
|
|
52
|
+
* Emit an event to a specific socket by Socket.IO socket ID
|
|
53
|
+
* @deprecated Use toUser() for user targeting
|
|
54
|
+
*/
|
|
55
|
+
emitTo?: (socketId: string, event: string, ...args: any[]) => void;
|
|
56
|
+
/**
|
|
57
|
+
* Emit an event to a specific client by custom clientId
|
|
58
|
+
* @deprecated Use toUser() for user targeting
|
|
59
|
+
*/
|
|
60
|
+
emitToClient?: (clientId: string, event: string, ...args: any[]) => void;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Route middleware function type.
|
|
64
|
+
* Middlewares run before getServerSideProps and can modify ctx.locals, set headers, redirect, etc.
|
|
65
|
+
*
|
|
66
|
+
* @param ctx - Server context with optional theme
|
|
67
|
+
* @param next - Function to call the next middleware in the chain (must be awaited if used)
|
|
68
|
+
* @returns Promise<void> | void
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* // Simple middleware that adds data to ctx.locals
|
|
72
|
+
* export const beforeServerData: RouteMiddleware[] = [
|
|
73
|
+
* async (ctx, next) => {
|
|
74
|
+
* ctx.locals.user = await getUser(ctx.req);
|
|
75
|
+
* await next();
|
|
76
|
+
* }
|
|
77
|
+
* ];
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* // Middleware that redirects
|
|
81
|
+
* export const beforeServerData: RouteMiddleware[] = [
|
|
82
|
+
* async (ctx, next) => {
|
|
83
|
+
* if (!ctx.locals.user) {
|
|
84
|
+
* ctx.res.redirect('/login');
|
|
85
|
+
* return; // Don't call next() if redirecting
|
|
86
|
+
* }
|
|
87
|
+
* await next();
|
|
88
|
+
* }
|
|
89
|
+
* ];
|
|
90
|
+
*/
|
|
91
|
+
type RouteMiddleware = (ctx: ServerContext & {
|
|
92
|
+
theme?: string;
|
|
93
|
+
}, next: () => Promise<void>) => Promise<void> | void;
|
|
94
|
+
/**
|
|
95
|
+
* Result returned by a server loader (getServerSideProps).
|
|
96
|
+
* @template TProps - Type of props that will be passed to the component (defaults to Record<string, any>)
|
|
97
|
+
*/
|
|
98
|
+
interface LoaderResult<TProps extends Record<string, any> = Record<string, any>> {
|
|
99
|
+
props?: TProps;
|
|
100
|
+
redirect?: {
|
|
101
|
+
destination: string;
|
|
102
|
+
permanent?: boolean;
|
|
103
|
+
};
|
|
104
|
+
notFound?: boolean;
|
|
105
|
+
metadata?: PageMetadata | null;
|
|
106
|
+
className?: string;
|
|
107
|
+
theme?: string;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Server loader function type (getServerSideProps).
|
|
111
|
+
* This function is exported from server.hook.ts files.
|
|
112
|
+
*
|
|
113
|
+
* @template TProps - Type of props that will be returned (defaults to Record<string, any>)
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* // Typed loader
|
|
117
|
+
* export const getServerSideProps: ServerLoader<{ user: User; posts: Post[] }> = async (ctx) => ({
|
|
118
|
+
* props: { user: await getUser(), posts: await getPosts() }
|
|
119
|
+
* });
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* // Untyped loader (backward compatible)
|
|
123
|
+
* export const getServerSideProps: ServerLoader = async (ctx) => ({
|
|
124
|
+
* props: { any: 'data' }
|
|
125
|
+
* });
|
|
126
|
+
*/
|
|
127
|
+
type ServerLoader<TProps extends Record<string, any> = Record<string, any>> = (ctx: ServerContext) => Promise<LoaderResult<TProps>>;
|
|
128
|
+
/**
|
|
129
|
+
* Comprehensive page metadata for SEO and social sharing.
|
|
130
|
+
* Supports standard HTML meta tags, Open Graph, Twitter Cards, and more.
|
|
131
|
+
*/
|
|
132
|
+
interface PageMetadata {
|
|
133
|
+
/** Page title (sets <title> tag) */
|
|
134
|
+
title?: string;
|
|
135
|
+
/** Page description (sets <meta name="description">) */
|
|
136
|
+
description?: string;
|
|
137
|
+
/** Language code (sets <html lang="...">) */
|
|
138
|
+
lang?: string;
|
|
139
|
+
/** Canonical URL (sets <link rel="canonical">) */
|
|
140
|
+
canonical?: string;
|
|
141
|
+
/** Robots directive (sets <meta name="robots">) */
|
|
142
|
+
robots?: string;
|
|
143
|
+
/** Theme color (sets <meta name="theme-color">) */
|
|
144
|
+
themeColor?: string;
|
|
145
|
+
/** Viewport configuration (sets <meta name="viewport">) */
|
|
146
|
+
viewport?: string;
|
|
147
|
+
/** Open Graph metadata for social sharing */
|
|
148
|
+
openGraph?: {
|
|
149
|
+
title?: string;
|
|
150
|
+
description?: string;
|
|
151
|
+
type?: string;
|
|
152
|
+
url?: string;
|
|
153
|
+
image?: string | {
|
|
154
|
+
url: string;
|
|
155
|
+
width?: number;
|
|
156
|
+
height?: number;
|
|
157
|
+
alt?: string;
|
|
158
|
+
};
|
|
159
|
+
siteName?: string;
|
|
160
|
+
locale?: string;
|
|
161
|
+
};
|
|
162
|
+
/** Twitter Card metadata */
|
|
163
|
+
twitter?: {
|
|
164
|
+
card?: "summary" | "summary_large_image" | "app" | "player";
|
|
165
|
+
title?: string;
|
|
166
|
+
description?: string;
|
|
167
|
+
image?: string;
|
|
168
|
+
imageAlt?: string;
|
|
169
|
+
site?: string;
|
|
170
|
+
creator?: string;
|
|
171
|
+
};
|
|
172
|
+
/** Additional custom meta tags */
|
|
173
|
+
metaTags?: {
|
|
174
|
+
name?: string;
|
|
175
|
+
property?: string;
|
|
176
|
+
httpEquiv?: string;
|
|
177
|
+
content: string;
|
|
178
|
+
}[];
|
|
179
|
+
/** Additional link tags (e.g., preconnect, dns-prefetch) */
|
|
180
|
+
links?: {
|
|
181
|
+
rel: string;
|
|
182
|
+
href: string;
|
|
183
|
+
as?: string;
|
|
184
|
+
crossorigin?: string;
|
|
185
|
+
type?: string;
|
|
186
|
+
}[];
|
|
187
|
+
}
|
|
188
|
+
type MetadataLoader = (ctx: ServerContext) => PageMetadata | Promise<PageMetadata>;
|
|
189
|
+
interface ApiContext {
|
|
190
|
+
req: Request;
|
|
191
|
+
res: Response;
|
|
192
|
+
Response: (body?: any, status?: number) => Response<any, Record<string, any>>;
|
|
193
|
+
NotFound: (body?: any) => Response<any, Record<string, any>>;
|
|
194
|
+
params: Record<string, string>;
|
|
195
|
+
pathname: string;
|
|
196
|
+
locals: Record<string, any>;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* API middleware function type.
|
|
200
|
+
* Middlewares run before the API handler and can modify ctx.locals, set headers, etc.
|
|
201
|
+
*
|
|
202
|
+
* @param ctx - API context
|
|
203
|
+
* @param next - Function to call the next middleware in the chain (must be awaited if used)
|
|
204
|
+
* @returns Promise<void> | void
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* // Authentication middleware
|
|
208
|
+
* export const middlewares: ApiMiddleware[] = [
|
|
209
|
+
* async (ctx, next) => {
|
|
210
|
+
* const token = ctx.req.headers.authorization;
|
|
211
|
+
* if (!token) {
|
|
212
|
+
* return ctx.Response({ error: 'Unauthorized' }, 401);
|
|
213
|
+
* }
|
|
214
|
+
* ctx.locals.user = await verifyToken(token);
|
|
215
|
+
* await next();
|
|
216
|
+
* }
|
|
217
|
+
* ];
|
|
218
|
+
*/
|
|
219
|
+
type ApiMiddleware = (ctx: ApiContext, next: () => Promise<void>) => void | Promise<void>;
|
|
220
|
+
|
|
221
|
+
export type { ApiMiddleware as A, GenerateStaticParams as G, LoaderResult as L, MetadataLoader as M, PageMetadata as P, RouteMiddleware as R, ServerContext as S, WssActions as W, ApiContext as a, ServerLoader as b };
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { Request, Response } from 'express';
|
|
2
|
+
|
|
3
|
+
type GenerateStaticParams = () => Array<Record<string, string>> | Promise<Array<Record<string, string>>>;
|
|
4
|
+
interface ServerContext {
|
|
5
|
+
req: Request;
|
|
6
|
+
res: Response;
|
|
7
|
+
params: Record<string, string>;
|
|
8
|
+
pathname: string;
|
|
9
|
+
locals: Record<string, any>;
|
|
10
|
+
}
|
|
11
|
+
interface WssActions {
|
|
12
|
+
/**
|
|
13
|
+
* Emit to current socket only (reply)
|
|
14
|
+
*/
|
|
15
|
+
reply(event: string, payload?: any): void;
|
|
16
|
+
/**
|
|
17
|
+
* Emit an event to all clients in the namespace
|
|
18
|
+
*/
|
|
19
|
+
emit: (event: string, ...args: any[]) => void;
|
|
20
|
+
/**
|
|
21
|
+
* Emit to everyone except current socket
|
|
22
|
+
*/
|
|
23
|
+
broadcast(event: string, payload?: any, opts?: {
|
|
24
|
+
excludeSelf?: boolean;
|
|
25
|
+
}): void;
|
|
26
|
+
/**
|
|
27
|
+
* Join a room
|
|
28
|
+
*/
|
|
29
|
+
join(room: string): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Leave a room
|
|
32
|
+
*/
|
|
33
|
+
leave(room: string): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Emit to a specific room
|
|
36
|
+
*/
|
|
37
|
+
toRoom(room: string): {
|
|
38
|
+
emit(event: string, payload?: any): void;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Emit to a specific user (by userId)
|
|
42
|
+
* Uses presence mapping to find user's sockets
|
|
43
|
+
*/
|
|
44
|
+
toUser(userId: string): {
|
|
45
|
+
emit(event: string, payload?: any): void;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Emit error event (reserved event: __loly:error)
|
|
49
|
+
*/
|
|
50
|
+
error(code: string, message: string, details?: any): void;
|
|
51
|
+
/**
|
|
52
|
+
* Emit an event to a specific socket by Socket.IO socket ID
|
|
53
|
+
* @deprecated Use toUser() for user targeting
|
|
54
|
+
*/
|
|
55
|
+
emitTo?: (socketId: string, event: string, ...args: any[]) => void;
|
|
56
|
+
/**
|
|
57
|
+
* Emit an event to a specific client by custom clientId
|
|
58
|
+
* @deprecated Use toUser() for user targeting
|
|
59
|
+
*/
|
|
60
|
+
emitToClient?: (clientId: string, event: string, ...args: any[]) => void;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Route middleware function type.
|
|
64
|
+
* Middlewares run before getServerSideProps and can modify ctx.locals, set headers, redirect, etc.
|
|
65
|
+
*
|
|
66
|
+
* @param ctx - Server context with optional theme
|
|
67
|
+
* @param next - Function to call the next middleware in the chain (must be awaited if used)
|
|
68
|
+
* @returns Promise<void> | void
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* // Simple middleware that adds data to ctx.locals
|
|
72
|
+
* export const beforeServerData: RouteMiddleware[] = [
|
|
73
|
+
* async (ctx, next) => {
|
|
74
|
+
* ctx.locals.user = await getUser(ctx.req);
|
|
75
|
+
* await next();
|
|
76
|
+
* }
|
|
77
|
+
* ];
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* // Middleware that redirects
|
|
81
|
+
* export const beforeServerData: RouteMiddleware[] = [
|
|
82
|
+
* async (ctx, next) => {
|
|
83
|
+
* if (!ctx.locals.user) {
|
|
84
|
+
* ctx.res.redirect('/login');
|
|
85
|
+
* return; // Don't call next() if redirecting
|
|
86
|
+
* }
|
|
87
|
+
* await next();
|
|
88
|
+
* }
|
|
89
|
+
* ];
|
|
90
|
+
*/
|
|
91
|
+
type RouteMiddleware = (ctx: ServerContext & {
|
|
92
|
+
theme?: string;
|
|
93
|
+
}, next: () => Promise<void>) => Promise<void> | void;
|
|
94
|
+
/**
|
|
95
|
+
* Result returned by a server loader (getServerSideProps).
|
|
96
|
+
* @template TProps - Type of props that will be passed to the component (defaults to Record<string, any>)
|
|
97
|
+
*/
|
|
98
|
+
interface LoaderResult<TProps extends Record<string, any> = Record<string, any>> {
|
|
99
|
+
props?: TProps;
|
|
100
|
+
redirect?: {
|
|
101
|
+
destination: string;
|
|
102
|
+
permanent?: boolean;
|
|
103
|
+
};
|
|
104
|
+
notFound?: boolean;
|
|
105
|
+
metadata?: PageMetadata | null;
|
|
106
|
+
className?: string;
|
|
107
|
+
theme?: string;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Server loader function type (getServerSideProps).
|
|
111
|
+
* This function is exported from server.hook.ts files.
|
|
112
|
+
*
|
|
113
|
+
* @template TProps - Type of props that will be returned (defaults to Record<string, any>)
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* // Typed loader
|
|
117
|
+
* export const getServerSideProps: ServerLoader<{ user: User; posts: Post[] }> = async (ctx) => ({
|
|
118
|
+
* props: { user: await getUser(), posts: await getPosts() }
|
|
119
|
+
* });
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* // Untyped loader (backward compatible)
|
|
123
|
+
* export const getServerSideProps: ServerLoader = async (ctx) => ({
|
|
124
|
+
* props: { any: 'data' }
|
|
125
|
+
* });
|
|
126
|
+
*/
|
|
127
|
+
type ServerLoader<TProps extends Record<string, any> = Record<string, any>> = (ctx: ServerContext) => Promise<LoaderResult<TProps>>;
|
|
128
|
+
/**
|
|
129
|
+
* Comprehensive page metadata for SEO and social sharing.
|
|
130
|
+
* Supports standard HTML meta tags, Open Graph, Twitter Cards, and more.
|
|
131
|
+
*/
|
|
132
|
+
interface PageMetadata {
|
|
133
|
+
/** Page title (sets <title> tag) */
|
|
134
|
+
title?: string;
|
|
135
|
+
/** Page description (sets <meta name="description">) */
|
|
136
|
+
description?: string;
|
|
137
|
+
/** Language code (sets <html lang="...">) */
|
|
138
|
+
lang?: string;
|
|
139
|
+
/** Canonical URL (sets <link rel="canonical">) */
|
|
140
|
+
canonical?: string;
|
|
141
|
+
/** Robots directive (sets <meta name="robots">) */
|
|
142
|
+
robots?: string;
|
|
143
|
+
/** Theme color (sets <meta name="theme-color">) */
|
|
144
|
+
themeColor?: string;
|
|
145
|
+
/** Viewport configuration (sets <meta name="viewport">) */
|
|
146
|
+
viewport?: string;
|
|
147
|
+
/** Open Graph metadata for social sharing */
|
|
148
|
+
openGraph?: {
|
|
149
|
+
title?: string;
|
|
150
|
+
description?: string;
|
|
151
|
+
type?: string;
|
|
152
|
+
url?: string;
|
|
153
|
+
image?: string | {
|
|
154
|
+
url: string;
|
|
155
|
+
width?: number;
|
|
156
|
+
height?: number;
|
|
157
|
+
alt?: string;
|
|
158
|
+
};
|
|
159
|
+
siteName?: string;
|
|
160
|
+
locale?: string;
|
|
161
|
+
};
|
|
162
|
+
/** Twitter Card metadata */
|
|
163
|
+
twitter?: {
|
|
164
|
+
card?: "summary" | "summary_large_image" | "app" | "player";
|
|
165
|
+
title?: string;
|
|
166
|
+
description?: string;
|
|
167
|
+
image?: string;
|
|
168
|
+
imageAlt?: string;
|
|
169
|
+
site?: string;
|
|
170
|
+
creator?: string;
|
|
171
|
+
};
|
|
172
|
+
/** Additional custom meta tags */
|
|
173
|
+
metaTags?: {
|
|
174
|
+
name?: string;
|
|
175
|
+
property?: string;
|
|
176
|
+
httpEquiv?: string;
|
|
177
|
+
content: string;
|
|
178
|
+
}[];
|
|
179
|
+
/** Additional link tags (e.g., preconnect, dns-prefetch) */
|
|
180
|
+
links?: {
|
|
181
|
+
rel: string;
|
|
182
|
+
href: string;
|
|
183
|
+
as?: string;
|
|
184
|
+
crossorigin?: string;
|
|
185
|
+
type?: string;
|
|
186
|
+
}[];
|
|
187
|
+
}
|
|
188
|
+
type MetadataLoader = (ctx: ServerContext) => PageMetadata | Promise<PageMetadata>;
|
|
189
|
+
interface ApiContext {
|
|
190
|
+
req: Request;
|
|
191
|
+
res: Response;
|
|
192
|
+
Response: (body?: any, status?: number) => Response<any, Record<string, any>>;
|
|
193
|
+
NotFound: (body?: any) => Response<any, Record<string, any>>;
|
|
194
|
+
params: Record<string, string>;
|
|
195
|
+
pathname: string;
|
|
196
|
+
locals: Record<string, any>;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* API middleware function type.
|
|
200
|
+
* Middlewares run before the API handler and can modify ctx.locals, set headers, etc.
|
|
201
|
+
*
|
|
202
|
+
* @param ctx - API context
|
|
203
|
+
* @param next - Function to call the next middleware in the chain (must be awaited if used)
|
|
204
|
+
* @returns Promise<void> | void
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* // Authentication middleware
|
|
208
|
+
* export const middlewares: ApiMiddleware[] = [
|
|
209
|
+
* async (ctx, next) => {
|
|
210
|
+
* const token = ctx.req.headers.authorization;
|
|
211
|
+
* if (!token) {
|
|
212
|
+
* return ctx.Response({ error: 'Unauthorized' }, 401);
|
|
213
|
+
* }
|
|
214
|
+
* ctx.locals.user = await verifyToken(token);
|
|
215
|
+
* await next();
|
|
216
|
+
* }
|
|
217
|
+
* ];
|
|
218
|
+
*/
|
|
219
|
+
type ApiMiddleware = (ctx: ApiContext, next: () => Promise<void>) => void | Promise<void>;
|
|
220
|
+
|
|
221
|
+
export type { ApiMiddleware as A, GenerateStaticParams as G, LoaderResult as L, MetadataLoader as M, PageMetadata as P, RouteMiddleware as R, ServerContext as S, WssActions as W, ApiContext as a, ServerLoader as b };
|
package/dist/react/cache.cjs
CHANGED
|
@@ -113,14 +113,16 @@ function deleteCacheEntry(key) {
|
|
|
113
113
|
function buildDataUrl(url) {
|
|
114
114
|
return url + (url.includes("?") ? "&" : "?") + "__fw_data=1";
|
|
115
115
|
}
|
|
116
|
-
async function fetchRouteDataOnce(url) {
|
|
116
|
+
async function fetchRouteDataOnce(url, skipLayoutHooks = true) {
|
|
117
117
|
const dataUrl = buildDataUrl(url);
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
118
|
+
const headers = {
|
|
119
|
+
"x-fw-data": "1",
|
|
120
|
+
Accept: "application/json"
|
|
121
|
+
};
|
|
122
|
+
if (skipLayoutHooks) {
|
|
123
|
+
headers["x-skip-layout-hooks"] = "true";
|
|
124
|
+
}
|
|
125
|
+
const res = await fetch(dataUrl, { headers });
|
|
124
126
|
let json = {};
|
|
125
127
|
try {
|
|
126
128
|
const text = await res.text();
|
|
@@ -140,7 +142,7 @@ async function fetchRouteDataOnce(url) {
|
|
|
140
142
|
};
|
|
141
143
|
return result;
|
|
142
144
|
}
|
|
143
|
-
function revalidatePath(path) {
|
|
145
|
+
function revalidatePath(path, skipAutoRevalidate = false) {
|
|
144
146
|
const normalizedPath = path.split("?")[0];
|
|
145
147
|
const hasQueryParams = path.includes("?");
|
|
146
148
|
const keysForPath = pathIndex.get(normalizedPath);
|
|
@@ -167,30 +169,86 @@ function revalidatePath(path) {
|
|
|
167
169
|
keysToDelete.forEach((key) => {
|
|
168
170
|
deleteCacheEntry(key);
|
|
169
171
|
});
|
|
172
|
+
if (!skipAutoRevalidate && typeof window !== "undefined") {
|
|
173
|
+
const currentPathname = window.location.pathname;
|
|
174
|
+
const currentSearch = window.location.search;
|
|
175
|
+
const matchesCurrentPath = normalizedPath === currentPathname;
|
|
176
|
+
if (matchesCurrentPath) {
|
|
177
|
+
if (hasQueryParams && specificQueryParams) {
|
|
178
|
+
const currentQueryParams = currentSearch.replace("?", "").split("&").filter((p) => !p.startsWith("__fw_data=")).sort().join("&");
|
|
179
|
+
if (currentQueryParams === specificQueryParams) {
|
|
180
|
+
revalidate().catch((err) => {
|
|
181
|
+
console.error(
|
|
182
|
+
"[client][cache] Error revalidating current route:",
|
|
183
|
+
err
|
|
184
|
+
);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
revalidate().catch((err) => {
|
|
189
|
+
console.error(
|
|
190
|
+
"[client][cache] Error revalidating current route:",
|
|
191
|
+
err
|
|
192
|
+
);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
170
197
|
}
|
|
198
|
+
var isRevalidating = false;
|
|
171
199
|
async function revalidate() {
|
|
172
200
|
if (typeof window === "undefined") {
|
|
173
201
|
throw new Error("revalidate() can only be called on the client");
|
|
174
202
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
203
|
+
if (isRevalidating) {
|
|
204
|
+
const key = buildDataUrl(window.location.pathname + window.location.search);
|
|
205
|
+
const entry = dataCache.get(key);
|
|
206
|
+
if (entry && entry.status === "pending") {
|
|
207
|
+
return entry.promise;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
isRevalidating = true;
|
|
211
|
+
try {
|
|
212
|
+
const pathname = window.location.pathname + window.location.search;
|
|
213
|
+
revalidatePath(pathname, true);
|
|
214
|
+
const freshData = await getRouteData(pathname, { revalidate: true });
|
|
215
|
+
if (window.__FW_DATA__ && freshData.ok && freshData.json) {
|
|
216
|
+
const currentData = window.__FW_DATA__;
|
|
217
|
+
if (freshData.json.layoutProps !== void 0 && freshData.json.layoutProps !== null) {
|
|
218
|
+
window.__FW_LAYOUT_PROPS__ = freshData.json.layoutProps;
|
|
219
|
+
}
|
|
220
|
+
let combinedProps = currentData.props || {};
|
|
221
|
+
if (freshData.json.layoutProps !== void 0 && freshData.json.layoutProps !== null) {
|
|
222
|
+
combinedProps = {
|
|
223
|
+
...freshData.json.layoutProps,
|
|
224
|
+
...freshData.json.pageProps ?? freshData.json.props ?? {}
|
|
225
|
+
};
|
|
226
|
+
} else if (freshData.json.pageProps !== void 0) {
|
|
227
|
+
const preservedLayoutProps = window.__FW_LAYOUT_PROPS__ || {};
|
|
228
|
+
combinedProps = {
|
|
229
|
+
...preservedLayoutProps,
|
|
230
|
+
...freshData.json.pageProps
|
|
231
|
+
};
|
|
232
|
+
} else if (freshData.json.props) {
|
|
233
|
+
combinedProps = freshData.json.props;
|
|
234
|
+
}
|
|
235
|
+
window.__FW_DATA__ = {
|
|
236
|
+
...currentData,
|
|
237
|
+
pathname: pathname.split("?")[0],
|
|
238
|
+
params: freshData.json.params || currentData.params || {},
|
|
239
|
+
props: combinedProps,
|
|
240
|
+
metadata: freshData.json.metadata ?? currentData.metadata ?? null,
|
|
241
|
+
notFound: freshData.json.notFound ?? false,
|
|
242
|
+
error: freshData.json.error ?? false
|
|
243
|
+
};
|
|
244
|
+
window.dispatchEvent(new CustomEvent("fw-data-refresh", {
|
|
245
|
+
detail: { data: freshData }
|
|
246
|
+
}));
|
|
247
|
+
}
|
|
248
|
+
return freshData;
|
|
249
|
+
} finally {
|
|
250
|
+
isRevalidating = false;
|
|
251
|
+
}
|
|
194
252
|
}
|
|
195
253
|
function revalidateRouteData(url) {
|
|
196
254
|
revalidatePath(url);
|
|
@@ -204,7 +262,7 @@ function prefetchRouteData(url) {
|
|
|
204
262
|
}
|
|
205
263
|
return;
|
|
206
264
|
}
|
|
207
|
-
const promise = fetchRouteDataOnce(url).then((value) => {
|
|
265
|
+
const promise = fetchRouteDataOnce(url, true).then((value) => {
|
|
208
266
|
setCacheEntry(key, { status: "fulfilled", value });
|
|
209
267
|
return value;
|
|
210
268
|
}).catch((error) => {
|
|
@@ -220,7 +278,7 @@ async function getRouteData(url, options) {
|
|
|
220
278
|
deleteCacheEntry(key);
|
|
221
279
|
}
|
|
222
280
|
const entry = dataCache.get(key);
|
|
223
|
-
if (entry) {
|
|
281
|
+
if (entry && !options?.revalidate) {
|
|
224
282
|
if (entry.status === "fulfilled") {
|
|
225
283
|
updateLRU(key);
|
|
226
284
|
return entry.value;
|
|
@@ -229,12 +287,29 @@ async function getRouteData(url, options) {
|
|
|
229
287
|
return entry.promise;
|
|
230
288
|
}
|
|
231
289
|
}
|
|
232
|
-
const
|
|
233
|
-
|
|
290
|
+
const skipLayoutHooks = !options?.revalidate;
|
|
291
|
+
const currentEntry = dataCache.get(key);
|
|
292
|
+
if (currentEntry && !options?.revalidate) {
|
|
293
|
+
if (currentEntry.status === "fulfilled") {
|
|
294
|
+
updateLRU(key);
|
|
295
|
+
return currentEntry.value;
|
|
296
|
+
}
|
|
297
|
+
if (currentEntry.status === "pending") {
|
|
298
|
+
return currentEntry.promise;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
const promise = fetchRouteDataOnce(url, skipLayoutHooks).then((value) => {
|
|
302
|
+
const entryAfterFetch = dataCache.get(key);
|
|
303
|
+
if (!entryAfterFetch || entryAfterFetch.status === "pending") {
|
|
304
|
+
setCacheEntry(key, { status: "fulfilled", value });
|
|
305
|
+
}
|
|
234
306
|
return value;
|
|
235
307
|
}).catch((error) => {
|
|
236
308
|
console.error("[client][cache] Error fetching route data:", error);
|
|
237
|
-
dataCache.
|
|
309
|
+
const entryAfterFetch = dataCache.get(key);
|
|
310
|
+
if (!entryAfterFetch || entryAfterFetch.status === "pending") {
|
|
311
|
+
dataCache.set(key, { status: "rejected", error });
|
|
312
|
+
}
|
|
238
313
|
throw error;
|
|
239
314
|
});
|
|
240
315
|
dataCache.set(key, { status: "pending", promise });
|