@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,523 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Solid SSR Renderer
|
|
3
|
+
*
|
|
4
|
+
* Provides server-side rendering for Solid components using:
|
|
5
|
+
* - renderToString / renderToStringAsync from solid-js/web
|
|
6
|
+
* - Solid Meta for head management
|
|
7
|
+
* - Async rendering support
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
SSRContext,
|
|
12
|
+
SSRElement,
|
|
13
|
+
SSRPage,
|
|
14
|
+
FrameworkSSRRenderer,
|
|
15
|
+
} from "../types.js";
|
|
16
|
+
|
|
17
|
+
// Solid types (dynamically imported)
|
|
18
|
+
interface SolidElement {
|
|
19
|
+
type: unknown;
|
|
20
|
+
props: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface SolidComponent {
|
|
24
|
+
(props: Record<string, unknown>): SolidElement | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface SolidRenderResult {
|
|
28
|
+
html: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Head element storage
|
|
32
|
+
let headElements: SSRElement[] = [];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Reset head elements for a new render
|
|
36
|
+
*/
|
|
37
|
+
export function resetHead(): void {
|
|
38
|
+
headElements = [];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get collected head elements
|
|
43
|
+
*/
|
|
44
|
+
export function getHeadElements(): SSRElement[] {
|
|
45
|
+
return [...headElements];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Add a head element
|
|
50
|
+
*/
|
|
51
|
+
export function addHeadElement(element: SSRElement): void {
|
|
52
|
+
headElements.push(element);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Solid SSR Renderer implementation
|
|
57
|
+
*/
|
|
58
|
+
export class SolidSSRRenderer implements FrameworkSSRRenderer {
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
60
|
+
private solidJs: any = null;
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
62
|
+
private solidWeb: any = null;
|
|
63
|
+
private initialized = false;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Initialize Solid modules
|
|
67
|
+
*/
|
|
68
|
+
async init(): Promise<void> {
|
|
69
|
+
if (this.initialized) return;
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
this.solidJs = await import("solid-js");
|
|
73
|
+
this.solidWeb = await import("solid-js/web");
|
|
74
|
+
this.initialized = true;
|
|
75
|
+
} catch (error) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
"Solid is not installed. Install it with: bun add solid-js"
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Render a component to HTML string
|
|
84
|
+
*/
|
|
85
|
+
async renderToString(component: unknown, context: SSRContext): Promise<string> {
|
|
86
|
+
await this.init();
|
|
87
|
+
|
|
88
|
+
if (!this.solidWeb) {
|
|
89
|
+
throw new Error("Solid Web not initialized");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
resetHead();
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const { renderToString } = this.solidWeb;
|
|
96
|
+
|
|
97
|
+
// Create a wrapper that provides context
|
|
98
|
+
const wrappedComponent = this.wrapWithContext(component, context);
|
|
99
|
+
|
|
100
|
+
// Use synchronous renderToString
|
|
101
|
+
const html = renderToString(() => wrappedComponent);
|
|
102
|
+
return html;
|
|
103
|
+
} catch (error) {
|
|
104
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
105
|
+
throw new Error(`Solid renderToString failed: ${errorMessage}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Render a component to a stream
|
|
111
|
+
*/
|
|
112
|
+
renderToStream(component: unknown, context: SSRContext): ReadableStream<Uint8Array> {
|
|
113
|
+
const encoder = new TextEncoder();
|
|
114
|
+
|
|
115
|
+
return new ReadableStream<Uint8Array>({
|
|
116
|
+
start: async (controller) => {
|
|
117
|
+
try {
|
|
118
|
+
await this.init();
|
|
119
|
+
|
|
120
|
+
if (!this.solidWeb) {
|
|
121
|
+
controller.error(new Error("Solid Web not initialized"));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
resetHead();
|
|
126
|
+
|
|
127
|
+
const { renderToStringAsync, renderToStream } = this.solidWeb;
|
|
128
|
+
|
|
129
|
+
// Create a wrapper that provides context
|
|
130
|
+
const wrappedComponent = this.wrapWithContext(component, context);
|
|
131
|
+
|
|
132
|
+
// Check if renderToStream is available
|
|
133
|
+
if (renderToStream) {
|
|
134
|
+
const stream = renderToStream(() => wrappedComponent);
|
|
135
|
+
const reader = stream.getReader();
|
|
136
|
+
|
|
137
|
+
while (true) {
|
|
138
|
+
const { done, value } = await reader.read();
|
|
139
|
+
if (done) {
|
|
140
|
+
controller.close();
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
controller.enqueue(value);
|
|
144
|
+
}
|
|
145
|
+
} else if (renderToStringAsync) {
|
|
146
|
+
// Fallback to async render
|
|
147
|
+
const html = await renderToStringAsync(() => wrappedComponent);
|
|
148
|
+
controller.enqueue(encoder.encode(html));
|
|
149
|
+
controller.close();
|
|
150
|
+
} else {
|
|
151
|
+
// Final fallback to sync render
|
|
152
|
+
const html = this.solidWeb.renderToString(() => wrappedComponent);
|
|
153
|
+
controller.enqueue(encoder.encode(html));
|
|
154
|
+
controller.close();
|
|
155
|
+
}
|
|
156
|
+
} catch (error) {
|
|
157
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
158
|
+
controller.error(new Error(`Solid renderToStream failed: ${errorMessage}`));
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Get head elements from component
|
|
166
|
+
*/
|
|
167
|
+
getHeadElements(context: SSRContext): SSRElement[] {
|
|
168
|
+
return getHeadElements();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Create the framework-specific component
|
|
173
|
+
*/
|
|
174
|
+
createComponent(page: SSRPage, context: SSRContext): unknown {
|
|
175
|
+
return {
|
|
176
|
+
page,
|
|
177
|
+
context,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Wrap component with context provider
|
|
183
|
+
*/
|
|
184
|
+
private wrapWithContext(component: unknown, context: SSRContext): () => unknown {
|
|
185
|
+
return () => {
|
|
186
|
+
// In a real implementation, this would use Solid's context API
|
|
187
|
+
// to provide the SSR context to child components
|
|
188
|
+
if (typeof component === "function") {
|
|
189
|
+
return (component as SolidComponent)({ context, ...context.data });
|
|
190
|
+
}
|
|
191
|
+
return component;
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Render with async support
|
|
197
|
+
*/
|
|
198
|
+
async renderAsync(component: unknown, context: SSRContext): Promise<string> {
|
|
199
|
+
await this.init();
|
|
200
|
+
|
|
201
|
+
if (!this.solidWeb) {
|
|
202
|
+
throw new Error("Solid Web not initialized");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
resetHead();
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
const { renderToStringAsync } = this.solidWeb;
|
|
209
|
+
|
|
210
|
+
if (!renderToStringAsync) {
|
|
211
|
+
// Fallback to sync render
|
|
212
|
+
return this.renderToString(component, context);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const wrappedComponent = this.wrapWithContext(component, context);
|
|
216
|
+
const html = await renderToStringAsync(() => wrappedComponent);
|
|
217
|
+
return html;
|
|
218
|
+
} catch (error) {
|
|
219
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
220
|
+
throw new Error(`Solid renderAsync failed: ${errorMessage}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Render with suspense support
|
|
226
|
+
*/
|
|
227
|
+
async renderWithSuspense(
|
|
228
|
+
component: unknown,
|
|
229
|
+
context: SSRContext,
|
|
230
|
+
fallback: string = "<div>Loading...</div>"
|
|
231
|
+
): Promise<string> {
|
|
232
|
+
await this.init();
|
|
233
|
+
|
|
234
|
+
if (!this.solidJs || !this.solidWeb) {
|
|
235
|
+
throw new Error("Solid not initialized");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
resetHead();
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
const { Suspense } = this.solidJs;
|
|
242
|
+
const { renderToStringAsync } = this.solidWeb;
|
|
243
|
+
|
|
244
|
+
if (!renderToStringAsync) {
|
|
245
|
+
return this.renderToString(component, context);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Wrap with Suspense
|
|
249
|
+
const wrappedComponent = () => {
|
|
250
|
+
return Suspense({
|
|
251
|
+
fallback,
|
|
252
|
+
get children() {
|
|
253
|
+
return this.wrapWithContext(component, context)();
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const html = await renderToStringAsync(wrappedComponent);
|
|
259
|
+
return html;
|
|
260
|
+
} catch (error) {
|
|
261
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
262
|
+
throw new Error(`Solid renderWithSuspense failed: ${errorMessage}`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Create a Solid SSR renderer
|
|
269
|
+
*/
|
|
270
|
+
export function createSolidSSRRenderer(): SolidSSRRenderer {
|
|
271
|
+
return new SolidSSRRenderer();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Solid Meta-like head management
|
|
276
|
+
*/
|
|
277
|
+
export class SolidMeta {
|
|
278
|
+
private static instance: SolidMeta;
|
|
279
|
+
private title = "";
|
|
280
|
+
private metaTags: SSRElement[] = [];
|
|
281
|
+
private linkTags: SSRElement[] = [];
|
|
282
|
+
private scriptTags: SSRElement[] = [];
|
|
283
|
+
private styleTags: SSRElement[] = [];
|
|
284
|
+
|
|
285
|
+
static getInstance(): SolidMeta {
|
|
286
|
+
if (!SolidMeta.instance) {
|
|
287
|
+
SolidMeta.instance = new SolidMeta();
|
|
288
|
+
}
|
|
289
|
+
return SolidMeta.instance;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
setTitle(title: string): this {
|
|
293
|
+
this.title = title;
|
|
294
|
+
addHeadElement({ tag: "title", attrs: {}, children: [{ tag: "#text", attrs: {}, innerHTML: title }] });
|
|
295
|
+
return this;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
addMeta(attrs: Record<string, string>): this {
|
|
299
|
+
this.metaTags.push({ tag: "meta", attrs });
|
|
300
|
+
addHeadElement({ tag: "meta", attrs });
|
|
301
|
+
return this;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
addLink(attrs: Record<string, string>): this {
|
|
305
|
+
this.linkTags.push({ tag: "link", attrs });
|
|
306
|
+
addHeadElement({ tag: "link", attrs });
|
|
307
|
+
return this;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
addScript(attrs: Record<string, string>, innerHTML?: string): this {
|
|
311
|
+
this.scriptTags.push({ tag: "script", attrs, innerHTML });
|
|
312
|
+
addHeadElement({ tag: "script", attrs, innerHTML });
|
|
313
|
+
return this;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
addStyle(innerHTML: string, attrs?: Record<string, string>): this {
|
|
317
|
+
this.styleTags.push({ tag: "style", attrs: attrs || {}, innerHTML });
|
|
318
|
+
addHeadElement({ tag: "style", attrs: attrs || {}, innerHTML });
|
|
319
|
+
return this;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
reset(): void {
|
|
323
|
+
this.title = "";
|
|
324
|
+
this.metaTags = [];
|
|
325
|
+
this.linkTags = [];
|
|
326
|
+
this.scriptTags = [];
|
|
327
|
+
this.styleTags = [];
|
|
328
|
+
resetHead();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
getTitle(): string {
|
|
332
|
+
return this.title;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
getMetaTags(): SSRElement[] {
|
|
336
|
+
return [...this.metaTags];
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
getLinkTags(): SSRElement[] {
|
|
340
|
+
return [...this.linkTags];
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
getScriptTags(): SSRElement[] {
|
|
344
|
+
return [...this.scriptTags];
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
getStyleTags(): SSRElement[] {
|
|
348
|
+
return [...this.styleTags];
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Get Solid Meta instance
|
|
354
|
+
*/
|
|
355
|
+
export function solidMeta(): SolidMeta {
|
|
356
|
+
return SolidMeta.getInstance();
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Convert SSRElement to HTML string
|
|
361
|
+
*/
|
|
362
|
+
export function ssrElementToString(element: SSRElement): string {
|
|
363
|
+
if (element.tag === "#text") {
|
|
364
|
+
return escapeHtml(element.innerHTML || "");
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const attrs = Object.entries(element.attrs)
|
|
368
|
+
.map(([key, value]) => `${key}="${escapeHtml(value)}"`)
|
|
369
|
+
.join(" ");
|
|
370
|
+
|
|
371
|
+
const openTag = attrs ? `<${element.tag} ${attrs}>` : `<${element.tag}>`;
|
|
372
|
+
|
|
373
|
+
if (element.innerHTML) {
|
|
374
|
+
return `${openTag}${element.innerHTML}</${element.tag}>`;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (element.children && element.children.length > 0) {
|
|
378
|
+
const children = element.children.map(ssrElementToString).join("");
|
|
379
|
+
return `${openTag}${children}</${element.tag}>`;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Self-closing tags
|
|
383
|
+
const voidElements = ["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"];
|
|
384
|
+
if (voidElements.includes(element.tag)) {
|
|
385
|
+
return attrs ? `<${element.tag} ${attrs}>` : `<${element.tag}>`;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return `${openTag}</${element.tag}>`;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Escape HTML special characters
|
|
393
|
+
*/
|
|
394
|
+
function escapeHtml(str: string): string {
|
|
395
|
+
return str
|
|
396
|
+
.replace(/&/g, "\x26amp;")
|
|
397
|
+
.replace(/</g, "\x26lt;")
|
|
398
|
+
.replace(/>/g, "\x26gt;")
|
|
399
|
+
.replace(/"/g, "\x26quot;")
|
|
400
|
+
.replace(/'/g, "'");
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Create Solid SSR context
|
|
405
|
+
*/
|
|
406
|
+
export function createSolidSSRContext(
|
|
407
|
+
request: Request,
|
|
408
|
+
initialState: Record<string, unknown> = {}
|
|
409
|
+
): SSRContext {
|
|
410
|
+
const url = new URL(request.url);
|
|
411
|
+
|
|
412
|
+
return {
|
|
413
|
+
url: request.url,
|
|
414
|
+
request,
|
|
415
|
+
headers: new Headers(),
|
|
416
|
+
status: 200,
|
|
417
|
+
head: [],
|
|
418
|
+
body: [],
|
|
419
|
+
data: initialState,
|
|
420
|
+
modules: new Set(),
|
|
421
|
+
pathname: url.pathname,
|
|
422
|
+
query: url.searchParams,
|
|
423
|
+
params: {},
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Solid Start-like route data helper
|
|
429
|
+
*/
|
|
430
|
+
export function createRouteData<T>(
|
|
431
|
+
fetcher: () => Promise<T>,
|
|
432
|
+
options: {
|
|
433
|
+
key?: () => unknown[];
|
|
434
|
+
deferStream?: boolean;
|
|
435
|
+
} = {}
|
|
436
|
+
): {
|
|
437
|
+
data: T | undefined;
|
|
438
|
+
loading: boolean;
|
|
439
|
+
error: Error | null;
|
|
440
|
+
} {
|
|
441
|
+
let data: T | undefined;
|
|
442
|
+
let loading = true;
|
|
443
|
+
let error: Error | null = null;
|
|
444
|
+
|
|
445
|
+
// Start fetching
|
|
446
|
+
fetcher()
|
|
447
|
+
.then((result) => {
|
|
448
|
+
data = result;
|
|
449
|
+
loading = false;
|
|
450
|
+
})
|
|
451
|
+
.catch((err) => {
|
|
452
|
+
error = err instanceof Error ? err : new Error(String(err));
|
|
453
|
+
loading = false;
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
return { data, loading, error };
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Solid resource wrapper for SSR
|
|
461
|
+
*/
|
|
462
|
+
export class SolidResource<T> {
|
|
463
|
+
private data: T | undefined;
|
|
464
|
+
private error: Error | null = null;
|
|
465
|
+
private loading = true;
|
|
466
|
+
private promise: Promise<T> | null = null;
|
|
467
|
+
|
|
468
|
+
constructor(fetcher: () => Promise<T>) {
|
|
469
|
+
this.promise = fetcher();
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
async load(): Promise<T> {
|
|
473
|
+
if (this.data !== undefined) return this.data;
|
|
474
|
+
if (this.error) throw this.error;
|
|
475
|
+
|
|
476
|
+
try {
|
|
477
|
+
this.data = (await this.promise) as T;
|
|
478
|
+
this.loading = false;
|
|
479
|
+
return this.data;
|
|
480
|
+
} catch (err) {
|
|
481
|
+
this.error = err instanceof Error ? err : new Error(String(err));
|
|
482
|
+
this.loading = false;
|
|
483
|
+
throw this.error;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
get(): T | undefined {
|
|
488
|
+
return this.data;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
getState(): { data: T | undefined; loading: boolean; error: Error | null } {
|
|
492
|
+
return {
|
|
493
|
+
data: this.data,
|
|
494
|
+
loading: this.loading,
|
|
495
|
+
error: this.error,
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Create a Solid resource
|
|
502
|
+
*/
|
|
503
|
+
export function createSolidResource<T>(fetcher: () => Promise<T>): SolidResource<T> {
|
|
504
|
+
return new SolidResource(fetcher);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* No hydration marker for solid
|
|
509
|
+
*/
|
|
510
|
+
export const NO_HYDRATE = "data-no-hydrate";
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Mark element as no-hydrate
|
|
514
|
+
*/
|
|
515
|
+
export function noHydrate(element: SSRElement): SSRElement {
|
|
516
|
+
return {
|
|
517
|
+
...element,
|
|
518
|
+
attrs: {
|
|
519
|
+
...element.attrs,
|
|
520
|
+
[NO_HYDRATE]: "",
|
|
521
|
+
},
|
|
522
|
+
};
|
|
523
|
+
}
|