@meekojs/meeko 0.0.3 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -16
- package/dist/autoimports-ts.d.ts +26 -0
- package/dist/autoimports-ts.js +183 -0
- package/dist/autoimports-ts.js.map +1 -0
- package/dist/autoimports.d.ts +21 -0
- package/dist/autoimports.js +301 -0
- package/dist/autoimports.js.map +1 -0
- package/dist/check.js +74 -33
- package/dist/check.js.map +1 -0
- package/dist/cli.js +67 -5
- package/dist/cli.js.map +1 -0
- package/dist/compiler/events.d.ts +2 -0
- package/dist/compiler/events.js +6 -0
- package/dist/compiler/events.js.map +1 -0
- package/dist/compiler/import-graph.d.ts +8 -0
- package/dist/compiler/import-graph.js +33 -0
- package/dist/compiler/import-graph.js.map +1 -0
- package/dist/compiler/mapping.js +1 -0
- package/dist/compiler/mapping.js.map +1 -0
- package/dist/compiler/project-root.d.ts +2 -0
- package/dist/compiler/project-root.js +18 -0
- package/dist/compiler/project-root.js.map +1 -0
- package/dist/compiler/props.d.ts +12 -0
- package/dist/compiler/props.js +32 -0
- package/dist/compiler/props.js.map +1 -0
- package/dist/compiler/sfc.js +1 -0
- package/dist/compiler/sfc.js.map +1 -0
- package/dist/compiler/sourcemap.d.ts +3 -0
- package/dist/compiler/sourcemap.js +75 -0
- package/dist/compiler/sourcemap.js.map +1 -0
- package/dist/compiler/template.js +1 -0
- package/dist/compiler/template.js.map +1 -0
- package/dist/compiler.js +66 -17
- package/dist/compiler.js.map +1 -0
- package/dist/component-errors.d.ts +18 -0
- package/dist/component-errors.js +65 -0
- package/dist/component-errors.js.map +1 -0
- package/dist/devtools-ui/index.js +1 -0
- package/dist/devtools-ui/index.js.map +1 -0
- package/dist/devtools.js +3 -4
- package/dist/devtools.js.map +1 -0
- package/dist/dom.d.ts +18 -5
- package/dist/dom.js +295 -90
- package/dist/dom.js.map +1 -0
- package/dist/error-observer.d.ts +6 -0
- package/dist/error-observer.js +41 -0
- package/dist/error-observer.js.map +1 -0
- package/dist/framework/check-app.d.ts +5 -0
- package/dist/framework/check-app.js +214 -0
- package/dist/framework/check-app.js.map +1 -0
- package/dist/framework/check-project.d.ts +4 -0
- package/dist/framework/check-project.js +42 -0
- package/dist/framework/check-project.js.map +1 -0
- package/dist/framework/client.d.ts +48 -0
- package/dist/framework/client.js +249 -0
- package/dist/framework/client.js.map +1 -0
- package/dist/framework/clients.d.ts +82 -0
- package/dist/framework/clients.js +190 -0
- package/dist/framework/clients.js.map +1 -0
- package/dist/framework/config-type.d.ts +10 -0
- package/dist/framework/config-type.js +4 -0
- package/dist/framework/config-type.js.map +1 -0
- package/dist/framework/config.d.ts +15 -0
- package/dist/framework/config.js +138 -0
- package/dist/framework/config.js.map +1 -0
- package/dist/framework/contracts-generate.d.ts +8 -0
- package/dist/framework/contracts-generate.js +290 -0
- package/dist/framework/contracts-generate.js.map +1 -0
- package/dist/framework/generate.d.ts +7 -0
- package/dist/framework/generate.js +67 -0
- package/dist/framework/generate.js.map +1 -0
- package/dist/framework/init.d.ts +2 -0
- package/dist/framework/init.js +183 -0
- package/dist/framework/init.js.map +1 -0
- package/dist/framework/middleware.d.ts +14 -0
- package/dist/framework/middleware.js +16 -0
- package/dist/framework/middleware.js.map +1 -0
- package/dist/framework/modules.d.ts +22 -0
- package/dist/framework/modules.js +29 -0
- package/dist/framework/modules.js.map +1 -0
- package/dist/framework/page-contracts-check.d.ts +8 -0
- package/dist/framework/page-contracts-check.js +110 -0
- package/dist/framework/page-contracts-check.js.map +1 -0
- package/dist/framework/page-contracts-generate.d.ts +2 -0
- package/dist/framework/page-contracts-generate.js +94 -0
- package/dist/framework/page-contracts-generate.js.map +1 -0
- package/dist/framework/page.d.ts +64 -0
- package/dist/framework/page.js +44 -0
- package/dist/framework/page.js.map +1 -0
- package/dist/framework/prerender.d.ts +14 -0
- package/dist/framework/prerender.js +110 -0
- package/dist/framework/prerender.js.map +1 -0
- package/dist/framework/project-templates.d.ts +3 -0
- package/dist/framework/project-templates.js +119 -0
- package/dist/framework/project-templates.js.map +1 -0
- package/dist/framework/project.d.ts +9 -0
- package/dist/framework/project.js +272 -0
- package/dist/framework/project.js.map +1 -0
- package/dist/framework/router.d.ts +41 -0
- package/dist/framework/router.js +259 -0
- package/dist/framework/router.js.map +1 -0
- package/dist/framework/server.d.ts +65 -0
- package/dist/framework/server.js +239 -0
- package/dist/framework/server.js.map +1 -0
- package/dist/framework/socket-contracts-generate.d.ts +5 -0
- package/dist/framework/socket-contracts-generate.js +162 -0
- package/dist/framework/socket-contracts-generate.js.map +1 -0
- package/dist/framework/socket.d.ts +72 -0
- package/dist/framework/socket.js +252 -0
- package/dist/framework/socket.js.map +1 -0
- package/dist/framework/standard-schema-types.d.ts +7 -0
- package/dist/framework/standard-schema-types.js +69 -0
- package/dist/framework/standard-schema-types.js.map +1 -0
- package/dist/framework/state.d.ts +22 -0
- package/dist/framework/state.js +136 -0
- package/dist/framework/state.js.map +1 -0
- package/dist/framework/styles.d.ts +2 -0
- package/dist/framework/styles.js +6 -0
- package/dist/framework/styles.js.map +1 -0
- package/dist/framework/type-declarations.d.ts +5 -0
- package/dist/framework/type-declarations.js +84 -0
- package/dist/framework/type-declarations.js.map +1 -0
- package/dist/framework-server/auto-clients.d.ts +3 -0
- package/dist/framework-server/auto-clients.js +27 -0
- package/dist/framework-server/auto-clients.js.map +1 -0
- package/dist/framework-server/context.d.ts +21 -0
- package/dist/framework-server/context.js +22 -0
- package/dist/framework-server/context.js.map +1 -0
- package/dist/framework-server/contracts.d.ts +73 -0
- package/dist/framework-server/contracts.js +184 -0
- package/dist/framework-server/contracts.js.map +1 -0
- package/dist/framework-server/discovery.d.ts +43 -0
- package/dist/framework-server/discovery.js +257 -0
- package/dist/framework-server/discovery.js.map +1 -0
- package/dist/framework-server/local.d.ts +21 -0
- package/dist/framework-server/local.js +100 -0
- package/dist/framework-server/local.js.map +1 -0
- package/dist/framework-server/middleware.d.ts +4 -0
- package/dist/framework-server/middleware.js +56 -0
- package/dist/framework-server/middleware.js.map +1 -0
- package/dist/framework-server/node-upgrade.d.ts +23 -0
- package/dist/framework-server/node-upgrade.js +96 -0
- package/dist/framework-server/node-upgrade.js.map +1 -0
- package/dist/framework-server/node-websocket.d.ts +16 -0
- package/dist/framework-server/node-websocket.js +127 -0
- package/dist/framework-server/node-websocket.js.map +1 -0
- package/dist/framework-server/openapi.d.ts +17 -0
- package/dist/framework-server/openapi.js +118 -0
- package/dist/framework-server/openapi.js.map +1 -0
- package/dist/framework-server/session-stores.d.ts +73 -0
- package/dist/framework-server/session-stores.js +122 -0
- package/dist/framework-server/session-stores.js.map +1 -0
- package/dist/framework-server/sessions.d.ts +70 -0
- package/dist/framework-server/sessions.js +167 -0
- package/dist/framework-server/sessions.js.map +1 -0
- package/dist/framework-server/socket.d.ts +83 -0
- package/dist/framework-server/socket.js +196 -0
- package/dist/framework-server/socket.js.map +1 -0
- package/dist/framework-server.d.ts +23 -0
- package/dist/framework-server.js +13 -0
- package/dist/framework-server.js.map +1 -0
- package/dist/framework.d.ts +31 -0
- package/dist/framework.js +14 -0
- package/dist/framework.js.map +1 -0
- package/dist/hmr.js +75 -24
- package/dist/hmr.js.map +1 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -0
- package/dist/init.js +31 -3
- package/dist/init.js.map +1 -0
- package/dist/internal.d.ts +2 -2
- package/dist/internal.js +3 -2
- package/dist/internal.js.map +1 -0
- package/dist/node.d.ts +16 -0
- package/dist/node.js +122 -0
- package/dist/node.js.map +1 -0
- package/dist/reactivity.d.ts +22 -1
- package/dist/reactivity.js +132 -18
- package/dist/reactivity.js.map +1 -0
- package/dist/server.js +1 -0
- package/dist/server.js.map +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.js +26 -9
- package/dist/types.js.map +1 -0
- package/dist/vite.js +112 -7
- package/dist/vite.js.map +1 -0
- package/package.json +15 -4
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
3
|
+
import ts from "@typescript/typescript6";
|
|
4
|
+
import { discoverConfiguredRoutes } from "./config.js";
|
|
5
|
+
function importPath(from, target) {
|
|
6
|
+
const path = relative(dirname(from), target).replaceAll("\\", "/");
|
|
7
|
+
return path.startsWith(".") ? path : `./${path}`;
|
|
8
|
+
}
|
|
9
|
+
function displayPath(root, target) {
|
|
10
|
+
return relative(root, target).replaceAll("\\", "/");
|
|
11
|
+
}
|
|
12
|
+
async function declaresLoader(file) {
|
|
13
|
+
if (!file)
|
|
14
|
+
return false;
|
|
15
|
+
const source = ts.createSourceFile(file, await readFile(file, "utf8"), ts.ScriptTarget.Latest, true);
|
|
16
|
+
for (const statement of source.statements) {
|
|
17
|
+
if (!ts.isVariableStatement(statement))
|
|
18
|
+
continue;
|
|
19
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
20
|
+
if (!ts.isIdentifier(declaration.name) || declaration.name.text !== "route")
|
|
21
|
+
continue;
|
|
22
|
+
const call = declaration.initializer;
|
|
23
|
+
if (!call || !ts.isCallExpression(call) || !call.arguments[0] || !ts.isObjectLiteralExpression(call.arguments[0]))
|
|
24
|
+
continue;
|
|
25
|
+
return call.arguments[0].properties.some((property) => {
|
|
26
|
+
if (!ts.isMethodDeclaration(property) && !ts.isPropertyAssignment(property))
|
|
27
|
+
return false;
|
|
28
|
+
return ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)
|
|
29
|
+
? property.name.text === "load"
|
|
30
|
+
: false;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
/** Checks that each page accepts its route params, search and loader data. */
|
|
37
|
+
export async function generatePageContracts(projectRoot) {
|
|
38
|
+
const root = resolve(projectRoot);
|
|
39
|
+
const output = join(root, ".meeko", "generated", "page-contracts.server.ts");
|
|
40
|
+
const routes = await discoverConfiguredRoutes(root);
|
|
41
|
+
const lines = [
|
|
42
|
+
"// Generated by Meeko. Page props must accept the values delivered by the route runtime.",
|
|
43
|
+
'type __Output<S> = S extends { readonly "~standard": { readonly types?: infer T } } ? NonNullable<T> extends { readonly output: infer O } ? O : never : never;',
|
|
44
|
+
'type __Schema<Route, Key extends "params" | "search", Fallback> = [Route] extends [undefined] ? Fallback : Route extends { readonly [K in Key]?: infer S } ? [__Output<NonNullable<S>>] extends [never] ? Fallback : __Output<NonNullable<S>> : Fallback;',
|
|
45
|
+
'type __Data<Route> = Route extends { readonly load: (...args: never[]) => infer D } ? Exclude<Awaited<D>, Response> : Route extends import("@meekojs/meeko/framework").PageDefinition<infer P extends import("@meekojs/meeko/framework").PageSchema | undefined, infer S extends import("@meekojs/meeko/framework").PageSchema | undefined, infer D, infer C> ? Exclude<D, Response> : never;',
|
|
46
|
+
'type __HasLoad<Route, Declared extends boolean> = Declared extends true ? true : Route extends { readonly load: (...args: never[]) => unknown } ? true : false;',
|
|
47
|
+
'type __PageProps<Route, Params, HasLoad extends boolean> = { params: __Schema<Route, "params", Params>; search: __Schema<Route, "search", Record<string, string | string[]>> } & (HasLoad extends false ? {} : [__Data<Route>] extends [never] ? {} : undefined extends __Data<Route> ? { data?: Exclude<__Data<Route>, undefined> } : { data: __Data<Route> });',
|
|
48
|
+
'type __UnsafeProps<T> = [T] extends [never] ? true : 0 extends (1 & T) ? true : unknown extends T ? true : false;',
|
|
49
|
+
'type __Check<Actual, Expected, Page extends string, Sidecar extends string> = __UnsafeProps<Actual> extends true ? `Page ${Page} props cannot be any, never or unknown` : Actual extends Record<string, never> ? true : Expected extends Actual ? true : `Page ${Page} props do not match loader ${Sidecar}`;',
|
|
50
|
+
'type __CheckShell<Actual, Expected, File extends string, Kind extends string> = __UnsafeProps<Actual> extends true ? `${Kind} ${File} props cannot be any, never or unknown` : Actual extends Record<string, never> ? true : Expected extends Actual ? true : `${Kind} ${File} props do not match the values supplied by Meeko`;',
|
|
51
|
+
'type __RequireTrue<Result extends true> = Result;',
|
|
52
|
+
];
|
|
53
|
+
const layouts = new Set();
|
|
54
|
+
const errors = new Set();
|
|
55
|
+
for (const [index, route] of routes.entries()) {
|
|
56
|
+
const page = JSON.stringify(displayPath(root, route.file));
|
|
57
|
+
const sidecar = JSON.stringify(route.sidecar ? displayPath(root, route.sidecar) : "(no sidecar)");
|
|
58
|
+
const pageType = `import(${JSON.stringify(importPath(output, route.file))}).ComponentProps`;
|
|
59
|
+
const routeType = route.sidecar ? `typeof import(${JSON.stringify(importPath(output, route.sidecar))}).route` : "undefined";
|
|
60
|
+
const paramsType = `import("@meekojs/meeko/framework").GeneratedRoutes[${JSON.stringify(route.id)}]`;
|
|
61
|
+
const hasLoad = await declaresLoader(route.sidecar);
|
|
62
|
+
lines.push(`type __check${index} = __RequireTrue<__Check<${pageType}, __PageProps<${routeType}, ${paramsType}, __HasLoad<${routeType}, ${hasLoad}>>, ${page}, ${sidecar}>>;`);
|
|
63
|
+
for (const layout of route.layouts)
|
|
64
|
+
layouts.add(layout);
|
|
65
|
+
if (route.error)
|
|
66
|
+
errors.add(route.error);
|
|
67
|
+
}
|
|
68
|
+
for (const [index, layout] of [...layouts].entries()) {
|
|
69
|
+
const file = JSON.stringify(displayPath(root, layout));
|
|
70
|
+
const props = `import(${JSON.stringify(importPath(output, layout))}).ComponentProps`;
|
|
71
|
+
const supplied = '{ outlet: import("@meekojs/meeko").Component<Record<string, unknown>> }';
|
|
72
|
+
lines.push(`type __layout${index} = __RequireTrue<__CheckShell<${props}, ${supplied}, ${file}, "Layout">>;`);
|
|
73
|
+
}
|
|
74
|
+
for (const [index, error] of [...errors].entries()) {
|
|
75
|
+
const file = JSON.stringify(displayPath(root, error));
|
|
76
|
+
const props = `import(${JSON.stringify(importPath(output, error))}).ComponentProps`;
|
|
77
|
+
lines.push(`type __error${index} = __RequireTrue<__CheckShell<${props}, { status: number; message: string }, ${file}, "Error page">>;`);
|
|
78
|
+
}
|
|
79
|
+
lines.push("export {};", "");
|
|
80
|
+
const source = lines.join("\n");
|
|
81
|
+
await mkdir(dirname(output), { recursive: true });
|
|
82
|
+
let previous;
|
|
83
|
+
try {
|
|
84
|
+
previous = await readFile(output, "utf8");
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
if (error.code !== "ENOENT")
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
if (previous !== source)
|
|
91
|
+
await writeFile(output, source, "utf8");
|
|
92
|
+
return output;
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=page-contracts-generate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-contracts-generate.js","sourceRoot":"","sources":["../../src/framework/page-contracts-generate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAE,MAAM,yBAAyB,CAAC;AACzC,OAAO,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAEvD,SAAS,UAAU,CAAC,IAAY,EAAE,MAAc;IAC9C,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACnE,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;AACnD,CAAC;AAED,SAAS,WAAW,CAAC,IAAY,EAAE,MAAc;IAC/C,OAAO,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AACtD,CAAC;AAED,KAAK,UAAU,cAAc,CAAC,IAAwB;IACpD,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,MAAM,MAAM,GAAG,EAAE,CAAC,gBAAgB,CAAC,IAAI,EAAE,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACrG,KAAK,MAAM,SAAS,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QAC1C,IAAI,CAAC,EAAE,CAAC,mBAAmB,CAAC,SAAS,CAAC;YAAE,SAAS;QACjD,KAAK,MAAM,WAAW,IAAI,SAAS,CAAC,eAAe,CAAC,YAAY,EAAE,CAAC;YACjE,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,KAAK,OAAO;gBAAE,SAAS;YACtF,MAAM,IAAI,GAAG,WAAW,CAAC,WAAW,CAAC;YACrC,IAAI,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,yBAAyB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;gBAAE,SAAS;YAC5H,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE;gBACpD,IAAI,CAAC,EAAE,CAAC,mBAAmB,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,oBAAoB,CAAC,QAAQ,CAAC;oBAAE,OAAO,KAAK,CAAC;gBAC1F,OAAO,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC;oBACxE,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM;oBAC/B,CAAC,CAAC,KAAK,CAAC;YACZ,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,8EAA8E;AAC9E,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,WAAmB;IAC7D,MAAM,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAClC,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,0BAA0B,CAAC,CAAC;IAC7E,MAAM,MAAM,GAAG,MAAM,wBAAwB,CAAC,IAAI,CAAC,CAAC;IACpD,MAAM,KAAK,GAAG;QACZ,0FAA0F;QAC1F,gKAAgK;QAChK,2PAA2P;QAC3P,+XAA+X;QAC/X,iKAAiK;QACjK,kWAAkW;QAClW,mHAAmH;QACnH,+SAA+S;QAC/S,kUAAkU;QAClU,mDAAmD;KACpD,CAAC;IACF,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IACjC,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC;QAC9C,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QAC3D,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC;QAClG,MAAM,QAAQ,GAAG,UAAU,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,kBAAkB,CAAC;QAC5F,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,iBAAiB,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC;QAC5H,MAAM,UAAU,GAAG,sDAAsD,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC;QACrG,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACpD,KAAK,CAAC,IAAI,CAAC,eAAe,KAAK,4BAA4B,QAAQ,iBAAiB,SAAS,KAAK,UAAU,eAAe,SAAS,KAAK,OAAO,OAAO,IAAI,KAAK,OAAO,KAAK,CAAC,CAAC;QAC9K,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,OAAO;YAAE,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACxD,IAAI,KAAK,CAAC,KAAK;YAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC3C,CAAC;IACD,KAAK,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;QACrD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;QACvD,MAAM,KAAK,GAAG,UAAU,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,kBAAkB,CAAC;QACrF,MAAM,QAAQ,GAAG,yEAAyE,CAAC;QAC3F,KAAK,CAAC,IAAI,CAAC,gBAAgB,KAAK,iCAAiC,KAAK,KAAK,QAAQ,KAAK,IAAI,eAAe,CAAC,CAAC;IAC/G,CAAC;IACD,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;QACnD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;QACtD,MAAM,KAAK,GAAG,UAAU,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,kBAAkB,CAAC;QACpF,KAAK,CAAC,IAAI,CAAC,eAAe,KAAK,iCAAiC,KAAK,0CAA0C,IAAI,mBAAmB,CAAC,CAAC;IAC1I,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IAC7B,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChC,MAAM,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,IAAI,QAA4B,CAAC;IACjC,IAAI,CAAC;QAAC,QAAQ,GAAG,MAAM,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,CAAC;IAClD,OAAO,KAAK,EAAE,CAAC;QAAC,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;YAAE,MAAM,KAAK,CAAC;IAAC,CAAC;IACtF,IAAI,QAAQ,KAAK,MAAM;QAAE,MAAM,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACjE,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join, relative, resolve } from \"node:path\";\nimport ts from \"@typescript/typescript6\";\nimport { discoverConfiguredRoutes } from \"./config.js\";\n\nfunction importPath(from: string, target: string): string {\n const path = relative(dirname(from), target).replaceAll(\"\\\\\", \"/\");\n return path.startsWith(\".\") ? path : `./${path}`;\n}\n\nfunction displayPath(root: string, target: string): string {\n return relative(root, target).replaceAll(\"\\\\\", \"/\");\n}\n\nasync function declaresLoader(file: string | undefined): Promise<boolean> {\n if (!file) return false;\n const source = ts.createSourceFile(file, await readFile(file, \"utf8\"), ts.ScriptTarget.Latest, true);\n for (const statement of source.statements) {\n if (!ts.isVariableStatement(statement)) continue;\n for (const declaration of statement.declarationList.declarations) {\n if (!ts.isIdentifier(declaration.name) || declaration.name.text !== \"route\") continue;\n const call = declaration.initializer;\n if (!call || !ts.isCallExpression(call) || !call.arguments[0] || !ts.isObjectLiteralExpression(call.arguments[0])) continue;\n return call.arguments[0].properties.some((property) => {\n if (!ts.isMethodDeclaration(property) && !ts.isPropertyAssignment(property)) return false;\n return ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)\n ? property.name.text === \"load\"\n : false;\n });\n }\n }\n return false;\n}\n\n/** Checks that each page accepts its route params, search and loader data. */\nexport async function generatePageContracts(projectRoot: string): Promise<string> {\n const root = resolve(projectRoot);\n const output = join(root, \".meeko\", \"generated\", \"page-contracts.server.ts\");\n const routes = await discoverConfiguredRoutes(root);\n const lines = [\n \"// Generated by Meeko. Page props must accept the values delivered by the route runtime.\",\n 'type __Output<S> = S extends { readonly \"~standard\": { readonly types?: infer T } } ? NonNullable<T> extends { readonly output: infer O } ? O : never : never;',\n 'type __Schema<Route, Key extends \"params\" | \"search\", Fallback> = [Route] extends [undefined] ? Fallback : Route extends { readonly [K in Key]?: infer S } ? [__Output<NonNullable<S>>] extends [never] ? Fallback : __Output<NonNullable<S>> : Fallback;',\n 'type __Data<Route> = Route extends { readonly load: (...args: never[]) => infer D } ? Exclude<Awaited<D>, Response> : Route extends import(\"@meekojs/meeko/framework\").PageDefinition<infer P extends import(\"@meekojs/meeko/framework\").PageSchema | undefined, infer S extends import(\"@meekojs/meeko/framework\").PageSchema | undefined, infer D, infer C> ? Exclude<D, Response> : never;',\n 'type __HasLoad<Route, Declared extends boolean> = Declared extends true ? true : Route extends { readonly load: (...args: never[]) => unknown } ? true : false;',\n 'type __PageProps<Route, Params, HasLoad extends boolean> = { params: __Schema<Route, \"params\", Params>; search: __Schema<Route, \"search\", Record<string, string | string[]>> } & (HasLoad extends false ? {} : [__Data<Route>] extends [never] ? {} : undefined extends __Data<Route> ? { data?: Exclude<__Data<Route>, undefined> } : { data: __Data<Route> });',\n 'type __UnsafeProps<T> = [T] extends [never] ? true : 0 extends (1 & T) ? true : unknown extends T ? true : false;',\n 'type __Check<Actual, Expected, Page extends string, Sidecar extends string> = __UnsafeProps<Actual> extends true ? `Page ${Page} props cannot be any, never or unknown` : Actual extends Record<string, never> ? true : Expected extends Actual ? true : `Page ${Page} props do not match loader ${Sidecar}`;',\n 'type __CheckShell<Actual, Expected, File extends string, Kind extends string> = __UnsafeProps<Actual> extends true ? `${Kind} ${File} props cannot be any, never or unknown` : Actual extends Record<string, never> ? true : Expected extends Actual ? true : `${Kind} ${File} props do not match the values supplied by Meeko`;',\n 'type __RequireTrue<Result extends true> = Result;',\n ];\n const layouts = new Set<string>();\n const errors = new Set<string>();\n for (const [index, route] of routes.entries()) {\n const page = JSON.stringify(displayPath(root, route.file));\n const sidecar = JSON.stringify(route.sidecar ? displayPath(root, route.sidecar) : \"(no sidecar)\");\n const pageType = `import(${JSON.stringify(importPath(output, route.file))}).ComponentProps`;\n const routeType = route.sidecar ? `typeof import(${JSON.stringify(importPath(output, route.sidecar))}).route` : \"undefined\";\n const paramsType = `import(\"@meekojs/meeko/framework\").GeneratedRoutes[${JSON.stringify(route.id)}]`;\n const hasLoad = await declaresLoader(route.sidecar);\n lines.push(`type __check${index} = __RequireTrue<__Check<${pageType}, __PageProps<${routeType}, ${paramsType}, __HasLoad<${routeType}, ${hasLoad}>>, ${page}, ${sidecar}>>;`);\n for (const layout of route.layouts) layouts.add(layout);\n if (route.error) errors.add(route.error);\n }\n for (const [index, layout] of [...layouts].entries()) {\n const file = JSON.stringify(displayPath(root, layout));\n const props = `import(${JSON.stringify(importPath(output, layout))}).ComponentProps`;\n const supplied = '{ outlet: import(\"@meekojs/meeko\").Component<Record<string, unknown>> }';\n lines.push(`type __layout${index} = __RequireTrue<__CheckShell<${props}, ${supplied}, ${file}, \"Layout\">>;`);\n }\n for (const [index, error] of [...errors].entries()) {\n const file = JSON.stringify(displayPath(root, error));\n const props = `import(${JSON.stringify(importPath(output, error))}).ComponentProps`;\n lines.push(`type __error${index} = __RequireTrue<__CheckShell<${props}, { status: number; message: string }, ${file}, \"Error page\">>;`);\n }\n lines.push(\"export {};\", \"\");\n const source = lines.join(\"\\n\");\n await mkdir(dirname(output), { recursive: true });\n let previous: string | undefined;\n try { previous = await readFile(output, \"utf8\"); }\n catch (error) { if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error; }\n if (previous !== source) await writeFile(output, source, \"utf8\");\n return output;\n}\n"]}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/** Browser-safe subset of Standard Schema v1, implemented by Valibot schemas. */
|
|
2
|
+
import type { StateStore } from "./state.js";
|
|
3
|
+
export interface PageSchema<I = unknown, O = I> {
|
|
4
|
+
readonly "~standard": {
|
|
5
|
+
readonly version: 1;
|
|
6
|
+
readonly validate: (value: unknown) => {
|
|
7
|
+
value: O;
|
|
8
|
+
issues?: undefined;
|
|
9
|
+
} | {
|
|
10
|
+
issues: readonly unknown[];
|
|
11
|
+
} | Promise<{
|
|
12
|
+
value: O;
|
|
13
|
+
issues?: undefined;
|
|
14
|
+
} | {
|
|
15
|
+
issues: readonly unknown[];
|
|
16
|
+
}>;
|
|
17
|
+
readonly types?: {
|
|
18
|
+
readonly input: I;
|
|
19
|
+
readonly output: O;
|
|
20
|
+
} | undefined;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
type Output<S> = S extends PageSchema<unknown, infer O> ? O : never;
|
|
24
|
+
type Params<P> = P extends PageSchema ? Output<P> : Readonly<Record<string, string>>;
|
|
25
|
+
type Search<S> = S extends PageSchema ? Output<S> : Readonly<Record<string, string | string[]>>;
|
|
26
|
+
export interface PageLoadContext<P extends PageSchema | undefined, S extends PageSchema | undefined, C = unknown> {
|
|
27
|
+
readonly params: Params<P>;
|
|
28
|
+
readonly search: Search<S>;
|
|
29
|
+
readonly request: Request;
|
|
30
|
+
readonly signal: AbortSignal;
|
|
31
|
+
readonly state: StateStore;
|
|
32
|
+
readonly context?: C;
|
|
33
|
+
}
|
|
34
|
+
export interface PageDefinition<P extends PageSchema | undefined, S extends PageSchema | undefined, D, C = unknown> {
|
|
35
|
+
readonly params?: P;
|
|
36
|
+
readonly search?: S;
|
|
37
|
+
readonly render?: "ssr" | "prerender";
|
|
38
|
+
readonly prerenderEntries?: readonly Readonly<Record<string, string>>[] | (() => Promise<readonly Readonly<Record<string, string>>[]>);
|
|
39
|
+
readonly load?: (args: PageLoadContext<P, S, C>) => Promise<D> | D;
|
|
40
|
+
readonly head?: (args: PageLoadContext<P, S, C> & {
|
|
41
|
+
data: D;
|
|
42
|
+
}) => {
|
|
43
|
+
title?: string;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/** Captures route schemas and keeps loader/head inference in one declaration. */
|
|
47
|
+
export declare function definePage<const P extends PageSchema | undefined = undefined, const S extends PageSchema | undefined = undefined, D = unknown, C = unknown>(definition: PageDefinition<P, S, D, C> & {
|
|
48
|
+
readonly load: NonNullable<PageDefinition<P, S, D, C>["load"]>;
|
|
49
|
+
}): PageDefinition<P, S, D, C> & {
|
|
50
|
+
readonly load: NonNullable<PageDefinition<P, S, D, C>["load"]>;
|
|
51
|
+
};
|
|
52
|
+
export declare function definePage<const P extends PageSchema | undefined = undefined, const S extends PageSchema | undefined = undefined, D = unknown, C = unknown>(definition: PageDefinition<P, S, D, C>): PageDefinition<P, S, D, C>;
|
|
53
|
+
export declare class PageInputError extends Error {
|
|
54
|
+
readonly code: "INVALID_PARAMS" | "INVALID_SEARCH";
|
|
55
|
+
constructor(code: "INVALID_PARAMS" | "INVALID_SEARCH");
|
|
56
|
+
}
|
|
57
|
+
/** Validates both inputs before a page loader executes. */
|
|
58
|
+
export declare function validatePageInputs(definition: Pick<PageDefinition<PageSchema | undefined, PageSchema | undefined, unknown>, "params" | "search"> | undefined, params: Readonly<Record<string, string>>, search: Readonly<Record<string, string | string[]>>): Promise<{
|
|
59
|
+
params: Readonly<Record<string, unknown>>;
|
|
60
|
+
search: Readonly<Record<string, unknown>>;
|
|
61
|
+
}>;
|
|
62
|
+
/** Preserves duplicate search keys for schema validation. */
|
|
63
|
+
export declare function pageSearchObject(searchParams: URLSearchParams): Record<string, string | string[]>;
|
|
64
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export function definePage(definition) {
|
|
2
|
+
if (typeof definition === "object" && definition !== null && "render" in definition && definition.render === "client") {
|
|
3
|
+
throw new TypeError('render: "client" is not supported; use SSR or prerender');
|
|
4
|
+
}
|
|
5
|
+
return definition;
|
|
6
|
+
}
|
|
7
|
+
export class PageInputError extends Error {
|
|
8
|
+
code;
|
|
9
|
+
constructor(code) {
|
|
10
|
+
super(code);
|
|
11
|
+
this.code = code;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function record(value) {
|
|
15
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16
|
+
}
|
|
17
|
+
async function parse(schema, raw, code) {
|
|
18
|
+
if (!schema)
|
|
19
|
+
return raw;
|
|
20
|
+
const result = await schema["~standard"].validate(raw);
|
|
21
|
+
if ("issues" in result && result.issues)
|
|
22
|
+
throw new PageInputError(code);
|
|
23
|
+
const value = result.value;
|
|
24
|
+
if (!record(value))
|
|
25
|
+
throw new PageInputError(code);
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
/** Validates both inputs before a page loader executes. */
|
|
29
|
+
export async function validatePageInputs(definition, params, search) {
|
|
30
|
+
return {
|
|
31
|
+
params: await parse(definition?.params, { ...params }, "INVALID_PARAMS"),
|
|
32
|
+
search: await parse(definition?.search, { ...search }, "INVALID_SEARCH"),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** Preserves duplicate search keys for schema validation. */
|
|
36
|
+
export function pageSearchObject(searchParams) {
|
|
37
|
+
const result = Object.create(null);
|
|
38
|
+
for (const [key, value] of searchParams) {
|
|
39
|
+
const previous = result[key];
|
|
40
|
+
result[key] = previous === undefined ? value : Array.isArray(previous) ? [...previous, value] : [previous, value];
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=page.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page.js","sourceRoot":"","sources":["../../src/framework/page.ts"],"names":[],"mappings":"AAiDA,MAAM,UAAU,UAAU,CAAC,UAAmB;IAC5C,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,KAAK,IAAI,IAAI,QAAQ,IAAI,UAAU,IAAI,UAAU,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;QACtH,MAAM,IAAI,SAAS,CAAC,yDAAyD,CAAC,CAAC;IACjF,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,MAAM,OAAO,cAAe,SAAQ,KAAK;IACX,IAAI;IAAhC,YAA4B,IAAyC;QACnE,KAAK,CAAC,IAAI,CAAC,CAAC;oBADc,IAAI;IAEhC,CAAC;CACF;AAED,SAAS,MAAM,CAAC,KAAc;IAC5B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,KAAK,UAAU,KAAK,CAAC,MAA8B,EAAE,GAA4B,EAAE,IAA4B;IAC7G,IAAI,CAAC,MAAM;QAAE,OAAO,GAAG,CAAC;IACxB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IACvD,IAAI,QAAQ,IAAI,MAAM,IAAI,MAAM,CAAC,MAAM;QAAE,MAAM,IAAI,cAAc,CAAC,IAAI,CAAC,CAAC;IACxE,MAAM,KAAK,GAAI,MAA6B,CAAC,KAAK,CAAC;IACnD,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,cAAc,CAAC,IAAI,CAAC,CAAC;IACnD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,2DAA2D;AAC3D,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,UAA0H,EAC1H,MAAwC,EACxC,MAAmD;IAEnD,OAAO;QACL,MAAM,EAAE,MAAM,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,GAAG,MAAM,EAAE,EAAE,gBAAgB,CAAC;QACxE,MAAM,EAAE,MAAM,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,GAAG,MAAM,EAAE,EAAE,gBAAgB,CAAC;KACzE,CAAC;AACJ,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,gBAAgB,CAAC,YAA6B;IAC5D,MAAM,MAAM,GAAsC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACtE,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,YAAY,EAAE,CAAC;QACxC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7B,MAAM,CAAC,GAAG,CAAC,GAAG,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IACpH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["/** Browser-safe subset of Standard Schema v1, implemented by Valibot schemas. */\nimport type { StateStore } from \"./state.js\";\n\nexport interface PageSchema<I = unknown, O = I> {\n readonly \"~standard\": {\n readonly version: 1;\n readonly validate: (value: unknown) =>\n | { value: O; issues?: undefined }\n | { issues: readonly unknown[] }\n | Promise<{ value: O; issues?: undefined } | { issues: readonly unknown[] }>;\n readonly types?: { readonly input: I; readonly output: O } | undefined;\n };\n}\n\ntype Output<S> = S extends PageSchema<unknown, infer O> ? O : never;\ntype Params<P> = P extends PageSchema ? Output<P> : Readonly<Record<string, string>>;\ntype Search<S> = S extends PageSchema ? Output<S> : Readonly<Record<string, string | string[]>>;\n\nexport interface PageLoadContext<P extends PageSchema | undefined, S extends PageSchema | undefined, C = unknown> {\n readonly params: Params<P>;\n readonly search: Search<S>;\n readonly request: Request;\n readonly signal: AbortSignal;\n readonly state: StateStore;\n readonly context?: C;\n}\n\nexport interface PageDefinition<P extends PageSchema | undefined, S extends PageSchema | undefined, D, C = unknown> {\n readonly params?: P;\n readonly search?: S;\n readonly render?: \"ssr\" | \"prerender\";\n readonly prerenderEntries?: readonly Readonly<Record<string, string>>[] | (() => Promise<readonly Readonly<Record<string, string>>[]>);\n readonly load?: (args: PageLoadContext<P, S, C>) => Promise<D> | D;\n readonly head?: (args: PageLoadContext<P, S, C> & { data: D }) => { title?: string };\n}\n\n/** Captures route schemas and keeps loader/head inference in one declaration. */\nexport function definePage<\n const P extends PageSchema | undefined = undefined,\n const S extends PageSchema | undefined = undefined,\n D = unknown,\n C = unknown,\n>(definition: PageDefinition<P, S, D, C> & { readonly load: NonNullable<PageDefinition<P, S, D, C>[\"load\"]> }): PageDefinition<P, S, D, C> & { readonly load: NonNullable<PageDefinition<P, S, D, C>[\"load\"]> };\nexport function definePage<\n const P extends PageSchema | undefined = undefined,\n const S extends PageSchema | undefined = undefined,\n D = unknown,\n C = unknown,\n>(definition: PageDefinition<P, S, D, C>): PageDefinition<P, S, D, C>;\nexport function definePage(definition: unknown): unknown {\n if (typeof definition === \"object\" && definition !== null && \"render\" in definition && definition.render === \"client\") {\n throw new TypeError('render: \"client\" is not supported; use SSR or prerender');\n }\n return definition;\n}\n\nexport class PageInputError extends Error {\n constructor(public readonly code: \"INVALID_PARAMS\" | \"INVALID_SEARCH\") {\n super(code);\n }\n}\n\nfunction record(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nasync function parse(schema: PageSchema | undefined, raw: Record<string, unknown>, code: PageInputError[\"code\"]): Promise<Record<string, unknown>> {\n if (!schema) return raw;\n const result = await schema[\"~standard\"].validate(raw);\n if (\"issues\" in result && result.issues) throw new PageInputError(code);\n const value = (result as { value: unknown }).value;\n if (!record(value)) throw new PageInputError(code);\n return value;\n}\n\n/** Validates both inputs before a page loader executes. */\nexport async function validatePageInputs(\n definition: Pick<PageDefinition<PageSchema | undefined, PageSchema | undefined, unknown>, \"params\" | \"search\"> | undefined,\n params: Readonly<Record<string, string>>,\n search: Readonly<Record<string, string | string[]>>,\n): Promise<{ params: Readonly<Record<string, unknown>>; search: Readonly<Record<string, unknown>> }> {\n return {\n params: await parse(definition?.params, { ...params }, \"INVALID_PARAMS\"),\n search: await parse(definition?.search, { ...search }, \"INVALID_SEARCH\"),\n };\n}\n\n/** Preserves duplicate search keys for schema validation. */\nexport function pageSearchObject(searchParams: URLSearchParams): Record<string, string | string[]> {\n const result: Record<string, string | string[]> = Object.create(null);\n for (const [key, value] of searchParams) {\n const previous = result[key];\n result[key] = previous === undefined ? value : Array.isArray(previous) ? [...previous, value] : [previous, value];\n }\n return result;\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { RouteRecord } from "./router.js";
|
|
2
|
+
import type { PageRoute } from "./server.js";
|
|
3
|
+
export interface PrerenderOptions {
|
|
4
|
+
readonly routes: readonly RouteRecord[];
|
|
5
|
+
readonly handler: (request: Request) => Promise<Response>;
|
|
6
|
+
readonly loadRoute?: (file: string) => Promise<PageRoute>;
|
|
7
|
+
readonly outputRoot: string;
|
|
8
|
+
readonly origin?: string;
|
|
9
|
+
readonly transformHtml?: (html: string) => string | Promise<string>;
|
|
10
|
+
}
|
|
11
|
+
/** Writes only fully resolved, successful HTML responses to the prerender output. */
|
|
12
|
+
export declare function prerenderRoutes(options: PrerenderOptions): Promise<string[]>;
|
|
13
|
+
/** Renders from the production server bundle and client manifest. */
|
|
14
|
+
export declare function prerenderProject(projectRoot: string): Promise<string[]>;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join, resolve, sep } from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { serializeJson } from "../server.js";
|
|
5
|
+
import { href, matchRoute } from "./router.js";
|
|
6
|
+
import { createFrameworkHandler } from "./server.js";
|
|
7
|
+
import { discoverConfiguredRoutes } from "./config.js";
|
|
8
|
+
import { stylesheetLinks } from "./styles.js";
|
|
9
|
+
function hasParams(id) {
|
|
10
|
+
return /\[(?:\.\.\.)?[A-Za-z_$][\w$]*\]/.test(id);
|
|
11
|
+
}
|
|
12
|
+
/** Writes only fully resolved, successful HTML responses to the prerender output. */
|
|
13
|
+
export async function prerenderRoutes(options) {
|
|
14
|
+
const outputRoot = resolve(options.outputRoot);
|
|
15
|
+
const origin = options.origin ?? "http://meeko.local";
|
|
16
|
+
const urls = new Map();
|
|
17
|
+
for (const route of options.routes) {
|
|
18
|
+
const sidecar = route.sidecar && options.loadRoute ? await options.loadRoute(route.sidecar) : undefined;
|
|
19
|
+
if (sidecar?.render !== "prerender")
|
|
20
|
+
continue;
|
|
21
|
+
const dynamic = hasParams(route.id);
|
|
22
|
+
const entries = dynamic
|
|
23
|
+
? typeof sidecar?.prerenderEntries === "function" ? await sidecar.prerenderEntries() : sidecar?.prerenderEntries
|
|
24
|
+
: [{}];
|
|
25
|
+
if (!entries?.length)
|
|
26
|
+
throw new Error(`Dynamic prerender route ${route.id} requires prerenderEntries`);
|
|
27
|
+
for (const params of entries) {
|
|
28
|
+
const pathname = href(options.routes, route.id, { params });
|
|
29
|
+
if (matchRoute(options.routes, pathname)?.route !== route) {
|
|
30
|
+
throw new Error(`Prerender URL ${pathname} is shadowed by another route`);
|
|
31
|
+
}
|
|
32
|
+
const previous = urls.get(pathname);
|
|
33
|
+
if (previous)
|
|
34
|
+
throw new Error(`Duplicate prerender URL ${pathname}: ${previous} and ${route.file}`);
|
|
35
|
+
urls.set(pathname, route.file);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const outputs = [];
|
|
39
|
+
await mkdir(outputRoot, { recursive: true });
|
|
40
|
+
for (const pathname of urls.keys()) {
|
|
41
|
+
const request = new Request(new URL(pathname, origin));
|
|
42
|
+
const response = await options.handler(request);
|
|
43
|
+
if (response.status !== 200 || !(response.headers.get("content-type") ?? "").includes("text/html")) {
|
|
44
|
+
throw new Error(`Prerender ${pathname} returned HTTP ${response.status} instead of HTML 200`);
|
|
45
|
+
}
|
|
46
|
+
const parts = pathname.split("/").filter(Boolean);
|
|
47
|
+
const output = resolve(outputRoot, ...parts, "index.html");
|
|
48
|
+
if (!output.startsWith(outputRoot + sep))
|
|
49
|
+
throw new Error(`Prerender path escaped output: ${pathname}`);
|
|
50
|
+
await mkdir(resolve(output, ".."), { recursive: true });
|
|
51
|
+
const html = await response.text();
|
|
52
|
+
await writeFile(output, options.transformHtml ? await options.transformHtml(html) : html, "utf8");
|
|
53
|
+
outputs.push(output);
|
|
54
|
+
}
|
|
55
|
+
await writeFile(join(outputRoot, "manifest.json"), serializeJson({ paths: [...urls.keys()] }) + "\n", "utf8");
|
|
56
|
+
return outputs;
|
|
57
|
+
}
|
|
58
|
+
/** Renders from the production server bundle and client manifest. */
|
|
59
|
+
export async function prerenderProject(projectRoot) {
|
|
60
|
+
const root = resolve(projectRoot);
|
|
61
|
+
const manifestFile = resolve(root, ".meeko/output/client/.vite/manifest.json");
|
|
62
|
+
let manifest;
|
|
63
|
+
try {
|
|
64
|
+
manifest = JSON.parse(await readFile(manifestFile, "utf8"));
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
throw new Error("Client build manifest is missing; run the client build before prerender");
|
|
68
|
+
}
|
|
69
|
+
const entry = manifest[".meeko/generated/entry-client.ts"]?.file
|
|
70
|
+
?? manifest["app/entry-client.ts"]?.file;
|
|
71
|
+
if (!entry)
|
|
72
|
+
throw new Error("Client build manifest has no Meeko client entry");
|
|
73
|
+
const entryManifest = manifest[".meeko/generated/entry-client.ts"] ?? manifest["app/entry-client.ts"];
|
|
74
|
+
const globalStyles = stylesheetLinks((entryManifest?.css ?? []).map((file) => "/" + file));
|
|
75
|
+
const serverBundle = resolve(root, ".meeko/output/server/entry.js");
|
|
76
|
+
let server;
|
|
77
|
+
try {
|
|
78
|
+
server = await import(pathToFileURL(serverBundle).href);
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
throw new Error(`Cannot load the server build at ${serverBundle}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
82
|
+
}
|
|
83
|
+
if (typeof server.loadPage !== "function" || typeof server.loadRoute !== "function") {
|
|
84
|
+
throw new Error("Server build does not expose the Meeko prerender loaders");
|
|
85
|
+
}
|
|
86
|
+
const routes = await discoverConfiguredRoutes(root);
|
|
87
|
+
const loadPage = (file) => server.loadPage(root, file);
|
|
88
|
+
const loadRoute = (file) => server.loadRoute(root, file);
|
|
89
|
+
const handler = createFrameworkHandler({ routes, loadPage, loadRoute, clientEntry: `/${entry}` });
|
|
90
|
+
const portable = routes.map((route) => ({
|
|
91
|
+
id: route.id,
|
|
92
|
+
file: "/" + fileRelative(root, route.file),
|
|
93
|
+
layouts: route.layouts.map((file) => "/" + fileRelative(root, file)),
|
|
94
|
+
...(route.error ? { error: "/" + fileRelative(root, route.error) } : {}),
|
|
95
|
+
}));
|
|
96
|
+
return await prerenderRoutes({
|
|
97
|
+
routes, handler, loadRoute,
|
|
98
|
+
outputRoot: resolve(root, ".meeko/output/prerender"),
|
|
99
|
+
transformHtml: (html) => html
|
|
100
|
+
.replace("</head>", `${globalStyles}</head>`)
|
|
101
|
+
.replace("</body>", `<script id="meeko-routes" type="application/json">${serializeJson(portable)}</script></body>`),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
function fileRelative(root, file) {
|
|
105
|
+
const absolute = resolve(file);
|
|
106
|
+
if (!absolute.startsWith(root + sep))
|
|
107
|
+
throw new Error(`Route file escaped project: ${file}`);
|
|
108
|
+
return absolute.slice(root.length + 1).replaceAll("\\", "/");
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=prerender.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prerender.js","sourceRoot":"","sources":["../../src/framework/prerender.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { join, resolve, sep } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { serializeJson } from \"../server.js\";\nimport { href, matchRoute } from \"./router.js\";\nimport type { RouteRecord } from \"./router.js\";\nimport { createFrameworkHandler } from \"./server.js\";\nimport { discoverConfiguredRoutes } from \"./config.js\";\nimport { stylesheetLinks } from \"./styles.js\";\nimport type { PageModule, PageRoute } from \"./server.js\";\n\nexport interface PrerenderOptions {\n readonly routes: readonly RouteRecord[];\n readonly handler: (request: Request) => Promise<Response>;\n readonly loadRoute?: (file: string) => Promise<PageRoute>;\n readonly outputRoot: string;\n readonly origin?: string;\n readonly transformHtml?: (html: string) => string | Promise<string>;\n}\n\nfunction hasParams(id: string): boolean {\n return /\\[(?:\\.\\.\\.)?[A-Za-z_$][\\w$]*\\]/.test(id);\n}\n\n/** Writes only fully resolved, successful HTML responses to the prerender output. */\nexport async function prerenderRoutes(options: PrerenderOptions): Promise<string[]> {\n const outputRoot = resolve(options.outputRoot);\n const origin = options.origin ?? \"http://meeko.local\";\n const urls = new Map<string, string>();\n for (const route of options.routes) {\n const sidecar = route.sidecar && options.loadRoute ? await options.loadRoute(route.sidecar) : undefined;\n if (sidecar?.render !== \"prerender\") continue;\n const dynamic = hasParams(route.id);\n const entries = dynamic\n ? typeof sidecar?.prerenderEntries === \"function\" ? await sidecar.prerenderEntries() : sidecar?.prerenderEntries\n : [{}];\n if (!entries?.length) throw new Error(`Dynamic prerender route ${route.id} requires prerenderEntries`);\n for (const params of entries) {\n const pathname = href(options.routes, route.id, { params });\n if (matchRoute(options.routes, pathname)?.route !== route) {\n throw new Error(`Prerender URL ${pathname} is shadowed by another route`);\n }\n const previous = urls.get(pathname);\n if (previous) throw new Error(`Duplicate prerender URL ${pathname}: ${previous} and ${route.file}`);\n urls.set(pathname, route.file);\n }\n }\n const outputs: string[] = [];\n await mkdir(outputRoot, { recursive: true });\n for (const pathname of urls.keys()) {\n const request = new Request(new URL(pathname, origin));\n const response = await options.handler(request);\n if (response.status !== 200 || !(response.headers.get(\"content-type\") ?? \"\").includes(\"text/html\")) {\n throw new Error(`Prerender ${pathname} returned HTTP ${response.status} instead of HTML 200`);\n }\n const parts = pathname.split(\"/\").filter(Boolean);\n const output = resolve(outputRoot, ...parts, \"index.html\");\n if (!output.startsWith(outputRoot + sep)) throw new Error(`Prerender path escaped output: ${pathname}`);\n await mkdir(resolve(output, \"..\"), { recursive: true });\n const html = await response.text();\n await writeFile(output, options.transformHtml ? await options.transformHtml(html) : html, \"utf8\");\n outputs.push(output);\n }\n await writeFile(join(outputRoot, \"manifest.json\"), serializeJson({ paths: [...urls.keys()] }) + \"\\n\", \"utf8\");\n return outputs;\n}\n\n/** Renders from the production server bundle and client manifest. */\nexport async function prerenderProject(projectRoot: string): Promise<string[]> {\n const root = resolve(projectRoot);\n const manifestFile = resolve(root, \".meeko/output/client/.vite/manifest.json\");\n let manifest: Record<string, { file?: string; css?: string[] }>;\n try {\n manifest = JSON.parse(await readFile(manifestFile, \"utf8\")) as Record<string, { file?: string }>;\n } catch {\n throw new Error(\"Client build manifest is missing; run the client build before prerender\");\n }\n const entry = manifest[\".meeko/generated/entry-client.ts\"]?.file\n ?? manifest[\"app/entry-client.ts\"]?.file;\n if (!entry) throw new Error(\"Client build manifest has no Meeko client entry\");\n const entryManifest = manifest[\".meeko/generated/entry-client.ts\"] ?? manifest[\"app/entry-client.ts\"];\n const globalStyles = stylesheetLinks((entryManifest?.css ?? []).map((file) => \"/\" + file));\n const serverBundle = resolve(root, \".meeko/output/server/entry.js\");\n let server: {\n loadPage(root: string, file: string): Promise<PageModule>;\n loadRoute(root: string, file: string): Promise<PageRoute>;\n };\n try {\n server = await import(pathToFileURL(serverBundle).href) as typeof server;\n } catch (error) {\n throw new Error(`Cannot load the server build at ${serverBundle}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });\n }\n if (typeof server.loadPage !== \"function\" || typeof server.loadRoute !== \"function\") {\n throw new Error(\"Server build does not expose the Meeko prerender loaders\");\n }\n const routes = await discoverConfiguredRoutes(root);\n const loadPage = (file: string) => server.loadPage(root, file);\n const loadRoute = (file: string) => server.loadRoute(root, file);\n const handler = createFrameworkHandler({ routes, loadPage, loadRoute, clientEntry: `/${entry}` });\n const portable = routes.map((route) => ({\n id: route.id,\n file: \"/\" + fileRelative(root, route.file),\n layouts: route.layouts.map((file) => \"/\" + fileRelative(root, file)),\n ...(route.error ? { error: \"/\" + fileRelative(root, route.error) } : {}),\n }));\n return await prerenderRoutes({\n routes, handler, loadRoute,\n outputRoot: resolve(root, \".meeko/output/prerender\"),\n transformHtml: (html) => html\n .replace(\"</head>\", `${globalStyles}</head>`)\n .replace(\"</body>\", `<script id=\"meeko-routes\" type=\"application/json\">${serializeJson(portable)}</script></body>`),\n });\n}\n\nfunction fileRelative(root: string, file: string): string {\n const absolute = resolve(file);\n if (!absolute.startsWith(root + sep)) throw new Error(`Route file escaped project: ${file}`);\n return absolute.slice(root.length + 1).replaceAll(\"\\\\\", \"/\");\n}\n"]}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** Vite entry modules generated for a framework application. */
|
|
2
|
+
export declare const clientEntrySource = "import { mountClientApp, type ClientComponent, type RouteRecord, type RouteMiddleware } from \"@meekojs/meeko/framework\";\nimport { modules as pages } from \"virtual:meeko-client-pages\";\n\nconst middlewareModules = import.meta.glob([\"/app/middleware/**/*.ts\", \"!/app/middleware/**/*.test.ts\", \"!/app/middleware/**/*.spec.ts\", \"!/app/middleware/**/*.d.ts\", \"!/app/middleware/**/*.server.ts\", \"!/app/middleware/**/*.client.ts\"], { eager: true });\nconst loadMiddleware = async (): Promise<RouteMiddleware[]> => Object.entries(middlewareModules)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([file, module]) => {\n const middleware = (module as { default?: unknown }).default;\n if (typeof middleware !== \"function\") throw new Error(file + \": route middleware requires a default function export\");\n return middleware as RouteMiddleware;\n });\n\n// Optional global stylesheet. Vite includes it in the client bundle when present.\nimport.meta.glob(\"/app/styles/main.css\", { eager: true });\n\nconst root = document.getElementById(\"app\");\nconst payload = document.getElementById(\"meeko-payload\")?.textContent;\nconst routesText = document.getElementById(\"meeko-routes\")?.textContent;\nif (!root || !payload || !routesText) throw new Error(\"Missing Meeko SSR data\");\nconst routes = JSON.parse(routesText) as RouteRecord[];\n\nconst hydrationStart = performance.now();\nawait mountClientApp({\n root, payload, routes, serverData: true, loadMiddleware,\n async loadPage(file) {\n const load = pages[file];\n if (!load) throw new Error(\"Page module missing: \" + file);\n return await load() as ClientComponent;\n },\n});\nperformance.measure(\"meeko-hydration\", { start: hydrationStart, end: performance.now() });\n";
|
|
3
|
+
export declare const serverEntrySource = "import { createFrameworkHandler, createLocalServerFetch, createRequestContext, createServerDispatcher, createSocketHub, discoverServerManifest, loadSocketChannels, type FrameworkHandlerOptions, type ServerMiddleware } from \"@meekojs/meeko/server\";\nimport type { RouteRecord, RouteMiddleware } from \"@meekojs/meeko/framework\";\nimport { modules as pages } from \"virtual:meeko-server-pages\";\nimport { modules as routeModules } from \"virtual:meeko-server-routes\";\nimport { createSessionManager } from \"../../server/session.js\";\n\nconst handlers = import.meta.glob([\"/server/api/**/*.ts\", \"/server/rpc/**/*.ts\", \"/server/socket/**/*.ts\"], { eager: true });\nconst routeMiddlewareModules = import.meta.glob([\"/app/middleware/**/*.ts\", \"!/app/middleware/**/*.test.ts\", \"!/app/middleware/**/*.spec.ts\", \"!/app/middleware/**/*.d.ts\", \"!/app/middleware/**/*.server.ts\", \"!/app/middleware/**/*.client.ts\"], { eager: true });\nconst serverMiddlewareModules = import.meta.glob([\"/server/middleware/**/*.ts\", \"!/server/middleware/**/*.test.ts\", \"!/server/middleware/**/*.spec.ts\", \"!/server/middleware/**/*.d.ts\", \"!/server/middleware/**/*.client.ts\"], { eager: true });\n\nfunction keyFor(root: string, file: string): string {\n return \"/\" + file.slice(root.length).replaceAll(\"\\\\\", \"/\").replace(/^\\//, \"\");\n}\n\nexport const loadPage = async (root: string, file: string): Promise<Awaited<ReturnType<FrameworkHandlerOptions[\"loadPage\"]>>> => {\n const key = keyFor(root, file);\n const module = pages[key];\n if (!module) throw new Error(\"Page module missing: \" + key);\n return module as Awaited<ReturnType<FrameworkHandlerOptions[\"loadPage\"]>>;\n};\n\nexport const loadRoute = async (root: string, file: string): Promise<Awaited<ReturnType<NonNullable<FrameworkHandlerOptions[\"loadRoute\"]>>>> => {\n const key = keyFor(root, file);\n const module = routeModules[key] as Record<string, unknown> | undefined;\n if (!module) throw new Error(\"Route module missing: \" + key);\n return (module.route ?? module.default ?? module) as Awaited<ReturnType<NonNullable<FrameworkHandlerOptions[\"loadRoute\"]>>>;\n};\n\nexport async function createAppRuntime(\n root: string,\n routes: readonly RouteRecord[],\n clientEntry: string,\n production: boolean,\n loadDevModule?: (file: string) => Promise<Record<string, unknown>>,\n) {\n const sessions = createSessionManager(production);\n const manifest = await discoverServerManifest(root);\n const load = async (file: string) => {\n if (loadDevModule) return loadDevModule(file);\n const module = handlers[keyFor(root, file)];\n if (!module) throw new Error(\"Server handler missing: \" + file);\n return module;\n };\n const services = {};\n const loadMiddleware = async (): Promise<RouteMiddleware[]> => Promise.all(Object.keys(routeMiddlewareModules).sort().map(async (key) => {\n const module = loadDevModule ? await loadDevModule(root + key) : routeMiddlewareModules[key] as Record<string, unknown>;\n const middleware = (module as { default?: unknown }).default;\n if (typeof middleware !== \"function\") throw new Error(key + \": route middleware requires a default function export\");\n return middleware as RouteMiddleware;\n }));\n const serverMiddleware: ServerMiddleware[] = Object.keys(serverMiddlewareModules).sort().map((key) => async (request, next) => {\n const module = loadDevModule ? await loadDevModule(root + key) : serverMiddlewareModules[key] as Record<string, unknown>;\n const middleware = (module as { default?: unknown }).default;\n if (typeof middleware !== \"function\") throw new Error(key + \": server middleware requires a default function export\");\n return (middleware as ServerMiddleware)(request, next);\n });\n const reportError = (error: unknown, requestId: string): void => {\n console.error(\"[meeko] request error \" + requestId + \" (\" + (error instanceof Error ? error.name : \"UnknownError\") + \")\");\n if (!production) console.error(error);\n };\n const pageHandler = createFrameworkHandler({\n routes,\n onError: reportError,\n loadPage: async (file) => {\n if (!loadDevModule) return loadPage(root, file);\n const module = await loadDevModule(file);\n if (typeof module.renderWithState !== \"function\") throw new Error(\"Invalid page module: \" + file);\n return module as unknown as Awaited<ReturnType<FrameworkHandlerOptions[\"loadPage\"]>>;\n },\n loadRoute: async (file) => {\n if (!loadDevModule) return loadRoute(root, file);\n const module = await loadDevModule(file);\n return (module.route ?? module.default ?? module) as Awaited<ReturnType<NonNullable<FrameworkHandlerOptions[\"loadRoute\"]>>>;\n },\n clientEntry,\n loadMiddleware,\n createContext: async (request) => createRequestContext(request, services, await sessions.load(request)),\n serverFetch: createLocalServerFetch({ manifest, sessions, load, onError: reportError }),\n });\n const endpointHandler = createServerDispatcher({ manifest, sessions, services, load, onError: reportError });\n const channels = await loadSocketChannels(manifest, load);\n const socketHub = createSocketHub({ channels, sessions, services });\n return { pageHandler, endpointHandler, socketHub, sessions, manifest, serverMiddleware };\n}\n";
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/** Vite entry modules generated for a framework application. */
|
|
2
|
+
export const clientEntrySource = `import { mountClientApp, type ClientComponent, type RouteRecord, type RouteMiddleware } from "@meekojs/meeko/framework";
|
|
3
|
+
import { modules as pages } from "virtual:meeko-client-pages";
|
|
4
|
+
|
|
5
|
+
const middlewareModules = import.meta.glob(["/app/middleware/**/*.ts", "!/app/middleware/**/*.test.ts", "!/app/middleware/**/*.spec.ts", "!/app/middleware/**/*.d.ts", "!/app/middleware/**/*.server.ts", "!/app/middleware/**/*.client.ts"], { eager: true });
|
|
6
|
+
const loadMiddleware = async (): Promise<RouteMiddleware[]> => Object.entries(middlewareModules)
|
|
7
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
8
|
+
.map(([file, module]) => {
|
|
9
|
+
const middleware = (module as { default?: unknown }).default;
|
|
10
|
+
if (typeof middleware !== "function") throw new Error(file + ": route middleware requires a default function export");
|
|
11
|
+
return middleware as RouteMiddleware;
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// Optional global stylesheet. Vite includes it in the client bundle when present.
|
|
15
|
+
import.meta.glob("/app/styles/main.css", { eager: true });
|
|
16
|
+
|
|
17
|
+
const root = document.getElementById("app");
|
|
18
|
+
const payload = document.getElementById("meeko-payload")?.textContent;
|
|
19
|
+
const routesText = document.getElementById("meeko-routes")?.textContent;
|
|
20
|
+
if (!root || !payload || !routesText) throw new Error("Missing Meeko SSR data");
|
|
21
|
+
const routes = JSON.parse(routesText) as RouteRecord[];
|
|
22
|
+
|
|
23
|
+
const hydrationStart = performance.now();
|
|
24
|
+
await mountClientApp({
|
|
25
|
+
root, payload, routes, serverData: true, loadMiddleware,
|
|
26
|
+
async loadPage(file) {
|
|
27
|
+
const load = pages[file];
|
|
28
|
+
if (!load) throw new Error("Page module missing: " + file);
|
|
29
|
+
return await load() as ClientComponent;
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
performance.measure("meeko-hydration", { start: hydrationStart, end: performance.now() });
|
|
33
|
+
`;
|
|
34
|
+
export const serverEntrySource = `import { createFrameworkHandler, createLocalServerFetch, createRequestContext, createServerDispatcher, createSocketHub, discoverServerManifest, loadSocketChannels, type FrameworkHandlerOptions, type ServerMiddleware } from "@meekojs/meeko/server";
|
|
35
|
+
import type { RouteRecord, RouteMiddleware } from "@meekojs/meeko/framework";
|
|
36
|
+
import { modules as pages } from "virtual:meeko-server-pages";
|
|
37
|
+
import { modules as routeModules } from "virtual:meeko-server-routes";
|
|
38
|
+
import { createSessionManager } from "../../server/session.js";
|
|
39
|
+
|
|
40
|
+
const handlers = import.meta.glob(["/server/api/**/*.ts", "/server/rpc/**/*.ts", "/server/socket/**/*.ts"], { eager: true });
|
|
41
|
+
const routeMiddlewareModules = import.meta.glob(["/app/middleware/**/*.ts", "!/app/middleware/**/*.test.ts", "!/app/middleware/**/*.spec.ts", "!/app/middleware/**/*.d.ts", "!/app/middleware/**/*.server.ts", "!/app/middleware/**/*.client.ts"], { eager: true });
|
|
42
|
+
const serverMiddlewareModules = import.meta.glob(["/server/middleware/**/*.ts", "!/server/middleware/**/*.test.ts", "!/server/middleware/**/*.spec.ts", "!/server/middleware/**/*.d.ts", "!/server/middleware/**/*.client.ts"], { eager: true });
|
|
43
|
+
|
|
44
|
+
function keyFor(root: string, file: string): string {
|
|
45
|
+
return "/" + file.slice(root.length).replaceAll("\\\\", "/").replace(/^\\//, "");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const loadPage = async (root: string, file: string): Promise<Awaited<ReturnType<FrameworkHandlerOptions["loadPage"]>>> => {
|
|
49
|
+
const key = keyFor(root, file);
|
|
50
|
+
const module = pages[key];
|
|
51
|
+
if (!module) throw new Error("Page module missing: " + key);
|
|
52
|
+
return module as Awaited<ReturnType<FrameworkHandlerOptions["loadPage"]>>;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const loadRoute = async (root: string, file: string): Promise<Awaited<ReturnType<NonNullable<FrameworkHandlerOptions["loadRoute"]>>>> => {
|
|
56
|
+
const key = keyFor(root, file);
|
|
57
|
+
const module = routeModules[key] as Record<string, unknown> | undefined;
|
|
58
|
+
if (!module) throw new Error("Route module missing: " + key);
|
|
59
|
+
return (module.route ?? module.default ?? module) as Awaited<ReturnType<NonNullable<FrameworkHandlerOptions["loadRoute"]>>>;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export async function createAppRuntime(
|
|
63
|
+
root: string,
|
|
64
|
+
routes: readonly RouteRecord[],
|
|
65
|
+
clientEntry: string,
|
|
66
|
+
production: boolean,
|
|
67
|
+
loadDevModule?: (file: string) => Promise<Record<string, unknown>>,
|
|
68
|
+
) {
|
|
69
|
+
const sessions = createSessionManager(production);
|
|
70
|
+
const manifest = await discoverServerManifest(root);
|
|
71
|
+
const load = async (file: string) => {
|
|
72
|
+
if (loadDevModule) return loadDevModule(file);
|
|
73
|
+
const module = handlers[keyFor(root, file)];
|
|
74
|
+
if (!module) throw new Error("Server handler missing: " + file);
|
|
75
|
+
return module;
|
|
76
|
+
};
|
|
77
|
+
const services = {};
|
|
78
|
+
const loadMiddleware = async (): Promise<RouteMiddleware[]> => Promise.all(Object.keys(routeMiddlewareModules).sort().map(async (key) => {
|
|
79
|
+
const module = loadDevModule ? await loadDevModule(root + key) : routeMiddlewareModules[key] as Record<string, unknown>;
|
|
80
|
+
const middleware = (module as { default?: unknown }).default;
|
|
81
|
+
if (typeof middleware !== "function") throw new Error(key + ": route middleware requires a default function export");
|
|
82
|
+
return middleware as RouteMiddleware;
|
|
83
|
+
}));
|
|
84
|
+
const serverMiddleware: ServerMiddleware[] = Object.keys(serverMiddlewareModules).sort().map((key) => async (request, next) => {
|
|
85
|
+
const module = loadDevModule ? await loadDevModule(root + key) : serverMiddlewareModules[key] as Record<string, unknown>;
|
|
86
|
+
const middleware = (module as { default?: unknown }).default;
|
|
87
|
+
if (typeof middleware !== "function") throw new Error(key + ": server middleware requires a default function export");
|
|
88
|
+
return (middleware as ServerMiddleware)(request, next);
|
|
89
|
+
});
|
|
90
|
+
const reportError = (error: unknown, requestId: string): void => {
|
|
91
|
+
console.error("[meeko] request error " + requestId + " (" + (error instanceof Error ? error.name : "UnknownError") + ")");
|
|
92
|
+
if (!production) console.error(error);
|
|
93
|
+
};
|
|
94
|
+
const pageHandler = createFrameworkHandler({
|
|
95
|
+
routes,
|
|
96
|
+
onError: reportError,
|
|
97
|
+
loadPage: async (file) => {
|
|
98
|
+
if (!loadDevModule) return loadPage(root, file);
|
|
99
|
+
const module = await loadDevModule(file);
|
|
100
|
+
if (typeof module.renderWithState !== "function") throw new Error("Invalid page module: " + file);
|
|
101
|
+
return module as unknown as Awaited<ReturnType<FrameworkHandlerOptions["loadPage"]>>;
|
|
102
|
+
},
|
|
103
|
+
loadRoute: async (file) => {
|
|
104
|
+
if (!loadDevModule) return loadRoute(root, file);
|
|
105
|
+
const module = await loadDevModule(file);
|
|
106
|
+
return (module.route ?? module.default ?? module) as Awaited<ReturnType<NonNullable<FrameworkHandlerOptions["loadRoute"]>>>;
|
|
107
|
+
},
|
|
108
|
+
clientEntry,
|
|
109
|
+
loadMiddleware,
|
|
110
|
+
createContext: async (request) => createRequestContext(request, services, await sessions.load(request)),
|
|
111
|
+
serverFetch: createLocalServerFetch({ manifest, sessions, load, onError: reportError }),
|
|
112
|
+
});
|
|
113
|
+
const endpointHandler = createServerDispatcher({ manifest, sessions, services, load, onError: reportError });
|
|
114
|
+
const channels = await loadSocketChannels(manifest, load);
|
|
115
|
+
const socketHub = createSocketHub({ channels, sessions, services });
|
|
116
|
+
return { pageHandler, endpointHandler, socketHub, sessions, manifest, serverMiddleware };
|
|
117
|
+
}
|
|
118
|
+
`;
|
|
119
|
+
//# sourceMappingURL=project-templates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-templates.js","sourceRoot":"","sources":["../../src/framework/project-templates.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,MAAM,CAAC,MAAM,iBAAiB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BhC,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoFhC,CAAC","sourcesContent":["/** Vite entry modules generated for a framework application. */\nexport const clientEntrySource = `import { mountClientApp, type ClientComponent, type RouteRecord, type RouteMiddleware } from \"@meekojs/meeko/framework\";\nimport { modules as pages } from \"virtual:meeko-client-pages\";\n\nconst middlewareModules = import.meta.glob([\"/app/middleware/**/*.ts\", \"!/app/middleware/**/*.test.ts\", \"!/app/middleware/**/*.spec.ts\", \"!/app/middleware/**/*.d.ts\", \"!/app/middleware/**/*.server.ts\", \"!/app/middleware/**/*.client.ts\"], { eager: true });\nconst loadMiddleware = async (): Promise<RouteMiddleware[]> => Object.entries(middlewareModules)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([file, module]) => {\n const middleware = (module as { default?: unknown }).default;\n if (typeof middleware !== \"function\") throw new Error(file + \": route middleware requires a default function export\");\n return middleware as RouteMiddleware;\n });\n\n// Optional global stylesheet. Vite includes it in the client bundle when present.\nimport.meta.glob(\"/app/styles/main.css\", { eager: true });\n\nconst root = document.getElementById(\"app\");\nconst payload = document.getElementById(\"meeko-payload\")?.textContent;\nconst routesText = document.getElementById(\"meeko-routes\")?.textContent;\nif (!root || !payload || !routesText) throw new Error(\"Missing Meeko SSR data\");\nconst routes = JSON.parse(routesText) as RouteRecord[];\n\nconst hydrationStart = performance.now();\nawait mountClientApp({\n root, payload, routes, serverData: true, loadMiddleware,\n async loadPage(file) {\n const load = pages[file];\n if (!load) throw new Error(\"Page module missing: \" + file);\n return await load() as ClientComponent;\n },\n});\nperformance.measure(\"meeko-hydration\", { start: hydrationStart, end: performance.now() });\n`;\n\nexport const serverEntrySource = `import { createFrameworkHandler, createLocalServerFetch, createRequestContext, createServerDispatcher, createSocketHub, discoverServerManifest, loadSocketChannels, type FrameworkHandlerOptions, type ServerMiddleware } from \"@meekojs/meeko/server\";\nimport type { RouteRecord, RouteMiddleware } from \"@meekojs/meeko/framework\";\nimport { modules as pages } from \"virtual:meeko-server-pages\";\nimport { modules as routeModules } from \"virtual:meeko-server-routes\";\nimport { createSessionManager } from \"../../server/session.js\";\n\nconst handlers = import.meta.glob([\"/server/api/**/*.ts\", \"/server/rpc/**/*.ts\", \"/server/socket/**/*.ts\"], { eager: true });\nconst routeMiddlewareModules = import.meta.glob([\"/app/middleware/**/*.ts\", \"!/app/middleware/**/*.test.ts\", \"!/app/middleware/**/*.spec.ts\", \"!/app/middleware/**/*.d.ts\", \"!/app/middleware/**/*.server.ts\", \"!/app/middleware/**/*.client.ts\"], { eager: true });\nconst serverMiddlewareModules = import.meta.glob([\"/server/middleware/**/*.ts\", \"!/server/middleware/**/*.test.ts\", \"!/server/middleware/**/*.spec.ts\", \"!/server/middleware/**/*.d.ts\", \"!/server/middleware/**/*.client.ts\"], { eager: true });\n\nfunction keyFor(root: string, file: string): string {\n return \"/\" + file.slice(root.length).replaceAll(\"\\\\\\\\\", \"/\").replace(/^\\\\//, \"\");\n}\n\nexport const loadPage = async (root: string, file: string): Promise<Awaited<ReturnType<FrameworkHandlerOptions[\"loadPage\"]>>> => {\n const key = keyFor(root, file);\n const module = pages[key];\n if (!module) throw new Error(\"Page module missing: \" + key);\n return module as Awaited<ReturnType<FrameworkHandlerOptions[\"loadPage\"]>>;\n};\n\nexport const loadRoute = async (root: string, file: string): Promise<Awaited<ReturnType<NonNullable<FrameworkHandlerOptions[\"loadRoute\"]>>>> => {\n const key = keyFor(root, file);\n const module = routeModules[key] as Record<string, unknown> | undefined;\n if (!module) throw new Error(\"Route module missing: \" + key);\n return (module.route ?? module.default ?? module) as Awaited<ReturnType<NonNullable<FrameworkHandlerOptions[\"loadRoute\"]>>>;\n};\n\nexport async function createAppRuntime(\n root: string,\n routes: readonly RouteRecord[],\n clientEntry: string,\n production: boolean,\n loadDevModule?: (file: string) => Promise<Record<string, unknown>>,\n) {\n const sessions = createSessionManager(production);\n const manifest = await discoverServerManifest(root);\n const load = async (file: string) => {\n if (loadDevModule) return loadDevModule(file);\n const module = handlers[keyFor(root, file)];\n if (!module) throw new Error(\"Server handler missing: \" + file);\n return module;\n };\n const services = {};\n const loadMiddleware = async (): Promise<RouteMiddleware[]> => Promise.all(Object.keys(routeMiddlewareModules).sort().map(async (key) => {\n const module = loadDevModule ? await loadDevModule(root + key) : routeMiddlewareModules[key] as Record<string, unknown>;\n const middleware = (module as { default?: unknown }).default;\n if (typeof middleware !== \"function\") throw new Error(key + \": route middleware requires a default function export\");\n return middleware as RouteMiddleware;\n }));\n const serverMiddleware: ServerMiddleware[] = Object.keys(serverMiddlewareModules).sort().map((key) => async (request, next) => {\n const module = loadDevModule ? await loadDevModule(root + key) : serverMiddlewareModules[key] as Record<string, unknown>;\n const middleware = (module as { default?: unknown }).default;\n if (typeof middleware !== \"function\") throw new Error(key + \": server middleware requires a default function export\");\n return (middleware as ServerMiddleware)(request, next);\n });\n const reportError = (error: unknown, requestId: string): void => {\n console.error(\"[meeko] request error \" + requestId + \" (\" + (error instanceof Error ? error.name : \"UnknownError\") + \")\");\n if (!production) console.error(error);\n };\n const pageHandler = createFrameworkHandler({\n routes,\n onError: reportError,\n loadPage: async (file) => {\n if (!loadDevModule) return loadPage(root, file);\n const module = await loadDevModule(file);\n if (typeof module.renderWithState !== \"function\") throw new Error(\"Invalid page module: \" + file);\n return module as unknown as Awaited<ReturnType<FrameworkHandlerOptions[\"loadPage\"]>>;\n },\n loadRoute: async (file) => {\n if (!loadDevModule) return loadRoute(root, file);\n const module = await loadDevModule(file);\n return (module.route ?? module.default ?? module) as Awaited<ReturnType<NonNullable<FrameworkHandlerOptions[\"loadRoute\"]>>>;\n },\n clientEntry,\n loadMiddleware,\n createContext: async (request) => createRequestContext(request, services, await sessions.load(request)),\n serverFetch: createLocalServerFetch({ manifest, sessions, load, onError: reportError }),\n });\n const endpointHandler = createServerDispatcher({ manifest, sessions, services, load, onError: reportError });\n const channels = await loadSocketChannels(manifest, load);\n const socketHub = createSocketHub({ channels, sessions, services });\n return { pageHandler, endpointHandler, socketHub, sessions, manifest, serverMiddleware };\n}\n`;\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Server } from "node:http";
|
|
2
|
+
export declare const frameworkClientEntry = ".meeko/generated/entry-client.ts";
|
|
3
|
+
export declare const frameworkServerEntry = ".meeko/generated/entry-server.ts";
|
|
4
|
+
/** Writes framework-owned Vite entries; no application file is overwritten. */
|
|
5
|
+
export declare function prepareProject(projectRoot: string): Promise<void>;
|
|
6
|
+
/** Builds both Vite targets and prerenders routes with the same generated entries. */
|
|
7
|
+
export declare function buildProject(projectRoot: string): Promise<void>;
|
|
8
|
+
/** Starts the package-owned Node 24 HTTP host for development or production. */
|
|
9
|
+
export declare function startProject(projectRoot: string, production: boolean): Promise<Server>;
|