davaux 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/BASELINE.md +169 -0
- package/CLAUDE.md +518 -0
- package/LICENSE +21 -0
- package/README.md +36 -0
- package/ROADMAP.md +198 -0
- package/build.mjs +101 -0
- package/client/control.ts +247 -0
- package/client/hydrate.ts +37 -0
- package/client/index.ts +19 -0
- package/client/jsx-runtime.ts +209 -0
- package/client/resource.ts +122 -0
- package/client/signal.ts +211 -0
- package/client/store.ts +110 -0
- package/client/useHead.ts +63 -0
- package/dist/build/config.d.ts +3 -0
- package/dist/build/config.d.ts.map +1 -0
- package/dist/build/config.js +38 -0
- package/dist/build/config.js.map +7 -0
- package/dist/build/index.d.ts +2 -0
- package/dist/build/index.d.ts.map +1 -0
- package/dist/build/index.js +13 -0
- package/dist/build/index.js.map +7 -0
- package/dist/build/plugins.d.ts +7 -0
- package/dist/build/plugins.d.ts.map +1 -0
- package/dist/build/plugins.js +85 -0
- package/dist/build/plugins.js.map +7 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +427 -0
- package/dist/cli.js.map +7 -0
- package/dist/client/control.d.ts +49 -0
- package/dist/client/control.d.ts.map +1 -0
- package/dist/client/control.js +154 -0
- package/dist/client/control.js.map +7 -0
- package/dist/client/hydrate.d.ts +7 -0
- package/dist/client/hydrate.d.ts.map +1 -0
- package/dist/client/hydrate.js +23 -0
- package/dist/client/hydrate.js.map +7 -0
- package/dist/client/index.d.ts +12 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +32 -0
- package/dist/client/index.js.map +7 -0
- package/dist/client/jsx-runtime.d.ts +40 -0
- package/dist/client/jsx-runtime.d.ts.map +1 -0
- package/dist/client/jsx-runtime.js +139 -0
- package/dist/client/jsx-runtime.js.map +7 -0
- package/dist/client/resource.d.ts +31 -0
- package/dist/client/resource.d.ts.map +1 -0
- package/dist/client/resource.js +64 -0
- package/dist/client/resource.js.map +7 -0
- package/dist/client/signal.d.ts +90 -0
- package/dist/client/signal.d.ts.map +1 -0
- package/dist/client/signal.js +115 -0
- package/dist/client/signal.js.map +7 -0
- package/dist/client/store.d.ts +26 -0
- package/dist/client/store.d.ts.map +1 -0
- package/dist/client/store.js +63 -0
- package/dist/client/store.js.map +7 -0
- package/dist/client/useHead.d.ts +28 -0
- package/dist/client/useHead.d.ts.map +1 -0
- package/dist/client/useHead.js +33 -0
- package/dist/client/useHead.js.map +7 -0
- package/dist/config.d.ts +182 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +21 -0
- package/dist/config.js.map +7 -0
- package/dist/create-multisite.d.ts +2 -0
- package/dist/create-multisite.d.ts.map +1 -0
- package/dist/create-multisite.js +291 -0
- package/dist/create-multisite.js.map +7 -0
- package/dist/create.d.ts +2 -0
- package/dist/create.d.ts.map +1 -0
- package/dist/create.js +179 -0
- package/dist/create.js.map +7 -0
- package/dist/dev/blueprints.d.ts +11 -0
- package/dist/dev/blueprints.d.ts.map +1 -0
- package/dist/dev/blueprints.js +65 -0
- package/dist/dev/blueprints.js.map +7 -0
- package/dist/dev/components.d.ts +19 -0
- package/dist/dev/components.d.ts.map +1 -0
- package/dist/dev/components.js +87 -0
- package/dist/dev/components.js.map +7 -0
- package/dist/dev/insert.d.ts +11 -0
- package/dist/dev/insert.d.ts.map +1 -0
- package/dist/dev/insert.js +160 -0
- package/dist/dev/insert.js.map +7 -0
- package/dist/dev/remove.d.ts +53 -0
- package/dist/dev/remove.d.ts.map +1 -0
- package/dist/dev/remove.js +518 -0
- package/dist/dev/remove.js.map +7 -0
- package/dist/dev/watch.d.ts +26 -0
- package/dist/dev/watch.d.ts.map +1 -0
- package/dist/dev/watch.js +2905 -0
- package/dist/dev/watch.js.map +7 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +63 -0
- package/dist/errors.js.map +7 -0
- package/dist/generate.d.ts +2 -0
- package/dist/generate.d.ts.map +1 -0
- package/dist/generate.js +191 -0
- package/dist/generate.js.map +7 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +57 -0
- package/dist/index.js.map +7 -0
- package/dist/island.d.ts +24 -0
- package/dist/island.d.ts.map +1 -0
- package/dist/island.js +15 -0
- package/dist/island.js.map +7 -0
- package/dist/jsx-runtime.d.ts +406 -0
- package/dist/jsx-runtime.d.ts.map +1 -0
- package/dist/jsx-runtime.js +90 -0
- package/dist/jsx-runtime.js.map +7 -0
- package/dist/link.d.ts +27 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +29 -0
- package/dist/link.js.map +7 -0
- package/dist/oml/fragment.d.ts +16 -0
- package/dist/oml/fragment.d.ts.map +1 -0
- package/dist/oml/fragment.js +26 -0
- package/dist/oml/fragment.js.map +7 -0
- package/dist/oml/index.d.ts +11 -0
- package/dist/oml/index.d.ts.map +1 -0
- package/dist/oml/index.js +21 -0
- package/dist/oml/index.js.map +7 -0
- package/dist/oml/jsx-runtime.d.ts +34 -0
- package/dist/oml/jsx-runtime.d.ts.map +1 -0
- package/dist/oml/jsx-runtime.js +59 -0
- package/dist/oml/jsx-runtime.js.map +7 -0
- package/dist/oml/jsx.d.ts +14 -0
- package/dist/oml/jsx.d.ts.map +1 -0
- package/dist/oml/jsx.js +96 -0
- package/dist/oml/jsx.js.map +7 -0
- package/dist/oml/page.d.ts +7 -0
- package/dist/oml/page.d.ts.map +1 -0
- package/dist/oml/page.js +6 -0
- package/dist/oml/page.js.map +7 -0
- package/dist/oml/render.d.ts +13 -0
- package/dist/oml/render.d.ts.map +1 -0
- package/dist/oml/render.js +117 -0
- package/dist/oml/render.js.map +7 -0
- package/dist/oml/types.d.ts +79 -0
- package/dist/oml/types.d.ts.map +1 -0
- package/dist/oml/types.js +64 -0
- package/dist/oml/types.js.map +7 -0
- package/dist/router/handler.d.ts +53 -0
- package/dist/router/handler.d.ts.map +1 -0
- package/dist/router/handler.js +342 -0
- package/dist/router/handler.js.map +7 -0
- package/dist/router/matcher.d.ts +21 -0
- package/dist/router/matcher.d.ts.map +1 -0
- package/dist/router/matcher.js +28 -0
- package/dist/router/matcher.js.map +7 -0
- package/dist/router/scanner.d.ts +17 -0
- package/dist/router/scanner.d.ts.map +1 -0
- package/dist/router/scanner.js +197 -0
- package/dist/router/scanner.js.map +7 -0
- package/dist/server/index.d.ts +23 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +29 -0
- package/dist/server/index.js.map +7 -0
- package/dist/signal.d.ts +15 -0
- package/dist/signal.d.ts.map +1 -0
- package/dist/signal.js +29 -0
- package/dist/signal.js.map +7 -0
- package/dist/ssg.d.ts +45 -0
- package/dist/ssg.d.ts.map +1 -0
- package/dist/ssg.js +175 -0
- package/dist/ssg.js.map +7 -0
- package/dist/test/actions.test.d.ts +2 -0
- package/dist/test/actions.test.d.ts.map +1 -0
- package/dist/test/body-limits.test.d.ts +2 -0
- package/dist/test/body-limits.test.d.ts.map +1 -0
- package/dist/test/errors.test.d.ts +2 -0
- package/dist/test/errors.test.d.ts.map +1 -0
- package/dist/test/fixtures/routes/[id].page.d.ts +4 -0
- package/dist/test/fixtures/routes/[id].page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/_error.d.ts +3 -0
- package/dist/test/fixtures/routes/_error.d.ts.map +1 -0
- package/dist/test/fixtures/routes/_global.d.ts +3 -0
- package/dist/test/fixtures/routes/_global.d.ts.map +1 -0
- package/dist/test/fixtures/routes/_layout-template.d.ts +3 -0
- package/dist/test/fixtures/routes/_layout-template.d.ts.map +1 -0
- package/dist/test/fixtures/routes/_layout.d.ts +3 -0
- package/dist/test/fixtures/routes/_layout.d.ts.map +1 -0
- package/dist/test/fixtures/routes/_layout_scripts.d.ts +3 -0
- package/dist/test/fixtures/routes/_layout_scripts.d.ts.map +1 -0
- package/dist/test/fixtures/routes/_middleware.d.ts +3 -0
- package/dist/test/fixtures/routes/_middleware.d.ts.map +1 -0
- package/dist/test/fixtures/routes/_redirect301_mw.d.ts +3 -0
- package/dist/test/fixtures/routes/_redirect301_mw.d.ts.map +1 -0
- package/dist/test/fixtures/routes/_redirect_mw.d.ts +3 -0
- package/dist/test/fixtures/routes/_redirect_mw.d.ts.map +1 -0
- package/dist/test/fixtures/routes/about.page.d.ts +3 -0
- package/dist/test/fixtures/routes/about.page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/action.page.d.ts +6 -0
- package/dist/test/fixtures/routes/action.page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/api/form-all.post.d.ts +3 -0
- package/dist/test/fixtures/routes/api/form-all.post.d.ts.map +1 -0
- package/dist/test/fixtures/routes/api/form-limited.post.d.ts +6 -0
- package/dist/test/fixtures/routes/api/form-limited.post.d.ts.map +1 -0
- package/dist/test/fixtures/routes/api/response-obj.get.d.ts +3 -0
- package/dist/test/fixtures/routes/api/response-obj.get.d.ts.map +1 -0
- package/dist/test/fixtures/routes/api/upload.post.d.ts +12 -0
- package/dist/test/fixtures/routes/api/upload.post.d.ts.map +1 -0
- package/dist/test/fixtures/routes/api/users.get.d.ts +6 -0
- package/dist/test/fixtures/routes/api/users.get.d.ts.map +1 -0
- package/dist/test/fixtures/routes/api/xml.get.d.ts +3 -0
- package/dist/test/fixtures/routes/api/xml.get.d.ts.map +1 -0
- package/dist/test/fixtures/routes/auth/_middleware.d.ts +3 -0
- package/dist/test/fixtures/routes/auth/_middleware.d.ts.map +1 -0
- package/dist/test/fixtures/routes/auth/protected.page.d.ts +3 -0
- package/dist/test/fixtures/routes/auth/protected.page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/index.page.d.ts +3 -0
- package/dist/test/fixtures/routes/index.page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/oml.page.d.ts +3 -0
- package/dist/test/fixtures/routes/oml.page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/redirect.page.d.ts +3 -0
- package/dist/test/fixtures/routes/redirect.page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/ssg/[slug].page.d.ts +5 -0
- package/dist/test/fixtures/routes/ssg/[slug].page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/ssg/server.page.d.ts +4 -0
- package/dist/test/fixtures/routes/ssg/server.page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/state.page.d.ts +3 -0
- package/dist/test/fixtures/routes/state.page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/throw.page.d.ts +3 -0
- package/dist/test/fixtures/routes/throw.page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/wiki/[...slug].page.d.ts +3 -0
- package/dist/test/fixtures/routes/wiki/[...slug].page.d.ts.map +1 -0
- package/dist/test/helpers.d.ts +37 -0
- package/dist/test/helpers.d.ts.map +1 -0
- package/dist/test/layouts.test.d.ts +2 -0
- package/dist/test/layouts.test.d.ts.map +1 -0
- package/dist/test/middleware.test.d.ts +2 -0
- package/dist/test/middleware.test.d.ts.map +1 -0
- package/dist/test/multipart.test.d.ts +2 -0
- package/dist/test/multipart.test.d.ts.map +1 -0
- package/dist/test/oml-routing.test.d.ts +2 -0
- package/dist/test/oml-routing.test.d.ts.map +1 -0
- package/dist/test/oml.test.d.ts +2 -0
- package/dist/test/oml.test.d.ts.map +1 -0
- package/dist/test/redirects.test.d.ts +2 -0
- package/dist/test/redirects.test.d.ts.map +1 -0
- package/dist/test/routing.test.d.ts +2 -0
- package/dist/test/routing.test.d.ts.map +1 -0
- package/dist/test/ssg.test.d.ts +2 -0
- package/dist/test/ssg.test.d.ts.map +1 -0
- package/dist/test/web-response.test.d.ts +2 -0
- package/dist/test/web-response.test.d.ts.map +1 -0
- package/dist/types.d.ts +314 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +292 -0
- package/dist/types.js.map +7 -0
- package/package.json +103 -0
- package/pka.config.json +32 -0
- package/src/build/config.ts +42 -0
- package/src/build/index.ts +6 -0
- package/src/build/plugins.ts +118 -0
- package/src/cli.ts +502 -0
- package/src/config.ts +197 -0
- package/src/create-multisite.ts +310 -0
- package/src/create.ts +194 -0
- package/src/dev/blueprints.ts +75 -0
- package/src/dev/components.ts +108 -0
- package/src/dev/insert.ts +221 -0
- package/src/dev/remove.ts +677 -0
- package/src/dev/watch.ts +3098 -0
- package/src/env.d.ts +5 -0
- package/src/errors.ts +64 -0
- package/src/generate.ts +228 -0
- package/src/index.ts +67 -0
- package/src/island.ts +47 -0
- package/src/jsx-runtime.d.ts +408 -0
- package/src/jsx-runtime.d.ts.map +1 -0
- package/src/jsx-runtime.ts +536 -0
- package/src/link.ts +49 -0
- package/src/oml/fragment.ts +54 -0
- package/src/oml/index.ts +21 -0
- package/src/oml/jsx-runtime.ts +121 -0
- package/src/oml/jsx.ts +151 -0
- package/src/oml/page.ts +13 -0
- package/src/oml/render.ts +181 -0
- package/src/oml/types.ts +159 -0
- package/src/router/handler.ts +515 -0
- package/src/router/matcher.ts +52 -0
- package/src/router/scanner.ts +272 -0
- package/src/server/index.ts +49 -0
- package/src/signal.ts +39 -0
- package/src/ssg.ts +253 -0
- package/src/test/actions.test.ts +40 -0
- package/src/test/body-limits.test.ts +83 -0
- package/src/test/errors.test.ts +53 -0
- package/src/test/fixtures/routes/[id].page.ts +3 -0
- package/src/test/fixtures/routes/_error.ts +6 -0
- package/src/test/fixtures/routes/_global.ts +8 -0
- package/src/test/fixtures/routes/_layout-template.ts +7 -0
- package/src/test/fixtures/routes/_layout.ts +7 -0
- package/src/test/fixtures/routes/_layout_scripts.ts +8 -0
- package/src/test/fixtures/routes/_middleware.ts +8 -0
- package/src/test/fixtures/routes/_redirect301_mw.ts +5 -0
- package/src/test/fixtures/routes/_redirect_mw.ts +5 -0
- package/src/test/fixtures/routes/about.page.ts +6 -0
- package/src/test/fixtures/routes/action.page.ts +11 -0
- package/src/test/fixtures/routes/api/form-all.post.ts +5 -0
- package/src/test/fixtures/routes/api/form-limited.post.ts +6 -0
- package/src/test/fixtures/routes/api/response-obj.get.ts +17 -0
- package/src/test/fixtures/routes/api/upload.post.ts +14 -0
- package/src/test/fixtures/routes/api/users.get.ts +3 -0
- package/src/test/fixtures/routes/api/xml.get.ts +5 -0
- package/src/test/fixtures/routes/auth/_middleware.ts +11 -0
- package/src/test/fixtures/routes/auth/protected.page.ts +3 -0
- package/src/test/fixtures/routes/index.page.ts +3 -0
- package/src/test/fixtures/routes/oml.page.ts +7 -0
- package/src/test/fixtures/routes/redirect.page.ts +3 -0
- package/src/test/fixtures/routes/ssg/[slug].page.ts +8 -0
- package/src/test/fixtures/routes/ssg/server.page.ts +5 -0
- package/src/test/fixtures/routes/state.page.ts +4 -0
- package/src/test/fixtures/routes/throw.page.ts +5 -0
- package/src/test/fixtures/routes/wiki/[...slug].page.ts +3 -0
- package/src/test/helpers.ts +132 -0
- package/src/test/layouts.test.ts +76 -0
- package/src/test/middleware.test.ts +69 -0
- package/src/test/multipart.test.ts +91 -0
- package/src/test/oml-routing.test.ts +59 -0
- package/src/test/oml.test.ts +429 -0
- package/src/test/redirects.test.ts +32 -0
- package/src/test/routing.test.ts +118 -0
- package/src/test/ssg.test.ts +273 -0
- package/src/test/web-response.test.ts +33 -0
- package/src/types.ts +670 -0
- package/tsconfig.client.json +17 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/types.ts"],
|
|
4
|
+
"sourcesContent": ["import type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { OmlNode } from './oml/types.js'\n\n// \u2500\u2500\u2500 Cookie helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Options for the `Set-Cookie` header written by `ctx.cookies.set()`. */\nexport interface CookieOptions {\n httpOnly?: boolean\n secure?: boolean\n sameSite?: 'Strict' | 'Lax' | 'None'\n /** Max age in seconds. */\n maxAge?: number\n expires?: Date\n /** Defaults to `'/'` when not specified. */\n path?: string\n domain?: string\n}\n\nfunction sanitizeCookieAttr(val: string): string {\n return val.replace(/[;\\r\\n]/g, '')\n}\n\n/** Reads incoming cookies and writes `Set-Cookie` response headers.\n * Available as `ctx.cookies` in every handler, layout, and middleware. */\nexport class CookieJar {\n private readonly incoming: Record<string, string>\n private readonly res: ServerResponse\n\n constructor(req: IncomingMessage, res: ServerResponse) {\n this.res = res\n this.incoming = CookieJar.parse(req.headers.cookie ?? '')\n }\n\n private static parse(header: string): Record<string, string> {\n const out: Record<string, string> = {}\n for (const pair of header.split(';')) {\n const eq = pair.indexOf('=')\n if (eq === -1) continue\n const name = pair.slice(0, eq).trim()\n const value = pair.slice(eq + 1).trim()\n if (name) out[name] = decodeURIComponent(value)\n }\n return out\n }\n\n /** Read a cookie sent by the browser. Returns `undefined` if absent. */\n get(name: string): string | undefined {\n return this.incoming[name]\n }\n\n /** Return all incoming cookies as a plain object. */\n getAll(): Record<string, string> {\n return { ...this.incoming }\n }\n\n /** Write a `Set-Cookie` header on the response. */\n set(name: string, value: string, options: CookieOptions = {}): void {\n const encoded = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`\n const parts = [encoded]\n const path = sanitizeCookieAttr(options.path ?? '/')\n\n if (options.maxAge != null) parts.push(`Max-Age=${options.maxAge}`)\n if (options.expires) parts.push(`Expires=${options.expires.toUTCString()}`)\n parts.push(`Path=${path}`)\n if (options.domain) parts.push(`Domain=${sanitizeCookieAttr(options.domain)}`)\n if (options.httpOnly) parts.push('HttpOnly')\n if (options.secure) parts.push('Secure')\n if (options.sameSite) parts.push(`SameSite=${options.sameSite}`)\n\n const existing = this.res.getHeader('Set-Cookie')\n const prev: string[] = existing\n ? Array.isArray(existing)\n ? (existing as string[])\n : [String(existing)]\n : []\n this.res.setHeader('Set-Cookie', [...prev, parts.join('; ')])\n }\n\n /** Expire a cookie by setting `Max-Age=0`. */\n delete(name: string, options?: Pick<CookieOptions, 'path' | 'domain'>): void {\n this.set(name, '', { ...options, maxAge: 0, expires: new Date(0) })\n }\n}\n\n// \u2500\u2500\u2500 Validation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Schema interface satisfied by zod, valibot, arktype, and any library whose\n * `.parse()` throws on invalid input. Pass a schema to `ctx.json()` or\n * `ctx.form()` to validate and type the parsed body in one call. */\nexport interface Validator<T> {\n parse(data: unknown): T\n}\n\n/** Thrown when a body validator rejects the request data.\n * Caught by the framework and sent as `400 { error, issues }`. */\nexport class ValidationError extends Error {\n readonly isValidation = true\n constructor(cause: unknown) {\n super(cause instanceof Error ? cause.message : 'Validation failed', { cause })\n this.name = 'ValidationError'\n }\n}\n\n/** Thrown when a request body exceeds the configured size limit.\n * Caught by the framework and sent as `413 Payload Too Large`. */\nexport class PayloadTooLargeError extends Error {\n readonly isPayloadTooLarge = true\n readonly status = 413\n constructor(limitBytes: number) {\n super(`Payload Too Large \u2014 body exceeds ${limitBytes.toLocaleString()} bytes`)\n this.name = 'PayloadTooLargeError'\n }\n}\n\n// \u2500\u2500\u2500 Multipart \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** An uploaded file parsed from a `multipart/form-data` body. */\nexport interface MultipartFile {\n filename: string\n mimetype: string\n data: Buffer\n}\n\n/** Parsed result of `ctx.multipart()`. */\nexport interface MultipartResult {\n fields: Record<string, string>\n files: Record<string, MultipartFile>\n}\n\n// \u2500\u2500\u2500 Redirect \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Thrown by `redirect()`. Never construct directly. */\nexport class RedirectError extends Error {\n readonly isRedirect = true\n constructor(\n public readonly url: string,\n public readonly status: 301 | 302 | 307 | 308 = 302,\n ) {\n super(`Redirect \u2192 ${url}`)\n this.name = 'RedirectError'\n }\n}\n\n/**\n * Throw a redirect to the given URL.\n *\n * **Security note:** If `url` comes from user input (e.g. `ctx.query.get('next')`),\n * validate it before passing it here to prevent open-redirect attacks. At minimum,\n * ensure the value starts with `/` and does not contain `//` or a protocol.\n */\nexport function redirect(url: string, status: 301 | 302 | 307 | 308 = 302): never {\n throw new RedirectError(url, status)\n}\n\n// \u2500\u2500\u2500 Head context \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Per-request `<head>` metadata. Set values in pages and layouts; the root\n * layout renders them. Pages render before their layouts, so all values are\n * populated by the time the root layout runs. */\nexport interface HeadContext {\n title?: string\n description?: string\n /** Arbitrary `<meta name=\"...\" content=\"...\">` entries. */\n meta: Record<string, string>\n /** CSS file URLs injected as `<link rel=\"stylesheet\">`. */\n stylesheets: string[]\n /** JS module URLs injected as `<script type=\"module\">`. */\n scripts: string[]\n}\n\n// \u2500\u2500\u2500 Route param type inference \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n// Recursively collect bracket-param names from a path pattern string.\ntype RouteParamNames<T extends string> = T extends `${string}[${infer Param}]${infer Rest}`\n ? Param | RouteParamNames<Rest>\n : never\n\n/**\n * Infer typed route params from a file path pattern (relative to your routes\n * directory, without the method suffix or extension).\n *\n * @example\n * // src/routes/blog/[slug].page.tsx\n * export default definePage<ExtractParams<'blog/[slug]'>>(async (ctx) => {\n * ctx.params.slug // string \u2713 \u2014 TypeScript error if you typo the key\n * })\n *\n * // src/routes/users/[id]/posts/[postId].get.ts\n * export default defineHandler<unknown, ExtractParams<'users/[id]/posts/[postId]'>>(async (ctx) => {\n * ctx.params.id // string \u2713\n * ctx.params.postId // string \u2713\n * })\n */\nexport type ExtractParams<T extends string> = { [K in RouteParamNames<T>]: string }\n\n// \u2500\u2500\u2500 Request context \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Open interface for middleware-to-handler pass-through data.\n * Ecosystem packages augment this via declaration merging to add typed properties:\n *\n * @example\n * declare module 'davaux' {\n * interface State {\n * session: Session\n * }\n * }\n */\nexport interface State extends Record<string, unknown> {\n /** Result returned by `defineAction` before the page re-renders on form POST. */\n actionResult?: unknown\n}\n\n/**\n * Context object passed to every page, API, layout, middleware, and error handler.\n * @typeParam P - Typed URL params. Use `ExtractParams<'path/[param]'>` for compile-time safety.\n */\nexport interface RequestContext<P extends Record<string, string> = Record<string, string>> {\n req: IncomingMessage\n res: ServerResponse\n params: P\n query: URLSearchParams\n url: URL\n /** Base path for subdirectory deployments (e.g. '/docs'). Empty string in dev/SSR. */\n basePath: string\n cookies: CookieJar\n /** Mutable head metadata \u2014 set in pages/layouts, rendered by the root layout. */\n head: HeadContext\n /** Pass-through bag for middleware \u2192 handler data. Augment via `State` interface merging. */\n state: State\n /** Parse the request body as JSON. Pass a validator (zod schema, etc.) to\n * validate and type the result; throws `ValidationError` (\u2192 400) on failure.\n * Default body size limit: 4 MB. Override with `options.maxBytes`. */\n json<T = Record<string, unknown>>(\n validator?: Validator<T>,\n options?: { maxBytes?: number },\n ): Promise<T>\n /** Parse the request body as URL-encoded form data. Pass a validator to\n * validate and type the result; throws `ValidationError` (\u2192 400) on failure.\n * Default body size limit: 1 MB. Override with `options.maxBytes`.\n * When a field name appears more than once, the last value wins \u2014 use\n * `ctx.formAll()` to collect all values for multi-select / checkbox groups. */\n form<T = Record<string, string>>(\n validator?: Validator<T>,\n options?: { maxBytes?: number },\n ): Promise<T>\n /** Like `ctx.form()`, but returns every value for every key as a `string[]`.\n * Use this for `<select multiple>` and repeated checkbox fields where\n * `ctx.form()` would silently drop all but the last value. */\n formAll<T = Record<string, string[]>>(\n validator?: Validator<T>,\n options?: { maxBytes?: number },\n ): Promise<T>\n /** Parse a multipart/form-data request body. Returns parsed text fields and\n * uploaded files. Default body size limit: 10 MB. Override with `options.maxBytes`. */\n multipart(options?: { maxBytes?: number }): Promise<MultipartResult>\n /** Write a raw response with a custom content-type. The response is immediately\n * sent; no further framework serialization occurs. */\n send(body: string | Buffer, contentType: string, status?: number): void\n /** Write a one-time message that survives a single redirect. Requires @davaux/session. */\n flash(key: string, value: string): void\n /** Read and consume a flash message set by a previous request. Returns undefined if absent. */\n flash(key: string): string | undefined\n /**\n * Register a named deferred HTML slot. Returns a `<?marker name=\"...\">` placeholder node\n * that renders inline; when the promise resolves the framework streams a\n * `<template for=\"name\">` element into the response to fill the slot.\n * Polyfill scripts are injected automatically for browsers without native support.\n */\n defer(name: string, content: Promise<OmlNode | string>): Promise<OmlNode>\n /** @internal Pending deferred slots registered via `ctx.defer()`. */\n _deferredSlots: Map<string, Promise<OmlNode | string>>\n}\n\ninterface FlashSession {\n get(key: string): unknown\n set(key: string, value: unknown): void\n delete(key: string): void\n}\n\nconst DEFAULT_FORM_LIMIT = 1_000_000 // 1 MB\nconst DEFAULT_JSON_LIMIT = 4_000_000 // 4 MB\nconst DEFAULT_MULTIPART_LIMIT = 10_000_000 // 10 MB\n\nfunction readRawBuffer(req: IncomingMessage, maxBytes: number): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = []\n let size = 0\n req.on('data', (chunk: Buffer) => {\n size += chunk.length\n if (size > maxBytes) {\n req.destroy()\n reject(new PayloadTooLargeError(maxBytes))\n return\n }\n chunks.push(chunk)\n })\n req.on('end', () => resolve(Buffer.concat(chunks)))\n req.on('error', reject)\n })\n}\n\nfunction splitBuffer(buf: Buffer, sep: Buffer): Buffer[] {\n const parts: Buffer[] = []\n let start = 0\n while (true) {\n const idx = buf.indexOf(sep, start)\n if (idx === -1) break\n parts.push(buf.subarray(start, idx))\n start = idx + sep.length\n }\n parts.push(buf.subarray(start))\n return parts\n}\n\nfunction extractMultipartParam(header: string, param: string): string | undefined {\n const rq = new RegExp(`(?:^|;)\\\\s*${param}=\"([^\"]*)\"`, 'i')\n const mq = header.match(rq)\n if (mq) return mq[1]\n const ru = new RegExp(`(?:^|;)\\\\s*${param}=([^;\\\\s]*)`, 'i')\n const mu = header.match(ru)\n return mu ? mu[1] : undefined\n}\n\nfunction parseMultipart(body: Buffer, boundary: string): MultipartResult {\n const fields: Record<string, string> = {}\n const files: Record<string, MultipartFile> = {}\n\n // Prepend \\r\\n so every boundary (including the first) matches `\\r\\n--boundary`\n const sep = Buffer.from(`\\r\\n--${boundary}`)\n const parts = splitBuffer(Buffer.concat([Buffer.from('\\r\\n'), body]), sep)\n\n for (const part of parts) {\n if (part.length < 2) continue\n const prefix = part.subarray(0, 2).toString('ascii')\n if (prefix === '--') break // final `--boundary--` terminator\n if (prefix !== '\\r\\n') continue // preamble before first boundary\n\n const content = part.subarray(2) // skip leading \\r\\n\n const headerEnd = content.indexOf('\\r\\n\\r\\n')\n if (headerEnd === -1) continue\n\n const headerStr = content.subarray(0, headerEnd).toString('utf-8')\n const partBody = content.subarray(headerEnd + 4)\n\n const headers: Record<string, string> = {}\n for (const line of headerStr.split('\\r\\n')) {\n const colon = line.indexOf(':')\n if (colon === -1) continue\n headers[line.slice(0, colon).trim().toLowerCase()] = line.slice(colon + 1).trim()\n }\n\n const disposition = headers['content-disposition'] ?? ''\n const name = extractMultipartParam(disposition, 'name')\n if (!name) continue\n\n const filename = extractMultipartParam(disposition, 'filename')\n if (filename !== undefined) {\n files[name] = {\n filename,\n mimetype: headers['content-type'] ?? 'application/octet-stream',\n data: partBody,\n }\n } else {\n fields[name] = partBody.toString('utf-8')\n }\n }\n\n return { fields, files }\n}\n\n/** @internal Construct a `RequestContext` for a given request. Used by the router and SSG. */\nexport function makeContext(\n req: IncomingMessage,\n res: ServerResponse,\n params: Record<string, string>,\n url: URL,\n basePath = '',\n): RequestContext {\n let bodyCache: Promise<Buffer> | undefined\n function rawBuffer(maxBytes: number): Promise<Buffer> {\n if (bodyCache === undefined) bodyCache = readRawBuffer(req, maxBytes)\n return bodyCache\n }\n\n return {\n req,\n res,\n params,\n query: url.searchParams,\n url,\n basePath,\n cookies: new CookieJar(req, res),\n head: { meta: {}, stylesheets: [], scripts: [] },\n state: {},\n async json<T = Record<string, unknown>>(\n validator?: Validator<T>,\n options?: { maxBytes?: number },\n ) {\n const raw: unknown = JSON.parse(\n (await rawBuffer(options?.maxBytes ?? DEFAULT_JSON_LIMIT)).toString('utf-8'),\n )\n if (!validator) return raw as T\n try {\n return validator.parse(raw)\n } catch (err) {\n throw new ValidationError(err)\n }\n },\n async form<T = Record<string, string>>(\n validator?: Validator<T>,\n options?: { maxBytes?: number },\n ) {\n const text = (await rawBuffer(options?.maxBytes ?? DEFAULT_FORM_LIMIT)).toString('utf-8')\n const out: Record<string, string> = {}\n for (const [k, v] of new URLSearchParams(text)) out[k] = v\n if (!validator) return out as unknown as T\n try {\n return validator.parse(out)\n } catch (err) {\n throw new ValidationError(err)\n }\n },\n async formAll<T = Record<string, string[]>>(\n validator?: Validator<T>,\n options?: { maxBytes?: number },\n ) {\n const text = (await rawBuffer(options?.maxBytes ?? DEFAULT_FORM_LIMIT)).toString('utf-8')\n const out: Record<string, string[]> = {}\n for (const [k, v] of new URLSearchParams(text)) {\n if (out[k] === undefined) out[k] = []\n out[k].push(v)\n }\n if (!validator) return out as unknown as T\n try {\n return validator.parse(out)\n } catch (err) {\n throw new ValidationError(err)\n }\n },\n async multipart(options?: { maxBytes?: number }): Promise<MultipartResult> {\n const ct = req.headers['content-type'] ?? ''\n const boundary = extractMultipartParam(ct, 'boundary')\n if (!boundary)\n throw new Error(\n '[davaux] ctx.multipart() requires a multipart/form-data request with a boundary',\n )\n const buf = await rawBuffer(options?.maxBytes ?? DEFAULT_MULTIPART_LIMIT)\n return parseMultipart(buf, boundary)\n },\n send(body: string | Buffer, contentType: string, status = 200): void {\n if (!res.headersSent) {\n res.writeHead(status, { 'Content-Type': contentType })\n res.end(body)\n }\n },\n flash(key: string, value?: string): string | undefined {\n const session = this.state.session as FlashSession | undefined\n if (!session)\n throw new Error(\n '[davaux] ctx.flash() requires @davaux/session \u2014 add sessionMiddleware to your middleware config',\n )\n const NS = '_flash'\n if (value !== undefined) {\n const bag = (session.get(NS) as Record<string, string> | undefined) ?? {}\n session.set(NS, { ...bag, [key]: value })\n return\n }\n const bag = (session.get(NS) as Record<string, string> | undefined) ?? {}\n const msg = bag[key]\n if (msg !== undefined) {\n const { [key as string]: _, ...rest } = bag\n Object.keys(rest).length > 0 ? session.set(NS, rest) : session.delete(NS)\n }\n return msg\n },\n _deferredSlots: new Map<string, Promise<OmlNode | string>>(),\n defer(name: string, content: Promise<OmlNode | string>): Promise<OmlNode> {\n if (this._deferredSlots.size === 0) {\n this.head.scripts.push('/_davaux/partial-updates.js')\n }\n this._deferredSlots.set(name, content)\n return Promise.resolve({ type: '#raw' as const, value: `<?marker name=\"${name}\">` })\n },\n }\n}\n\n// \u2500\u2500\u2500 Route file types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** The HTTP method (or `'page'` for full-page SSR routes) a route file handles. */\nexport type RouteType = 'page' | 'get' | 'post' | 'put' | 'patch' | 'delete' | 'head' | 'options'\n\n/** A discovered route file and its compiled URL metadata. */\nexport interface RouteFile {\n filePath: string\n urlPattern: string\n type: RouteType\n params: string[]\n}\n\n/** A discovered `_layout` file and the directory it covers. */\nexport interface LayoutFile {\n /** Absolute path to the _layout.tsx file */\n filePath: string\n /** Absolute path to the directory this layout covers */\n dirPath: string\n}\n\n/** A discovered `_middleware` file and the directory it covers. */\nexport interface MiddlewareFile {\n /** Absolute path to the _middleware.ts file */\n filePath: string\n /** Absolute path to the directory this middleware covers */\n dirPath: string\n}\n\n/** A discovered island component file and its derived hydration ID. */\nexport interface IslandFile {\n /** Absolute path to the island source file */\n filePath: string\n /** Island ID derived from filename \u2014 used as the key in the hydration manifest */\n id: string\n}\n\n/** The full result of scanning a routes directory. */\nexport interface ScanResult {\n routes: RouteFile[]\n layouts: LayoutFile[]\n middlewares: MiddlewareFile[]\n /** Absolute path to _error.tsx if one exists in the routes directory */\n errorPage?: string\n}\n\n// \u2500\u2500\u2500 Handler types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Function signature for a `.page.*` route handler \u2014 receives context and returns HTML or an OmlNode. */\nexport type PageHandler<P extends Record<string, string> = Record<string, string>> = (\n ctx: RequestContext<P>,\n) => string | OmlNode | Promise<string | OmlNode>\n\n/** Function signature for an API route handler \u2014 receives context and returns a JSON-serialisable value. */\nexport type ApiHandler<T = unknown, P extends Record<string, string> = Record<string, string>> = (\n ctx: RequestContext<P>,\n) => T | Promise<T>\n\n/** Function signature for a `_layout.*` handler \u2014 receives rendered children and returns HTML or an OmlNode. */\nexport type LayoutHandler = (props: LayoutProps) => string | OmlNode | Promise<string | OmlNode>\n\n/** Props passed to every layout handler. */\nexport interface LayoutProps {\n /** Already-rendered inner HTML. Use as a JSX child or template literal interpolation \u2014 it will not be escaped. */\n children: Promise<string>\n ctx: RequestContext\n}\n\n// \u2500\u2500\u2500 Error page \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Props passed to the `_error.tsx` error page handler. */\nexport interface ErrorPageProps {\n status: number\n message: string\n ctx: RequestContext\n}\n\n/** Function signature for the `_error.*` handler \u2014 receives status/message and returns HTML or an OmlNode. */\nexport type ErrorPageHandler = (\n props: ErrorPageProps,\n) => string | OmlNode | Promise<string | OmlNode>\n\n// \u2500\u2500\u2500 Developer ergonomics helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Wraps a page handler so TypeScript infers the correct return type.\n * Handlers may return an HTML string or an OmlNode \u2014 both are handled transparently.\n *\n * export default definePage(async (ctx) => <Layout>...</Layout>)\n */\nexport function definePage<P extends Record<string, string> = Record<string, string>>(\n fn: PageHandler<P>,\n): PageHandler<P> {\n return fn\n}\n\n/**\n * Wraps an API route handler.\n *\n * export default defineHandler(async (ctx) => ({ ok: true }))\n */\nexport function defineHandler<\n T = unknown,\n P extends Record<string, string> = Record<string, string>,\n>(fn: ApiHandler<T, P>): ApiHandler<T, P> {\n return fn\n}\n\n/**\n * Wraps a layout component.\n *\n * export default defineLayout(({ children, ctx }) => <html>...</html>)\n */\nexport function defineLayout(fn: LayoutHandler): LayoutHandler {\n return fn\n}\n\n/**\n * Wraps the `_error.tsx` handler for type inference.\n *\n * export default defineError(({ status, message, ctx }) => (\n * <div><h1>{status}</h1><p>{message}</p></div>\n * ))\n */\nexport function defineError(fn: ErrorPageHandler): ErrorPageHandler {\n return fn\n}\n\n/** A middleware function: runs code before and/or after the next handler in the chain. */\nexport type MiddlewareFn = (ctx: RequestContext, next: () => Promise<void>) => Promise<void>\n\n/**\n * Wraps a middleware function for type inference.\n *\n * export default defineMiddleware(async (ctx, next) => {\n * // run before the route handler\n * await next()\n * // run after\n * })\n */\nexport function defineMiddleware(fn: MiddlewareFn): MiddlewareFn {\n return fn\n}\n\n/**\n * Function signature for a form action handler.\n * The return value (if any) is set as `ctx.state.actionResult` before the\n * page re-renders. Throw `redirect()` to skip re-rendering entirely.\n */\nexport type ActionFn<T = unknown> = (ctx: RequestContext) => T | Promise<T>\n\n/**\n * Wraps a form action handler co-located with a page file.\n *\n * export const action = defineAction(async (ctx) => {\n * const { email, password } = await ctx.form()\n * const user = await db.authenticate(email, password)\n * if (!user) return { error: 'Invalid credentials' }\n * redirect('/dashboard')\n * })\n */\nexport function defineAction<T = unknown>(fn: ActionFn<T>): ActionFn<T> {\n return fn\n}\n\n// \u2500\u2500\u2500 Static generation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** A single set of params for a statically pre-rendered dynamic route. */\nexport type StaticPath = { params: Record<string, string> }\n\n/** Function signature for `getStaticPaths` \u2014 returns all param sets to render at build time. */\nexport type GetStaticPathsFn = () => StaticPath[] | Promise<StaticPath[]>\n\n/**\n * Wraps a `getStaticPaths` export for type inference.\n *\n * export const getStaticPaths = defineStaticPaths(async () => [\n * { params: { slug: 'hello-world' } },\n * ])\n */\nexport function defineStaticPaths(fn: GetStaticPathsFn): GetStaticPathsFn {\n return fn\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["bag"]
|
|
7
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "davaux",
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"description": "SSR-first JSX framework with file-based routing and signals",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"author": "David L Dyess II",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://codeberg.org/davaux/davaux"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://codeberg.org/davaux/davaux/issues"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://codeberg.org/davaux/davaux#readme",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"import": "./dist/index.js",
|
|
19
|
+
"types": "./dist/index.d.ts"
|
|
20
|
+
},
|
|
21
|
+
"./jsx-runtime": {
|
|
22
|
+
"import": "./dist/jsx-runtime.js",
|
|
23
|
+
"types": "./dist/jsx-runtime.d.ts"
|
|
24
|
+
},
|
|
25
|
+
"./jsx-dev-runtime": {
|
|
26
|
+
"import": "./dist/jsx-runtime.js",
|
|
27
|
+
"types": "./dist/jsx-runtime.d.ts"
|
|
28
|
+
},
|
|
29
|
+
"./signal": {
|
|
30
|
+
"import": "./dist/signal.js",
|
|
31
|
+
"types": "./dist/signal.d.ts"
|
|
32
|
+
},
|
|
33
|
+
"./island": {
|
|
34
|
+
"import": "./dist/island.js",
|
|
35
|
+
"types": "./dist/island.d.ts"
|
|
36
|
+
},
|
|
37
|
+
"./client": {
|
|
38
|
+
"import": "./dist/client/index.js",
|
|
39
|
+
"types": "./dist/client/index.d.ts"
|
|
40
|
+
},
|
|
41
|
+
"./client/jsx-runtime": {
|
|
42
|
+
"import": "./dist/client/jsx-runtime.js",
|
|
43
|
+
"types": "./dist/client/jsx-runtime.d.ts"
|
|
44
|
+
},
|
|
45
|
+
"./client/signal": {
|
|
46
|
+
"import": "./dist/client/signal.js",
|
|
47
|
+
"types": "./dist/client/signal.d.ts"
|
|
48
|
+
},
|
|
49
|
+
"./config": {
|
|
50
|
+
"import": "./dist/config.js",
|
|
51
|
+
"types": "./dist/config.d.ts"
|
|
52
|
+
},
|
|
53
|
+
"./scanner": {
|
|
54
|
+
"import": "./dist/router/scanner.js",
|
|
55
|
+
"types": "./dist/router/scanner.d.ts"
|
|
56
|
+
},
|
|
57
|
+
"./handler": {
|
|
58
|
+
"import": "./dist/router/handler.js",
|
|
59
|
+
"types": "./dist/router/handler.d.ts"
|
|
60
|
+
},
|
|
61
|
+
"./server": {
|
|
62
|
+
"import": "./dist/server/index.js",
|
|
63
|
+
"types": "./dist/server/index.d.ts"
|
|
64
|
+
},
|
|
65
|
+
"./build": {
|
|
66
|
+
"import": "./dist/build/index.js",
|
|
67
|
+
"types": "./dist/build/index.d.ts"
|
|
68
|
+
},
|
|
69
|
+
"./oml": {
|
|
70
|
+
"import": "./dist/oml/index.js",
|
|
71
|
+
"types": "./dist/oml/index.d.ts"
|
|
72
|
+
},
|
|
73
|
+
"./oml/jsx-runtime": {
|
|
74
|
+
"import": "./dist/oml/jsx-runtime.js",
|
|
75
|
+
"types": "./dist/oml/jsx-runtime.d.ts"
|
|
76
|
+
},
|
|
77
|
+
"./oml/jsx-dev-runtime": {
|
|
78
|
+
"import": "./dist/oml/jsx-runtime.js",
|
|
79
|
+
"types": "./dist/oml/jsx-runtime.d.ts"
|
|
80
|
+
},
|
|
81
|
+
"./env": {
|
|
82
|
+
"types": "./src/env.d.ts"
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"bin": {
|
|
86
|
+
"davaux": "./dist/cli.js"
|
|
87
|
+
},
|
|
88
|
+
"scripts": {
|
|
89
|
+
"build": "node build.mjs",
|
|
90
|
+
"build:types": "tsc --emitDeclarationOnly",
|
|
91
|
+
"typecheck": "tsc --noEmit",
|
|
92
|
+
"dev:framework": "node --watch dist/cli.js",
|
|
93
|
+
"test": "node --import tsx/esm --test 'src/test/**/*.test.ts'"
|
|
94
|
+
},
|
|
95
|
+
"dependencies": {
|
|
96
|
+
"esbuild": "^0.28.0"
|
|
97
|
+
},
|
|
98
|
+
"devDependencies": {
|
|
99
|
+
"@types/node": "^25.8.0",
|
|
100
|
+
"tsx": "^4.19.2",
|
|
101
|
+
"typescript": "^6.0.3"
|
|
102
|
+
}
|
|
103
|
+
}
|
package/pka.config.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"configVersion": 2,
|
|
3
|
+
"_pkaGenerated": true,
|
|
4
|
+
"mode": "claude-code",
|
|
5
|
+
"outputDir": "./.pka",
|
|
6
|
+
"maxFileSize": 1048576,
|
|
7
|
+
"noGit": false,
|
|
8
|
+
"noContext": false,
|
|
9
|
+
"noConcat": false,
|
|
10
|
+
"noFlatten": false,
|
|
11
|
+
"install": false,
|
|
12
|
+
"noGitignore": false,
|
|
13
|
+
"xml": false,
|
|
14
|
+
"agentsMd": null,
|
|
15
|
+
"copilot": null,
|
|
16
|
+
"cursorRules": null,
|
|
17
|
+
"hierarchical": false,
|
|
18
|
+
"scaffoldCommands": false,
|
|
19
|
+
"compact": false,
|
|
20
|
+
"compactTokens": 0,
|
|
21
|
+
"compactOmit": false,
|
|
22
|
+
"compactKeep": [],
|
|
23
|
+
"compactPreview": false,
|
|
24
|
+
"diff": false,
|
|
25
|
+
"force": false,
|
|
26
|
+
"watch": false,
|
|
27
|
+
"since": null,
|
|
28
|
+
"includeExt": [],
|
|
29
|
+
"excludeDir": [],
|
|
30
|
+
"description": "",
|
|
31
|
+
"instructions": ""
|
|
32
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { mkdir, rm } from 'node:fs/promises'
|
|
3
|
+
import { join, resolve } from 'node:path'
|
|
4
|
+
import type { DavauxConfig } from '../config.js'
|
|
5
|
+
|
|
6
|
+
const CONFIG_FILES = ['davaux.config.ts', 'davaux.config.js', 'davaux.config.mjs']
|
|
7
|
+
|
|
8
|
+
// Compile and import the project's davaux.config.* file.
|
|
9
|
+
// Returns an empty object if no config file is found.
|
|
10
|
+
export async function loadConfig(cwd: string): Promise<DavauxConfig> {
|
|
11
|
+
for (const name of CONFIG_FILES) {
|
|
12
|
+
const configPath = resolve(cwd, name)
|
|
13
|
+
if (!existsSync(configPath)) continue
|
|
14
|
+
|
|
15
|
+
// Write to node_modules/.cache/davaux/ so Node.js resolves externals
|
|
16
|
+
// (esbuild, davaux, etc.) via the project's own node_modules tree.
|
|
17
|
+
const cacheDir = resolve(cwd, 'node_modules', '.cache', 'davaux')
|
|
18
|
+
await mkdir(cacheDir, { recursive: true })
|
|
19
|
+
const outfile = join(cacheDir, `config-${Date.now()}.mjs`)
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const { build } = await import('esbuild')
|
|
23
|
+
await build({
|
|
24
|
+
entryPoints: [configPath],
|
|
25
|
+
outfile,
|
|
26
|
+
format: 'esm',
|
|
27
|
+
platform: 'node',
|
|
28
|
+
target: 'node22',
|
|
29
|
+
bundle: true,
|
|
30
|
+
// Keep node built-ins and framework packages as external imports;
|
|
31
|
+
// everything else (user plugin deps) is bundled in.
|
|
32
|
+
external: ['node:*', 'esbuild', 'davaux', 'davaux/*'],
|
|
33
|
+
logLevel: 'silent',
|
|
34
|
+
})
|
|
35
|
+
const mod = await import(outfile)
|
|
36
|
+
return (mod.default ?? {}) as DavauxConfig
|
|
37
|
+
} finally {
|
|
38
|
+
await rm(outfile, { force: true }).catch(() => {})
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return {}
|
|
42
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises'
|
|
2
|
+
import { basename, dirname, join } from 'node:path'
|
|
3
|
+
import { type Plugin, transform } from 'esbuild'
|
|
4
|
+
import type { IslandFile } from '../types.js'
|
|
5
|
+
|
|
6
|
+
// ─── Island server plugin ─────────────────────────────────────────────────────
|
|
7
|
+
//
|
|
8
|
+
// When the routes bundle imports a file from the islands directory, this plugin
|
|
9
|
+
// intercepts at load time and returns a thin server wrapper instead of the raw
|
|
10
|
+
// source. The wrapper re-exports the component wrapped with island() so the
|
|
11
|
+
// route gets back a function that renders SSR HTML + serialized props.
|
|
12
|
+
//
|
|
13
|
+
// The actual island source is loaded under the 'island-raw' namespace, which:
|
|
14
|
+
// 1. Strips any @jsxImportSource pragma (server context sets its own)
|
|
15
|
+
// 2. Allows the route context's jsxImportSource:'davaux' + alias to apply
|
|
16
|
+
|
|
17
|
+
export function islandServerPlugin(islandsDirs: string | string[]): Plugin {
|
|
18
|
+
const dirs = Array.isArray(islandsDirs) ? islandsDirs : [islandsDirs]
|
|
19
|
+
return {
|
|
20
|
+
name: 'davaux-island-server',
|
|
21
|
+
setup(build) {
|
|
22
|
+
// Intercept file-namespace loads of island source files
|
|
23
|
+
build.onLoad({ filter: /\.(tsx?|jsx?)$/ }, async (args) => {
|
|
24
|
+
if (args.namespace !== 'file') return
|
|
25
|
+
if (!dirs.some((d) => args.path.startsWith(d))) return
|
|
26
|
+
|
|
27
|
+
const id = basename(args.path).replace(/\.(tsx?|jsx?)$/, '')
|
|
28
|
+
return {
|
|
29
|
+
contents: [
|
|
30
|
+
`import Component from ${JSON.stringify(`${args.path}?island-raw`)};`,
|
|
31
|
+
`import { island } from 'davaux/island';`,
|
|
32
|
+
`export default island(Component, ${JSON.stringify(id)});`,
|
|
33
|
+
].join('\n'),
|
|
34
|
+
loader: 'js',
|
|
35
|
+
resolveDir: dirname(args.path),
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
// Resolve the ?island-raw marker → island-raw namespace (bypasses the wrapper)
|
|
40
|
+
build.onResolve({ filter: /\?island-raw$/ }, (args) => ({
|
|
41
|
+
path: args.path.replace(/\?island-raw$/, ''),
|
|
42
|
+
namespace: 'island-raw',
|
|
43
|
+
}))
|
|
44
|
+
|
|
45
|
+
// Load the raw island source, stripping any file-level @jsxImportSource pragma
|
|
46
|
+
// and replacing it with the standard 'davaux' runtime so the island renders to
|
|
47
|
+
// HTML strings on the server (OML is only needed at the route/page level).
|
|
48
|
+
build.onLoad({ filter: /.*/, namespace: 'island-raw' }, async (args) => {
|
|
49
|
+
let source = await readFile(args.path, 'utf8')
|
|
50
|
+
source = source.replace(/\/\*\*?\s*@jsxImportSource\s+\S+\s*\*\//g, '')
|
|
51
|
+
source = `/** @jsxImportSource davaux */\n${source}`
|
|
52
|
+
const ext = (args.path.match(/\.(tsx?|jsx?)$/)?.[1] ?? 'ts') as 'tsx' | 'ts' | 'jsx' | 'js'
|
|
53
|
+
return { contents: source, loader: ext, resolveDir: dirname(args.path) }
|
|
54
|
+
})
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ─── CSS collector ────────────────────────────────────────────────────────────
|
|
60
|
+
//
|
|
61
|
+
// Scans a directory recursively for all *.css files produced by esbuild
|
|
62
|
+
// (side-effect CSS imports in routes and islands) and concatenates them into a
|
|
63
|
+
// single stylesheet. Returns true if CSS was found and written, false otherwise.
|
|
64
|
+
|
|
65
|
+
export async function collectCss(
|
|
66
|
+
scanDir: string,
|
|
67
|
+
stylesOutFile: string,
|
|
68
|
+
minify = false,
|
|
69
|
+
): Promise<boolean> {
|
|
70
|
+
const cssFiles: string[] = []
|
|
71
|
+
async function walk(dir: string): Promise<void> {
|
|
72
|
+
const entries = await readdir(dir, { withFileTypes: true }).catch(() => [])
|
|
73
|
+
for (const e of entries) {
|
|
74
|
+
const full = join(dir, e.name)
|
|
75
|
+
if (e.isDirectory()) await walk(full)
|
|
76
|
+
else if (e.name.endsWith('.css') && full !== stylesOutFile) cssFiles.push(full)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
await walk(scanDir)
|
|
80
|
+
if (cssFiles.length === 0) return false
|
|
81
|
+
let css = (await Promise.all(cssFiles.map((f) => readFile(f, 'utf8')))).join('\n')
|
|
82
|
+
if (minify) {
|
|
83
|
+
const result = await transform(css, { loader: 'css', minify: true })
|
|
84
|
+
css = result.code
|
|
85
|
+
}
|
|
86
|
+
await mkdir(dirname(stylesOutFile), { recursive: true })
|
|
87
|
+
await writeFile(stylesOutFile, css)
|
|
88
|
+
return true
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ─── CSS collector plugin ─────────────────────────────────────────────────────
|
|
92
|
+
//
|
|
93
|
+
// Wraps collectCss as an esbuild onEnd plugin. Added to both the routes and
|
|
94
|
+
// islands contexts so either rebuild updates the combined stylesheet.
|
|
95
|
+
|
|
96
|
+
export function cssCollectorPlugin(dauxDir: string, stylesOutFile: string): Plugin {
|
|
97
|
+
return {
|
|
98
|
+
name: 'davaux-css-collector',
|
|
99
|
+
setup(build) {
|
|
100
|
+
build.onEnd(() => void collectCss(dauxDir, stylesOutFile))
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ─── Island client bundle entry ───────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
export function generateIslandsEntry(islands: IslandFile[]): string {
|
|
108
|
+
return [
|
|
109
|
+
`import { hydrate } from 'davaux/client'`,
|
|
110
|
+
...islands.map((isle, i) => `import __island_${i} from ${JSON.stringify(isle.filePath)}`),
|
|
111
|
+
'',
|
|
112
|
+
'hydrate({',
|
|
113
|
+
...islands.map(
|
|
114
|
+
(isle, i) => ` ${JSON.stringify(isle.id)}: async () => ({ default: __island_${i} }),`,
|
|
115
|
+
),
|
|
116
|
+
'})',
|
|
117
|
+
].join('\n')
|
|
118
|
+
}
|