@useavalon/avalon 0.1.48 → 0.1.50

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 (36) hide show
  1. package/dist/mod.d.ts +48 -48
  2. package/dist/mod.js +1 -1
  3. package/dist/src/build/page-island-transform.d.ts +1 -1
  4. package/dist/src/build/page-island-transform.js +2 -3
  5. package/dist/src/client/components.d.ts +8 -8
  6. package/dist/src/client/components.js +1 -1
  7. package/dist/src/components/IslandErrorBoundary.d.ts +2 -2
  8. package/dist/src/components/IslandErrorBoundary.js +1 -1
  9. package/dist/src/components/LayoutErrorBoundary.d.ts +3 -3
  10. package/dist/src/components/LayoutErrorBoundary.js +1 -1
  11. package/dist/src/islands/integration-loader.d.ts +2 -2
  12. package/dist/src/islands/island.d.ts +7 -7
  13. package/dist/src/islands/island.js +1 -1
  14. package/dist/src/layout-system.d.ts +14 -17
  15. package/dist/src/layout-system.js +1 -1
  16. package/dist/src/nitro/config.d.ts +13 -0
  17. package/dist/src/nitro/renderer.d.ts +21 -11
  18. package/dist/src/nitro/renderer.js +20 -20
  19. package/dist/src/persistence/island-state-serializer.d.ts +9 -19
  20. package/dist/src/persistence/island-state-serializer.js +1 -1
  21. package/dist/src/persistence/use-persistent-state.d.ts +2 -2
  22. package/dist/src/persistence/use-persistent-state.js +1 -1
  23. package/dist/src/post-build/index.d.ts +57 -0
  24. package/dist/src/post-build/index.js +31 -0
  25. package/dist/src/schemas/core.d.ts +2 -2
  26. package/dist/src/schemas/layout.d.ts +4 -4
  27. package/dist/src/schemas/routing/index.d.ts +2 -2
  28. package/dist/src/schemas/routing.d.ts +4 -4
  29. package/dist/src/types/index.d.ts +8 -5
  30. package/dist/src/types/island-prop.d.ts +14 -8
  31. package/dist/src/types/layout.d.ts +11 -11
  32. package/dist/src/types/layout.js +1 -1
  33. package/dist/src/types/virtual-modules.d.ts +56 -0
  34. package/dist/src/vite-plugin/nitro-integration.d.ts +8 -0
  35. package/dist/src/vite-plugin/nitro-integration.js +15 -10
  36. package/package.json +6 -2
