solidstep 0.1.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 (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +18 -0
  3. package/client.d.ts +4 -0
  4. package/client.d.ts.map +1 -0
  5. package/client.js +91 -0
  6. package/index.d.ts +11 -0
  7. package/index.d.ts.map +1 -0
  8. package/index.js +97 -0
  9. package/package.json +58 -0
  10. package/server.d.ts +3 -0
  11. package/server.d.ts.map +1 -0
  12. package/server.js +666 -0
  13. package/utils/cache.d.ts +5 -0
  14. package/utils/cache.d.ts.map +1 -0
  15. package/utils/cache.js +97 -0
  16. package/utils/cookies.d.ts +5 -0
  17. package/utils/cookies.d.ts.map +1 -0
  18. package/utils/cookies.js +13 -0
  19. package/utils/cors.d.ts +14 -0
  20. package/utils/cors.d.ts.map +1 -0
  21. package/utils/cors.js +15 -0
  22. package/utils/csp.d.ts +38 -0
  23. package/utils/csp.d.ts.map +1 -0
  24. package/utils/csp.js +165 -0
  25. package/utils/csrf.d.ts +5 -0
  26. package/utils/csrf.d.ts.map +1 -0
  27. package/utils/csrf.js +46 -0
  28. package/utils/error-handler.d.ts +37 -0
  29. package/utils/error-handler.d.ts.map +1 -0
  30. package/utils/error-handler.js +40 -0
  31. package/utils/fetch.client.d.ts +23 -0
  32. package/utils/fetch.client.d.ts.map +1 -0
  33. package/utils/fetch.client.js +55 -0
  34. package/utils/fetch.server.d.ts +22 -0
  35. package/utils/fetch.server.d.ts.map +1 -0
  36. package/utils/fetch.server.js +54 -0
  37. package/utils/hooks/action-state.d.ts +3 -0
  38. package/utils/hooks/action-state.d.ts.map +1 -0
  39. package/utils/hooks/action-state.js +13 -0
  40. package/utils/loader.d.ts +18 -0
  41. package/utils/loader.d.ts.map +1 -0
  42. package/utils/loader.js +23 -0
  43. package/utils/redirect.d.ts +5 -0
  44. package/utils/redirect.d.ts.map +1 -0
  45. package/utils/redirect.js +14 -0
  46. package/utils/router.d.ts +104 -0
  47. package/utils/router.d.ts.map +1 -0
  48. package/utils/router.js +258 -0
  49. package/utils/server-action.client.d.ts +2 -0
  50. package/utils/server-action.client.d.ts.map +1 -0
  51. package/utils/server-action.client.js +200 -0
  52. package/utils/server-action.server.d.ts +5 -0
  53. package/utils/server-action.server.d.ts.map +1 -0
  54. package/utils/server-action.server.js +264 -0
  55. package/utils/server-only.d.ts +2 -0
  56. package/utils/server-only.d.ts.map +1 -0
  57. package/utils/server-only.js +4 -0
  58. package/utils/types.d.ts +8 -0
  59. package/utils/types.d.ts.map +1 -0
  60. package/utils/types.js +1 -0
package/server.js ADDED
@@ -0,0 +1,666 @@
1
+ import { eventHandler, toWebRequest } from 'vinxi/http';
2
+ import { getManifest } from 'vinxi/manifest';
3
+ import { generateHydrationScript, renderToString } from 'solid-js/web';
4
+ import fileRoutes, {} from 'vinxi/routes';
5
+ import { RedirectError } from './utils/redirect';
6
+ import { setCache, getCache } from './utils/cache';
7
+ import { handleServerFunction } from './utils/server-action.server';
8
+ import util from 'node:util';
9
+ const isPageFile = (file) => file.endsWith('page.tsx')
10
+ || file.endsWith('page.jsx')
11
+ || file.endsWith('page.ts')
12
+ || file.endsWith('page.js');
13
+ const isRouteFile = (file) => file.endsWith('route.ts') || file.endsWith('route.js');
14
+ const parseSegment = (part) => part.startsWith('[') ? `:${part.slice(1, -1).replace(/\.\.\./, '*')}` : part;
15
+ const createRouteManifest = async () => {
16
+ const entries = {};
17
+ const allRoutes = [];
18
+ const allLayouts = [];
19
+ const allLoadingPages = [];
20
+ const allErrorPages = [];
21
+ const allGroups = [];
22
+ let notFoundPage;
23
+ for (const fileRoute of fileRoutes) {
24
+ if (fileRoute.type === 'route') {
25
+ allRoutes.push(fileRoute);
26
+ }
27
+ if (fileRoute.type === 'layout') {
28
+ allLayouts.push(fileRoute);
29
+ }
30
+ if (fileRoute.type === 'not-found') {
31
+ notFoundPage = fileRoute;
32
+ }
33
+ if (fileRoute.type === 'loading') {
34
+ allLoadingPages.push(fileRoute);
35
+ }
36
+ if (fileRoute.type === 'error') {
37
+ allErrorPages.push(fileRoute);
38
+ }
39
+ if (fileRoute.type === 'group') {
40
+ allGroups.push(fileRoute);
41
+ }
42
+ }
43
+ for (const fileRoute of allRoutes) {
44
+ const segments = fileRoute.path.split('/').slice(2).map(parseSegment);
45
+ const routePath = `/${segments.filter(s => !(s.startsWith('('))).join('/')}`;
46
+ const regex = /\?(?:pick=.*)*/g;
47
+ const src = fileRoute.$handler?.src.replace(regex, '');
48
+ if (src && isPageFile(src)) {
49
+ const loadingPage = allLoadingPages.find(route => {
50
+ const path = `/${route.path.split('/').slice(2).map(parseSegment).join('/')}`;
51
+ return path === routePath;
52
+ });
53
+ const matchedGroups = allGroups.filter(route => {
54
+ const parentPath = route.parent ? `/${route.parent.split('/').slice(2).map(parseSegment).join('/')}` : '';
55
+ return parentPath === routePath;
56
+ });
57
+ const groups = {};
58
+ if (matchedGroups && matchedGroups.length > 0) {
59
+ for (const group of matchedGroups) {
60
+ const groupName = group.path.split('/').filter(s => !(s.startsWith('('))).map(parseSegment).at(-1);
61
+ if (!groupName)
62
+ continue;
63
+ groups[groupName] = {
64
+ manifestPath: group.path,
65
+ page: group.$component,
66
+ loader: group.$loader,
67
+ };
68
+ }
69
+ }
70
+ let errorPage;
71
+ const layouts = [];
72
+ for (let i = segments.length; i > (routePath === '/' ? 0 : -1); i--) {
73
+ const path = `/${segments.slice(0, i).join('/')}`;
74
+ if (!errorPage) {
75
+ errorPage = allErrorPages.find(route => {
76
+ const routePath = `/${route.path.split('/').slice(2).map(parseSegment).join('/')}`;
77
+ return routePath === path;
78
+ });
79
+ }
80
+ const layout = allLayouts.find(route => {
81
+ const routePath = `/${route.path.split('/').slice(2).map(parseSegment).join('/')}`;
82
+ return routePath === path;
83
+ });
84
+ if (layout) {
85
+ layouts.unshift({
86
+ manifestPath: layout.path,
87
+ layout: layout.$component,
88
+ loader: layout.$loader,
89
+ generateMeta: layout.$generateMeta,
90
+ });
91
+ }
92
+ }
93
+ entries[routePath] = {
94
+ type: 'page',
95
+ mainPage: {
96
+ manifestPath: fileRoute.path,
97
+ page: fileRoute.$component,
98
+ loader: fileRoute.$loader,
99
+ generateMeta: fileRoute.$generateMeta,
100
+ options: fileRoute.$options,
101
+ },
102
+ loadingPage: loadingPage ? {
103
+ page: loadingPage.$component,
104
+ generateMeta: loadingPage.$generateMeta,
105
+ manifestPath: loadingPage.path,
106
+ } : undefined,
107
+ errorPage: errorPage ? {
108
+ page: errorPage.$component,
109
+ generateMeta: errorPage.$generateMeta,
110
+ manifestPath: errorPage.path,
111
+ } : undefined,
112
+ notFoundPage: routePath === '/' && notFoundPage ? {
113
+ page: notFoundPage.$component,
114
+ generateMeta: notFoundPage.$generateMeta,
115
+ manifestPath: notFoundPage.path,
116
+ } : undefined,
117
+ layouts: layouts,
118
+ groups: groups,
119
+ };
120
+ }
121
+ else if (src && isRouteFile(src)) {
122
+ entries[routePath] = {
123
+ type: 'route',
124
+ handler: fileRoute.$handler,
125
+ manifestPath: fileRoute.path,
126
+ };
127
+ }
128
+ }
129
+ return entries;
130
+ };
131
+ const extractRouteParams = (route, url) => {
132
+ const routeSegments = route.split('/').filter(s => !(s.startsWith('('))).filter(Boolean);
133
+ const urlSegments = url.split('/').filter(Boolean);
134
+ const params = {};
135
+ let matched = true;
136
+ for (let i = 0; i < routeSegments.length; i++) {
137
+ const routeSeg = routeSegments[i];
138
+ const urlSeg = urlSegments[i];
139
+ const isDynamic = routeSeg.startsWith('[') && routeSeg.endsWith(']');
140
+ if (isDynamic) {
141
+ if (routeSeg.includes('...')) {
142
+ // Catch-all parameter
143
+ const isCatchAll = routeSeg.startsWith('[[') && routeSeg.endsWith(']]');
144
+ const paramName = routeSeg.slice(isCatchAll ? 5 : 4, isCatchAll ? -2 : -1);
145
+ params[paramName] = urlSegments.slice(i);
146
+ break; // No more segments to match
147
+ }
148
+ const paramName = routeSeg.slice(1, -1);
149
+ params[paramName] = urlSeg;
150
+ }
151
+ else if (routeSeg !== urlSeg) {
152
+ matched = false;
153
+ break;
154
+ }
155
+ }
156
+ if (matched)
157
+ return { route, params };
158
+ };
159
+ const template = `
160
+ <!DOCTYPE html>
161
+ <html lang="en">
162
+ <head><!--app-head--></head>
163
+ <!--app-body-->
164
+ </html>
165
+ `;
166
+ const generateHtmlHead = (meta) => {
167
+ const head = Object.entries(meta)
168
+ .map(([key, value]) => {
169
+ if (value.type === 'title') {
170
+ return `<title>${value.content}</title>`;
171
+ }
172
+ if (value.type === 'meta') {
173
+ const attrs = Object.entries(value.attributes)
174
+ .map(([attrKey, attrValue]) => `${attrKey}="${attrValue}"`)
175
+ .join(' ');
176
+ return `<meta ${attrs}>`;
177
+ }
178
+ if (value.type === 'link' || value.type === 'style' || value.type === 'script') {
179
+ const attrs = Object.entries(value.attributes)
180
+ .map(([attrKey, attrValue]) => `${attrKey}="${attrValue}"`)
181
+ .join(' ');
182
+ return `<${value.type} ${attrs}></${value.type}>`;
183
+ }
184
+ return '';
185
+ })
186
+ .join('\n');
187
+ return head;
188
+ };
189
+ const sendNodeResponse = async (res, response) => {
190
+ // Set status code
191
+ res.statusCode = response.status;
192
+ // Set headers
193
+ response.headers.forEach((value, key) => {
194
+ res.setHeader(key, value);
195
+ });
196
+ // Stream the body
197
+ if (response.body) {
198
+ const reader = response.body.getReader();
199
+ const push = async () => {
200
+ const { done, value } = await reader.read();
201
+ if (done) {
202
+ res.end();
203
+ return;
204
+ }
205
+ res.write(Buffer.from(value));
206
+ await push();
207
+ };
208
+ await push();
209
+ }
210
+ else {
211
+ const text = await response.text();
212
+ res.end(text);
213
+ }
214
+ };
215
+ const render = async ({ toRender, entry, routeParams, searchParams, req, cspNonce, }) => {
216
+ const url = req.url || '/';
217
+ const cachedEntry = getCache(url);
218
+ if (cachedEntry && toRender === 'main') {
219
+ return {
220
+ rendered: cachedEntry.rendered,
221
+ documentMeta: cachedEntry.documentMeta,
222
+ documentAssets: cachedEntry.documentAssets,
223
+ loaderData: cachedEntry.loaderData,
224
+ };
225
+ }
226
+ let cachingOptions = undefined;
227
+ let meta = {};
228
+ const loaderData = {};
229
+ const clientManifest = getManifest('client');
230
+ const assets = [];
231
+ const compose = entry.layouts.reduceRight((children, layout, index) => async () => {
232
+ const moduleSrc = `${layout.layout.src}&pick=$css`;
233
+ const moduleAssets = await clientManifest.inputs[moduleSrc].assets();
234
+ for (const asset of moduleAssets) {
235
+ assets.push(asset);
236
+ }
237
+ const { default: layoutModule } = await layout.layout.import();
238
+ const { loader: layoutLoader } = layout.loader ? await layout.loader.import() : { loader: null };
239
+ const { generateMeta: generateMetaPage } = layout.generateMeta ? await layout.generateMeta.import() : { generateMeta: null };
240
+ let data = {};
241
+ if (generateMetaPage) {
242
+ const metaData = await generateMetaPage({
243
+ req,
244
+ cspNonce,
245
+ });
246
+ if (metaData) {
247
+ meta = {
248
+ ...meta,
249
+ ...metaData
250
+ };
251
+ }
252
+ }
253
+ if (layoutLoader) {
254
+ const result = await layoutLoader.loader(req);
255
+ data = result.data || {};
256
+ loaderData[layout.manifestPath] = data;
257
+ }
258
+ const slots = {};
259
+ const slotPromises = [children()];
260
+ if (index === entry.layouts.length - 1) {
261
+ // last layout, we can render slots
262
+ const groups = entry.groups || {};
263
+ for (const [groupName, group] of Object.entries(groups)) {
264
+ slotPromises.push((async () => {
265
+ const moduleSrc = `${group.page.src}&pick=$css`;
266
+ const moduleAssets = await clientManifest.inputs[moduleSrc].assets();
267
+ for (const asset of moduleAssets) {
268
+ assets.push(asset);
269
+ }
270
+ const { default: groupPage } = await group.page.import();
271
+ const { loader: groupLoader } = group.loader ? await group.loader.import() : { loader: null };
272
+ let data = {};
273
+ if (groupLoader) {
274
+ const result = await groupLoader.loader(req);
275
+ data = result.data || {};
276
+ loaderData[group.manifestPath] = data;
277
+ }
278
+ slots[groupName.replace('@', '')] = () => groupPage({
279
+ routeParams,
280
+ searchParams,
281
+ loaderData: data
282
+ });
283
+ })());
284
+ }
285
+ }
286
+ const [childrenRendered] = await Promise.all(slotPromises);
287
+ return () => layoutModule({
288
+ children: childrenRendered,
289
+ routeParams,
290
+ searchParams,
291
+ loaderData: data,
292
+ slots: slots,
293
+ locals: {
294
+ cspNonce: cspNonce,
295
+ }
296
+ });
297
+ }, async () => {
298
+ const pageToRender = toRender === 'loading'
299
+ ? entry.loadingPage
300
+ : toRender === 'error'
301
+ ? entry.errorPage
302
+ : toRender === 'not-found'
303
+ ? entry.notFoundPage
304
+ : entry.mainPage;
305
+ const moduleSrc = `${pageToRender.page.src}&pick=$css`;
306
+ const moduleAssets = await clientManifest.inputs[moduleSrc].assets();
307
+ for (const asset of moduleAssets) {
308
+ assets.push(asset);
309
+ }
310
+ const { default: page } = await pageToRender.page.import();
311
+ const { loader: pageLoader } = pageToRender.loader ? await pageToRender.loader.import() : { loader: null };
312
+ const { generateMeta } = pageToRender.generateMeta ? await pageToRender.generateMeta.import() : { generateMeta: null };
313
+ const { options } = pageToRender.options ? await pageToRender.options.import() : { options: {} };
314
+ if (options?.cache) {
315
+ cachingOptions = options.cache;
316
+ }
317
+ let data = {};
318
+ if (pageLoader) {
319
+ const result = await pageLoader.loader(req);
320
+ data = result.data || {};
321
+ loaderData[pageToRender.manifestPath] = data;
322
+ }
323
+ if (generateMeta) {
324
+ const metaData = await generateMeta({
325
+ req,
326
+ cspNonce,
327
+ });
328
+ if (metaData) {
329
+ meta = {
330
+ ...meta,
331
+ ...metaData
332
+ };
333
+ }
334
+ }
335
+ return () => page({
336
+ routeParams,
337
+ searchParams,
338
+ loaderData: data,
339
+ locals: {
340
+ cspNonce: cspNonce,
341
+ }
342
+ });
343
+ });
344
+ const composed = await compose();
345
+ const rendered = await renderToString(() => composed());
346
+ if (cachingOptions && toRender === 'main') {
347
+ setCache(url, {
348
+ rendered: rendered,
349
+ documentMeta: meta,
350
+ documentAssets: assets,
351
+ loaderData: loaderData,
352
+ }, cachingOptions.ttl);
353
+ }
354
+ return {
355
+ rendered: rendered,
356
+ documentMeta: meta,
357
+ documentAssets: assets,
358
+ loaderData: loaderData,
359
+ };
360
+ };
361
+ let routeManifest = {};
362
+ const hydrationScript = ({ nonce, }) => {
363
+ const script = generateHydrationScript();
364
+ return nonce ? script.replace('<script', `<script nonce="${nonce}"`) : script;
365
+ };
366
+ const onStart = async () => {
367
+ try {
368
+ routeManifest = await createRouteManifest();
369
+ }
370
+ catch (e) {
371
+ console.error('Error creating route manifest:', e);
372
+ }
373
+ };
374
+ onStart();
375
+ const handler = eventHandler(async (event) => {
376
+ const req = event.node.req;
377
+ const res = event.node.res;
378
+ try {
379
+ if (req.url?.includes('_server')) {
380
+ return handleServerFunction(event);
381
+ }
382
+ const clientManifest = getManifest('client');
383
+ if (!routeManifest || Object.keys(routeManifest).length === 0) {
384
+ routeManifest = await createRouteManifest();
385
+ }
386
+ const cspNonce = event.locals?.cspNonce;
387
+ const url = req.url || '/';
388
+ // extract route params and search params
389
+ const params = {};
390
+ const searchParams = {};
391
+ const [pathnamePart, searchParamPart] = url.split('?');
392
+ if (searchParamPart) {
393
+ for (const param of searchParamPart.split('&')) {
394
+ const [key, value] = param.split('=');
395
+ searchParams[key] = decodeURIComponent(value || '');
396
+ }
397
+ }
398
+ const matched = Object.entries(routeManifest).find(([path, entry]) => {
399
+ const pattern = path
400
+ .replace(/:\[\*[^/\]]+\]/g, '?(.*)?') // [[...slug]] -> (.*)?
401
+ .replace(/:\*[^/]*/g, '.*') // :*slug or :* -> .*
402
+ .replace(/:[^/]+/g, '[^/]+'); // :post -> [^/]+
403
+ const re = new RegExp(`^${pattern}$`);
404
+ return re.test(pathnamePart);
405
+ })?.[1];
406
+ const routePath = matched && matched.type === 'route'
407
+ ? matched.manifestPath.split('/').slice(2).join('/')
408
+ : matched && matched.type === 'page'
409
+ ? matched.mainPage.manifestPath.split('/').slice(2).join('/')
410
+ : '/';
411
+ const routeParams = extractRouteParams(routePath, pathnamePart);
412
+ if (routeParams) {
413
+ Object.assign(params, routeParams.params);
414
+ }
415
+ if (matched && matched.type === 'route') {
416
+ const routeModule = await matched.handler.import();
417
+ const reqMethod = req.method?.toUpperCase();
418
+ if (reqMethod) {
419
+ const handler = routeModule[reqMethod];
420
+ if (typeof handler === 'function') {
421
+ const result = await handler(toWebRequest(event), {
422
+ params: params,
423
+ searchParams: searchParams,
424
+ });
425
+ await sendNodeResponse(res, result);
426
+ return;
427
+ }
428
+ throw new Error(`Method ${reqMethod} not implemented in ${matched.handler.src}`);
429
+ }
430
+ throw new Error(`Unsupported request method: ${reqMethod}`);
431
+ }
432
+ let loading = false;
433
+ let html = undefined;
434
+ let meta = {
435
+ charset: {
436
+ type: 'meta',
437
+ attributes: {
438
+ charset: 'UTF-8'
439
+ }
440
+ },
441
+ viewport: {
442
+ type: 'meta',
443
+ attributes: {
444
+ name: 'viewport',
445
+ content: 'width=device-width, initial-scale=1.0'
446
+ }
447
+ },
448
+ title: {
449
+ type: 'title',
450
+ attributes: {},
451
+ content: 'SolidStep'
452
+ }
453
+ };
454
+ const assets = await clientManifest.inputs[clientManifest.handler].assets();
455
+ const manifestHtml = `<script ${cspNonce ? `nonce="${cspNonce}"` : ''}>window.manifest=${JSON.stringify(await clientManifest.json())}</script>`;
456
+ let clientHydrationScript = undefined;
457
+ res.setHeader('Content-Type', 'text/html');
458
+ res.setHeader('Cache-Control', 'no-cache');
459
+ try {
460
+ if (!matched) {
461
+ try {
462
+ const notFoundPage = routeManifest['/'];
463
+ const { rendered, documentMeta, documentAssets, loaderData, } = await render({
464
+ toRender: 'not-found',
465
+ entry: notFoundPage,
466
+ routeParams: {},
467
+ searchParams: {},
468
+ req: toWebRequest(event),
469
+ cspNonce,
470
+ });
471
+ for (const asset of documentAssets) {
472
+ assets.push(asset);
473
+ }
474
+ clientHydrationScript = `
475
+ <script type="module" ${cspNonce ? `nonce="${cspNonce}"` : ''}>
476
+ import main from '${clientManifest.inputs[clientManifest.handler].output.path}';
477
+ main('/not-found/',${JSON.stringify(params)},${JSON.stringify(searchParams)}, ${JSON.stringify(loaderData)});
478
+ </script>
479
+ `;
480
+ html = rendered;
481
+ meta = {
482
+ ...meta,
483
+ ...documentMeta
484
+ };
485
+ res.statusCode = 404;
486
+ }
487
+ catch (e) {
488
+ console.error('404 module not found:', e);
489
+ res.statusCode = 404;
490
+ return res.end('Not Found');
491
+ }
492
+ }
493
+ else {
494
+ try {
495
+ const { rendered, documentMeta, documentAssets, loaderData, } = await render({
496
+ toRender: 'loading',
497
+ entry: matched,
498
+ routeParams: params,
499
+ searchParams,
500
+ req: toWebRequest(event),
501
+ cspNonce,
502
+ });
503
+ const assetsHtml = assets.concat(documentAssets).map((asset) => {
504
+ const attributeString = Object.entries(asset.attrs)
505
+ .map(([key, value]) => `${key}="${value}"`)
506
+ .join(' ');
507
+ if (asset.tag === 'script') {
508
+ return `<script ${attributeString}></script>`;
509
+ }
510
+ if (asset.tag === 'link') {
511
+ return `<link ${attributeString}>`;
512
+ }
513
+ if (asset.tag === 'style') {
514
+ return `<style ${attributeString}>${asset.children || ''}</style>`;
515
+ }
516
+ }).join('\n');
517
+ const html = `
518
+ <!doctype html>
519
+ <html lang="en">
520
+ <head>
521
+ ${generateHtmlHead({
522
+ ...meta,
523
+ ...documentMeta,
524
+ })}
525
+ ${assetsHtml}
526
+ ${hydrationScript({ nonce: cspNonce })}
527
+ </head>
528
+ <noscript>
529
+ Please enable JavaScript to view the content.<br/>
530
+ </noscript>
531
+ ${rendered}
532
+ </html>
533
+ `;
534
+ res.write(html);
535
+ res.write(`
536
+ <script type="module" data-hydration="loading" ${cspNonce ? `nonce="${cspNonce}"` : ''}>
537
+ import main from '${clientManifest.inputs[clientManifest.handler].output.path}';
538
+ main('${matched.loadingPage?.manifestPath}',${JSON.stringify(params)},${JSON.stringify(searchParams)}, ${JSON.stringify(loaderData)});
539
+ </script>
540
+ `);
541
+ loading = true;
542
+ }
543
+ catch (e) {
544
+ // skip
545
+ }
546
+ const { rendered, documentMeta, documentAssets, loaderData, } = await render({
547
+ toRender: 'main',
548
+ entry: matched,
549
+ routeParams: params,
550
+ searchParams,
551
+ req: toWebRequest(event),
552
+ cspNonce,
553
+ });
554
+ for (const asset of documentAssets) {
555
+ assets.push(asset);
556
+ }
557
+ clientHydrationScript = `
558
+ <script type="module" ${cspNonce ? `nonce="${cspNonce}"` : ''}>
559
+ import main from '${clientManifest.inputs[clientManifest.handler].output.path}';
560
+ main('${matched.mainPage.manifestPath}',${JSON.stringify(params)},${JSON.stringify(searchParams)}, ${JSON.stringify(loaderData)});
561
+ </script>
562
+ `;
563
+ html = rendered;
564
+ meta = {
565
+ ...meta,
566
+ ...documentMeta
567
+ };
568
+ }
569
+ }
570
+ catch (e1) {
571
+ if (e1 instanceof RedirectError) {
572
+ throw e1;
573
+ }
574
+ try {
575
+ const errorPage = matched.errorPage;
576
+ if (!errorPage) {
577
+ throw e1;
578
+ }
579
+ const { rendered, documentMeta, documentAssets, loaderData, } = await render({
580
+ toRender: 'error',
581
+ entry: matched,
582
+ routeParams: params,
583
+ searchParams,
584
+ req: toWebRequest(event),
585
+ cspNonce,
586
+ });
587
+ for (const asset of documentAssets) {
588
+ assets.push(asset);
589
+ }
590
+ clientHydrationScript = `
591
+ <script type="module" ${cspNonce ? `nonce="${cspNonce}"` : ''}>
592
+ import main from '${clientManifest.inputs[clientManifest.handler].output.path}';
593
+ main('${errorPage.manifestPath}',${JSON.stringify(params)},${JSON.stringify(searchParams)}, ${JSON.stringify(loaderData)});
594
+ </script>
595
+ `;
596
+ html = rendered;
597
+ meta = {
598
+ ...meta,
599
+ ...documentMeta
600
+ };
601
+ res.statusCode = 500;
602
+ }
603
+ catch (e2) {
604
+ throw e1;
605
+ }
606
+ }
607
+ if (loading) {
608
+ const assetsHtml = assets.map((asset) => {
609
+ const attributeString = Object.entries(asset.attrs)
610
+ .map(([key, value]) => `${key}="${value}"`)
611
+ .join(' ');
612
+ if (asset.tag === 'link') {
613
+ return `<link ${attributeString}>`;
614
+ }
615
+ if (asset.tag === 'style') {
616
+ return `<style ${attributeString}>${asset.children || ''}</style>`;
617
+ }
618
+ return '';
619
+ }).join('\n');
620
+ res.write(`
621
+ <script ${cspNonce ? `nonce="${cspNonce}"` : ''}>
622
+ const head = document.querySelector('head');
623
+ const scripts = Array.from(head.querySelectorAll('script'));
624
+ head.innerHTML = \`${generateHtmlHead(meta) + assetsHtml}\`;
625
+ scripts.forEach(script => {
626
+ head.appendChild(script);
627
+ });
628
+ document.querySelector('script[data-hydration="loading"]')?.remove();
629
+ const loading = document.querySelector('body');
630
+ loading.innerHTML = \`${html}\`;
631
+ </script>
632
+ `);
633
+ res.write(manifestHtml);
634
+ return res.end(clientHydrationScript);
635
+ }
636
+ const assetsHtml = assets.map((asset) => {
637
+ const attributeString = Object.entries(asset.attrs)
638
+ .map(([key, value]) => `${key}="${value}"`)
639
+ .join(' ');
640
+ if (asset.tag === 'script') {
641
+ return `<script ${attributeString} ${cspNonce ? `nonce="${cspNonce}"` : ''}></script>`;
642
+ }
643
+ if (asset.tag === 'link') {
644
+ return `<link ${attributeString}>`;
645
+ }
646
+ if (asset.tag === 'style') {
647
+ return `<style ${attributeString}>${asset.children || ''}</style>`;
648
+ }
649
+ }).join('\n');
650
+ const transformHtml = template
651
+ .replace('<!--app-head-->', `${generateHtmlHead(meta)}\n${assetsHtml}\n${hydrationScript({ nonce: cspNonce })}`)
652
+ .replace('<!--app-body-->', (html ?? '') + manifestHtml + clientHydrationScript);
653
+ return res.end(transformHtml);
654
+ }
655
+ catch (e) {
656
+ if (e instanceof RedirectError) {
657
+ res.statusCode = 302;
658
+ res.setHeader('Location', e.message);
659
+ return res.end('Redirecting...');
660
+ }
661
+ console.error(e);
662
+ res.statusCode = 500;
663
+ return res.end('Internal Server Error');
664
+ }
665
+ });
666
+ export default handler;
@@ -0,0 +1,5 @@
1
+ export declare const getCache: <T>(key: string) => T | null;
2
+ export declare const setCache: <T>(key: string, value: T, ttlMs?: number) => void;
3
+ export declare const invalidateCache: (key: string) => void;
4
+ export declare const clearAllCache: () => void;
5
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../utils/cache.ts"],"names":[],"mappings":"AA6CA,eAAO,MAAM,QAAQ,GAAI,CAAC,EAAE,KAAK,MAAM,KAAG,CAAC,GAAG,IAe7C,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,CAAC,EAAE,KAAK,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,MAAM,SA0BhE,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,KAAK,MAAM,SAU1C,CAAC;AAEF,eAAO,MAAM,aAAa,YAGzB,CAAC"}