@withl5e/l5e 0.1.0-alpha.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 (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +24 -0
  3. package/dist/action.js +10 -0
  4. package/dist/action.js.map +1 -0
  5. package/dist/client-D67hK4Yy.js +9 -0
  6. package/dist/client-D67hK4Yy.js.map +1 -0
  7. package/dist/entry-server-Ckh6zfgm.js +258 -0
  8. package/dist/entry-server-Ckh6zfgm.js.map +1 -0
  9. package/dist/entry-server.js +12 -0
  10. package/dist/entry-server.js.map +1 -0
  11. package/dist/generateMetadata-C5QsMS-H.js +144 -0
  12. package/dist/generateMetadata-C5QsMS-H.js.map +1 -0
  13. package/dist/index-BIt7MJT9.js +163 -0
  14. package/dist/index-BIt7MJT9.js.map +1 -0
  15. package/dist/index.js +49 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/island/client.js +5 -0
  18. package/dist/island/client.js.map +1 -0
  19. package/dist/island/runtime.js +98 -0
  20. package/dist/island/runtime.js.map +1 -0
  21. package/dist/island.js +39 -0
  22. package/dist/island.js.map +1 -0
  23. package/dist/jsx-runtime-C2Vw67N2.js +256 -0
  24. package/dist/jsx-runtime-C2Vw67N2.js.map +1 -0
  25. package/dist/jsx-runtime.js +26 -0
  26. package/dist/jsx-runtime.js.map +1 -0
  27. package/dist/middleware.js +9 -0
  28. package/dist/middleware.js.map +1 -0
  29. package/dist/seo.js +7 -0
  30. package/dist/seo.js.map +1 -0
  31. package/dist/server.js +489 -0
  32. package/dist/server.js.map +1 -0
  33. package/dist/swap/server.js +15 -0
  34. package/dist/swap/server.js.map +1 -0
  35. package/dist/swap.js +121 -0
  36. package/dist/swap.js.map +1 -0
  37. package/dist/tooltip.js +129 -0
  38. package/dist/tooltip.js.map +1 -0
  39. package/dist/vite-plugin.js +381 -0
  40. package/dist/vite-plugin.js.map +1 -0
  41. package/index.ts +1 -0
  42. package/package.json +129 -0
  43. package/src/action/define-action.ts +8 -0
  44. package/src/action/index.ts +2 -0
  45. package/src/action/types.ts +21 -0
  46. package/src/core/bundler.ts +275 -0
  47. package/src/core/const.ts +2 -0
  48. package/src/core/entry-server.d.ts +1 -0
  49. package/src/core/entry-server.ts +381 -0
  50. package/src/core/exceptions.ts +80 -0
  51. package/src/core/head-priority.ts +15 -0
  52. package/src/core/index.ts +40 -0
  53. package/src/core/jsx-runtime.ts +325 -0
  54. package/src/core/jsx-types.d.ts +548 -0
  55. package/src/core/render.ts +181 -0
  56. package/src/core/request.ts +31 -0
  57. package/src/core/server.ts +740 -0
  58. package/src/core/vite-plugin.ts +779 -0
  59. package/src/island/ClientIsland.ts +71 -0
  60. package/src/island/client.ts +3 -0
  61. package/src/island/index.ts +3 -0
  62. package/src/island/runtime.ts +149 -0
  63. package/src/island/strategy-registry.ts +10 -0
  64. package/src/island/types.ts +28 -0
  65. package/src/middleware/defineMiddleware.ts +5 -0
  66. package/src/middleware/index.ts +133 -0
  67. package/src/middleware/sequence.ts +105 -0
  68. package/src/middleware/types.ts +28 -0
  69. package/src/seo/generateMetadata.tsx +559 -0
  70. package/src/seo/index.ts +10 -0
  71. package/src/seo/mergeMetadata.ts +200 -0
  72. package/src/seo/types.ts +316 -0
  73. package/src/swap/SwapResponse.tsx +16 -0
  74. package/src/swap/create-swap.ts +121 -0
  75. package/src/swap/index.ts +8 -0
  76. package/src/swap/parse.ts +12 -0
  77. package/src/swap/server.ts +1 -0
  78. package/src/swap/swap.ts +57 -0
  79. package/src/swap/types.ts +47 -0
  80. package/src/swap/utils.ts +7 -0
  81. package/src/tooltip/index.ts +2 -0
  82. package/src/tooltip/tooltip-loader.ts +108 -0
  83. package/src/tooltip/tooltip-runtime.ts +173 -0
  84. package/types.d.ts +14 -0
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Base HTTP Exception class
3
+ */
4
+ export class HttpException extends Error {
5
+ public readonly statusCode: number;
6
+ public readonly data?: Record<string, any>;
7
+
8
+ constructor(statusCode: number, message: string, data?: Record<string, any>) {
9
+ super(message);
10
+ this.name = 'HttpException';
11
+ this.statusCode = statusCode;
12
+ this.data = data;
13
+
14
+ // Maintains proper stack trace for where our error was thrown (only available on V8)
15
+ if (Error.captureStackTrace) {
16
+ Error.captureStackTrace(this, this.constructor);
17
+ }
18
+ }
19
+ }
20
+
21
+ /**
22
+ * 400 Bad Request Exception
23
+ */
24
+ export class BadRequestException extends HttpException {
25
+ constructor(message: string = 'Bad Request', data?: Record<string, any>) {
26
+ super(400, message, data);
27
+ this.name = 'BadRequestException';
28
+ }
29
+ }
30
+
31
+ /**
32
+ * 404 Not Found Exception
33
+ */
34
+ export class NotFoundException extends HttpException {
35
+ constructor(message: string = 'Not Found', data?: Record<string, any>) {
36
+ super(404, message, data);
37
+ this.name = 'NotFoundException';
38
+ }
39
+ }
40
+
41
+ /**
42
+ * 500 Internal Server Error Exception
43
+ */
44
+ export class InternalServerErrorException extends HttpException {
45
+ constructor(message: string = 'Internal Server Error', data?: Record<string, any>) {
46
+ super(500, message, data);
47
+ this.name = 'InternalServerErrorException';
48
+ }
49
+ }
50
+
51
+ /**
52
+ * 503 Service Unavailable Exception
53
+ */
54
+ export class ServiceUnavailableException extends HttpException {
55
+ constructor(message: string = 'Service Unavailable', data?: Record<string, any>) {
56
+ super(503, message, data);
57
+ this.name = 'ServiceUnavailableException';
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Redirect Exception - for HTTP redirects (301/302)
63
+ */
64
+ export class RedirectException extends Error {
65
+ public readonly url: string;
66
+ public readonly statusCode: number;
67
+ public readonly data?: Record<string, any>;
68
+
69
+ constructor(url: string, statusCode: number = 302, data?: Record<string, any>) {
70
+ super(`Redirect to ${url}`);
71
+ this.name = 'RedirectException';
72
+ this.url = url;
73
+ this.statusCode = statusCode;
74
+ this.data = data;
75
+
76
+ if (Error.captureStackTrace) {
77
+ Error.captureStackTrace(this, this.constructor);
78
+ }
79
+ }
80
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Priority constants for Head component rendering order
3
+ * Lower numbers render first (higher priority)
4
+ */
5
+ export const HEAD_PRIORITY = {
6
+ CRITICAL: 0, // charset, viewport - must be first
7
+ HIGH: 10, // title, description, canonical
8
+ MEDIUM: 50, // meta tags, robots
9
+ SEO: 80, // OpenGraph, Twitter
10
+ LOW: 100, // Custom head elements
11
+ SCRIPTS: 200, // Scripts
12
+ STYLES: 300, // Styles
13
+ } as const;
14
+
15
+ export type HeadPriority = (typeof HEAD_PRIORITY)[keyof typeof HEAD_PRIORITY] | number;
@@ -0,0 +1,40 @@
1
+ export {
2
+ render,
3
+ type GenerateMetadataFunction,
4
+ type GenerateSchemaFunction,
5
+ type LoaderFunction,
6
+ type LoaderResult,
7
+ type RenderResult,
8
+ type RequestInfo,
9
+ type SchemaMarkup,
10
+ } from './entry-server';
11
+ export {
12
+ BadRequestException,
13
+ HttpException,
14
+ InternalServerErrorException,
15
+ NotFoundException,
16
+ RedirectException,
17
+ ServiceUnavailableException,
18
+ } from './exceptions';
19
+ export { HEAD_PRIORITY, type HeadPriority } from './head-priority';
20
+ export * from './jsx-runtime';
21
+ export { escapeHTML, escapeProp, renderJsxToHtmlString, renderToRenderedNode } from './render';
22
+ export {
23
+ createServer,
24
+ startServer,
25
+ type ServerOptions,
26
+ type ServerContext,
27
+ } from './server';
28
+ export { coreVite } from './vite-plugin';
29
+ export {
30
+ createContext,
31
+ defineMiddleware,
32
+ isLocalsSerializable,
33
+ sequence,
34
+ trySerializeLocals,
35
+ type CreateContext,
36
+ type MiddlewareContext,
37
+ type MiddlewareHandler,
38
+ type MiddlewareNext,
39
+ type RewritePayload,
40
+ } from '../middleware';
@@ -0,0 +1,325 @@
1
+ import { mergeMetadata } from '../seo/mergeMetadata';
2
+ import type { Metadata } from '../seo/types';
3
+ import { RAW_HTML_MARKER } from './const';
4
+ import { RequestInfo } from './entry-server';
5
+ import { HEAD_PRIORITY, type HeadPriority } from './head-priority';
6
+
7
+ export type JSXChild =
8
+ | string
9
+ | number
10
+ | boolean
11
+ | null
12
+ | undefined
13
+ | JSXNode
14
+ | JSXChild[]
15
+ | RawHtmlObject
16
+ | HtmlContentObject;
17
+
18
+ export type RawHtmlObject = {
19
+ [RAW_HTML_MARKER]: true;
20
+ content: string;
21
+ };
22
+
23
+ export type HtmlContentObject = {
24
+ htmlContent: string;
25
+ };
26
+
27
+ export type RenderedNode = {
28
+ string: string;
29
+ };
30
+
31
+ export type JSXNode = {
32
+ type: string | ((props: any) => JSXChild);
33
+ props: Record<string, any>;
34
+ children: JSXChild[];
35
+ };
36
+
37
+ export function jsxFactory(type: any, props: any, ...children: any): JSXNode {
38
+ return { type, props: props || {}, children: children.flat() };
39
+ }
40
+
41
+ export function Fragment({
42
+ children,
43
+ ...props
44
+ }: { children?: JSXChild; setHtml?: unknown } & Record<string, any>): JSXChild {
45
+ // Hỗ trợ setHtml cho Fragment
46
+ if (props.setHtml !== undefined) {
47
+ // Trả về object đặc biệt để không bị escape
48
+ return { [RAW_HTML_MARKER]: true, content: props.setHtml?.toString() || '' };
49
+ }
50
+ return children;
51
+ }
52
+
53
+ // AsyncLocalStorage for render context (request-level)
54
+ import { AsyncLocalStorage } from 'async_hooks';
55
+
56
+ interface HeadEntry {
57
+ content: JSXChild;
58
+ priority: number; // Số càng nhỏ, render càng sớm
59
+ source?: string; // Để debug (ví dụ: 'layout', 'page', 'seo')
60
+ }
61
+
62
+ interface IslandEntry {
63
+ key: string; // "Counter_a3f2" — registry key
64
+ src: string; // "src/views/.../Counter.tsx" — manifest-compatible path
65
+ name: string; // "Counter" — export name
66
+ }
67
+
68
+ interface RenderContext {
69
+ clientJsRegistry: Array<{ path: string; from: string }>;
70
+ cssRegistry: Array<{ path: string; from: string }>;
71
+ islandRegistry: IslandEntry[];
72
+ cacheTags: Set<string>;
73
+ headRegistry: HeadEntry[]; // Thay vì JSXChild[]
74
+ metadataStack: Metadata[]; // Stack để track metadata hierarchy
75
+ schemaRegistry: Array<Record<string, any>>; // Schema.org structured data từ loaders
76
+ request: RequestInfo;
77
+ viewName?: string; // View name from route handler
78
+ }
79
+
80
+ const renderStore = new AsyncLocalStorage<RenderContext>();
81
+
82
+ // Create context for each request
83
+ function createRequestContext(requestInfo: RequestInfo): RenderContext {
84
+ return {
85
+ clientJsRegistry: [],
86
+ cssRegistry: [],
87
+ islandRegistry: [],
88
+ cacheTags: new Set(),
89
+ headRegistry: [],
90
+ metadataStack: [],
91
+ schemaRegistry: [],
92
+ request: requestInfo,
93
+ };
94
+ }
95
+
96
+ export function useClientJs(path: string): string {
97
+ if (typeof path === 'string' && path.length > 0) {
98
+ const renderContext = renderStore.getStore();
99
+ if (renderContext) {
100
+ renderContext.clientJsRegistry.push({ path, from: 'Unknown' });
101
+ }
102
+ }
103
+ return '';
104
+ }
105
+
106
+ export function registerIsland(key: string, src: string, name: string): void {
107
+ const renderContext = renderStore.getStore();
108
+ if (renderContext) {
109
+ // Dedupe by key
110
+ if (!renderContext.islandRegistry.some((e) => e.key === key)) {
111
+ renderContext.islandRegistry.push({ key, src, name });
112
+ }
113
+ }
114
+ }
115
+
116
+ export function getIslandEntries(): IslandEntry[] {
117
+ const context = renderStore.getStore();
118
+ if (!context) return [];
119
+ return context.islandRegistry.slice();
120
+ }
121
+
122
+ export function useCss(path: string): string {
123
+ if (typeof path === 'string' && path.length > 0) {
124
+ const renderContext = renderStore.getStore();
125
+ if (renderContext) {
126
+ renderContext.cssRegistry.push({ path, from: 'Unknown' });
127
+ }
128
+ }
129
+ return '';
130
+ }
131
+
132
+ // Wrapper to run render in async context
133
+ export function runInRenderContext<T>(
134
+ renderFn: () => T | Promise<T>,
135
+ requestInfo: RequestInfo,
136
+ viewName?: string,
137
+ ): Promise<T> {
138
+ const context = createRequestContext(requestInfo);
139
+ if (viewName) {
140
+ context.viewName = viewName;
141
+ }
142
+ return renderStore.run(context, () => Promise.resolve(renderFn()));
143
+ }
144
+
145
+ // Set view name in current render context
146
+ export function setViewName(viewName: string): void {
147
+ const context = renderStore.getStore();
148
+ if (context) {
149
+ context.viewName = viewName;
150
+ }
151
+ }
152
+
153
+ // Get entries from current context
154
+ export function getClientJsEntries(): Array<{ path: string; from: string }> {
155
+ const context = renderStore.getStore();
156
+ if (!context) return [];
157
+ return context.clientJsRegistry.slice();
158
+ }
159
+
160
+ // Get cache tags from current context
161
+ export function getCacheTags(): string[] {
162
+ const context = renderStore.getStore();
163
+ if (!context) return [];
164
+ return Array.from(context.cacheTags);
165
+ }
166
+
167
+ // Add cache tags to current context
168
+ export function addCacheTag(tag: string | string[] | Record<string, boolean>): void {
169
+ const context = renderStore.getStore();
170
+ if (!context) return;
171
+
172
+ if (Array.isArray(tag)) {
173
+ tag.forEach((t) => {
174
+ if (typeof t === 'string' && t.trim()) {
175
+ context.cacheTags.add(t.trim());
176
+ }
177
+ });
178
+ } else if (typeof tag === 'string' && tag.trim()) {
179
+ context.cacheTags.add(tag.trim());
180
+ } else if (typeof tag === 'object' && tag !== null) {
181
+ Object.entries(tag).forEach(([key, value]) => {
182
+ if (value && typeof key === 'string' && key.trim()) {
183
+ context.cacheTags.add(key.trim());
184
+ }
185
+ });
186
+ }
187
+ }
188
+
189
+ // Get CSS entries from current context
190
+ export function getCssEntries(): Array<{ path: string; from: string }> {
191
+ const context = renderStore.getStore();
192
+ if (!context) return [];
193
+ return context.cssRegistry.slice();
194
+ }
195
+
196
+ // Head component to collect head elements with priority support
197
+ export function Head({
198
+ children,
199
+ priority = HEAD_PRIORITY.LOW, // Default priority
200
+ }: {
201
+ children?: JSXChild;
202
+ priority?: HeadPriority;
203
+ }): null {
204
+ const renderContext = renderStore.getStore();
205
+ if (renderContext && children) {
206
+ renderContext.headRegistry.push({
207
+ content: children,
208
+ priority: typeof priority === 'number' ? priority : HEAD_PRIORITY.LOW,
209
+ source: 'manual',
210
+ });
211
+
212
+ // Sort theo priority sau mỗi lần push để đảm bảo thứ tự đúng
213
+ renderContext.headRegistry.sort((a, b) => a.priority - b.priority);
214
+ }
215
+ return null;
216
+ }
217
+
218
+ // Get head content from current context (already sorted by priority)
219
+ export function getHeadContent(): JSXChild[] {
220
+ const context = renderStore.getStore();
221
+ if (!context) return [];
222
+ // Đã được sort trong Head component, chỉ cần map để lấy content
223
+ return context.headRegistry.map((entry) => entry.content);
224
+ }
225
+
226
+ // Push metadata to stack (for hierarchical metadata support)
227
+ export function pushMetadata(metadata: Metadata): void {
228
+ const context = renderStore.getStore();
229
+ if (context && metadata) {
230
+ context.metadataStack.push(metadata);
231
+ }
232
+ }
233
+
234
+ // Resolve and merge all metadata from stack (root → leaf)
235
+ export function resolveMetadata(): Metadata | null {
236
+ const context = renderStore.getStore();
237
+ if (!context || context.metadataStack.length === 0) {
238
+ return null;
239
+ }
240
+
241
+ // Merge from root → leaf (reduce left to right)
242
+ return context.metadataStack.reduce(
243
+ (acc, current) => mergeMetadata(acc, current),
244
+ null as Metadata | null,
245
+ );
246
+ }
247
+
248
+ // Push schema to registry (for schema markup from loaders)
249
+ // Accepts schema-dts types (WithContext<T> or array of schemas)
250
+ export function pushSchema(schema: any | Array<any>): void {
251
+ const context = renderStore.getStore();
252
+ if (!context) return;
253
+
254
+ if (Array.isArray(schema)) {
255
+ context.schemaRegistry.push(...schema);
256
+ } else {
257
+ context.schemaRegistry.push(schema);
258
+ }
259
+ }
260
+
261
+ // Get all schemas from registry
262
+ export function getSchemas(): Array<Record<string, any>> {
263
+ const context = renderStore.getStore();
264
+ if (!context) return [];
265
+ return context.schemaRegistry.slice();
266
+ }
267
+
268
+ // Hook to get render request context
269
+ export function useRequest() {
270
+ const context = renderStore.getStore();
271
+
272
+ if (!context) {
273
+ throw new Error('useRequest called outside of render context');
274
+ }
275
+
276
+ return {
277
+ request: context.request,
278
+ view: context.viewName,
279
+ locals: (context.request.locals ?? {}) as Record<string, unknown>,
280
+
281
+ // Add cache tags
282
+ addCacheTag: (tag: string | string[] | Record<string, boolean>) => {
283
+ addCacheTag(tag);
284
+ },
285
+
286
+ // Get all cache tags
287
+ getCacheTags: () => {
288
+ return Array.from(context.cacheTags);
289
+ },
290
+ };
291
+ }
292
+
293
+ /**
294
+ * Checks if a value is a valid JSX element (JSXNode)
295
+ * Similar to React.isValidElement
296
+ */
297
+ export function isValidElement(value: any): value is JSXNode {
298
+ return (
299
+ value !== null &&
300
+ typeof value === 'object' &&
301
+ 'type' in value &&
302
+ 'props' in value &&
303
+ 'children' in value &&
304
+ (typeof value.type === 'string' || typeof value.type === 'function')
305
+ );
306
+ }
307
+
308
+ /**
309
+ * Clones a JSX element with new props and/or children
310
+ * Similar to React.cloneElement
311
+ */
312
+ export function cloneElement(
313
+ element: JSXNode,
314
+ props?: Record<string, any>,
315
+ ...children: JSXChild[]
316
+ ): JSXNode {
317
+ const newProps = { ...element.props, ...props };
318
+ const newChildren = children.length > 0 ? children.flat() : element.children;
319
+
320
+ return {
321
+ type: element.type,
322
+ props: newProps,
323
+ children: newChildren,
324
+ };
325
+ }