@buenojs/bueno 0.8.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/.env.example +109 -0
- package/.github/workflows/ci.yml +31 -0
- package/LICENSE +21 -0
- package/README.md +892 -0
- package/architecture.md +652 -0
- package/bun.lock +70 -0
- package/dist/cli/index.js +3233 -0
- package/dist/index.js +9014 -0
- package/package.json +77 -0
- package/src/cache/index.ts +795 -0
- package/src/cli/ARCHITECTURE.md +837 -0
- package/src/cli/bin.ts +10 -0
- package/src/cli/commands/build.ts +425 -0
- package/src/cli/commands/dev.ts +248 -0
- package/src/cli/commands/generate.ts +541 -0
- package/src/cli/commands/help.ts +55 -0
- package/src/cli/commands/index.ts +112 -0
- package/src/cli/commands/migration.ts +355 -0
- package/src/cli/commands/new.ts +804 -0
- package/src/cli/commands/start.ts +208 -0
- package/src/cli/core/args.ts +283 -0
- package/src/cli/core/console.ts +349 -0
- package/src/cli/core/index.ts +60 -0
- package/src/cli/core/prompt.ts +424 -0
- package/src/cli/core/spinner.ts +265 -0
- package/src/cli/index.ts +135 -0
- package/src/cli/templates/deploy.ts +295 -0
- package/src/cli/templates/docker.ts +307 -0
- package/src/cli/templates/index.ts +24 -0
- package/src/cli/utils/fs.ts +428 -0
- package/src/cli/utils/index.ts +8 -0
- package/src/cli/utils/strings.ts +197 -0
- package/src/config/env.ts +408 -0
- package/src/config/index.ts +506 -0
- package/src/config/loader.ts +329 -0
- package/src/config/merge.ts +285 -0
- package/src/config/types.ts +320 -0
- package/src/config/validation.ts +441 -0
- package/src/container/forward-ref.ts +143 -0
- package/src/container/index.ts +386 -0
- package/src/context/index.ts +360 -0
- package/src/database/index.ts +1142 -0
- package/src/database/migrations/index.ts +371 -0
- package/src/database/schema/index.ts +619 -0
- package/src/frontend/api-routes.ts +640 -0
- package/src/frontend/bundler.ts +643 -0
- package/src/frontend/console-client.ts +419 -0
- package/src/frontend/console-stream.ts +587 -0
- package/src/frontend/dev-server.ts +846 -0
- package/src/frontend/file-router.ts +611 -0
- package/src/frontend/frameworks/index.ts +106 -0
- package/src/frontend/frameworks/react.ts +85 -0
- package/src/frontend/frameworks/solid.ts +104 -0
- package/src/frontend/frameworks/svelte.ts +110 -0
- package/src/frontend/frameworks/vue.ts +92 -0
- package/src/frontend/hmr-client.ts +663 -0
- package/src/frontend/hmr.ts +728 -0
- package/src/frontend/index.ts +342 -0
- package/src/frontend/islands.ts +552 -0
- package/src/frontend/isr.ts +555 -0
- package/src/frontend/layout.ts +475 -0
- package/src/frontend/ssr/react.ts +446 -0
- package/src/frontend/ssr/solid.ts +523 -0
- package/src/frontend/ssr/svelte.ts +546 -0
- package/src/frontend/ssr/vue.ts +504 -0
- package/src/frontend/ssr.ts +699 -0
- package/src/frontend/types.ts +2274 -0
- package/src/health/index.ts +604 -0
- package/src/index.ts +410 -0
- package/src/lock/index.ts +587 -0
- package/src/logger/index.ts +444 -0
- package/src/logger/transports/index.ts +969 -0
- package/src/metrics/index.ts +494 -0
- package/src/middleware/built-in.ts +360 -0
- package/src/middleware/index.ts +94 -0
- package/src/modules/filters.ts +458 -0
- package/src/modules/guards.ts +405 -0
- package/src/modules/index.ts +1256 -0
- package/src/modules/interceptors.ts +574 -0
- package/src/modules/lazy.ts +418 -0
- package/src/modules/lifecycle.ts +478 -0
- package/src/modules/metadata.ts +90 -0
- package/src/modules/pipes.ts +626 -0
- package/src/router/index.ts +339 -0
- package/src/router/linear.ts +371 -0
- package/src/router/regex.ts +292 -0
- package/src/router/tree.ts +562 -0
- package/src/rpc/index.ts +1263 -0
- package/src/security/index.ts +436 -0
- package/src/ssg/index.ts +631 -0
- package/src/storage/index.ts +456 -0
- package/src/telemetry/index.ts +1097 -0
- package/src/testing/index.ts +1586 -0
- package/src/types/index.ts +236 -0
- package/src/types/optional-deps.d.ts +219 -0
- package/src/validation/index.ts +276 -0
- package/src/websocket/index.ts +1004 -0
- package/tests/integration/cli.test.ts +1016 -0
- package/tests/integration/fullstack.test.ts +234 -0
- package/tests/unit/cache.test.ts +174 -0
- package/tests/unit/cli-commands.test.ts +892 -0
- package/tests/unit/cli.test.ts +1258 -0
- package/tests/unit/container.test.ts +279 -0
- package/tests/unit/context.test.ts +221 -0
- package/tests/unit/database.test.ts +183 -0
- package/tests/unit/linear-router.test.ts +280 -0
- package/tests/unit/lock.test.ts +336 -0
- package/tests/unit/middleware.test.ts +184 -0
- package/tests/unit/modules.test.ts +142 -0
- package/tests/unit/pubsub.test.ts +257 -0
- package/tests/unit/regex-router.test.ts +265 -0
- package/tests/unit/router.test.ts +373 -0
- package/tests/unit/rpc.test.ts +1248 -0
- package/tests/unit/security.test.ts +174 -0
- package/tests/unit/telemetry.test.ts +371 -0
- package/tests/unit/test-cache.test.ts +110 -0
- package/tests/unit/test-database.test.ts +282 -0
- package/tests/unit/tree-router.test.ts +325 -0
- package/tests/unit/validation.test.ts +794 -0
- package/tsconfig.json +27 -0
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelte SSR Renderer
|
|
3
|
+
*
|
|
4
|
+
* Provides server-side rendering for Svelte components using:
|
|
5
|
+
* - render() from svelte/server
|
|
6
|
+
* - Svelte Head component support
|
|
7
|
+
* - SvelteKit-like SSR patterns
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
SSRContext,
|
|
12
|
+
SSRElement,
|
|
13
|
+
SSRPage,
|
|
14
|
+
FrameworkSSRRenderer,
|
|
15
|
+
} from "../types.js";
|
|
16
|
+
|
|
17
|
+
// Svelte types (dynamically imported)
|
|
18
|
+
interface SvelteComponent {
|
|
19
|
+
render(props?: Record<string, unknown>): SvelteRenderResult;
|
|
20
|
+
$$render(result: string, props: Record<string, unknown>, bindings: unknown, context: unknown): string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface SvelteRenderResult {
|
|
24
|
+
html: string;
|
|
25
|
+
head: string;
|
|
26
|
+
css: {
|
|
27
|
+
code: string;
|
|
28
|
+
map: unknown;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface SvelteComponentConstructor {
|
|
33
|
+
new (options: {
|
|
34
|
+
target: object | null;
|
|
35
|
+
props?: Record<string, unknown>;
|
|
36
|
+
hydrate?: boolean;
|
|
37
|
+
intro?: boolean;
|
|
38
|
+
$inline?: boolean;
|
|
39
|
+
}): SvelteComponent;
|
|
40
|
+
|
|
41
|
+
render(props?: Record<string, unknown>): SvelteRenderResult;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Head element storage
|
|
45
|
+
let headElements: SSRElement[] = [];
|
|
46
|
+
let headString = "";
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Reset head elements for a new render
|
|
50
|
+
*/
|
|
51
|
+
export function resetHead(): void {
|
|
52
|
+
headElements = [];
|
|
53
|
+
headString = "";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get collected head elements
|
|
58
|
+
*/
|
|
59
|
+
export function getHeadElements(): SSRElement[] {
|
|
60
|
+
return [...headElements];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get head string from Svelte render
|
|
65
|
+
*/
|
|
66
|
+
export function getHeadString(): string {
|
|
67
|
+
return headString;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Set head string from Svelte render
|
|
72
|
+
*/
|
|
73
|
+
export function setHeadString(head: string): void {
|
|
74
|
+
headString = head;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Add a head element
|
|
79
|
+
*/
|
|
80
|
+
export function addHeadElement(element: SSRElement): void {
|
|
81
|
+
headElements.push(element);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Svelte SSR Renderer implementation
|
|
86
|
+
*/
|
|
87
|
+
export class SvelteSSRRenderer implements FrameworkSSRRenderer {
|
|
88
|
+
private svelteServer: typeof import("svelte/server") | null = null;
|
|
89
|
+
private initialized = false;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Initialize Svelte server module
|
|
93
|
+
*/
|
|
94
|
+
async init(): Promise<void> {
|
|
95
|
+
if (this.initialized) return;
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
this.svelteServer = await import("svelte/server");
|
|
99
|
+
this.initialized = true;
|
|
100
|
+
} catch (error) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
"Svelte is not installed. Install it with: bun add svelte"
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Render a component to HTML string
|
|
109
|
+
*/
|
|
110
|
+
async renderToString(component: unknown, context: SSRContext): Promise<string> {
|
|
111
|
+
await this.init();
|
|
112
|
+
|
|
113
|
+
resetHead();
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
// Check if component has render method (Svelte SSR component)
|
|
117
|
+
if (this.isSvelteComponent(component)) {
|
|
118
|
+
const result = component.render({
|
|
119
|
+
context,
|
|
120
|
+
...context.data,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Store head content from Svelte <svelte:head>
|
|
124
|
+
if (result.head) {
|
|
125
|
+
setHeadString(result.head);
|
|
126
|
+
this.parseHeadElements(result.head);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return result.html;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// If it's a constructor, call static render
|
|
133
|
+
if (typeof component === "function" && "render" in component) {
|
|
134
|
+
const result = (component as SvelteComponentConstructor).render({
|
|
135
|
+
context,
|
|
136
|
+
...context.data,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
if (result.head) {
|
|
140
|
+
setHeadString(result.head);
|
|
141
|
+
this.parseHeadElements(result.head);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return result.html;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
throw new Error("Invalid Svelte component");
|
|
148
|
+
} catch (error) {
|
|
149
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
150
|
+
throw new Error(`Svelte renderToString failed: ${errorMessage}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Render a component to a stream
|
|
156
|
+
*/
|
|
157
|
+
renderToStream(component: unknown, context: SSRContext): ReadableStream<Uint8Array> {
|
|
158
|
+
const encoder = new TextEncoder();
|
|
159
|
+
|
|
160
|
+
return new ReadableStream<Uint8Array>({
|
|
161
|
+
start: async (controller) => {
|
|
162
|
+
try {
|
|
163
|
+
await this.init();
|
|
164
|
+
|
|
165
|
+
resetHead();
|
|
166
|
+
|
|
167
|
+
// Svelte doesn't have native streaming, so we render to string
|
|
168
|
+
// and then stream it
|
|
169
|
+
const html = await this.renderToString(component, context);
|
|
170
|
+
|
|
171
|
+
// Simulate streaming by chunking the output
|
|
172
|
+
const chunkSize = 8192; // 8KB chunks
|
|
173
|
+
for (let i = 0; i < html.length; i += chunkSize) {
|
|
174
|
+
const chunk = html.slice(i, i + chunkSize);
|
|
175
|
+
controller.enqueue(encoder.encode(chunk));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
controller.close();
|
|
179
|
+
} catch (error) {
|
|
180
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
181
|
+
controller.error(new Error(`Svelte renderToStream failed: ${errorMessage}`));
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Get head elements from component
|
|
189
|
+
*/
|
|
190
|
+
getHeadElements(context: SSRContext): SSRElement[] {
|
|
191
|
+
return getHeadElements();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Create the framework-specific component
|
|
196
|
+
*/
|
|
197
|
+
createComponent(page: SSRPage, context: SSRContext): unknown {
|
|
198
|
+
return {
|
|
199
|
+
page,
|
|
200
|
+
context,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Check if object is a Svelte component
|
|
206
|
+
*/
|
|
207
|
+
private isSvelteComponent(obj: unknown): obj is SvelteComponent {
|
|
208
|
+
return (
|
|
209
|
+
typeof obj === "object" &&
|
|
210
|
+
obj !== null &&
|
|
211
|
+
"render" in obj &&
|
|
212
|
+
typeof (obj as SvelteComponent).render === "function"
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Parse head HTML string into SSRElement array
|
|
218
|
+
*/
|
|
219
|
+
private parseHeadElements(headHtml: string): void {
|
|
220
|
+
// Simple regex-based parsing for common head elements
|
|
221
|
+
const titleMatch = headHtml.match(/<title>([^<]*)<\/title>/);
|
|
222
|
+
if (titleMatch) {
|
|
223
|
+
addHeadElement({
|
|
224
|
+
tag: "title",
|
|
225
|
+
attrs: {},
|
|
226
|
+
children: [{ tag: "#text", attrs: {}, innerHTML: titleMatch[1] }],
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Parse meta tags
|
|
231
|
+
const metaRegex = /<meta\s+([^>]*)\/?>/g;
|
|
232
|
+
let metaMatch;
|
|
233
|
+
while ((metaMatch = metaRegex.exec(headHtml)) !== null) {
|
|
234
|
+
const attrs = this.parseAttributes(metaMatch[1]);
|
|
235
|
+
addHeadElement({ tag: "meta", attrs });
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Parse link tags
|
|
239
|
+
const linkRegex = /<link\s+([^>]*)\/?>/g;
|
|
240
|
+
let linkMatch;
|
|
241
|
+
while ((linkMatch = linkRegex.exec(headHtml)) !== null) {
|
|
242
|
+
const attrs = this.parseAttributes(linkMatch[1]);
|
|
243
|
+
addHeadElement({ tag: "link", attrs });
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Parse style tags
|
|
247
|
+
const styleRegex = /<style([^>]*)>([^<]*)<\/style>/g;
|
|
248
|
+
let styleMatch;
|
|
249
|
+
while ((styleMatch = styleRegex.exec(headHtml)) !== null) {
|
|
250
|
+
const attrs = this.parseAttributes(styleMatch[1]);
|
|
251
|
+
addHeadElement({ tag: "style", attrs, innerHTML: styleMatch[2] });
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Parse HTML attributes string into object
|
|
257
|
+
*/
|
|
258
|
+
private parseAttributes(attrString: string): Record<string, string> {
|
|
259
|
+
const attrs: Record<string, string> = {};
|
|
260
|
+
const regex = /(\w+)=["']([^"']*)["']/g;
|
|
261
|
+
let match;
|
|
262
|
+
|
|
263
|
+
while ((match = regex.exec(attrString)) !== null) {
|
|
264
|
+
attrs[match[1]] = match[2];
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return attrs;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Render with CSS extraction
|
|
272
|
+
*/
|
|
273
|
+
async renderWithCSS(
|
|
274
|
+
component: unknown,
|
|
275
|
+
context: SSRContext
|
|
276
|
+
): Promise<{
|
|
277
|
+
html: string;
|
|
278
|
+
head: string;
|
|
279
|
+
css: string;
|
|
280
|
+
}> {
|
|
281
|
+
await this.init();
|
|
282
|
+
|
|
283
|
+
resetHead();
|
|
284
|
+
|
|
285
|
+
if (this.isSvelteComponent(component)) {
|
|
286
|
+
const result = component.render({
|
|
287
|
+
context,
|
|
288
|
+
...context.data,
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
if (result.head) {
|
|
292
|
+
setHeadString(result.head);
|
|
293
|
+
this.parseHeadElements(result.head);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
html: result.html,
|
|
298
|
+
head: result.head,
|
|
299
|
+
css: result.css.code,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (typeof component === "function" && "render" in component) {
|
|
304
|
+
const result = (component as SvelteComponentConstructor).render({
|
|
305
|
+
context,
|
|
306
|
+
...context.data,
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
if (result.head) {
|
|
310
|
+
setHeadString(result.head);
|
|
311
|
+
this.parseHeadElements(result.head);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
html: result.html,
|
|
316
|
+
head: result.head,
|
|
317
|
+
css: result.css.code,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
throw new Error("Invalid Svelte component");
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Create a Svelte SSR renderer
|
|
327
|
+
*/
|
|
328
|
+
export function createSvelteSSRRenderer(): SvelteSSRRenderer {
|
|
329
|
+
return new SvelteSSRRenderer();
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Svelte Head component helper
|
|
334
|
+
*/
|
|
335
|
+
export class SvelteHead {
|
|
336
|
+
private static instance: SvelteHead;
|
|
337
|
+
private title = "";
|
|
338
|
+
private metaTags: SSRElement[] = [];
|
|
339
|
+
private linkTags: SSRElement[] = [];
|
|
340
|
+
private scriptTags: SSRElement[] = [];
|
|
341
|
+
private styleTags: SSRElement[] = [];
|
|
342
|
+
|
|
343
|
+
static getInstance(): SvelteHead {
|
|
344
|
+
if (!SvelteHead.instance) {
|
|
345
|
+
SvelteHead.instance = new SvelteHead();
|
|
346
|
+
}
|
|
347
|
+
return SvelteHead.instance;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
setTitle(title: string): this {
|
|
351
|
+
this.title = title;
|
|
352
|
+
addHeadElement({ tag: "title", attrs: {}, children: [{ tag: "#text", attrs: {}, innerHTML: title }] });
|
|
353
|
+
return this;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
addMeta(attrs: Record<string, string>): this {
|
|
357
|
+
this.metaTags.push({ tag: "meta", attrs });
|
|
358
|
+
addHeadElement({ tag: "meta", attrs });
|
|
359
|
+
return this;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
addLink(attrs: Record<string, string>): this {
|
|
363
|
+
this.linkTags.push({ tag: "link", attrs });
|
|
364
|
+
addHeadElement({ tag: "link", attrs });
|
|
365
|
+
return this;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
addScript(attrs: Record<string, string>, innerHTML?: string): this {
|
|
369
|
+
this.scriptTags.push({ tag: "script", attrs, innerHTML });
|
|
370
|
+
addHeadElement({ tag: "script", attrs, innerHTML });
|
|
371
|
+
return this;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
addStyle(innerHTML: string, attrs?: Record<string, string>): this {
|
|
375
|
+
this.styleTags.push({ tag: "style", attrs: attrs || {}, innerHTML });
|
|
376
|
+
addHeadElement({ tag: "style", attrs: attrs || {}, innerHTML });
|
|
377
|
+
return this;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
reset(): void {
|
|
381
|
+
this.title = "";
|
|
382
|
+
this.metaTags = [];
|
|
383
|
+
this.linkTags = [];
|
|
384
|
+
this.scriptTags = [];
|
|
385
|
+
this.styleTags = [];
|
|
386
|
+
resetHead();
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
getTitle(): string {
|
|
390
|
+
return this.title;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
getMetaTags(): SSRElement[] {
|
|
394
|
+
return [...this.metaTags];
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
getLinkTags(): SSRElement[] {
|
|
398
|
+
return [...this.linkTags];
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
getScriptTags(): SSRElement[] {
|
|
402
|
+
return [...this.scriptTags];
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
getStyleTags(): SSRElement[] {
|
|
406
|
+
return [...this.styleTags];
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Get Svelte Head instance
|
|
412
|
+
*/
|
|
413
|
+
export function svelteHead(): SvelteHead {
|
|
414
|
+
return SvelteHead.getInstance();
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Convert SSRElement to HTML string
|
|
419
|
+
*/
|
|
420
|
+
export function ssrElementToString(element: SSRElement): string {
|
|
421
|
+
if (element.tag === "#text") {
|
|
422
|
+
return escapeHtml(element.innerHTML || "");
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const attrs = Object.entries(element.attrs)
|
|
426
|
+
.map(([key, value]) => `${key}="${escapeHtml(value)}"`)
|
|
427
|
+
.join(" ");
|
|
428
|
+
|
|
429
|
+
const openTag = attrs ? `<${element.tag} ${attrs}>` : `<${element.tag}>`;
|
|
430
|
+
|
|
431
|
+
if (element.innerHTML) {
|
|
432
|
+
return `${openTag}${element.innerHTML}</${element.tag}>`;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (element.children && element.children.length > 0) {
|
|
436
|
+
const children = element.children.map(ssrElementToString).join("");
|
|
437
|
+
return `${openTag}${children}</${element.tag}>`;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Self-closing tags
|
|
441
|
+
const voidElements = ["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"];
|
|
442
|
+
if (voidElements.includes(element.tag)) {
|
|
443
|
+
return attrs ? `<${element.tag} ${attrs}>` : `<${element.tag}>`;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return `${openTag}</${element.tag}>`;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Escape HTML special characters
|
|
451
|
+
*/
|
|
452
|
+
function escapeHtml(str: string): string {
|
|
453
|
+
return str
|
|
454
|
+
.replace(/&/g, "\x26amp;")
|
|
455
|
+
.replace(/</g, "\x26lt;")
|
|
456
|
+
.replace(/>/g, "\x26gt;")
|
|
457
|
+
.replace(/"/g, "\x26quot;")
|
|
458
|
+
.replace(/'/g, "'");
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Load a Svelte component from file path
|
|
463
|
+
*/
|
|
464
|
+
export async function loadSvelteComponent(
|
|
465
|
+
filePath: string
|
|
466
|
+
): Promise<SvelteComponentConstructor> {
|
|
467
|
+
try {
|
|
468
|
+
// Dynamic import of the compiled Svelte component
|
|
469
|
+
const module = await import(filePath);
|
|
470
|
+
return module.default;
|
|
471
|
+
} catch (error) {
|
|
472
|
+
throw new Error(`Failed to load Svelte component: ${filePath}`);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Create Svelte SSR context
|
|
478
|
+
*/
|
|
479
|
+
export function createSvelteSSRContext(
|
|
480
|
+
request: Request,
|
|
481
|
+
initialState: Record<string, unknown> = {}
|
|
482
|
+
): SSRContext {
|
|
483
|
+
const url = new URL(request.url);
|
|
484
|
+
|
|
485
|
+
return {
|
|
486
|
+
url: request.url,
|
|
487
|
+
request,
|
|
488
|
+
headers: new Headers(),
|
|
489
|
+
status: 200,
|
|
490
|
+
head: [],
|
|
491
|
+
body: [],
|
|
492
|
+
data: initialState,
|
|
493
|
+
modules: new Set(),
|
|
494
|
+
pathname: url.pathname,
|
|
495
|
+
query: url.searchParams,
|
|
496
|
+
params: {},
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* SvelteKit-like page store for SSR
|
|
502
|
+
*/
|
|
503
|
+
export class SveltePageStore {
|
|
504
|
+
private static instance: SveltePageStore;
|
|
505
|
+
private state: {
|
|
506
|
+
url: URL;
|
|
507
|
+
params: Record<string, string>;
|
|
508
|
+
route: { id: string };
|
|
509
|
+
status: number;
|
|
510
|
+
error: Error | null;
|
|
511
|
+
data: Record<string, unknown>;
|
|
512
|
+
} | null = null;
|
|
513
|
+
|
|
514
|
+
static getInstance(): SveltePageStore {
|
|
515
|
+
if (!SveltePageStore.instance) {
|
|
516
|
+
SveltePageStore.instance = new SveltePageStore();
|
|
517
|
+
}
|
|
518
|
+
return SveltePageStore.instance;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
set(context: SSRContext): void {
|
|
522
|
+
this.state = {
|
|
523
|
+
url: new URL(context.url),
|
|
524
|
+
params: context.params,
|
|
525
|
+
route: { id: context.pathname },
|
|
526
|
+
status: context.status,
|
|
527
|
+
error: null,
|
|
528
|
+
data: context.data,
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
get(): typeof this.state {
|
|
533
|
+
return this.state;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
reset(): void {
|
|
537
|
+
this.state = null;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Get Svelte page store
|
|
543
|
+
*/
|
|
544
|
+
export function getPageStore(): SveltePageStore {
|
|
545
|
+
return SveltePageStore.getInstance();
|
|
546
|
+
}
|