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,64 @@
|
|
|
1
|
+
function isObject(v) {
|
|
2
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3
|
+
}
|
|
4
|
+
function parseNode(v) {
|
|
5
|
+
if (v === null) return null;
|
|
6
|
+
if (!isObject(v)) throw new Error(`OML: expected node object, got ${typeof v}`);
|
|
7
|
+
const { type } = v;
|
|
8
|
+
if (type === "#text") {
|
|
9
|
+
if (typeof v.value !== "string") throw new Error("OML: #text node missing string value");
|
|
10
|
+
return { type: "#text", value: v.value };
|
|
11
|
+
}
|
|
12
|
+
if (type === "#fragment") {
|
|
13
|
+
return { type: "#fragment", children: parseChildren(v.children) };
|
|
14
|
+
}
|
|
15
|
+
if (type === "#component") {
|
|
16
|
+
if (typeof v.name !== "string") throw new Error("OML: #component node missing name");
|
|
17
|
+
const parsed = {
|
|
18
|
+
type: "#component",
|
|
19
|
+
name: v.name,
|
|
20
|
+
id: typeof v.id === "string" ? v.id : void 0,
|
|
21
|
+
props: isObject(v.props) ? v.props : {},
|
|
22
|
+
output: parseNode(v.output)
|
|
23
|
+
};
|
|
24
|
+
if (Array.isArray(v.children) && v.children.length > 0) {
|
|
25
|
+
parsed.children = parseChildren(v.children);
|
|
26
|
+
}
|
|
27
|
+
return parsed;
|
|
28
|
+
}
|
|
29
|
+
if (type === "element") {
|
|
30
|
+
if (typeof v.tag !== "string") throw new Error("OML: element node missing tag");
|
|
31
|
+
return {
|
|
32
|
+
type: "element",
|
|
33
|
+
tag: v.tag,
|
|
34
|
+
id: typeof v.id === "string" ? v.id : void 0,
|
|
35
|
+
props: isObject(v.props) ? v.props : {},
|
|
36
|
+
children: parseChildren(v.children)
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
throw new Error(`OML: unknown node type "${type}"`);
|
|
40
|
+
}
|
|
41
|
+
function parseChildren(v) {
|
|
42
|
+
if (!Array.isArray(v)) return [];
|
|
43
|
+
return v.map(parseNode);
|
|
44
|
+
}
|
|
45
|
+
function parseOml(json) {
|
|
46
|
+
return parseNode(json);
|
|
47
|
+
}
|
|
48
|
+
function parseOmlBlueprint(json) {
|
|
49
|
+
if (!isObject(json)) throw new Error("OML: blueprint must be an object");
|
|
50
|
+
if (typeof json.id !== "string") throw new Error("OML: blueprint missing id");
|
|
51
|
+
if (typeof json.name !== "string") throw new Error("OML: blueprint missing name");
|
|
52
|
+
return {
|
|
53
|
+
id: json.id,
|
|
54
|
+
name: json.name,
|
|
55
|
+
props: isObject(json.props) ? json.props : {},
|
|
56
|
+
output: parseNode(json.output),
|
|
57
|
+
imports: isObject(json.imports) ? json.imports : void 0
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export {
|
|
61
|
+
parseOml,
|
|
62
|
+
parseOmlBlueprint
|
|
63
|
+
};
|
|
64
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/oml/types.ts"],
|
|
4
|
+
"sourcesContent": ["// OML \u2014 Object Markup Language\n// A stable, serializable intermediate representation of a rendered component tree.\n// Produced by the OML JSX runtime; consumed by the OML renderer and visual editor.\n\nexport type OmlProps = Record<string, unknown>\n\n/** A plain text node. */\nexport type OmlText = {\n type: '#text'\n value: string\n}\n\n/** A raw HTML passthrough node \u2014 not escaped; used for island server stubs and similar. */\nexport type OmlRaw = {\n type: '#raw'\n value: string\n}\n\n/** A rendered HTML element. */\nexport type OmlElement = {\n type: 'element'\n /** The HTML tag name (div, span, button, etc.). */\n tag: string\n /** Stable identity from the JSX `key` prop \u2014 used for diffing and caching. */\n id?: string\n props: OmlProps\n children: OmlNode[]\n}\n\n/**\n * A rendered component call \u2014 preserves component boundaries for the visual\n * editor and inspector. The `return` field is what the component rendered.\n */\nexport type OmlComponent = {\n type: '#component'\n /** Component function name \u2014 shown in the inspector. */\n name: string\n id?: string\n /** Props passed to the component (children excluded). */\n props: OmlProps\n /**\n * Resolved JSX children passed to the component \u2014 stored separately from `output`\n * so the visual editor can show the JSX hierarchy even when `output` is #raw\n * (i.e. when a library component renders to an HTML string).\n */\n children?: OmlNode[]\n /** The component's rendered output. */\n output: OmlNode\n}\n\n/** A JSX Fragment \u2014 a grouping with no wrapper element. */\nexport type OmlFragment = {\n type: '#fragment'\n children: OmlNode[]\n}\n\nexport type OmlNode = OmlElement | OmlComponent | OmlFragment | OmlText | OmlRaw | null\n\n// \u2500\u2500\u2500 Blueprint \u2014 JSON component definition \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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// Stored in .oml.json files; naturally maps to SurrealDB records.\n\nexport type OmlPropType = 'string' | 'number' | 'boolean' | 'function' | 'node' | 'array'\n\nexport type OmlPropSchema = {\n type: OmlPropType\n default?: unknown\n required?: boolean\n description?: string\n}\n\n/**\n * A component definition stored in a Blueprint JSON file.\n * The `return` field is the root OmlNode of the component's render tree.\n * Wires (signal connections) between nodes are stored as id references.\n * `imports` maps component names used in the tree to their module paths,\n * enabling round-trip export back to .tsx without losing import information.\n */\nexport type OmlBlueprint = {\n id: string\n name: string\n props: Record<string, OmlPropSchema>\n output: OmlNode\n imports?: Record<string, string>\n}\n\n// \u2500\u2500\u2500 JSON parse / validate \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nfunction isObject(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v)\n}\n\nfunction parseNode(v: unknown): OmlNode {\n if (v === null) return null\n if (!isObject(v)) throw new Error(`OML: expected node object, got ${typeof v}`)\n\n const { type } = v\n if (type === '#text') {\n if (typeof v.value !== 'string') throw new Error('OML: #text node missing string value')\n return { type: '#text', value: v.value }\n }\n if (type === '#fragment') {\n return { type: '#fragment', children: parseChildren(v.children) }\n }\n if (type === '#component') {\n if (typeof v.name !== 'string') throw new Error('OML: #component node missing name')\n const parsed: OmlComponent = {\n type: '#component',\n name: v.name,\n id: typeof v.id === 'string' ? v.id : undefined,\n props: isObject(v.props) ? (v.props as OmlProps) : {},\n output: parseNode(v.output),\n }\n if (Array.isArray(v.children) && v.children.length > 0) {\n parsed.children = parseChildren(v.children)\n }\n return parsed\n }\n if (type === 'element') {\n if (typeof v.tag !== 'string') throw new Error('OML: element node missing tag')\n return {\n type: 'element',\n tag: v.tag,\n id: typeof v.id === 'string' ? v.id : undefined,\n props: isObject(v.props) ? (v.props as OmlProps) : {},\n children: parseChildren(v.children),\n }\n }\n throw new Error(`OML: unknown node type \"${type}\"`)\n}\n\nfunction parseChildren(v: unknown): OmlNode[] {\n if (!Array.isArray(v)) return []\n return v.map(parseNode)\n}\n\n/**\n * Parse and validate an `OmlNode` from untrusted JSON (e.g. a Blueprint file).\n * Throws a descriptive error if the shape is invalid.\n */\nexport function parseOml(json: unknown): OmlNode {\n return parseNode(json)\n}\n\n/**\n * Parse and validate an `OmlBlueprint` from untrusted JSON.\n * Throws a descriptive error if the shape is invalid.\n */\nexport function parseOmlBlueprint(json: unknown): OmlBlueprint {\n if (!isObject(json)) throw new Error('OML: blueprint must be an object')\n if (typeof json.id !== 'string') throw new Error('OML: blueprint missing id')\n if (typeof json.name !== 'string') throw new Error('OML: blueprint missing name')\n return {\n id: json.id,\n name: json.name,\n props: isObject(json.props) ? (json.props as Record<string, OmlPropSchema>) : {},\n output: parseNode(json.output),\n imports: isObject(json.imports) ? (json.imports as Record<string, string>) : undefined,\n }\n}\n"],
|
|
5
|
+
"mappings": "AAuFA,SAAS,SAAS,GAA0C;AAC1D,SAAO,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,MAAM,QAAQ,CAAC;AAChE;AAEA,SAAS,UAAU,GAAqB;AACtC,MAAI,MAAM,KAAM,QAAO;AACvB,MAAI,CAAC,SAAS,CAAC,EAAG,OAAM,IAAI,MAAM,kCAAkC,OAAO,CAAC,EAAE;AAE9E,QAAM,EAAE,KAAK,IAAI;AACjB,MAAI,SAAS,SAAS;AACpB,QAAI,OAAO,EAAE,UAAU,SAAU,OAAM,IAAI,MAAM,sCAAsC;AACvF,WAAO,EAAE,MAAM,SAAS,OAAO,EAAE,MAAM;AAAA,EACzC;AACA,MAAI,SAAS,aAAa;AACxB,WAAO,EAAE,MAAM,aAAa,UAAU,cAAc,EAAE,QAAQ,EAAE;AAAA,EAClE;AACA,MAAI,SAAS,cAAc;AACzB,QAAI,OAAO,EAAE,SAAS,SAAU,OAAM,IAAI,MAAM,mCAAmC;AACnF,UAAM,SAAuB;AAAA,MAC3B,MAAM;AAAA,MACN,MAAM,EAAE;AAAA,MACR,IAAI,OAAO,EAAE,OAAO,WAAW,EAAE,KAAK;AAAA,MACtC,OAAO,SAAS,EAAE,KAAK,IAAK,EAAE,QAAqB,CAAC;AAAA,MACpD,QAAQ,UAAU,EAAE,MAAM;AAAA,IAC5B;AACA,QAAI,MAAM,QAAQ,EAAE,QAAQ,KAAK,EAAE,SAAS,SAAS,GAAG;AACtD,aAAO,WAAW,cAAc,EAAE,QAAQ;AAAA,IAC5C;AACA,WAAO;AAAA,EACT;AACA,MAAI,SAAS,WAAW;AACtB,QAAI,OAAO,EAAE,QAAQ,SAAU,OAAM,IAAI,MAAM,+BAA+B;AAC9E,WAAO;AAAA,MACL,MAAM;AAAA,MACN,KAAK,EAAE;AAAA,MACP,IAAI,OAAO,EAAE,OAAO,WAAW,EAAE,KAAK;AAAA,MACtC,OAAO,SAAS,EAAE,KAAK,IAAK,EAAE,QAAqB,CAAC;AAAA,MACpD,UAAU,cAAc,EAAE,QAAQ;AAAA,IACpC;AAAA,EACF;AACA,QAAM,IAAI,MAAM,2BAA2B,IAAI,GAAG;AACpD;AAEA,SAAS,cAAc,GAAuB;AAC5C,MAAI,CAAC,MAAM,QAAQ,CAAC,EAAG,QAAO,CAAC;AAC/B,SAAO,EAAE,IAAI,SAAS;AACxB;AAMO,SAAS,SAAS,MAAwB;AAC/C,SAAO,UAAU,IAAI;AACvB;AAMO,SAAS,kBAAkB,MAA6B;AAC7D,MAAI,CAAC,SAAS,IAAI,EAAG,OAAM,IAAI,MAAM,kCAAkC;AACvE,MAAI,OAAO,KAAK,OAAO,SAAU,OAAM,IAAI,MAAM,2BAA2B;AAC5E,MAAI,OAAO,KAAK,SAAS,SAAU,OAAM,IAAI,MAAM,6BAA6B;AAChF,SAAO;AAAA,IACL,IAAI,KAAK;AAAA,IACT,MAAM,KAAK;AAAA,IACX,OAAO,SAAS,KAAK,KAAK,IAAK,KAAK,QAA0C,CAAC;AAAA,IAC/E,QAAQ,UAAU,KAAK,MAAM;AAAA,IAC7B,SAAS,SAAS,KAAK,OAAO,IAAK,KAAK,UAAqC;AAAA,EAC/E;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import type { OmlCacheConfig } from '../config.js';
|
|
3
|
+
import type { OmlNode } from '../oml/types.js';
|
|
4
|
+
import type { LayoutFile, MiddlewareFile, RouteFile, ScanResult } from '../types.js';
|
|
5
|
+
import { type MatchFn } from './matcher.js';
|
|
6
|
+
/** A route file paired with its compiled URL matcher, ready for dispatch. */
|
|
7
|
+
export interface CompiledRoute {
|
|
8
|
+
route: RouteFile;
|
|
9
|
+
match: MatchFn;
|
|
10
|
+
}
|
|
11
|
+
/** The compiled representation of a Davaux application, ready to dispatch requests. */
|
|
12
|
+
export interface CompiledApp {
|
|
13
|
+
routes: CompiledRoute[];
|
|
14
|
+
layouts: LayoutFile[];
|
|
15
|
+
middlewares: MiddlewareFile[];
|
|
16
|
+
/** Compiled path to `src/middleware.ts` — runs on every request before route matching. */
|
|
17
|
+
appMiddlewarePath?: string;
|
|
18
|
+
isDev: boolean;
|
|
19
|
+
/** Whether the visual editor and inspector overlay are active. Requires `editor.enabled: true` in config. */
|
|
20
|
+
editorEnabled: boolean;
|
|
21
|
+
clientScripts: string[];
|
|
22
|
+
clientStylesheets: string[];
|
|
23
|
+
errorPage?: string;
|
|
24
|
+
basePath: string;
|
|
25
|
+
/**
|
|
26
|
+
* In-process OML cache. Keyed by filePath::pathname+search. Only populated in production
|
|
27
|
+
* for routes matched by `omlCacheConfig`. Stores the OmlNode and pre-rendered HTML string
|
|
28
|
+
* so cache hits require zero renderToHtml traversal.
|
|
29
|
+
*/
|
|
30
|
+
omlCache: Map<string, {
|
|
31
|
+
node: OmlNode;
|
|
32
|
+
html: string;
|
|
33
|
+
}>;
|
|
34
|
+
/** In dev mode, stores the last rendered OmlNode per URL for the inspector. Not populated in production. */
|
|
35
|
+
devOmlStore?: Map<string, OmlNode>;
|
|
36
|
+
/**
|
|
37
|
+
* OML production cache configuration from `davaux.config.ts` `oml.cache`.
|
|
38
|
+
* When absent, no routes are cached in production.
|
|
39
|
+
*/
|
|
40
|
+
omlCacheConfig?: OmlCacheConfig;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Compile a scan result into a `CompiledApp` ready to dispatch requests.
|
|
44
|
+
* Typically called once at startup (or on each rebuild in dev mode).
|
|
45
|
+
*/
|
|
46
|
+
export declare function buildApp(result: ScanResult, isDev?: boolean, clientScripts?: string[], clientStylesheets?: string[], appMiddlewarePath?: string, basePath?: string, editorEnabled?: boolean, omlCacheConfig?: OmlCacheConfig): CompiledApp;
|
|
47
|
+
/**
|
|
48
|
+
* Dispatch a single HTTP request through the compiled app.
|
|
49
|
+
* Runs app middleware → route matching → scoped middleware → route handler.
|
|
50
|
+
* Handles redirects, validation errors, payload-too-large, and 404s internally.
|
|
51
|
+
*/
|
|
52
|
+
export declare function dispatch(req: IncomingMessage, res: ServerResponse, app: CompiledApp): Promise<void>;
|
|
53
|
+
//# sourceMappingURL=handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/router/handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAEhE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAElD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAC9C,OAAO,KAAK,EAIV,UAAU,EAEV,cAAc,EAGd,SAAS,EACT,UAAU,EACX,MAAM,aAAa,CAAA;AAOpB,OAAO,EAAiB,KAAK,OAAO,EAAE,MAAM,cAAc,CAAA;AAY1D,6EAA6E;AAC7E,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,SAAS,CAAA;IAChB,KAAK,EAAE,OAAO,CAAA;CACf;AAED,uFAAuF;AACvF,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,aAAa,EAAE,CAAA;IACvB,OAAO,EAAE,UAAU,EAAE,CAAA;IACrB,WAAW,EAAE,cAAc,EAAE,CAAA;IAC7B,0FAA0F;IAC1F,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,KAAK,EAAE,OAAO,CAAA;IACd,6GAA6G;IAC7G,aAAa,EAAE,OAAO,CAAA;IACtB,aAAa,EAAE,MAAM,EAAE,CAAA;IACvB,iBAAiB,EAAE,MAAM,EAAE,CAAA;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB;;;;OAIG;IACH,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACtD,4GAA4G;IAC5G,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAClC;;;OAGG;IACH,cAAc,CAAC,EAAE,cAAc,CAAA;CAChC;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CACtB,MAAM,EAAE,UAAU,EAClB,KAAK,UAAQ,EACb,aAAa,GAAE,MAAM,EAAO,EAC5B,iBAAiB,GAAE,MAAM,EAAO,EAChC,iBAAiB,CAAC,EAAE,MAAM,EAC1B,QAAQ,SAAK,EACb,aAAa,UAAQ,EACrB,cAAc,CAAC,EAAE,cAAc,GAC9B,WAAW,CAmBb;AAwOD;;;;GAIG;AACH,wBAAsB,QAAQ,CAC5B,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,WAAW,GACf,OAAO,CAAC,IAAI,CAAC,CAsJf"}
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { dirname } from "node:path";
|
|
2
|
+
import { renderToHtml, renderToHtmlDev } from "../oml/render.js";
|
|
3
|
+
import {
|
|
4
|
+
makeContext
|
|
5
|
+
} from "../types.js";
|
|
6
|
+
import { createMatcher } from "./matcher.js";
|
|
7
|
+
const METHOD_TYPES = {
|
|
8
|
+
GET: ["page", "get"],
|
|
9
|
+
POST: ["post", "page"],
|
|
10
|
+
// 'page' to support defineAction exports
|
|
11
|
+
PUT: ["put"],
|
|
12
|
+
PATCH: ["patch"],
|
|
13
|
+
DELETE: ["delete"],
|
|
14
|
+
HEAD: ["head", "page", "get"],
|
|
15
|
+
OPTIONS: ["options"]
|
|
16
|
+
};
|
|
17
|
+
function buildApp(result, isDev = false, clientScripts = [], clientStylesheets = [], appMiddlewarePath, basePath = "", editorEnabled = false, omlCacheConfig) {
|
|
18
|
+
return {
|
|
19
|
+
routes: result.routes.map((route) => ({
|
|
20
|
+
route,
|
|
21
|
+
match: createMatcher(route.urlPattern)
|
|
22
|
+
})),
|
|
23
|
+
layouts: result.layouts,
|
|
24
|
+
middlewares: result.middlewares,
|
|
25
|
+
appMiddlewarePath,
|
|
26
|
+
isDev,
|
|
27
|
+
editorEnabled,
|
|
28
|
+
clientScripts,
|
|
29
|
+
clientStylesheets,
|
|
30
|
+
errorPage: result.errorPage,
|
|
31
|
+
basePath,
|
|
32
|
+
omlCache: /* @__PURE__ */ new Map(),
|
|
33
|
+
devOmlStore: isDev && editorEnabled ? /* @__PURE__ */ new Map() : void 0,
|
|
34
|
+
omlCacheConfig
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function matchesCachePattern(pathname, pattern) {
|
|
38
|
+
const regexStr = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/:[^/]+/g, "[^/]+").replace(/\*/g, ".*");
|
|
39
|
+
return new RegExp(`^${regexStr}$`).test(pathname);
|
|
40
|
+
}
|
|
41
|
+
function isOmlCacheable(pathname, config) {
|
|
42
|
+
if (!config) return false;
|
|
43
|
+
if ("include" in config) return config.include.some((p) => matchesCachePattern(pathname, p));
|
|
44
|
+
return !config.exclude.some((p) => matchesCachePattern(pathname, p));
|
|
45
|
+
}
|
|
46
|
+
function isOmlNode(value) {
|
|
47
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
48
|
+
const t = value.type;
|
|
49
|
+
return t === "element" || t === "#component" || t === "#fragment" || t === "#text" || t === "#raw";
|
|
50
|
+
}
|
|
51
|
+
async function importModule(filePath, isDev) {
|
|
52
|
+
const url = isDev ? `${filePath}?t=${Date.now()}` : filePath;
|
|
53
|
+
try {
|
|
54
|
+
return await import(url);
|
|
55
|
+
} catch (err) {
|
|
56
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
57
|
+
throw new Error(`[davaux] Failed to load module: ${filePath}
|
|
58
|
+
${detail}`, { cause: err });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function findLayouts(routeFilePath, layouts) {
|
|
62
|
+
const routeDir = dirname(routeFilePath);
|
|
63
|
+
return layouts.filter((l) => routeDir === l.dirPath || routeDir.startsWith(`${l.dirPath}/`)).sort((a, b) => b.dirPath.length - a.dirPath.length);
|
|
64
|
+
}
|
|
65
|
+
function findMiddlewares(routeFilePath, middlewares) {
|
|
66
|
+
const routeDir = dirname(routeFilePath);
|
|
67
|
+
return middlewares.filter((m) => routeDir === m.dirPath || routeDir.startsWith(`${m.dirPath}/`)).sort((a, b) => a.dirPath.length - b.dirPath.length);
|
|
68
|
+
}
|
|
69
|
+
async function runWithMiddleware(middlewares, ctx, isDev, handler) {
|
|
70
|
+
const step = async (i) => {
|
|
71
|
+
if (i < middlewares.length) {
|
|
72
|
+
const mod = await importModule(middlewares[i].filePath, isDev);
|
|
73
|
+
const fn = mod.default;
|
|
74
|
+
await fn(ctx, () => step(i + 1));
|
|
75
|
+
} else {
|
|
76
|
+
await handler();
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
await step(0);
|
|
80
|
+
}
|
|
81
|
+
function sendPage(html, res) {
|
|
82
|
+
if (!res.headersSent) {
|
|
83
|
+
const trimmed = html.trimStart();
|
|
84
|
+
const full = trimmed.startsWith("<") && !trimmed.startsWith("<!DOCTYPE") ? `<!DOCTYPE html>${html}` : html;
|
|
85
|
+
res.writeHead(res.statusCode, {
|
|
86
|
+
"Content-Type": "text/html; charset=utf-8"
|
|
87
|
+
});
|
|
88
|
+
res.end(full);
|
|
89
|
+
} else if (!res.writableEnded) {
|
|
90
|
+
res.end(html);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async function sendPageWithDeferred(html, ctx, res) {
|
|
94
|
+
if (ctx._deferredSlots.size === 0) {
|
|
95
|
+
sendPage(html, res);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (res.headersSent) return;
|
|
99
|
+
const trimmed = html.trimStart();
|
|
100
|
+
const shell = trimmed.startsWith("<") && !trimmed.startsWith("<!DOCTYPE") ? `<!DOCTYPE html>${html}` : html;
|
|
101
|
+
res.writeHead(res.statusCode, { "Content-Type": "text/html; charset=utf-8" });
|
|
102
|
+
res.write(shell);
|
|
103
|
+
for (const [name, contentPromise] of ctx._deferredSlots) {
|
|
104
|
+
const content = await contentPromise;
|
|
105
|
+
const slotHtml = isOmlNode(content) ? renderToHtml(content) : content;
|
|
106
|
+
res.write(`<template for="${name}">${slotHtml}</template>`);
|
|
107
|
+
}
|
|
108
|
+
res.end();
|
|
109
|
+
}
|
|
110
|
+
async function renderPage(route, ctx, layouts, isDev, omlCache, opts) {
|
|
111
|
+
const { preloadedMod, devOmlStore, omlCacheConfig } = opts ?? {};
|
|
112
|
+
const mod = preloadedMod ?? await importModule(route.filePath, isDev);
|
|
113
|
+
const handler = mod.default;
|
|
114
|
+
const cacheKey = !isDev && isOmlCacheable(ctx.url.pathname, omlCacheConfig) ? `${route.filePath}::${ctx.url.pathname}${ctx.url.search}` : null;
|
|
115
|
+
const cached = cacheKey ? omlCache.get(cacheKey) : void 0;
|
|
116
|
+
let html;
|
|
117
|
+
if (cached !== void 0) {
|
|
118
|
+
html = cached.html;
|
|
119
|
+
} else {
|
|
120
|
+
const result = await handler(ctx);
|
|
121
|
+
if (isOmlNode(result)) {
|
|
122
|
+
html = isDev && devOmlStore ? renderToHtmlDev(result) : renderToHtml(result);
|
|
123
|
+
if (cacheKey) omlCache.set(cacheKey, { node: result, html });
|
|
124
|
+
if (devOmlStore) {
|
|
125
|
+
const sp = new URLSearchParams(ctx.url.search);
|
|
126
|
+
sp.delete("_editor");
|
|
127
|
+
const search = sp.toString();
|
|
128
|
+
devOmlStore.set(ctx.url.pathname + (search ? `?${search}` : ""), result);
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
html = result;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const applicable = findLayouts(route.filePath, layouts);
|
|
135
|
+
for (const layout of applicable) {
|
|
136
|
+
const lmod = await importModule(layout.filePath, isDev);
|
|
137
|
+
const lhandler = lmod.default;
|
|
138
|
+
const lresult = await lhandler({
|
|
139
|
+
children: Object.assign(Promise.resolve(html), { toString: () => html }),
|
|
140
|
+
ctx
|
|
141
|
+
});
|
|
142
|
+
html = isOmlNode(lresult) ? renderToHtml(lresult) : lresult;
|
|
143
|
+
}
|
|
144
|
+
return html;
|
|
145
|
+
}
|
|
146
|
+
function initCtx(req, res, params, url, app) {
|
|
147
|
+
const ctx = makeContext(req, res, params, url, app.basePath);
|
|
148
|
+
ctx.head.stylesheets.push(...app.clientStylesheets);
|
|
149
|
+
ctx.head.scripts.push(...app.clientScripts);
|
|
150
|
+
if (app.isDev) {
|
|
151
|
+
ctx.head.scripts.push("/_davaux/livereload.js");
|
|
152
|
+
if (app.editorEnabled && !url.searchParams.has("_editor")) {
|
|
153
|
+
ctx.head.scripts.push("/_davaux/inspector.js");
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return ctx;
|
|
157
|
+
}
|
|
158
|
+
async function sendErrorPage(status, message, req, res, url, app, existingCtx) {
|
|
159
|
+
if (res.headersSent) return;
|
|
160
|
+
if (!app.errorPage) {
|
|
161
|
+
res.writeHead(status, { "Content-Type": "text/plain" });
|
|
162
|
+
res.end(message);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
const ctx = existingCtx ?? initCtx(req, res, {}, url, app);
|
|
167
|
+
const mod = await importModule(app.errorPage, app.isDev);
|
|
168
|
+
const handler = mod.default;
|
|
169
|
+
const rawErrorResult = await handler({ status, message, ctx });
|
|
170
|
+
let html = isOmlNode(rawErrorResult) ? renderToHtml(rawErrorResult) : rawErrorResult;
|
|
171
|
+
const applicable = findLayouts(app.errorPage, app.layouts);
|
|
172
|
+
for (const layout of applicable) {
|
|
173
|
+
const lmod = await importModule(layout.filePath, app.isDev);
|
|
174
|
+
const lhandler = lmod.default;
|
|
175
|
+
const lresult = await lhandler({
|
|
176
|
+
children: Object.assign(Promise.resolve(html), { toString: () => html }),
|
|
177
|
+
ctx
|
|
178
|
+
});
|
|
179
|
+
html = isOmlNode(lresult) ? renderToHtml(lresult) : lresult;
|
|
180
|
+
}
|
|
181
|
+
const trimmed = html.trimStart();
|
|
182
|
+
const full = trimmed.startsWith("<") && !trimmed.startsWith("<!DOCTYPE") ? `<!DOCTYPE html>${html}` : html;
|
|
183
|
+
res.writeHead(status, { "Content-Type": "text/html; charset=utf-8" });
|
|
184
|
+
res.end(full);
|
|
185
|
+
} catch {
|
|
186
|
+
res.writeHead(status, { "Content-Type": "text/plain" });
|
|
187
|
+
res.end(message);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
async function dispatch(req, res, app) {
|
|
191
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
192
|
+
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
193
|
+
const allowed = METHOD_TYPES[method] ?? [];
|
|
194
|
+
const ctx = initCtx(req, res, {}, url, app);
|
|
195
|
+
const handleRoutes = async () => {
|
|
196
|
+
for (const { route, match } of app.routes) {
|
|
197
|
+
if (!allowed.includes(route.type)) continue;
|
|
198
|
+
const result = match(url.pathname);
|
|
199
|
+
if (!result) continue;
|
|
200
|
+
Object.assign(ctx.params, result.params);
|
|
201
|
+
const applicable = findMiddlewares(route.filePath, app.middlewares);
|
|
202
|
+
try {
|
|
203
|
+
await runWithMiddleware(applicable, ctx, app.isDev, async () => {
|
|
204
|
+
if (route.type === "page") {
|
|
205
|
+
if (method === "POST") {
|
|
206
|
+
const mod = await importModule(route.filePath, app.isDev);
|
|
207
|
+
const actionFn = mod.action;
|
|
208
|
+
if (typeof actionFn !== "function") {
|
|
209
|
+
if (!res.headersSent) {
|
|
210
|
+
res.writeHead(405, {
|
|
211
|
+
Allow: "GET, HEAD",
|
|
212
|
+
"Content-Type": "text/plain"
|
|
213
|
+
});
|
|
214
|
+
res.end("Method Not Allowed");
|
|
215
|
+
}
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const result2 = await actionFn(ctx);
|
|
219
|
+
if (result2 !== void 0) ctx.state.actionResult = result2;
|
|
220
|
+
await sendPageWithDeferred(
|
|
221
|
+
await renderPage(route, ctx, app.layouts, app.isDev, app.omlCache, {
|
|
222
|
+
preloadedMod: mod,
|
|
223
|
+
devOmlStore: app.devOmlStore,
|
|
224
|
+
omlCacheConfig: app.omlCacheConfig
|
|
225
|
+
}),
|
|
226
|
+
ctx,
|
|
227
|
+
res
|
|
228
|
+
);
|
|
229
|
+
} else {
|
|
230
|
+
await sendPageWithDeferred(
|
|
231
|
+
await renderPage(route, ctx, app.layouts, app.isDev, app.omlCache, {
|
|
232
|
+
devOmlStore: app.devOmlStore,
|
|
233
|
+
omlCacheConfig: app.omlCacheConfig
|
|
234
|
+
}),
|
|
235
|
+
ctx,
|
|
236
|
+
res
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
} else {
|
|
240
|
+
const mod = await importModule(route.filePath, app.isDev);
|
|
241
|
+
const handler = mod.default;
|
|
242
|
+
const value = await handler(ctx);
|
|
243
|
+
if (!res.headersSent && value !== void 0) {
|
|
244
|
+
if (value instanceof Response) {
|
|
245
|
+
await writeWebResponse(value, res);
|
|
246
|
+
} else {
|
|
247
|
+
res.writeHead(res.statusCode, {
|
|
248
|
+
"Content-Type": "application/json; charset=utf-8"
|
|
249
|
+
});
|
|
250
|
+
res.end(JSON.stringify(value));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
} catch (err) {
|
|
256
|
+
if (isRedirect(err)) throw err;
|
|
257
|
+
if (isValidation(err)) throw err;
|
|
258
|
+
if (isPayloadTooLarge(err)) throw err;
|
|
259
|
+
console.error(`[davaux] Error in ${route.filePath}:`, err);
|
|
260
|
+
await sendErrorPage(
|
|
261
|
+
500,
|
|
262
|
+
err instanceof Error ? err.message : "Internal Server Error",
|
|
263
|
+
req,
|
|
264
|
+
res,
|
|
265
|
+
url,
|
|
266
|
+
app,
|
|
267
|
+
ctx
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
await sendErrorPage(404, "Not Found", req, res, url, app, ctx);
|
|
273
|
+
};
|
|
274
|
+
try {
|
|
275
|
+
if (app.appMiddlewarePath) {
|
|
276
|
+
const mod = await importModule(app.appMiddlewarePath, app.isDev);
|
|
277
|
+
const fn = mod.default;
|
|
278
|
+
await fn(ctx, handleRoutes);
|
|
279
|
+
} else {
|
|
280
|
+
await handleRoutes();
|
|
281
|
+
}
|
|
282
|
+
} catch (err) {
|
|
283
|
+
if (isRedirect(err)) {
|
|
284
|
+
if (!res.headersSent) {
|
|
285
|
+
res.writeHead(err.status, { Location: err.url });
|
|
286
|
+
res.end();
|
|
287
|
+
}
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (isValidation(err)) {
|
|
291
|
+
const body = { error: err.message };
|
|
292
|
+
const cause = err.cause;
|
|
293
|
+
if (cause != null && typeof cause === "object" && Array.isArray(cause.issues)) {
|
|
294
|
+
body.issues = cause.issues;
|
|
295
|
+
}
|
|
296
|
+
if (!res.headersSent) {
|
|
297
|
+
res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
|
|
298
|
+
res.end(JSON.stringify(body));
|
|
299
|
+
}
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (isPayloadTooLarge(err)) {
|
|
303
|
+
if (!res.headersSent) {
|
|
304
|
+
res.writeHead(413, { "Content-Type": "text/plain" });
|
|
305
|
+
res.end(err.message);
|
|
306
|
+
}
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
console.error("[davaux] Error in global middleware:", err);
|
|
310
|
+
await sendErrorPage(
|
|
311
|
+
500,
|
|
312
|
+
err instanceof Error ? err.message : "Internal Server Error",
|
|
313
|
+
req,
|
|
314
|
+
res,
|
|
315
|
+
url,
|
|
316
|
+
app,
|
|
317
|
+
ctx
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
async function writeWebResponse(webRes, res) {
|
|
322
|
+
const headers = {};
|
|
323
|
+
webRes.headers.forEach((value, key) => {
|
|
324
|
+
headers[key] = value;
|
|
325
|
+
});
|
|
326
|
+
res.writeHead(webRes.status, headers);
|
|
327
|
+
res.end(Buffer.from(await webRes.arrayBuffer()));
|
|
328
|
+
}
|
|
329
|
+
function isRedirect(err) {
|
|
330
|
+
return err?.isRedirect === true;
|
|
331
|
+
}
|
|
332
|
+
function isValidation(err) {
|
|
333
|
+
return err?.isValidation === true;
|
|
334
|
+
}
|
|
335
|
+
function isPayloadTooLarge(err) {
|
|
336
|
+
return err?.isPayloadTooLarge === true;
|
|
337
|
+
}
|
|
338
|
+
export {
|
|
339
|
+
buildApp,
|
|
340
|
+
dispatch
|
|
341
|
+
};
|
|
342
|
+
//# sourceMappingURL=handler.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/router/handler.ts"],
|
|
4
|
+
"sourcesContent": ["import type { IncomingMessage, ServerResponse } from 'node:http'\nimport { dirname } from 'node:path'\nimport type { OmlCacheConfig } from '../config.js'\nimport { renderToHtml, renderToHtmlDev } from '../oml/render.js'\nimport type { OmlNode } from '../oml/types.js'\nimport type {\n ActionFn,\n ErrorPageHandler,\n ErrorPageProps,\n LayoutFile,\n LayoutProps,\n MiddlewareFile,\n MiddlewareFn,\n RequestContext,\n RouteFile,\n ScanResult,\n} from '../types.js'\nimport {\n makeContext,\n type PayloadTooLargeError,\n type RedirectError,\n type ValidationError,\n} from '../types.js'\nimport { createMatcher, type MatchFn } from './matcher.js'\n\nconst METHOD_TYPES: Record<string, string[]> = {\n GET: ['page', 'get'],\n POST: ['post', 'page'], // 'page' to support defineAction exports\n PUT: ['put'],\n PATCH: ['patch'],\n DELETE: ['delete'],\n HEAD: ['head', 'page', 'get'],\n OPTIONS: ['options'],\n}\n\n/** A route file paired with its compiled URL matcher, ready for dispatch. */\nexport interface CompiledRoute {\n route: RouteFile\n match: MatchFn\n}\n\n/** The compiled representation of a Davaux application, ready to dispatch requests. */\nexport interface CompiledApp {\n routes: CompiledRoute[]\n layouts: LayoutFile[]\n middlewares: MiddlewareFile[]\n /** Compiled path to `src/middleware.ts` \u2014 runs on every request before route matching. */\n appMiddlewarePath?: string\n isDev: boolean\n /** Whether the visual editor and inspector overlay are active. Requires `editor.enabled: true` in config. */\n editorEnabled: boolean\n clientScripts: string[]\n clientStylesheets: string[]\n errorPage?: string\n basePath: string\n /**\n * In-process OML cache. Keyed by filePath::pathname+search. Only populated in production\n * for routes matched by `omlCacheConfig`. Stores the OmlNode and pre-rendered HTML string\n * so cache hits require zero renderToHtml traversal.\n */\n omlCache: Map<string, { node: OmlNode; html: string }>\n /** In dev mode, stores the last rendered OmlNode per URL for the inspector. Not populated in production. */\n devOmlStore?: Map<string, OmlNode>\n /**\n * OML production cache configuration from `davaux.config.ts` `oml.cache`.\n * When absent, no routes are cached in production.\n */\n omlCacheConfig?: OmlCacheConfig\n}\n\n/**\n * Compile a scan result into a `CompiledApp` ready to dispatch requests.\n * Typically called once at startup (or on each rebuild in dev mode).\n */\nexport function buildApp(\n result: ScanResult,\n isDev = false,\n clientScripts: string[] = [],\n clientStylesheets: string[] = [],\n appMiddlewarePath?: string,\n basePath = '',\n editorEnabled = false,\n omlCacheConfig?: OmlCacheConfig,\n): CompiledApp {\n return {\n routes: result.routes.map((route) => ({\n route,\n match: createMatcher(route.urlPattern),\n })),\n layouts: result.layouts,\n middlewares: result.middlewares,\n appMiddlewarePath,\n isDev,\n editorEnabled,\n clientScripts,\n clientStylesheets,\n errorPage: result.errorPage,\n basePath,\n omlCache: new Map(),\n devOmlStore: isDev && editorEnabled ? new Map() : undefined,\n omlCacheConfig,\n }\n}\n\n// \u2500\u2500\u2500 OML cache 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\n\nfunction matchesCachePattern(pathname: string, pattern: string): boolean {\n const regexStr = pattern\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&') // escape regex special chars (not * or :)\n .replace(/:[^/]+/g, '[^/]+') // :param \u2192 any non-slash segment\n .replace(/\\*/g, '.*') // * \u2192 anything\n return new RegExp(`^${regexStr}$`).test(pathname)\n}\n\nfunction isOmlCacheable(pathname: string, config: OmlCacheConfig | undefined): boolean {\n if (!config) return false\n if ('include' in config) return config.include.some((p) => matchesCachePattern(pathname, p))\n return !config.exclude.some((p) => matchesCachePattern(pathname, p))\n}\n\nfunction isOmlNode(value: unknown): value is OmlNode {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) return false\n const t = (value as Record<string, unknown>).type\n return t === 'element' || t === '#component' || t === '#fragment' || t === '#text' || t === '#raw'\n}\n\nasync function importModule(filePath: string, isDev: boolean): Promise<Record<string, unknown>> {\n const url = isDev ? `${filePath}?t=${Date.now()}` : filePath\n try {\n return await (import(url) as Promise<Record<string, unknown>>)\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err)\n throw new Error(`[davaux] Failed to load module: ${filePath}\\n ${detail}`, { cause: err })\n }\n}\n\n// Find layouts that apply to a given route file, ordered innermost \u2192 outermost.\nfunction findLayouts(routeFilePath: string, layouts: LayoutFile[]): LayoutFile[] {\n const routeDir = dirname(routeFilePath)\n return layouts\n .filter((l) => routeDir === l.dirPath || routeDir.startsWith(`${l.dirPath}/`))\n .sort((a, b) => b.dirPath.length - a.dirPath.length) // deepest first\n}\n\n// Find middlewares that apply to a given route file, ordered outermost \u2192 innermost.\nfunction findMiddlewares(routeFilePath: string, middlewares: MiddlewareFile[]): MiddlewareFile[] {\n const routeDir = dirname(routeFilePath)\n return middlewares\n .filter((m) => routeDir === m.dirPath || routeDir.startsWith(`${m.dirPath}/`))\n .sort((a, b) => a.dirPath.length - b.dirPath.length) // shallowest first\n}\n\nasync function runWithMiddleware(\n middlewares: MiddlewareFile[],\n ctx: RequestContext,\n isDev: boolean,\n handler: () => Promise<void>,\n): Promise<void> {\n const step = async (i: number): Promise<void> => {\n if (i < middlewares.length) {\n const mod = await importModule(middlewares[i].filePath, isDev)\n const fn = mod.default as MiddlewareFn\n await fn(ctx, () => step(i + 1))\n } else {\n await handler()\n }\n }\n await step(0)\n}\n\nfunction sendPage(html: string, res: ServerResponse): void {\n if (!res.headersSent) {\n const trimmed = html.trimStart()\n const full =\n trimmed.startsWith('<') && !trimmed.startsWith('<!DOCTYPE') ? `<!DOCTYPE html>${html}` : html\n res.writeHead(res.statusCode, {\n 'Content-Type': 'text/html; charset=utf-8',\n })\n res.end(full)\n } else if (!res.writableEnded) {\n res.end(html)\n }\n}\n\nasync function sendPageWithDeferred(\n html: string,\n ctx: import('../types.js').RequestContext,\n res: ServerResponse,\n): Promise<void> {\n if (ctx._deferredSlots.size === 0) {\n sendPage(html, res)\n return\n }\n if (res.headersSent) return\n const trimmed = html.trimStart()\n const shell =\n trimmed.startsWith('<') && !trimmed.startsWith('<!DOCTYPE') ? `<!DOCTYPE html>${html}` : html\n res.writeHead(res.statusCode, { 'Content-Type': 'text/html; charset=utf-8' })\n res.write(shell)\n for (const [name, contentPromise] of ctx._deferredSlots) {\n const content = await contentPromise\n const slotHtml = isOmlNode(content) ? renderToHtml(content) : (content as string)\n res.write(`<template for=\"${name}\">${slotHtml}</template>`)\n }\n res.end()\n}\n\nasync function renderPage(\n route: RouteFile,\n ctx: RequestContext,\n layouts: LayoutFile[],\n isDev: boolean,\n omlCache: Map<string, { node: OmlNode; html: string }>,\n opts?: {\n preloadedMod?: Record<string, unknown>\n devOmlStore?: Map<string, OmlNode>\n omlCacheConfig?: OmlCacheConfig\n },\n): Promise<string> {\n const { preloadedMod, devOmlStore, omlCacheConfig } = opts ?? {}\n const mod = preloadedMod ?? (await importModule(route.filePath, isDev))\n const handler = mod.default as (\n ctx: RequestContext,\n ) => string | OmlNode | Promise<string | OmlNode>\n\n const cacheKey =\n !isDev && isOmlCacheable(ctx.url.pathname, omlCacheConfig)\n ? `${route.filePath}::${ctx.url.pathname}${ctx.url.search}`\n : null\n const cached = cacheKey ? omlCache.get(cacheKey) : undefined\n\n let html: string\n if (cached !== undefined) {\n html = cached.html\n } else {\n const result = await handler(ctx)\n if (isOmlNode(result)) {\n html = isDev && devOmlStore ? renderToHtmlDev(result) : renderToHtml(result)\n if (cacheKey) omlCache.set(cacheKey, { node: result, html })\n if (devOmlStore) {\n const sp = new URLSearchParams(ctx.url.search)\n sp.delete('_editor')\n const search = sp.toString()\n devOmlStore.set(ctx.url.pathname + (search ? `?${search}` : ''), result)\n }\n } else {\n html = result as string\n }\n }\n\n // Wrap in applicable layouts from innermost to outermost\n const applicable = findLayouts(route.filePath, layouts)\n for (const layout of applicable) {\n const lmod = await importModule(layout.filePath, isDev)\n const lhandler = lmod.default as (\n props: LayoutProps,\n ) => string | OmlNode | Promise<string | OmlNode>\n // Pass children as Promise<string> so the JSX runtime renders it without escaping\n const lresult = await lhandler({\n children: Object.assign(Promise.resolve(html), { toString: () => html }),\n ctx,\n })\n html = isOmlNode(lresult) ? renderToHtml(lresult) : (lresult as string)\n }\n\n return html\n}\n\nfunction initCtx(\n req: IncomingMessage,\n res: ServerResponse,\n params: Record<string, string>,\n url: URL,\n app: CompiledApp,\n): RequestContext {\n const ctx = makeContext(req, res, params, url, app.basePath)\n ctx.head.stylesheets.push(...app.clientStylesheets)\n ctx.head.scripts.push(...app.clientScripts)\n if (app.isDev) {\n ctx.head.scripts.push('/_davaux/livereload.js')\n if (app.editorEnabled && !url.searchParams.has('_editor')) {\n ctx.head.scripts.push('/_davaux/inspector.js')\n }\n }\n return ctx\n}\n\nasync function sendErrorPage(\n status: number,\n message: string,\n req: IncomingMessage,\n res: ServerResponse,\n url: URL,\n app: CompiledApp,\n existingCtx?: RequestContext,\n): Promise<void> {\n if (res.headersSent) return\n if (!app.errorPage) {\n res.writeHead(status, { 'Content-Type': 'text/plain' })\n res.end(message)\n return\n }\n try {\n const ctx = existingCtx ?? initCtx(req, res, {}, url, app)\n const mod = await importModule(app.errorPage, app.isDev)\n const handler = mod.default as ErrorPageHandler\n const rawErrorResult = await handler({ status, message, ctx } satisfies ErrorPageProps)\n let html = isOmlNode(rawErrorResult) ? renderToHtml(rawErrorResult) : (rawErrorResult as string)\n\n // Apply layouts so error pages inherit site chrome\n const applicable = findLayouts(app.errorPage, app.layouts)\n for (const layout of applicable) {\n const lmod = await importModule(layout.filePath, app.isDev)\n const lhandler = lmod.default as (\n props: LayoutProps,\n ) => string | OmlNode | Promise<string | OmlNode>\n const lresult = await lhandler({\n children: Object.assign(Promise.resolve(html), { toString: () => html }),\n ctx,\n })\n html = isOmlNode(lresult) ? renderToHtml(lresult) : (lresult as string)\n }\n\n const trimmed = html.trimStart()\n const full =\n trimmed.startsWith('<') && !trimmed.startsWith('<!DOCTYPE') ? `<!DOCTYPE html>${html}` : html\n res.writeHead(status, { 'Content-Type': 'text/html; charset=utf-8' })\n res.end(full)\n } catch {\n res.writeHead(status, { 'Content-Type': 'text/plain' })\n res.end(message)\n }\n}\n\n/**\n * Dispatch a single HTTP request through the compiled app.\n * Runs app middleware \u2192 route matching \u2192 scoped middleware \u2192 route handler.\n * Handles redirects, validation errors, payload-too-large, and 404s internally.\n */\nexport async function dispatch(\n req: IncomingMessage,\n res: ServerResponse,\n app: CompiledApp,\n): Promise<void> {\n const method = (req.method ?? 'GET').toUpperCase()\n const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`)\n const allowed = METHOD_TYPES[method] ?? []\n\n // ctx is created once and shared between global middleware and route handlers.\n // params starts empty and is populated in-place after a route match.\n const ctx = initCtx(req, res, {}, url, app)\n\n // Inner handler: route matching \u2192 scoped middleware \u2192 route handler \u2192 404.\n // Never throws \u2014 all errors are caught and turned into responses here.\n const handleRoutes = async (): Promise<void> => {\n for (const { route, match } of app.routes) {\n if (!allowed.includes(route.type)) continue\n\n const result = match(url.pathname)\n if (!result) continue\n\n // Populate params into the shared ctx now that we have a match\n Object.assign(ctx.params, result.params)\n\n const applicable = findMiddlewares(route.filePath, app.middlewares)\n\n try {\n await runWithMiddleware(applicable, ctx, app.isDev, async () => {\n if (route.type === 'page') {\n if (method === 'POST') {\n const mod = await importModule(route.filePath, app.isDev)\n const actionFn = mod.action as ActionFn | undefined\n if (typeof actionFn !== 'function') {\n if (!res.headersSent) {\n res.writeHead(405, {\n Allow: 'GET, HEAD',\n 'Content-Type': 'text/plain',\n })\n res.end('Method Not Allowed')\n }\n return\n }\n const result = await actionFn(ctx)\n if (result !== undefined) ctx.state.actionResult = result\n await sendPageWithDeferred(\n await renderPage(route, ctx, app.layouts, app.isDev, app.omlCache, {\n preloadedMod: mod,\n devOmlStore: app.devOmlStore,\n omlCacheConfig: app.omlCacheConfig,\n }),\n ctx,\n res,\n )\n } else {\n await sendPageWithDeferred(\n await renderPage(route, ctx, app.layouts, app.isDev, app.omlCache, {\n devOmlStore: app.devOmlStore,\n omlCacheConfig: app.omlCacheConfig,\n }),\n ctx,\n res,\n )\n }\n } else {\n // API route\n const mod = await importModule(route.filePath, app.isDev)\n const handler = mod.default as (ctx: RequestContext) => unknown\n const value = await handler(ctx)\n if (!res.headersSent && value !== undefined) {\n if (value instanceof Response) {\n await writeWebResponse(value, res)\n } else {\n res.writeHead(res.statusCode, {\n 'Content-Type': 'application/json; charset=utf-8',\n })\n res.end(JSON.stringify(value))\n }\n }\n }\n })\n } catch (err) {\n if (isRedirect(err)) throw err\n if (isValidation(err)) throw err\n if (isPayloadTooLarge(err)) throw err\n console.error(`[davaux] Error in ${route.filePath}:`, err)\n await sendErrorPage(\n 500,\n err instanceof Error ? err.message : 'Internal Server Error',\n req,\n res,\n url,\n app,\n ctx,\n )\n }\n return // route matched \u2014 stop iterating\n }\n\n await sendErrorPage(404, 'Not Found', req, res, url, app, ctx)\n }\n\n // App middleware (src/middleware.ts) \u2192 routes.\n // Errors thrown anywhere in the chain are caught here.\n try {\n if (app.appMiddlewarePath) {\n const mod = await importModule(app.appMiddlewarePath, app.isDev)\n const fn = mod.default as MiddlewareFn\n await fn(ctx, handleRoutes)\n } else {\n await handleRoutes()\n }\n } catch (err) {\n if (isRedirect(err)) {\n if (!res.headersSent) {\n res.writeHead(err.status, { Location: err.url })\n res.end()\n }\n return\n }\n if (isValidation(err)) {\n const body: Record<string, unknown> = { error: err.message }\n const cause = err.cause\n if (\n cause != null &&\n typeof cause === 'object' &&\n Array.isArray((cause as { issues?: unknown }).issues)\n ) {\n body.issues = (cause as { issues: unknown }).issues\n }\n if (!res.headersSent) {\n res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' })\n res.end(JSON.stringify(body))\n }\n return\n }\n if (isPayloadTooLarge(err)) {\n if (!res.headersSent) {\n res.writeHead(413, { 'Content-Type': 'text/plain' })\n res.end(err.message)\n }\n return\n }\n console.error('[davaux] Error in global middleware:', err)\n await sendErrorPage(\n 500,\n err instanceof Error ? err.message : 'Internal Server Error',\n req,\n res,\n url,\n app,\n ctx,\n )\n }\n}\n\nasync function writeWebResponse(webRes: Response, res: ServerResponse): Promise<void> {\n const headers: Record<string, string> = {}\n webRes.headers.forEach((value, key) => {\n headers[key] = value\n })\n res.writeHead(webRes.status, headers)\n res.end(Buffer.from(await webRes.arrayBuffer()))\n}\n\nfunction isRedirect(err: unknown): err is RedirectError {\n return (err as RedirectError)?.isRedirect === true\n}\n\nfunction isValidation(err: unknown): err is ValidationError {\n return (err as ValidationError)?.isValidation === true\n}\n\nfunction isPayloadTooLarge(err: unknown): err is PayloadTooLargeError {\n return (err as PayloadTooLargeError)?.isPayloadTooLarge === true\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["result"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Result of a successful URL pattern match, containing the extracted named params. */
|
|
2
|
+
export interface MatchResult {
|
|
3
|
+
params: Record<string, string>;
|
|
4
|
+
}
|
|
5
|
+
/** A compiled URL matcher. Returns a `MatchResult` on match, `null` on miss. */
|
|
6
|
+
export type MatchFn = (pathname: string) => MatchResult | null;
|
|
7
|
+
/**
|
|
8
|
+
* Compile a URL pattern string into a fast matcher function.
|
|
9
|
+
*
|
|
10
|
+
* Pattern segments:
|
|
11
|
+
* - `:param` — matches a single path segment and captures it by name.
|
|
12
|
+
* - `*param` — catch-all; matches the rest of the path including slashes.
|
|
13
|
+
* - anything else — matched literally.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* const match = createMatcher('/blog/:slug')
|
|
17
|
+
* match('/blog/hello') // → { params: { slug: 'hello' } }
|
|
18
|
+
* match('/about') // → null
|
|
19
|
+
*/
|
|
20
|
+
export declare function createMatcher(urlPattern: string): MatchFn;
|
|
21
|
+
//# sourceMappingURL=matcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"matcher.d.ts","sourceRoot":"","sources":["../../src/router/matcher.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC/B;AAED,gFAAgF;AAChF,MAAM,MAAM,OAAO,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,WAAW,GAAG,IAAI,CAAA;AAE9D;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CA8BzD"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
function createMatcher(urlPattern) {
|
|
2
|
+
const paramNames = [];
|
|
3
|
+
const regexParts = urlPattern.split("/").map((segment) => {
|
|
4
|
+
if (segment.startsWith("*")) {
|
|
5
|
+
paramNames.push(segment.slice(1));
|
|
6
|
+
return "(.+)";
|
|
7
|
+
}
|
|
8
|
+
if (segment.startsWith(":")) {
|
|
9
|
+
paramNames.push(segment.slice(1));
|
|
10
|
+
return "([^/]+)";
|
|
11
|
+
}
|
|
12
|
+
return segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
13
|
+
});
|
|
14
|
+
const pattern = new RegExp(`^${regexParts.join("\\/")}$`);
|
|
15
|
+
return (pathname) => {
|
|
16
|
+
const match = pathname.match(pattern);
|
|
17
|
+
if (!match) return null;
|
|
18
|
+
const params = {};
|
|
19
|
+
paramNames.forEach((name, i) => {
|
|
20
|
+
params[name] = decodeURIComponent(match[i + 1]);
|
|
21
|
+
});
|
|
22
|
+
return { params };
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
createMatcher
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=matcher.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/router/matcher.ts"],
|
|
4
|
+
"sourcesContent": ["/** Result of a successful URL pattern match, containing the extracted named params. */\nexport interface MatchResult {\n params: Record<string, string>\n}\n\n/** A compiled URL matcher. Returns a `MatchResult` on match, `null` on miss. */\nexport type MatchFn = (pathname: string) => MatchResult | null\n\n/**\n * Compile a URL pattern string into a fast matcher function.\n *\n * Pattern segments:\n * - `:param` \u2014 matches a single path segment and captures it by name.\n * - `*param` \u2014 catch-all; matches the rest of the path including slashes.\n * - anything else \u2014 matched literally.\n *\n * @example\n * const match = createMatcher('/blog/:slug')\n * match('/blog/hello') // \u2192 { params: { slug: 'hello' } }\n * match('/about') // \u2192 null\n */\nexport function createMatcher(urlPattern: string): MatchFn {\n const paramNames: string[] = []\n\n // Build regex from pattern segments\n const regexParts = urlPattern.split('/').map((segment) => {\n if (segment.startsWith('*')) {\n paramNames.push(segment.slice(1))\n return '(.+)' // catch-all: matches multiple path segments including slashes\n }\n if (segment.startsWith(':')) {\n paramNames.push(segment.slice(1))\n return '([^/]+)'\n }\n // Escape regex special chars in static segments\n return segment.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n })\n\n const pattern = new RegExp(`^${regexParts.join('\\\\/')}$`)\n\n return (pathname: string): MatchResult | null => {\n const match = pathname.match(pattern)\n if (!match) return null\n\n const params: Record<string, string> = {}\n paramNames.forEach((name, i) => {\n params[name] = decodeURIComponent(match[i + 1])\n })\n\n return { params }\n }\n}\n"],
|
|
5
|
+
"mappings": "AAqBO,SAAS,cAAc,YAA6B;AACzD,QAAM,aAAuB,CAAC;AAG9B,QAAM,aAAa,WAAW,MAAM,GAAG,EAAE,IAAI,CAAC,YAAY;AACxD,QAAI,QAAQ,WAAW,GAAG,GAAG;AAC3B,iBAAW,KAAK,QAAQ,MAAM,CAAC,CAAC;AAChC,aAAO;AAAA,IACT;AACA,QAAI,QAAQ,WAAW,GAAG,GAAG;AAC3B,iBAAW,KAAK,QAAQ,MAAM,CAAC,CAAC;AAChC,aAAO;AAAA,IACT;AAEA,WAAO,QAAQ,QAAQ,uBAAuB,MAAM;AAAA,EACtD,CAAC;AAED,QAAM,UAAU,IAAI,OAAO,IAAI,WAAW,KAAK,KAAK,CAAC,GAAG;AAExD,SAAO,CAAC,aAAyC;AAC/C,UAAM,QAAQ,SAAS,MAAM,OAAO;AACpC,QAAI,CAAC,MAAO,QAAO;AAEnB,UAAM,SAAiC,CAAC;AACxC,eAAW,QAAQ,CAAC,MAAM,MAAM;AAC9B,aAAO,IAAI,IAAI,mBAAmB,MAAM,IAAI,CAAC,CAAC;AAAA,IAChD,CAAC;AAED,WAAO,EAAE,OAAO;AAAA,EAClB;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { IslandFile, RouteType, ScanResult } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Recursively scan `islandsDir` for client island component files.
|
|
4
|
+
* Files beginning with `_` are ignored. Returns an empty array when the
|
|
5
|
+
* directory does not exist.
|
|
6
|
+
*/
|
|
7
|
+
export declare function scanIslands(islandsDir: string): Promise<IslandFile[]>;
|
|
8
|
+
/**
|
|
9
|
+
* Recursively scan `routesDir` for route, layout, middleware, and error-page files.
|
|
10
|
+
* Routes are sorted so static segments beat dynamic segments, and explicit
|
|
11
|
+
* HTTP-method routes beat page routes at the same URL pattern.
|
|
12
|
+
*
|
|
13
|
+
* @param extraSuffixes - Additional `[suffix, RouteType]` pairs contributed by
|
|
14
|
+
* plugins (e.g. `[['.page.md', 'page']]` from `@davaux/markdown`).
|
|
15
|
+
*/
|
|
16
|
+
export declare function scanRoutes(routesDir: string, extraSuffixes?: [string, RouteType][]): Promise<ScanResult>;
|
|
17
|
+
//# sourceMappingURL=scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/router/scanner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,UAAU,EAIV,SAAS,EACT,UAAU,EACX,MAAM,aAAa,CAAA;AA6FpB;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAoB3E;AAwDD;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,SAAS,EAAE,MAAM,EACjB,aAAa,GAAE,CAAC,MAAM,EAAE,SAAS,CAAC,EAAO,GACxC,OAAO,CAAC,UAAU,CAAC,CA4ErB"}
|