@@ -1,4 +1,4 @@
1
- import{getRequestURL as e}from"h3";import{createNotFoundError as t,isHttpError as n}from"./types.js";import{discoverScopedMiddleware as r,executeScopedMiddleware as i}from"../middleware/index.js";import{handleRenderError as a,discoverErrorPages as o}from"./error-handler.js";import{h as s}from"preact";import c from"preact-render-to-string";export function createRenderContext(e,t={}){let n=getRequestURL(e);return{url:n,params:t,query:Object.fromEntries(n.searchParams),request:toRequest(e),event:e}}export function getRequestURL(t){return new URL(e(t).pathname,`http://localhost`)}export function toRequest(e){let t=getRequestURL(e);return new Request(t,{method:e.req.method,headers:getRequestHeaders(e)})}export function getRequestHeaders(e){return new Headers}export function setResponseHeader(e,t,n){e.context.responseHeaders||(e.context.responseHeaders={}),e.context.responseHeaders[t]=n}export function createErrorResponse(e,t){let r=n(e)?e.statusCode:500;if(t){let t=h(e,r);return new Response(t,{status:r,headers:{"Content-Type":`text/html; charset=utf-8`}})}let i=g(r);return new Response(i,{status:r,headers:{"Content-Type":`text/html; charset=utf-8`}})}function h(e,t){return`<!DOCTYPE html>
1
+ import{getRequestURL as e}from"h3";import{h as t}from"preact";import n from"preact-render-to-string";import{discoverScopedMiddleware as r,executeScopedMiddleware as i}from"../middleware/index.js";import{discoverErrorPages as a,handleRenderError as o}from"./error-handler.js";import{createNotFoundError as s,isHttpError as c}from"./types.js";export function createRenderContext(e,t={}){let n=getRequestURL(e);return{url:n,params:t,query:Object.fromEntries(n.searchParams),request:toRequest(e),event:e}}export function createRenderContextFromRequest(e,t={}){let n=new URL(e.url,`http://localhost`),r={method:e.method,path:n.pathname+n.search,context:{params:t}};return{url:n,params:t,query:Object.fromEntries(n.searchParams),request:e,event:r}}export function getRequestURL(t){return new URL(e(t).pathname,`http://localhost`)}export function toRequest(e){let t=getRequestURL(e);return new Request(t,{method:e.req.method,headers:getRequestHeaders(e)})}export function getRequestHeaders(e){return new Headers}export function setResponseHeader(e,t,n){e.context.responseHeaders||(e.context.responseHeaders={}),e.context.responseHeaders[t]=n}export function createErrorResponse(e,t){let n=c(e)?e.statusCode:500;if(t){let t=g(e,n);return new Response(t,{status:n,headers:{"Content-Type":`text/html; charset=utf-8`}})}let r=_(n);return new Response(r,{status:n,headers:{"Content-Type":`text/html; charset=utf-8`}})}function g(e,t){return`<!DOCTYPE html>
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="utf-8">
@@ -56,15 +56,15 @@ import{getRequestURL as e}from"h3";import{createNotFoundError as t,isHttpError a
56
56
  <body>
57
57
  <div class="error-container">
58
58
  <div class="status-code">${t}</div>
59
- <h1>${_(t)}</h1>
60
- <p class="message">${v(e.message)}</p>
59
+ <h1>${v(t)}</h1>
60
+ <p class="message">${y(e.message)}</p>
61
61
  ${e.stack?`
62
62
  <div class="stack-title">Stack Trace</div>
63
- <pre>${v(e.stack)}</pre>
63
+ <pre>${y(e.stack)}</pre>
64
64
  `:``}
65
65
  </div>
66
66
  </body>
67
- </html>`}function g(e){return`<!DOCTYPE html>
67
+ </html>`}function _(e){return`<!DOCTYPE html>
68
68
  <html lang="en">
69
69
  <head>
70
70
  <meta charset="utf-8">
@@ -113,47 +113,47 @@ import{getRequestURL as e}from"h3";import{createNotFoundError as t,isHttpError a
113
113
  <body>
114
114
  <div class="error-container">
115
115
  <div class="status-code">${e}</div>
116
- <h1>${_(e)}</h1>
116
+ <h1>${v(e)}</h1>
117
117
  <p><a href="/">Return to home</a></p>
118
118
  </div>
119
119
  </body>
120
- </html>`}function _(e){return{400:`Bad Request`,401:`Unauthorized`,403:`Forbidden`,404:`Page Not Found`,405:`Method Not Allowed`,500:`Internal Server Error`,502:`Bad Gateway`,503:`Service Unavailable`}[e]||`Error`}function v(e){return e.replaceAll(`&`,`&amp;`).replaceAll(`<`,`&lt;`).replaceAll(`>`,`&gt;`).replaceAll(`"`,`&quot;`).replaceAll(`'`,`&#039;`)}export function extractIslandMarkers(e){let t=[],n=/<[^>]*data-framework="([^"]+)"[^>]*>/g,r;for(;(r=n.exec(e))!==null;){let e=r[0],n=r[1],i=/data-src="([^"]+)"/.exec(e),a=i?i[1]:``,o=/data-props="([^"]*)"/.exec(e),s=o?o[1]:void 0,c=/data-hydrate="([^"]+)"/.exec(e),l=c?c[1]:void 0;t.push({framework:n,src:a,props:s,hydrate:l})}return t}export function ensureHydrationMarkers(e,t){let n=e;return t.framework&&!n.includes(`data-framework=`)&&(n=n.replace(/>/,` data-framework="${t.framework}">`)),t.src&&!n.includes(`data-src=`)&&(n=n.replace(/>/,` data-src="${t.src}">`)),t.props!==void 0&&!n.includes(`data-props=`)&&(n=n.replace(/>/,` data-props="${t.props}">`)),n}export function injectHydrationScript(e,t,n={}){if(!(e.includes(`data-framework=`)||e.includes(`data-src=`))&&!n.forceInject||[`/src/client/main.js`,`/dist/client.js`,`client/main.js`].some(t=>e.includes(t)))return e;let r=n.scriptPath||(t?`/src/client/main.js`:`/dist/client.js`),i=[];i.push(`<script type="module" src="${r}"><\/script>`),n.additionalScripts&&i.push(...n.additionalScripts);let a=i.join(`
121
- `);return e.includes(`</body>`)?e.replace(`</body>`,`${a}\n</body>`):e+a}export function validateHydrationMarkers(e){let t=extractIslandMarkers(e),n=e.match(/data-framework="[^"]+"/g)||[],r=e.match(/data-src="[^"]+"/g)||[],i=e.match(/data-props="[^"]*"/g)||[],a=e.includes(`/src/client/main.js`)||e.includes(`/dist/client.js`)||e.includes(`client/main.js`),o=t.every(e=>e.framework&&e.src),s=t.length===0||o&&a;return{hasFrameworkAttr:n.length>0,hasSrcAttr:r.length>0,hasPropsAttr:i.length>0,islandCount:n.length,islands:t,hasClientScript:a,isValid:s}}export function processHydrationRequirements(e,t){let n=injectHydrationScript(e,t);return{html:n,validation:validateHydrationMarkers(n)}}export async function renderPage(e,t,n={},r){try{let i={};return e.getServerSideProps&&(i=await e.getServerSideProps(t)),{html:await y(e,i,t,n,r),statusCode:200,headers:{"Content-Type":`text/html; charset=utf-8`}}}catch(e){throw console.error(`[SSR Error]`,e),n.onError&&e instanceof Error&&n.onError(e),e}}async function y(e,t,n,r,i){let a=e.default,o=e.metadata||{},l=process.env.NODE_ENV!==`production`,u;try{let e=a(t);u=e instanceof Promise?await e:e}catch(e){console.error(`[renderer] Error calling page component:`,e),u=s(`div`,null,`Error rendering page`)}let d;try{d=c(u)}catch(e){console.error(`[renderer] Error in preactRenderToString:`,e),d=`<div>Error rendering page</div>`}if(i)return await i(d,e,n);let f=l?`
120
+ </html>`}function v(e){return{400:`Bad Request`,401:`Unauthorized`,403:`Forbidden`,404:`Page Not Found`,405:`Method Not Allowed`,500:`Internal Server Error`,502:`Bad Gateway`,503:`Service Unavailable`}[e]||`Error`}function y(e){return e.replaceAll(`&`,`&amp;`).replaceAll(`<`,`&lt;`).replaceAll(`>`,`&gt;`).replaceAll(`"`,`&quot;`).replaceAll(`'`,`&#039;`)}export function extractIslandMarkers(e){let t=[],n=/<[^>]*data-framework="([^"]+)"[^>]*>/g,r=n.exec(e);for(;r!==null;){let i=r[0],a=r[1],o=/data-src="([^"]+)"/.exec(i),s=o?o[1]:``,c=/data-props="([^"]*)"/.exec(i),l=c?c[1]:void 0,u=/data-hydrate="([^"]+)"/.exec(i),d=u?u[1]:void 0;t.push({framework:a,src:s,props:l,hydrate:d}),r=n.exec(e)}return t}export function ensureHydrationMarkers(e,t){let n=e;return t.framework&&!n.includes(`data-framework=`)&&(n=n.replace(/>/,` data-framework="${t.framework}">`)),t.src&&!n.includes(`data-src=`)&&(n=n.replace(/>/,` data-src="${t.src}">`)),t.props!==void 0&&!n.includes(`data-props=`)&&(n=n.replace(/>/,` data-props="${t.props}">`)),n}export function injectHydrationScript(e,t,n={}){if(!(e.includes(`data-framework=`)||e.includes(`data-src=`))&&!n.forceInject||[`/src/client/main.js`,`/dist/client.js`,`client/main.js`,`entry-client`].some(t=>e.includes(t)))return e;let r=n.scriptPath||(t?`/src/client/main.js`:`/dist/client.js`),i=[];i.push(`<script type="module" src="${r}"><\/script>`),n.additionalScripts&&i.push(...n.additionalScripts);let a=i.join(`
121
+ `);return e.includes(`</body>`)?e.replace(`</body>`,`${a}\n</body>`):e+a}export function validateHydrationMarkers(e){let t=extractIslandMarkers(e),n=e.match(/data-framework="[^"]+"/g)||[],r=e.match(/data-src="[^"]+"/g)||[],i=e.match(/data-props="[^"]*"/g)||[],a=e.includes(`/src/client/main.js`)||e.includes(`/dist/client.js`)||e.includes(`client/main.js`),o=t.every(e=>e.framework&&e.src),s=t.length===0||o&&a;return{hasFrameworkAttr:n.length>0,hasSrcAttr:r.length>0,hasPropsAttr:i.length>0,islandCount:n.length,islands:t,hasClientScript:a,isValid:s}}export function processHydrationRequirements(e,t){let n=injectHydrationScript(e,t);return{html:n,validation:validateHydrationMarkers(n)}}export async function renderPage(e,t,n={},r){try{let i={};return e.getServerSideProps&&(i=await e.getServerSideProps(t)),{html:await b(e,i,t,n,r),statusCode:200,headers:{"Content-Type":`text/html; charset=utf-8`}}}catch(e){throw console.error(`[SSR Error]`,e),n.onError&&e instanceof Error&&n.onError(e),e}}async function b(e,r,i,a,o){let s=e.default,c=e.metadata||{},l=process.env.NODE_ENV!==`production`,u;try{let e=s(r);u=e instanceof Promise?await e:e}catch(e){console.error(`[renderer] Error calling page component:`,e),u=t(`div`,null,`Error rendering page`)}let d;try{d=n(u)}catch(e){console.error(`[renderer] Error in preactRenderToString:`,e),d=`<div>Error rendering page</div>`}if(o)return await o(d,e,i);let f=l?`
122
122
  <script type="module" src="/src/client/main.js"><\/script>`:``;return`<!DOCTYPE html>
123
123
  <html lang="en">
124
124
  <head>
125
125
  <meta charset="utf-8">
126
126
  <meta name="viewport" content="width=device-width, initial-scale=1">
127
- <title>${v(String(o.title||`Avalon App`))}</title>
128
- ${o.description?`<meta name="description" content="${v(String(o.description))}">`:``}
127
+ <title>${y(String(c.title||`Avalon App`))}</title>
128
+ ${c.description?`<meta name="description" content="${y(String(c.description))}">`:``}
129
129
  </head>
130
130
  <body>
131
131
  <div id="app">
132
132
  ${d}
133
133
  </div>${f}
134
134
  </body>
135
- </html>`}export async function renderPageStream(e,t,n={}){let r=new TextEncoder,i=null,a={shellSent:!1,contentSent:!1,closed:!1,error:null},o=n.shellReadyTimeout,s=n.allReadyTimeout,l=null,u=null,d=()=>{l&&=(clearTimeout(l),null),u&&=(clearTimeout(u),null)};function f(e,t,n,r,i,a,o){if(n.error=e,a(),console.error(`[Streaming Error]`,{message:e.message,stack:e.stack,shellSent:n.shellSent,isShellError:t,timestamp:new Date().toISOString()}),t&&o.onShellError&&o.onShellError(e),o.onError&&o.onError(e),!n.closed&&r){if(n.shellSent){let t=C(e);r.enqueue(i.encode(t));let n=S();r.enqueue(i.encode(n))}else{let t=h(e,500);r.enqueue(i.encode(t))}n.closed=!0,r.close()}}async function p(o){i=o;let p={};e.getServerSideProps&&(p=await e.getServerSideProps(t));let m=b(e.metadata||{},t);if(a.closed||(o.enqueue(r.encode(m)),a.shellSent=!0,l&&=(clearTimeout(l),null),n.onShellReady&&n.onShellReady()),s&&s>0&&(u=setTimeout(()=>{!a.contentSent&&!a.closed&&f(Error(`All ready timeout after ${s}ms`),!1,a,i,r,d,n)},s)),!a.closed){let t=e.default;if(t&&typeof t==`function`){let n=t(p);if(n&&typeof n.then==`function`)try{let e=c(await n);o.enqueue(r.encode(` <div id="app">${e}</div>\n`))}catch(t){console.error(`[streaming] Async component error:`,t);let n=x(e,p);o.enqueue(r.encode(n))}else{let t=x(e,p);o.enqueue(r.encode(t))}}else{let t=x(e,p);o.enqueue(r.encode(t))}a.contentSent=!0}if(!a.closed){let e=S();o.enqueue(r.encode(e))}d(),n.onAllReady&&!a.closed&&n.onAllReady(),a.closed||(a.closed=!0,o.close())}return new ReadableStream({async start(e){i=e,o&&o>0&&(l=setTimeout(()=>{!a.shellSent&&!a.closed&&f(Error(`Shell ready timeout after ${o}ms`),!0,a,i,r,d,n)},o));try{await p(e)}catch(e){f(e instanceof Error?e:Error(String(e)),!a.shellSent,a,i,r,d,n)}},cancel(){if(d(),!a.closed&&i){a.closed=!0;try{i.close()}catch{}}}})}function b(e,t){return`<!DOCTYPE html>
135
+ </html>`}export async function renderPageStream(e,t,r={}){let i=new TextEncoder,a=null,o={shellSent:!1,contentSent:!1,closed:!1,error:null},s=r.shellReadyTimeout,c=r.allReadyTimeout,l=null,u=null,d=()=>{l&&=(clearTimeout(l),null),u&&=(clearTimeout(u),null)};function f(e,t,n,r,i,a,o){if(n.error=e,a(),console.error(`[Streaming Error]`,{message:e.message,stack:e.stack,shellSent:n.shellSent,isShellError:t,timestamp:new Date().toISOString()}),t&&o.onShellError&&o.onShellError(e),o.onError&&o.onError(e),!n.closed&&r){if(n.shellSent){let t=w(e);r.enqueue(i.encode(t));let n=C();r.enqueue(i.encode(n))}else{let t=g(e,500);r.enqueue(i.encode(t))}n.closed=!0,r.close()}}async function p(s){a=s;let p={};e.getServerSideProps&&(p=await e.getServerSideProps(t));let m=x(e.metadata||{},t);if(o.closed||(s.enqueue(i.encode(m)),o.shellSent=!0,l&&=(clearTimeout(l),null),r.onShellReady&&r.onShellReady()),c&&c>0&&(u=setTimeout(()=>{!o.contentSent&&!o.closed&&f(Error(`All ready timeout after ${c}ms`),!1,o,a,i,d,r)},c)),!o.closed){let t=e.default;if(t&&typeof t==`function`){let r=t(p);if(r&&typeof r.then==`function`)try{let e=n(await r);s.enqueue(i.encode(` <div id="app">${e}</div>\n`))}catch(t){console.error(`[streaming] Async component error:`,t);let n=S(e,p);s.enqueue(i.encode(n))}else{let t=S(e,p);s.enqueue(i.encode(t))}}else{let t=S(e,p);s.enqueue(i.encode(t))}o.contentSent=!0}if(!o.closed){let e=C();s.enqueue(i.encode(e))}d(),r.onAllReady&&!o.closed&&r.onAllReady(),o.closed||(o.closed=!0,s.close())}return new ReadableStream({async start(e){a=e,s&&s>0&&(l=setTimeout(()=>{!o.shellSent&&!o.closed&&f(Error(`Shell ready timeout after ${s}ms`),!0,o,a,i,d,r)},s));try{await p(e)}catch(e){f(e instanceof Error?e:Error(String(e)),!o.shellSent,o,a,i,d,r)}},cancel(){if(d(),!o.closed&&a){o.closed=!0;try{a.close()}catch{}}}})}function x(e,t){return`<!DOCTYPE html>
136
136
  <html lang="en">
137
137
  <head>
138
138
  <meta charset="utf-8">
139
139
  <meta name="viewport" content="width=device-width, initial-scale=1">
140
- <title>${v(String(e.title||`Avalon App`))}</title>
141
- ${e.description?`<meta name="description" content="${v(String(e.description))}">`:``}
140
+ <title>${y(String(e.title||`Avalon App`))}</title>
141
+ ${e.description?`<meta name="description" content="${y(String(e.description))}">`:``}
142
142
  </head>
143
143
  <body>
144
- `}function x(e,t){let n=e.default;if(n&&typeof n==`function`)try{let e=n(t);if(e&&typeof e.then==`function`){let e=n.name||`Page`;return` <div id="app" data-page="${v(String(e))}" data-props='${v(JSON.stringify(t))}'>
144
+ `}function S(e,t){let r=e.default;if(r&&typeof r==`function`)try{let e=r(t);if(e&&typeof e.then==`function`){let e=r.name||`Page`;return` <div id="app" data-page="${y(String(e))}" data-props='${y(JSON.stringify(t))}'>
145
145
  <!-- Async component — awaiting hydration -->
146
- </div>\n`}return` <div id="app">${c(e)}</div>\n`}catch(e){console.error(`[streaming] Error rendering page component:`,e)}let r=e.default?.name||`Page`;return` <div id="app" data-page="${v(String(r))}" data-props='${v(JSON.stringify(t))}'>
146
+ </div>\n`}return` <div id="app">${n(e)}</div>\n`}catch(e){console.error(`[streaming] Error rendering page component:`,e)}let i=e.default?.name||`Page`;return` <div id="app" data-page="${y(String(i))}" data-props='${y(JSON.stringify(t))}'>
147
147
  <!-- Component render fallback -->
148
- </div>\n`}function S(){return` </body>
149
- </html>`}function C(e){let t=process.env.NODE_ENV!==`production`,n=e.stack?`<pre style="
148
+ </div>\n`}function C(){return` </body>
149
+ </html>`}function w(e){let t=process.env.NODE_ENV!==`production`,n=e.stack?`<pre style="
150
150
  background: #f5f5f5;
151
151
  padding: 10px;
152
152
  border-radius: 4px;
153
153
  overflow-x: auto;
154
154
  font-size: 12px;
155
155
  margin-top: 10px;
156
- ">${v(e.stack)}</pre>`:``;return`
156
+ ">${y(e.stack)}</pre>`:``;return`
157
157
  <div class="streaming-error-boundary" data-error-boundary="true" style="
158
158
  background: #fff3cd;
159
159
  border: 2px solid #ffc107;
@@ -175,10 +175,10 @@ import{getRequestURL as e}from"h3";import{createNotFoundError as t,isHttpError a
175
175
  Error Details (Development Mode)
176
176
  </summary>
177
177
  <div style="margin-top: 10px;">
178
- <p><strong>Error:</strong> ${v(e.message)}</p>
178
+ <p><strong>Error:</strong> ${y(e.message)}</p>
179
179
  ${n}
180
180
  </div>
181
181
  </details>
182
182
  `:``}
183
183
  </div>
184
- `}export function createStreamingResponse(e,t={}){let n=new Headers({"Content-Type":`text/html; charset=utf-8`,"Transfer-Encoding":`chunked`,...t.headers});return new Response(e,{status:t.status||200,headers:n})}function w(e,t,n){return async()=>(e.value??=await r({baseDir:t,devMode:n}),e.value)}function T(e,t,n){return async(r,i)=>e?a(r,i,t):createErrorResponse(r,n)}export function createNitroRenderer(e){let{avalonConfig:n,isDev:r=!1,enableCustomErrorPages:a=!0}=e,s={isDev:r,avalonConfig:n,loadPageModule:e.loadPageModule,pagesDir:n.pagesDir};a&&o(s).catch(e=>{console.warn(`[renderer] Failed to discover error pages:`,e)});let c=w({value:null},n.srcDir||`src`,r),d=T(a,s,r);return async function(a){let o=getRequestURL(a).pathname;try{let s=await i(a,await c(),{devMode:r});if(s)return r&&console.log(`[renderer] Middleware terminated request for ${o}`),s;let u=a.context.route,f=null;if(f=u||(e.resolvePageRoute?await e.resolvePageRoute(o,n.pagesDir):await E(o,n.pagesDir)),!f)return d(t(`Page not found: ${o}`),a);let m=e.loadPageModule?await e.loadPageModule(f.filePath):await D(f.filePath),h=createRenderContext(a,a.context.params||f.params);if(e.resolveLayouts&&(h.layoutContext={layouts:await e.resolveLayouts(o,n)}),n.streaming){let e=await renderPageStream(m,h,{onShellReady:()=>{setResponseHeader(a,`Content-Type`,`text/html; charset=utf-8`)}});return new Response(e,{headers:{"Content-Type":`text/html; charset=utf-8`}})}else{let t=await renderPage(m,h,{},e.wrapWithLayouts),n=injectHydrationScript(t.html,r);return new Response(n,{status:t.statusCode,headers:t.headers})}}catch(e){return console.error(`[Nitro Renderer Error]`,e),d(e instanceof Error?e:Error(String(e)),a)}}}async function E(e,t){return e===`/`||e===``?{filePath:`src/pages/index.tsx`,pattern:`/`,params:{}}:{filePath:`src/pages/${e.replace(/^\//,``).replace(/\/$/,``)}.tsx`,pattern:e,params:{}}}async function D(e){return{default:()=>null,metadata:{title:`Avalon Page`}}}export function createNitroCatchAllRenderer(e){let{avalonConfig:n,isDev:r=!1,loadPageModule:a,resolveLayouts:s,enableCustomErrorPages:c=!0}=e,d={isDev:r,avalonConfig:n,loadPageModule:a,pagesDir:n.pagesDir};c&&o(d).catch(e=>{console.warn(`[renderer] Failed to discover error pages:`,e)});let f=w({value:null},n.srcDir||`src`,r),m=T(c,d,r);return async function(o){let c=getRequestURL(o).pathname;try{let u=await i(o,await f(),{devMode:r});if(u)return r&&console.log(`[renderer] Middleware terminated request for ${c}`),u;let d=o.context.params||{},h=d.slug||c.replace(/^\//,``)||`index`,g=`${n.pagesDir}/${h}.tsx`,_;try{_=await a(g)}catch(e){try{_=await a(`${n.pagesDir}/${h}/index.tsx`)}catch(i){return r&&(console.debug(`[renderer] Page not found: ${g}`,e),console.debug(`[renderer] Index fallback not found: ${n.pagesDir}/${h}/index.tsx`,i)),m(t(`Page not found: ${c}`),o)}}let v=createRenderContext(o,d);if(s&&(v.layoutContext={layouts:await s(c,n)}),n.streaming){let e=await renderPageStream(_,v,{onShellReady:()=>{setResponseHeader(o,`Content-Type`,`text/html; charset=utf-8`)}});return new Response(e,{headers:{"Content-Type":`text/html; charset=utf-8`}})}else{let t=await renderPage(_,v,{},e.wrapWithLayouts),n=injectHydrationScript(t.html,r);return new Response(n,{status:t.statusCode,headers:t.headers})}}catch(e){return console.error(`[Nitro Catch-All Renderer Error]`,e),m(e instanceof Error?e:Error(String(e)),o)}}}export{clearMiddlewareCache as clearRendererMiddlewareCache}from"../middleware/index.js";
184
+ `}export function createStreamingResponse(e,t={}){let n=new Headers({"Content-Type":`text/html; charset=utf-8`,"Transfer-Encoding":`chunked`,...t.headers});return new Response(e,{status:t.status||200,headers:n})}function T(e,t,n){return async()=>(e.value??=await r({baseDir:t,devMode:n}),e.value)}function E(e,t,n){return async(r,i)=>e?o(r,i,t):createErrorResponse(r,n)}export function createNitroRenderer(e){let{avalonConfig:t,isDev:n=!1,enableCustomErrorPages:r=!0}=e,o={isDev:n,avalonConfig:t,loadPageModule:e.loadPageModule,pagesDir:t.pagesDir};r&&a(o).catch(e=>{console.warn(`[renderer] Failed to discover error pages:`,e)});let c=T({value:null},t.srcDir||`src`,n),f=E(r,o,n);async function p(r){let a=getRequestURL(r).pathname;try{let o=await i(r,await c(),{devMode:n});if(o)return n&&console.log(`[renderer] Middleware terminated request for ${a}`),o;let u=r.context.route,d=null;if(d=u||(e.resolvePageRoute?await e.resolvePageRoute(a,t.pagesDir):await D(a,t.pagesDir)),!d)return f(s(`Page not found: ${a}`),r);let p=e.loadPageModule?await e.loadPageModule(d.filePath):await O(d.filePath),h=createRenderContext(r,r.context.params||d.params);if(e.resolveLayouts&&(h.layoutContext={layouts:await e.resolveLayouts(a,t)}),t.streaming){let e=await renderPageStream(p,h,{onShellReady:()=>{setResponseHeader(r,`Content-Type`,`text/html; charset=utf-8`)}});return new Response(e,{headers:{"Content-Type":`text/html; charset=utf-8`}})}else{let t=await renderPage(p,h,{},e.wrapWithLayouts),r=injectHydrationScript(t.html,n);return new Response(r,{status:t.statusCode,headers:t.headers})}}catch(e){return console.error(`[Nitro Renderer Error]`,e),f(e instanceof Error?e:Error(String(e)),r)}}return Object.assign(p,{async fetch(r){let i=new URL(r.url,`http://localhost`).pathname;try{let a=null;if(a=e.resolvePageRoute?await e.resolvePageRoute(i,t.pagesDir):await D(i,t.pagesDir),!a)return createErrorResponse(s(`Page not found: ${i}`),n);let o=await renderPage(e.loadPageModule?await e.loadPageModule(a.filePath):await O(a.filePath),createRenderContextFromRequest(r,a.params),{},e.wrapWithLayouts),c=injectHydrationScript(o.html,n);return new Response(c,{status:o.statusCode,headers:o.headers})}catch(e){return console.error(`[Nitro Renderer .fetch() Error]`,e),createErrorResponse(e instanceof Error?e:Error(String(e)),n)}}})}async function D(e,t){return e===`/`||e===``?{filePath:`src/pages/index.tsx`,pattern:`/`,params:{}}:{filePath:`src/pages/${e.replace(/^\//,``).replace(/\/$/,``)}.tsx`,pattern:e,params:{}}}async function O(e){return{default:()=>null,metadata:{title:`Avalon Page`}}}export function createNitroCatchAllRenderer(e){let{avalonConfig:t,isDev:n=!1,loadPageModule:r,resolveLayouts:o,enableCustomErrorPages:c=!0}=e,f={isDev:n,avalonConfig:t,loadPageModule:r,pagesDir:t.pagesDir};c&&a(f).catch(e=>{console.warn(`[renderer] Failed to discover error pages:`,e)});let p=T({value:null},t.srcDir||`src`,n),g=E(c,f,n);async function _(a){let c=getRequestURL(a).pathname;try{let u=await i(a,await p(),{devMode:n});if(u)return n&&console.log(`[renderer] Middleware terminated request for ${c}`),u;let d=a.context.params||{},f=d.slug||c.replace(/^\//,``)||`index`,h=`${t.pagesDir}/${f}.tsx`,_;try{_=await r(h)}catch(e){try{_=await r(`${t.pagesDir}/${f}/index.tsx`)}catch(r){return n&&(console.debug(`[renderer] Page not found: ${h}`,e),console.debug(`[renderer] Index fallback not found: ${t.pagesDir}/${f}/index.tsx`,r)),g(s(`Page not found: ${c}`),a)}}let v=createRenderContext(a,d);if(o&&(v.layoutContext={layouts:await o(c,t)}),t.streaming){let e=await renderPageStream(_,v,{onShellReady:()=>{setResponseHeader(a,`Content-Type`,`text/html; charset=utf-8`)}});return new Response(e,{headers:{"Content-Type":`text/html; charset=utf-8`}})}else{let t=await renderPage(_,v,{},e.wrapWithLayouts),r=injectHydrationScript(t.html,n);return new Response(r,{status:t.statusCode,headers:t.headers})}}catch(e){return console.error(`[Nitro Catch-All Renderer Error]`,e),g(e instanceof Error?e:Error(String(e)),a)}}return Object.assign(_,{async fetch(i){let a=new URL(i.url,`http://localhost`).pathname;try{let o=a.replace(/^\//,``)||`index`,c=`${t.pagesDir}/${o}.tsx`,l;try{l=await r(c)}catch{try{l=await r(`${t.pagesDir}/${o}/index.tsx`)}catch{return createErrorResponse(s(`Page not found: ${a}`),n)}}let d=createRenderContextFromRequest(i),f=await renderPage(l,d,{},e.wrapWithLayouts),p=injectHydrationScript(f.html,n);return new Response(p,{status:f.statusCode,headers:f.headers})}catch(e){return console.error(`[Nitro CatchAll .fetch() Error]`,e),createErrorResponse(e instanceof Error?e:Error(String(e)),n)}}})}export{clearMiddlewareCache as clearRendererMiddlewareCache}from"../middleware/index.js";
@@ -1,19 +1,9 @@
1
- import type { IslandState } from '../schemas/layout.ts';
2
- /**
3
- * Serializes and deserializes island state for browser storage.
4
- *
5
- * Handles complex types that `JSON.stringify` drops: Date, RegExp, Map, and Set.
6
- * Functions are silently stripped.
7
- */
8
- export declare class IslandStateSerializer {
9
- static serialize(state: IslandState): string;
10
- static deserialize(serializedState: string): IslandState;
11
- private static transformForSerialization;
12
- private static reviver;
13
- static validate(state: IslandState): {
14
- valid: boolean;
15
- errors: string[];
16
- };
17
- static clone(state: IslandState): IslandState;
18
- static equals(state1: IslandState, state2: IslandState): boolean;
19
- }
1
+ import type { IslandState } from "../schemas/layout.ts";
2
+ export declare function serialize(state: IslandState): string;
3
+ export declare function deserialize(serializedState: string): IslandState;
4
+ export declare function validate(state: IslandState): {
5
+ valid: boolean;
6
+ errors: string[];
7
+ };
8
+ export declare function clone(state: IslandState): IslandState;
9
+ export declare function equals(state1: IslandState, state2: IslandState): boolean;
@@ -1 +1 @@
1
- export class IslandStateSerializer{static serialize(t){try{let n=IslandStateSerializer.transformForSerialization(t);return JSON.stringify(n)}catch(e){throw Error(`State serialization failed: ${e instanceof Error?e.message:String(e)}`)}}static deserialize(t){try{return JSON.parse(t,IslandStateSerializer.reviver)}catch(e){throw Error(`State deserialization failed: ${e instanceof Error?e.message:String(e)}`)}}static transformForSerialization(t){if(t==null)return null;if(t instanceof Date)return{__type:`Date`,__value:t.toISOString()};if(t instanceof RegExp)return{__type:`RegExp`,__value:{source:t.source,flags:t.flags}};if(t instanceof Map)return{__type:`Map`,__value:Array.from(t.entries()).map(([t,n])=>[IslandStateSerializer.transformForSerialization(t),IslandStateSerializer.transformForSerialization(n)])};if(t instanceof Set)return{__type:`Set`,__value:Array.from(t.values()).map(t=>IslandStateSerializer.transformForSerialization(t))};if(typeof t==`function`)return null;if(Array.isArray(t))return t.map(t=>IslandStateSerializer.transformForSerialization(t));if(typeof t==`object`){let n={},r=t;for(let t in r)Object.hasOwn(r,t)&&(n[t]=IslandStateSerializer.transformForSerialization(r[t]));return n}return t}static reviver(e,t){if(t&&typeof t==`object`){let e=t;if(e.__type&&e.__value!==void 0)switch(e.__type){case`Date`:return new Date(e.__value);case`RegExp`:{let t=e.__value;return new RegExp(t.source,t.flags)}case`Map`:return new Map(e.__value);case`Set`:return new Set(e.__value);default:return e.__value}}return t}static validate(e){let t=[];try{let n=this.serialize(e),r=new Blob([n]).size;r>5*1024*1024&&t.push(`Serialized state size (${Math.round(r/1024)}KB) exceeds 5MB limit`),this.deserialize(n)}catch(e){t.push(e instanceof Error?e.message:String(e))}return{valid:t.length===0,errors:t}}static clone(e){try{return this.deserialize(this.serialize(e))}catch{return{...e}}}static equals(e,t){try{return this.serialize(e)===this.serialize(t)}catch{return!1}}}
1
+ function e(t){if(t==null)return null;if(t instanceof Date)return{__type:`Date`,__value:t.toISOString()};if(t instanceof RegExp)return{__type:`RegExp`,__value:{source:t.source,flags:t.flags}};if(t instanceof Map)return{__type:`Map`,__value:Array.from(t.entries()).map(([t,n])=>[e(t),e(n)])};if(t instanceof Set)return{__type:`Set`,__value:Array.from(t.values()).map(t=>e(t))};if(typeof t==`function`)return null;if(Array.isArray(t))return t.map(t=>e(t));if(typeof t==`object`){let n={},r=t;for(let t in r)Object.hasOwn(r,t)&&(n[t]=e(r[t]));return n}return t}function t(e,t){if(t&&typeof t==`object`){let e=t;if(e.__type&&e.__value!==void 0)switch(e.__type){case`Date`:return new Date(e.__value);case`RegExp`:{let t=e.__value;return new RegExp(t.source,t.flags)}case`Map`:return new Map(e.__value);case`Set`:return new Set(e.__value);default:return e.__value}}return t}export function serialize(t){try{let n=e(t);return JSON.stringify(n)}catch(e){throw Error(`State serialization failed: ${e instanceof Error?e.message:String(e)}`)}}export function deserialize(e){try{return JSON.parse(e,t)}catch(e){throw Error(`State deserialization failed: ${e instanceof Error?e.message:String(e)}`)}}export function validate(e){let t=[];try{let i=serialize(e),a=new Blob([i]).size;a>5*1024*1024&&t.push(`Serialized state size (${Math.round(a/1024)}KB) exceeds 5MB limit`),deserialize(i)}catch(e){t.push(e instanceof Error?e.message:String(e))}return{valid:t.length===0,errors:t}}export function clone(e){try{return deserialize(serialize(e))}catch{return{...e}}}export function equals(e,t){try{return serialize(e)===serialize(t)}catch{return!1}}
@@ -2,7 +2,7 @@
2
2
  * `useState` that survives page navigations.
3
3
  *
4
4
  * State is serialized to sessionStorage (or localStorage) via
5
- * {@link IslandStateSerializer}, so Date, Map, Set, and RegExp are preserved.
5
+ * {@link serialize}/{@link deserialize}, so Date, Map, Set, and RegExp are preserved.
6
6
  * On the server or when storage is unavailable, falls back to in-memory state.
7
7
  *
8
8
  * @param id - Unique key for this piece of state.
@@ -27,5 +27,5 @@
27
27
  * ```
28
28
  */
29
29
  export declare function usePersistentState<T>(id: string, initialValue: T, options?: {
30
- storage?: 'session' | 'local';
30
+ storage?: "session" | "local";
31
31
  }): [T, (value: T | ((prev: T) => T)) => void, () => void];
@@ -1 +1 @@
1
- import{useState as e,useEffect as t,useCallback as n}from"preact/hooks";import{IslandStateSerializer as r}from"./island-state-serializer.js";export function usePersistentState(i,a,o){let s=o?.storage??`session`,c=`avalon-island:${i}`,[l,u]=e(()=>{if(globalThis.window===void 0)return a;try{let e=(s===`local`?localStorage:sessionStorage).getItem(c);return e===null?a:r.deserialize(e).v??a}catch{return a}});return t(()=>{if(globalThis.window!==void 0)try{(s===`local`?localStorage:sessionStorage).setItem(c,r.serialize({v:l}))}catch{}},[l,c,s]),[l,n(e=>{u(t=>typeof e==`function`?e(t):e)},[]),n(()=>{if(u(a),globalThis.window!==void 0)try{(s===`local`?localStorage:sessionStorage).removeItem(c)}catch{}},[a,c,s])]}
1
+ import{useCallback as e,useEffect as t,useState as n}from"preact/hooks";import{deserialize as r,serialize as i}from"./island-state-serializer.js";export function usePersistentState(a,o,s){let c=s?.storage??`session`,l=`avalon-island:${a}`,[u,d]=n(()=>{if(globalThis.window===void 0)return o;try{let e=(c===`local`?localStorage:sessionStorage).getItem(l);return e===null?o:r(e).v??o}catch{return o}});return t(()=>{if(globalThis.window!==void 0)try{(c===`local`?localStorage:sessionStorage).setItem(l,i({v:u}))}catch{}},[u,l,c]),[u,e(e=>{d(t=>typeof e==`function`?e(t):e)},[]),e(()=>{if(d(o),globalThis.window!==void 0)try{(c===`local`?localStorage:sessionStorage).removeItem(l)}catch{}},[o,l,c])]}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Avalon Post-Build Script
3
+ *
4
+ * Handles all post-build tasks for production deployments:
5
+ *
6
+ * 1. Cleanup: Removes stale Vite template index.html files
7
+ * 2. CSS Patching: Copies SSR CSS to client assets, patches SSR bundle
8
+ * 3. Island Redirects: Generates _redirects for clean island paths (Netlify)
9
+ * 4. Adapter Copying: Copies framework adapters to dist/
10
+ * 5. Netlify Function Copying: Copies server function to all Netlify paths
11
+ * 6. Prerendering: Boots built server, fetches routes, writes static HTML
12
+ *
13
+ * Usage:
14
+ * import { runPostBuild } from '@useavalon/avalon/post-build';
15
+ * await runPostBuild();
16
+ *
17
+ * With custom prerender config:
18
+ * await runPostBuild({
19
+ * prerender: { routes: ['/'], crawlLinks: true, ignore: ['/admin'] },
20
+ * });
21
+ */
22
+ export interface PrerenderConfig {
23
+ /** Routes to prerender (default: ['/']) */
24
+ routes?: string[];
25
+ /** Crawl links found in prerendered pages (default: true) */
26
+ crawlLinks?: boolean;
27
+ /** Routes to ignore (default: []) */
28
+ ignore?: string[];
29
+ /** Fail build on prerender errors (default: false) */
30
+ failOnError?: boolean;
31
+ /** Max concurrent fetches (default: 4) */
32
+ concurrency?: number;
33
+ /** Create index.html in subdirectories (default: true) */
34
+ autoSubfolderIndex?: boolean;
35
+ /** Number of retry attempts (default: 3) */
36
+ retry?: number;
37
+ /** Delay between retries in ms (default: 500) */
38
+ retryDelay?: number;
39
+ }
40
+ export interface PostBuildOptions {
41
+ /** Project root directory (default: process.cwd()) */
42
+ cwd?: string;
43
+ /** Prerender configuration (default: { routes: ['/'], crawlLinks: true }) */
44
+ prerender?: PrerenderConfig | false;
45
+ /** Port for prerender server (default: 13172) */
46
+ prerenderPort?: number;
47
+ }
48
+ /**
49
+ * Run all post-build tasks.
50
+ *
51
+ * Usage in consumer's post-build.mjs:
52
+ * ```js
53
+ * import { runPostBuild } from '@useavalon/avalon/post-build';
54
+ * await runPostBuild();
55
+ * ```
56
+ */
57
+ export declare function runPostBuild(options?: PostBuildOptions): Promise<void>;
@@ -0,0 +1,31 @@
1
+ import{copyFileSync as e,cpSync as t,existsSync as n,mkdirSync as r,readdirSync as i,readFileSync as a,unlinkSync as o,writeFileSync as s}from"node:fs";import{dirname as c,join as l,relative as u}from"node:path";function d(e,t,r=[]){if(!n(e))return r;for(let n of i(e,{withFileTypes:!0})){let i=l(e,n.name);n.isDirectory()?d(i,t,r):t(n.name)&&r.push(i)}return r}function f(e){if(!n(e))return!1;let t=a(e,`utf-8`);return t.length<500&&!t.includes(`data-framework`)&&!t.includes(`avalon`)}function p(e){for(let t of[`dist/index.html`,`.netlify/functions-internal/server/public/index.html`]){let r=l(e,t);f(r)?(o(r),console.log(`[cleanup] Removed stale Vite template ${t}`)):n(r)&&console.log(`[cleanup] Preserved prerendered ${t}`)}}function m(e,t,r){if(!n(e))return;let i=[l(t,`assets`),l(r,`.netlify`,`functions-internal`,`server`,`public`,`assets`),l(r,`.output`,`public`,`assets`)].find(e=>n(e));if(!i)return;let o=d(i,e=>e.endsWith(`.css`)).map(e=>`/assets${e.substring(i.length).replaceAll(`\\`,`/`)}`);console.log(`[patch] Found ${o.length} CSS files in ${i}`);let c=a(e,`utf-8`);for(let{re:t,hrefRe:n,q:r}of[{re:/css:\[(\{href:`[^`]+`\}(?:,\{href:`[^`]+`\})*)\]/,hrefRe:/href:`([^`]+)`/g,q:"`"},{re:/css:\[(\{href:"[^"]+"\}(?:,\{href:"[^"]+"\})*)\]/,hrefRe:/href:"([^"]+)"/g,q:`"`}]){let i=t.exec(c);if(!i)continue;let a=new Set([...i[1].matchAll(n)].map(e=>e[1])),l=o.filter(e=>!a.has(e));if(l.length===0){console.log(`[patch] All CSS already included`);return}let u=l.map(e=>`{href:${r}${e}${r}}`).join(`,`);c=c.replace(i[0],`css:[${i[1]},${u}]`),s(e,c),console.log(`[patch] ✅ Added ${l.length} CSS files to SSR bundle`);return}console.warn(`[patch] Could not find CSS array in SSR bundle`)}function h(t,o){let c=[l(t,`node_modules`,`.nitro`,`vite`,`services`,`ssr`,`assets`)];for(let u of c){if(!n(u))continue;let c=i(u).filter(e=>e.endsWith(`.css`));if(c.length===0)continue;let d=[l(o,`assets`),l(t,`.output`,`public`,`assets`),l(t,`.netlify`,`functions-internal`,`server`,`public`,`assets`)];for(let t of d){r(t,{recursive:!0});for(let n of c)e(l(u,n),l(t,`ssr-${n}`))}let f=c[0],p=a(l(u,f)).length,m=`ssr-${f}`;console.log(`[ssr-css] Copied SSR CSS → /assets/${m} (${p} bytes)`);let h=[l(t,`.output`,`server`,`index.mjs`),l(t,`.netlify`,`functions-internal`,`server`,`main.mjs`)];for(let e of h){if(!n(e))continue;let t=a(e,`utf-8`),r=`/assets/${m}`;if(t.includes(r))continue;let i=/"\/assets\/[^"]+\.css":\{type:`text\/css[^}]+\}/.exec(t);if(i){let n=new Date().toISOString(),a=`,"${r}":{type:\`text/css; charset=utf-8\`,etag:\`${`"${p.toString(16)}-ssr"`}\`,mtime:\`${n}\`,size:${p},path:\`../public/assets/${m}\`}`;t=t.replace(i[0],i[0]+a),s(e,t),console.log(`[ssr-css] ✅ Patched asset manifest in ${e}`)}}return}console.log(`[ssr-css] No SSR CSS files found`)}function g(t){let i=[l(t,`assets`,`islands`),l(t,`islands`)].find(e=>n(e));if(!i)return;let o=d(i,e=>e.endsWith(`.js`)&&!e.endsWith(`.js.map`));if(o.length===0)return;let f=o.filter(e=>/-[A-Za-z0-9_-]{6,12}\.js$/.test(e));if(f.length===0){console.log(`[redirects] ${o.length} island(s) found with clean paths — no redirects needed`);return}let p=[];for(let n of f){let i=`/${u(t,n).replaceAll(`\\`,`/`)}`,a=i.replace(`/assets/`,`/`).replace(/-[A-Za-z0-9_-]{6,12}\.js$/,`.js`);p.push(`${a} ${i} 200`);let o=l(t,a.slice(1));r(c(o),{recursive:!0}),e(n,o)}let m=l(t,`_redirects`),h=n(m)?a(m,`utf-8`):``;h=h.replaceAll(/# Island JS path rewrites[^\n]*\n(?:\/islands\/[^\n]*\n)*/g,``).trim();let g=`# Island JS path rewrites (generated by Avalon post-build)
2
+ `;s(m,h?`${h}\n\n${g}${p.join(`
3
+ `)}\n`:`${g+p.join(`
4
+ `)}\n`),console.log(`[redirects] ✅ Wrote ${p.length} island redirects + local copies`)}function _(t,a){let o=[l(t,`.output`,`public`,`_adapters`),l(a,`_adapters`)];for(let t of o){if(!n(t))continue;let o=i(t).filter(e=>e.endsWith(`.js`));if(o.length===0)continue;let s=l(a,`_adapters`);r(s,{recursive:!0});for(let n of o){let r=l(t,n),i=l(s,n);r!==i&&e(r,i)}console.log(`[adapters] ✅ Copied ${o.length} framework adapters`);return}console.log(`[adapters] No _adapters/ directory found`)}function v(e){let r=l(e,`.netlify`,`functions-internal`,`server`);if(!n(r))return;let i=[l(e,`.netlify`,`v1`,`functions`,`server`),l(e,`netlify`,`functions`,`server`)];for(let n of i){t(r,n,{recursive:!0,force:!0});let i=n.substring(e.length).replaceAll(`\\`,`/`);console.log(`[netlify-fn] ✅ Copied server function to ${i}/`)}}function y(e){let t=a(e,`utf-8`);return t.includes(`path: "/*"`)||t.includes("path:`/*`")}function b(e,t,r){let i=l(c(e),`_prerender-server.mjs`),a=``,o=[l(r,`node_modules`,`urlpattern-polyfill`,`index.js`),l(r,`node_modules`,`urlpattern-polyfill`,`dist`,`urlpattern.js`)].find(e=>n(e));return o&&(a=`import '${o.replaceAll(`\\`,`/`)}';`),s(i,`
5
+ ${a}
6
+ import { createServer } from 'node:http';
7
+ import handler from './main.mjs';
8
+ const PORT = ${t};
9
+ const server = createServer(async (req, res) => {
10
+ try {
11
+ const url = new URL(req.url, 'http://localhost:' + PORT);
12
+ const headers = new Headers();
13
+ for (const [key, value] of Object.entries(req.headers)) {
14
+ if (value) headers.set(key, Array.isArray(value) ? value.join(', ') : value);
15
+ }
16
+ const request = new Request(url.toString(), { method: req.method, headers });
17
+ const response = await handler(request);
18
+ res.writeHead(response.status, Object.fromEntries(response.headers.entries()));
19
+ const body = await response.text();
20
+ res.end(body);
21
+ } catch (err) {
22
+ console.error('[prerender-wrapper] Error:', err);
23
+ res.writeHead(500);
24
+ res.end('Internal Server Error');
25
+ }
26
+ });
27
+ server.listen(PORT, '127.0.0.1', () => {
28
+ console.log('[prerender-wrapper] Listening on http://127.0.0.1:' + PORT);
29
+ });
30
+ `),i}function x(e){let t=[],n=/<a\s[^>]*href=["']([^"'#?]+)/gi,r=null;for(r=n.exec(e);r!==null;r=n.exec(e)){let e=r[1];e.startsWith(`/`)&&!e.startsWith(`//`)&&!e.startsWith(`/assets/`)&&!e.startsWith(`/islands/`)&&!e.startsWith(`/chunks/`)&&!e.startsWith(`/_`)&&!e.match(/\.\w{2,5}$/)&&t.push(e)}return[...new Set(t)]}async function S(t,i,d,f){let p=[l(t,`.output`,`server`,`index.mjs`),l(t,`.netlify`,`functions-internal`,`server`,`server.mjs`),l(t,`.netlify`,`v1`,`functions`,`server`,`server.mjs`)].find(e=>n(e));if(!p){console.log(`[prerender] No server entry found, skipping prerender`);return}let m=[l(t,`.output`,`public`),i].find(e=>n(e));if(!m){console.log(`[prerender] No output directory found, skipping prerender`);return}let h={routes:d.routes??[`/`],crawlLinks:d.crawlLinks??!0,ignore:d.ignore??[],failOnError:d.failOnError??!1,concurrency:d.concurrency??4,autoSubfolderIndex:d.autoSubfolderIndex??!0,retry:d.retry??3,retryDelay:d.retryDelay??500},g=`http://localhost:${f}`,_=y(p),v=p;if(_){let e=l(c(p),`main.mjs`);if(!n(e)){console.error(`[prerender] Netlify handler detected but main.mjs not found`);return}v=b(e,f,t),console.log(`[prerender] Netlify handler detected — using wrapper`)}{let e=[p,l(c(p),`main.mjs`)].filter(e=>n(e));for(let n of e){let e=a(n,`utf-8`),r=/"\/[^"]*\.html":\{[^}]+\},?/g,i=e.length;e=e.replaceAll(r,``),i!==e.length&&(s(n,e),console.log(`[prerender] Patched ${u(t,n)}: removed HTML asset entries`))}}console.log(`[prerender] Starting prerender with server: ${u(t,v)}`);let{spawn:S}=await import(`node:child_process`),C;try{C=S(`node`,[v],{env:{...process.env,PORT:String(f),NITRO_PORT:String(f),HOST:`127.0.0.1`,NITRO_HOST:`127.0.0.1`,NODE_ENV:`production`},stdio:[`ignore`,`pipe`,`pipe`]}),C.stdout?.on(`data`,e=>{let t=e.toString().trim();t&&console.log(`[prerender:server] ${t}`)}),C.stderr?.on(`data`,e=>{let t=e.toString().trim();t&&console.error(`[prerender:server:err] ${t}`)});let e=Date.now(),t=!1;for(;Date.now()-e<15e3;){try{let e=await fetch(`${g}/`);if(e.ok||e.status<500){t=!0;break}}catch{}await new Promise(e=>setTimeout(e,200))}if(!t)throw Error(`Server did not become ready within 15s`);console.log(`[prerender] Server ready at ${g}`)}catch(e){C?.kill(`SIGKILL`),console.error(`[prerender] Failed to start server:`,e.message);return}let w=new Set,T=[...h.routes],E=[],D=[];function O(e){for(let t of h.ignore)if(typeof t==`string`&&e===t||typeof t==`string`&&t.endsWith(`/**`)&&e.startsWith(t.slice(0,-2)))return!0;return!1}try{for(;T.length>0;){let e=T.splice(0,h.concurrency);await Promise.all(e.map(async e=>{let t=e.endsWith(`/`)&&e!==`/`?e.slice(0,-1):e;if(w.has(t)||(w.add(t),O(t)))return;let n=null;for(let e=1;e<=h.retry;e++)try{let e=await fetch(`${g}${t}`);n={html:await e.text(),status:e.status};break}catch{e<h.retry&&await new Promise(e=>setTimeout(e,h.retryDelay))}if(!n){D.push({route:t,error:`Failed after ${h.retry} attempts`});return}if(n.status>=400){D.push({route:t,error:`Returned ${n.status}`});return}let i=h.autoSubfolderIndex?l(t,`index.html`):`${t}.html`,a=l(m,i);if(r(c(a),{recursive:!0}),s(a,n.html.replace(`<!DOCTYPE html>`,`<!DOCTYPE html>
31
+ <!-- SSG: prerendered at build time -->`)),E.push(t),console.log(`[prerender] ✅ ${t} → ${i}`),h.crawlLinks)for(let e of x(n.html)){let t=e.endsWith(`/`)&&e!==`/`?e.slice(0,-1):e;!w.has(t)&&!O(t)&&T.push(t)}}))}}finally{console.log(`[prerender] Shutting down server...`),C?.kill(`SIGKILL`)}if(console.log(`[prerender] Done: ${E.length} page(s) prerendered`+(D.length>0?`, ${D.length} error(s)`:``)),_){let e=l(c(p),`_prerender-server.mjs`);n(e)&&(o(e),console.log(`[prerender] Cleaned up wrapper script`))}if(E.length>0){let a=[i,l(t,`.netlify`,`functions-internal`,`server`,`public`),l(t,`.netlify`,`v1`,`functions`,`server`,`public`)].filter(e=>e!==m&&n(c(e)));for(let t of a)for(let i of E){let a=l(i,`index.html`),o=l(m,a),s=l(t,a);n(o)&&(r(c(s),{recursive:!0}),e(o,s))}a.length>0&&console.log(`[prerender] Copied prerendered HTML to ${a.length} additional output dir(s)`)}}export async function runPostBuild(e={}){let t=e.cwd??process.cwd(),r=l(t,`dist`),i=e.prerenderPort??13172;p(t),h(t,r);for(let e of[l(t,`.netlify`,`functions-internal`,`server`,`_ssr`,`ssr.mjs`),l(t,`.netlify`,`v1`,`functions`,`server`,`_ssr`,`ssr.mjs`),l(t,`.output`,`server`,`_ssr`,`ssr.mjs`)])n(e)&&(console.log(`[patch] Patching ${e}`),m(e,r,t));g(r),_(t,r),v(t),e.prerender!==!1&&await S(t,r,e.prerender??{},i),console.log(`[post-build] ✅ Complete`)}
@@ -23,9 +23,9 @@ export declare const ScriptConfigSchema: z.ZodUnion<readonly [z.ZodString, z.Zod
23
23
  integrity: z.ZodOptional<z.ZodString>;
24
24
  nomodule: z.ZodOptional<z.ZodBoolean>;
25
25
  referrerpolicy: z.ZodOptional<z.ZodEnum<{
26
+ origin: "origin";
26
27
  "no-referrer": "no-referrer";
27
28
  "no-referrer-when-downgrade": "no-referrer-when-downgrade";
28
- origin: "origin";
29
29
  "origin-when-cross-origin": "origin-when-cross-origin";
30
30
  "same-origin": "same-origin";
31
31
  "strict-origin": "strict-origin";
@@ -53,9 +53,9 @@ export declare const RenderOptionsSchema: z.ZodObject<{
53
53
  integrity: z.ZodOptional<z.ZodString>;
54
54
  nomodule: z.ZodOptional<z.ZodBoolean>;
55
55
  referrerpolicy: z.ZodOptional<z.ZodEnum<{
56
+ origin: "origin";
56
57
  "no-referrer": "no-referrer";
57
58
  "no-referrer-when-downgrade": "no-referrer-when-downgrade";
58
- origin: "origin";
59
59
  "origin-when-cross-origin": "origin-when-cross-origin";
60
60
  "same-origin": "same-origin";
61
61
  "strict-origin": "strict-origin";
@@ -1,5 +1,5 @@
1
- import { z } from 'zod';
2
- import type { ComponentChildren } from 'preact';
1
+ import type { ComponentChildren } from "preact";
2
+ import { z } from "zod";
3
3
  /**
4
4
  * Layout Context Schema - Contains request information and state for layout processing
5
5
  */
@@ -132,10 +132,10 @@ export declare const LayoutErrorBoundaryPropsSchema: z.ZodObject<{
132
132
  */
133
133
  export declare const ErrorRecoveryStrategySchema: z.ZodObject<{
134
134
  type: z.ZodEnum<{
135
- redirect: "redirect";
136
- skip: "skip";
137
135
  fallback: "fallback";
138
136
  retry: "retry";
137
+ skip: "skip";
138
+ redirect: "redirect";
139
139
  }>;
140
140
  maxRetries: z.ZodOptional<z.ZodNumber>;
141
141
  fallbackComponent: z.ZodOptional<z.ZodAny>;
@@ -3,7 +3,7 @@ export declare const routingValidators: {
3
3
  readonly fileSystemRoute: (data: unknown) => {
4
4
  pattern: any;
5
5
  filePath: string;
6
- routeType: "static" | "index" | "group" | "dynamic" | "catch-all";
6
+ routeType: "group" | "static" | "index" | "dynamic" | "catch-all";
7
7
  dynamicSegments: string[];
8
8
  priority: number;
9
9
  isPrivate: boolean;
@@ -93,7 +93,7 @@ export declare const safeRoutingValidators: {
93
93
  readonly fileSystemRoute: (data: unknown) => import("../index.ts").ValidationResult<{
94
94
  pattern: any;
95
95
  filePath: string;
96
- routeType: "static" | "index" | "group" | "dynamic" | "catch-all";
96
+ routeType: "group" | "static" | "index" | "dynamic" | "catch-all";
97
97
  dynamicSegments: string[];
98
98
  priority: number;
99
99
  isPrivate: boolean;
@@ -4,9 +4,9 @@ import type { ComponentType } from 'preact/compat';
4
4
  * Route Type Schema - Defines the different types of routes supported
5
5
  */
6
6
  export declare const RouteTypeSchema: z.ZodEnum<{
7
+ group: "group";
7
8
  static: "static";
8
9
  index: "index";
9
- group: "group";
10
10
  dynamic: "dynamic";
11
11
  "catch-all": "catch-all";
12
12
  }>;
@@ -17,9 +17,9 @@ export declare const FileSystemRouteSchema: z.ZodObject<{
17
17
  pattern: z.ZodAny;
18
18
  filePath: z.ZodString;
19
19
  routeType: z.ZodEnum<{
20
+ group: "group";
20
21
  static: "static";
21
22
  index: "index";
22
- group: "group";
23
23
  dynamic: "dynamic";
24
24
  "catch-all": "catch-all";
25
25
  }>;
@@ -266,9 +266,9 @@ export declare const RouteHandlerSchema: z.ZodObject<{
266
266
  metadata: z.ZodObject<{
267
267
  filePath: z.ZodString;
268
268
  routeType: z.ZodEnum<{
269
+ group: "group";
269
270
  static: "static";
270
271
  index: "index";
271
- group: "group";
272
272
  dynamic: "dynamic";
273
273
  "catch-all": "catch-all";
274
274
  }>;
@@ -284,9 +284,9 @@ export declare const RouteCacheEntrySchema: z.ZodObject<{
284
284
  pattern: z.ZodAny;
285
285
  filePath: z.ZodString;
286
286
  routeType: z.ZodEnum<{
287
+ group: "group";
287
288
  static: "static";
288
289
  index: "index";
289
- group: "group";
290
290
  dynamic: "dynamic";
291
291
  "catch-all": "catch-all";
292
292
  }>;
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Avalon type definitions.
3
- *
3
+ *
4
4
  * Include this in your tsconfig.json `types` array to get island prop support:
5
- *
5
+ *
6
6
  * ```json
7
7
  * {
8
8
  * "compilerOptions": {
@@ -13,10 +13,13 @@
13
13
  */
14
14
 
15
15
  // Re-export island prop types
16
- export * from './island-prop.d.ts';
16
+ export * from "./island-prop.d.ts";
17
17
 
18
18
  // Import JSX augmentations (side-effect import for type augmentation)
19
- import './island-jsx.d.ts';
19
+ import "./island-jsx.d.ts";
20
20
 
21
21
  // Import image type declarations
22
- import './image.d.ts';
22
+ import "./image.d.ts";
23
+
24
+ // Import virtual module declarations (virtual:avalon/*)
25
+ import "./virtual-modules.d.ts";
@@ -11,12 +11,18 @@
11
11
  */
12
12
 
13
13
  export interface IslandDirective {
14
- /** Hydration condition (built-in or custom directive name) */
15
- condition?: 'on:visible' | 'on:interaction' | 'on:idle' | 'on:client' | `media:${string}` | `on:${string}`;
16
- /** Optional argument passed to custom hydration directives */
17
- conditionArg?: string;
18
- /** Force SSR-only rendering without client hydration */
19
- ssrOnly?: boolean;
20
- /** Whether to render server-side (default: true) */
21
- ssr?: boolean;
14
+ /** Hydration condition (built-in or custom directive name) */
15
+ condition?:
16
+ | "on:visible"
17
+ | "on:interaction"
18
+ | "on:idle"
19
+ | "on:client"
20
+ | `media:${string}`
21
+ | `on:${string}`;
22
+ /** Optional argument passed to custom hydration directives */
23
+ conditionArg?: string;
24
+ /** Force SSR-only rendering without client hydration */
25
+ ssrOnly?: boolean;
26
+ /** Whether to render server-side (default: true) */
27
+ ssr?: boolean;
22
28
  }
@@ -1,13 +1,13 @@
1
- import type { LayoutContext, LayoutRoute, LayoutHandler, LayoutProps, LayoutDiscoveryOptions, RouteInfo, LayoutRule, LayoutConfig, IslandState, PersistentIslandProps, LayoutErrorInfo, LayoutErrorBoundaryProps, ErrorRecoveryStrategy, StreamingLayoutProps, StreamingComponent, ResolvedLayout, LayoutLoader } from '../schemas/layout.ts';
2
- import type { ComponentType, ComponentChildren } from 'preact';
3
- export type { LayoutContext, LayoutData, LayoutRoute, LayoutHandler, LayoutProps, LayoutDiscoveryOptions, RouteInfo, LayoutRule, LayoutConfig, IslandState, PersistentIslandProps, PersistentIslandContext, LayoutErrorInfo, LayoutErrorBoundaryProps, ErrorRecoveryStrategy, StreamingLayoutProps, StreamingComponent, ResolvedLayout, LayoutCache, EnhancedLayoutContext, LayoutLoader, LayoutMatcherFunction, LayoutErrorHandler, LayoutRetryFunction, LayoutFallbackRenderer, IslandStateSaver, IslandStateLoader, IslandStateClearer, StreamingReadyCheck, } from '../schemas/layout.ts';
4
- export type { ComponentType, ComponentChildren } from 'preact';
5
- export { LayoutDiscovery } from '../core/layout/layout-discovery.ts';
6
- export { LayoutDataLoader } from '../core/layout/layout-data-loader.ts';
7
- export { LayoutMatcher as LayoutMatcherClass } from '../core/layout/layout-matcher.ts';
8
- export { LayoutComposer } from '../core/layout/layout-composer.ts';
9
- export { EnhancedLayoutResolver, createEnhancedLayoutResolver, EnhancedLayoutResolverUtils, type EnhancedLayoutResolverOptions, } from '../core/layout/enhanced-layout-resolver.ts';
10
- export { LayoutCacheManager, type CacheEntry, type CacheStats, type CacheConfig, } from '../core/layout/layout-cache-manager.ts';
1
+ import type { ComponentChildren, ComponentType } from "preact";
2
+ import type { ErrorRecoveryStrategy, IslandState, LayoutConfig, LayoutContext, LayoutDiscoveryOptions, LayoutErrorBoundaryProps, LayoutErrorInfo, LayoutHandler, LayoutLoader, LayoutProps, LayoutRoute, LayoutRule, PersistentIslandProps, ResolvedLayout, RouteInfo, StreamingComponent, StreamingLayoutProps } from "../schemas/layout.ts";
3
+ export type { ComponentChildren, ComponentType } from "preact";
4
+ export { createEnhancedLayoutResolver, EnhancedLayoutResolver, type EnhancedLayoutResolverOptions, EnhancedLayoutResolverUtils, } from "../core/layout/enhanced-layout-resolver.ts";
5
+ export { type CacheConfig, type CacheEntry, type CacheStats, LayoutCacheManager, } from "../core/layout/layout-cache-manager.ts";
6
+ export { LayoutComposer } from "../core/layout/layout-composer.ts";
7
+ export { LayoutDataLoader } from "../core/layout/layout-data-loader.ts";
8
+ export { LayoutDiscovery } from "../core/layout/layout-discovery.ts";
9
+ export { LayoutMatcher as LayoutMatcherClass } from "../core/layout/layout-matcher.ts";
10
+ export type { EnhancedLayoutContext, ErrorRecoveryStrategy, IslandState, IslandStateClearer, IslandStateLoader, IslandStateSaver, LayoutCache, LayoutConfig, LayoutContext, LayoutData, LayoutDiscoveryOptions, LayoutErrorBoundaryProps, LayoutErrorHandler, LayoutErrorInfo, LayoutFallbackRenderer, LayoutHandler, LayoutLoader, LayoutMatcherFunction, LayoutProps, LayoutRetryFunction, LayoutRoute, LayoutRule, PersistentIslandContext, PersistentIslandProps, ResolvedLayout, RouteInfo, StreamingComponent, StreamingLayoutProps, StreamingReadyCheck, } from "../schemas/layout.ts";
11
11
  /**
12
12
  * Layout Discovery Interface
13
13
  */
@@ -151,7 +151,7 @@ export interface LayoutDebugInfo {
151
151
  size: number;
152
152
  };
153
153
  }
154
- export type LayoutEventType = 'layout-discovered' | 'layout-loaded' | 'layout-rendered' | 'layout-error' | 'layout-cached' | 'island-state-saved' | 'island-state-loaded' | 'streaming-started' | 'streaming-completed';
154
+ export type LayoutEventType = "layout-discovered" | "layout-loaded" | "layout-rendered" | "layout-error" | "layout-cached" | "island-state-saved" | "island-state-loaded" | "streaming-started" | "streaming-completed";
155
155
  export interface LayoutEventData {
156
156
  type: LayoutEventType;
157
157
  timestamp: number;
@@ -1 +1 @@
1
- export{LayoutDiscovery}from"../core/layout/layout-discovery.js";export{LayoutDataLoader}from"../core/layout/layout-data-loader.js";export{LayoutMatcher as LayoutMatcherClass}from"../core/layout/layout-matcher.js";export{LayoutComposer}from"../core/layout/layout-composer.js";export{EnhancedLayoutResolver,createEnhancedLayoutResolver,EnhancedLayoutResolverUtils}from"../core/layout/enhanced-layout-resolver.js";export{LayoutCacheManager}from"../core/layout/layout-cache-manager.js";
1
+ export{createEnhancedLayoutResolver,EnhancedLayoutResolver,EnhancedLayoutResolverUtils}from"../core/layout/enhanced-layout-resolver.js";export{LayoutCacheManager}from"../core/layout/layout-cache-manager.js";export{LayoutComposer}from"../core/layout/layout-composer.js";export{LayoutDataLoader}from"../core/layout/layout-data-loader.js";export{LayoutDiscovery}from"../core/layout/layout-discovery.js";export{LayoutMatcher as LayoutMatcherClass}from"../core/layout/layout-matcher.js";
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Type declarations for Avalon virtual modules.
3
+ *
4
+ * These modules are resolved at build time by Avalon's Vite plugin.
5
+ * Including `@useavalon/avalon/types` in your tsconfig `types` array
6
+ * makes these declarations available project-wide.
7
+ */
8
+
9
+ declare module "virtual:avalon/page-loader" {
10
+ export function loadPage(
11
+ pathname: string,
12
+ ): { default: unknown; metadata?: Record<string, unknown> } | null;
13
+ }
14
+
15
+ declare module "virtual:avalon/config" {
16
+ const config: {
17
+ streaming: boolean;
18
+ pagesDir: string;
19
+ layoutsDir: string;
20
+ isDev: boolean;
21
+ [key: string]: unknown;
22
+ };
23
+ export default config;
24
+ }
25
+
26
+ declare module "virtual:avalon/layouts" {
27
+ import type { PageModule, NitroRenderContext } from "@useavalon/avalon/nitro/types";
28
+ export function wrapWithLayouts(
29
+ pageHtml: string,
30
+ pageModule: PageModule,
31
+ context: NitroRenderContext,
32
+ injectAssets?: (html: string) => string,
33
+ ): Promise<string>;
34
+ }
35
+
36
+ declare module "virtual:avalon/assets" {
37
+ export function injectAssets(html: string): string;
38
+ export const clientAssets: {
39
+ css: Array<{ href: string; [key: string]: string }>;
40
+ js: Array<{ href: string; [key: string]: string }>;
41
+ entry: string;
42
+ };
43
+ }
44
+
45
+ declare module "virtual:avalon/renderer" {
46
+ const handler: {
47
+ (event: unknown): Promise<Response>;
48
+ fetch(request: Request): Promise<Response>;
49
+ };
50
+ export default handler;
51
+ }
52
+
53
+ declare module "virtual:avalon/client-entry" {
54
+ // Side-effect-only module: imports hydration runtime, global CSS, and layout CSS.
55
+ // No exports — just import it as your client entry point.
56
+ }
@@ -15,6 +15,10 @@ export declare const VIRTUAL_MODULE_IDS: {
15
15
  readonly ISLAND_MANIFEST: "virtual:avalon/island-manifest";
16
16
  readonly RUNTIME_CONFIG: "virtual:avalon/runtime-config";
17
17
  readonly CONFIG: "virtual:avalon/config";
18
+ readonly LAYOUTS: "virtual:avalon/layouts";
19
+ readonly ASSETS: "virtual:avalon/assets";
20
+ readonly RENDERER: "virtual:avalon/renderer";
21
+ readonly CLIENT_ENTRY: "virtual:avalon/client-entry";
18
22
  };
19
23
  export declare const RESOLVED_VIRTUAL_IDS: {
20
24
  readonly PAGE_ROUTES: string;
@@ -22,6 +26,10 @@ export declare const RESOLVED_VIRTUAL_IDS: {
22
26
  readonly ISLAND_MANIFEST: string;
23
27
  readonly RUNTIME_CONFIG: string;
24
28
  readonly CONFIG: string;
29
+ readonly LAYOUTS: string;
30
+ readonly ASSETS: string;
31
+ readonly RENDERER: string;
32
+ readonly CLIENT_ENTRY: string;
25
33
  };
26
34
  export interface NitroIntegrationResult {
27
35
  nitroOptions: NitroConfigOutput;