@meekojs/meeko 0.0.4 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/README.md +21 -19
  2. package/dist/autoimports-ts.d.ts +4 -4
  3. package/dist/autoimports-ts.js +86 -30
  4. package/dist/autoimports-ts.js.map +1 -1
  5. package/dist/autoimports.d.ts +2 -2
  6. package/dist/autoimports.js +23 -7
  7. package/dist/autoimports.js.map +1 -1
  8. package/dist/check.js +39 -10
  9. package/dist/check.js.map +1 -1
  10. package/dist/cli.js +37 -4
  11. package/dist/cli.js.map +1 -1
  12. package/dist/compiler/events.d.ts +2 -0
  13. package/dist/compiler/events.js +6 -0
  14. package/dist/compiler/events.js.map +1 -0
  15. package/dist/compiler/props.d.ts +12 -0
  16. package/dist/compiler/props.js +32 -0
  17. package/dist/compiler/props.js.map +1 -0
  18. package/dist/compiler.js +55 -12
  19. package/dist/compiler.js.map +1 -1
  20. package/dist/component-errors.d.ts +18 -0
  21. package/dist/component-errors.js +65 -0
  22. package/dist/component-errors.js.map +1 -0
  23. package/dist/devtools.js +2 -4
  24. package/dist/devtools.js.map +1 -1
  25. package/dist/dom.d.ts +11 -5
  26. package/dist/dom.js +288 -88
  27. package/dist/dom.js.map +1 -1
  28. package/dist/error-observer.d.ts +6 -0
  29. package/dist/error-observer.js +41 -0
  30. package/dist/error-observer.js.map +1 -0
  31. package/dist/framework/check-app.js +160 -5
  32. package/dist/framework/check-app.js.map +1 -1
  33. package/dist/framework/check-project.d.ts +4 -0
  34. package/dist/framework/check-project.js +42 -0
  35. package/dist/framework/check-project.js.map +1 -0
  36. package/dist/framework/client.d.ts +3 -0
  37. package/dist/framework/client.js +136 -33
  38. package/dist/framework/client.js.map +1 -1
  39. package/dist/framework/clients.js +12 -4
  40. package/dist/framework/clients.js.map +1 -1
  41. package/dist/framework/contracts-generate.js +66 -3
  42. package/dist/framework/contracts-generate.js.map +1 -1
  43. package/dist/framework/init.js +21 -200
  44. package/dist/framework/init.js.map +1 -1
  45. package/dist/framework/middleware.d.ts +14 -0
  46. package/dist/framework/middleware.js +16 -0
  47. package/dist/framework/middleware.js.map +1 -0
  48. package/dist/framework/page-contracts-check.d.ts +8 -0
  49. package/dist/framework/page-contracts-check.js +110 -0
  50. package/dist/framework/page-contracts-check.js.map +1 -0
  51. package/dist/framework/page-contracts-generate.d.ts +2 -0
  52. package/dist/framework/page-contracts-generate.js +94 -0
  53. package/dist/framework/page-contracts-generate.js.map +1 -0
  54. package/dist/framework/page.d.ts +7 -3
  55. package/dist/framework/page.js +3 -1
  56. package/dist/framework/page.js.map +1 -1
  57. package/dist/framework/prerender.js +11 -5
  58. package/dist/framework/prerender.js.map +1 -1
  59. package/dist/framework/project-templates.d.ts +3 -0
  60. package/dist/framework/project-templates.js +119 -0
  61. package/dist/framework/project-templates.js.map +1 -0
  62. package/dist/framework/project.d.ts +9 -0
  63. package/dist/framework/project.js +272 -0
  64. package/dist/framework/project.js.map +1 -0
  65. package/dist/framework/router.d.ts +3 -1
  66. package/dist/framework/router.js +94 -8
  67. package/dist/framework/router.js.map +1 -1
  68. package/dist/framework/server.d.ts +17 -3
  69. package/dist/framework/server.js +71 -24
  70. package/dist/framework/server.js.map +1 -1
  71. package/dist/framework/socket-contracts-generate.js +8 -5
  72. package/dist/framework/socket-contracts-generate.js.map +1 -1
  73. package/dist/framework/socket.d.ts +2 -0
  74. package/dist/framework/socket.js +119 -17
  75. package/dist/framework/socket.js.map +1 -1
  76. package/dist/framework/standard-schema-types.d.ts +7 -0
  77. package/dist/framework/standard-schema-types.js +69 -0
  78. package/dist/framework/standard-schema-types.js.map +1 -0
  79. package/dist/framework/state.d.ts +7 -0
  80. package/dist/framework/state.js +78 -14
  81. package/dist/framework/state.js.map +1 -1
  82. package/dist/framework/styles.d.ts +2 -0
  83. package/dist/framework/styles.js +6 -0
  84. package/dist/framework/styles.js.map +1 -0
  85. package/dist/framework/type-declarations.d.ts +5 -0
  86. package/dist/framework/type-declarations.js +84 -0
  87. package/dist/framework/type-declarations.js.map +1 -0
  88. package/dist/framework-server/contracts.d.ts +11 -2
  89. package/dist/framework-server/contracts.js +62 -7
  90. package/dist/framework-server/contracts.js.map +1 -1
  91. package/dist/framework-server/discovery.d.ts +4 -1
  92. package/dist/framework-server/discovery.js +21 -16
  93. package/dist/framework-server/discovery.js.map +1 -1
  94. package/dist/framework-server/local.d.ts +2 -0
  95. package/dist/framework-server/local.js +5 -3
  96. package/dist/framework-server/local.js.map +1 -1
  97. package/dist/framework-server/middleware.d.ts +4 -0
  98. package/dist/framework-server/middleware.js +56 -0
  99. package/dist/framework-server/middleware.js.map +1 -0
  100. package/dist/framework-server/node-upgrade.js +27 -9
  101. package/dist/framework-server/node-upgrade.js.map +1 -1
  102. package/dist/framework-server/node-websocket.js +4 -2
  103. package/dist/framework-server/node-websocket.js.map +1 -1
  104. package/dist/framework-server/session-stores.d.ts +9 -2
  105. package/dist/framework-server/session-stores.js +37 -11
  106. package/dist/framework-server/session-stores.js.map +1 -1
  107. package/dist/framework-server/sessions.d.ts +6 -0
  108. package/dist/framework-server/sessions.js +41 -10
  109. package/dist/framework-server/sessions.js.map +1 -1
  110. package/dist/framework-server/socket.js +62 -27
  111. package/dist/framework-server/socket.js.map +1 -1
  112. package/dist/framework-server.d.ts +2 -0
  113. package/dist/framework-server.js +1 -0
  114. package/dist/framework-server.js.map +1 -1
  115. package/dist/framework.d.ts +3 -1
  116. package/dist/framework.js +1 -0
  117. package/dist/framework.js.map +1 -1
  118. package/dist/hmr.js +74 -24
  119. package/dist/hmr.js.map +1 -1
  120. package/dist/index.d.ts +2 -0
  121. package/dist/index.js +1 -0
  122. package/dist/index.js.map +1 -1
  123. package/dist/init.js +27 -1
  124. package/dist/init.js.map +1 -1
  125. package/dist/internal.d.ts +2 -2
  126. package/dist/internal.js +2 -2
  127. package/dist/internal.js.map +1 -1
  128. package/dist/node.d.ts +5 -1
  129. package/dist/node.js +80 -10
  130. package/dist/node.js.map +1 -1
  131. package/dist/reactivity.d.ts +12 -3
  132. package/dist/reactivity.js +94 -17
  133. package/dist/reactivity.js.map +1 -1
  134. package/dist/types.d.ts +2 -0
  135. package/dist/types.js +3 -1
  136. package/dist/types.js.map +1 -1
  137. package/dist/vite.js +29 -9
  138. package/dist/vite.js.map +1 -1
  139. package/package.json +2 -2
@@ -0,0 +1,110 @@
1
+ import { execFile } from "node:child_process";
2
+ import { readFile, writeFile } from "node:fs/promises";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { promisify } from "node:util";
6
+ import ts from "@typescript/typescript6";
7
+ import { parseSfc } from "../compiler/sfc.js";
8
+ import { prepareFrameworkTypeDeclarations } from "./type-declarations.js";
9
+ const execute = promisify(execFile);
10
+ const typescriptPackage = fileURLToPath(import.meta.resolve("typescript/package.json"));
11
+ function contractSourceOffset(source, filename) {
12
+ const script = parseSfc(source, filename).script;
13
+ const ast = ts.createSourceFile(filename, script.content, ts.ScriptTarget.Latest, true);
14
+ for (const statement of ast.statements) {
15
+ if (!ts.isVariableStatement(statement))
16
+ continue;
17
+ for (const declaration of statement.declarationList.declarations) {
18
+ const call = declaration.initializer;
19
+ if (call && ts.isCallExpression(call) && ts.isIdentifier(call.expression) && call.expression.text === "defineProps") {
20
+ return script.contentStart + (call.typeArguments?.[0]?.getStart(ast) ?? call.getStart(ast));
21
+ }
22
+ }
23
+ }
24
+ return script.contentStart;
25
+ }
26
+ function contractAliases(source, filename) {
27
+ const ast = ts.createSourceFile(filename, source, ts.ScriptTarget.Latest, true);
28
+ const aliases = new Map();
29
+ const literal = (node) => node && ts.isLiteralTypeNode(node) && ts.isStringLiteral(node.literal) ? node.literal.text : undefined;
30
+ for (const statement of ast.statements) {
31
+ if (!ts.isTypeAliasDeclaration(statement) || !ts.isTypeReferenceNode(statement.type))
32
+ continue;
33
+ const check = statement.type.typeArguments?.[0];
34
+ if (!check || !ts.isTypeReferenceNode(check))
35
+ continue;
36
+ const file = literal(check.typeArguments?.[2]);
37
+ if (!file)
38
+ continue;
39
+ const kind = statement.name.text.startsWith("__layout") ? "MEEK_LAYOUT_PROPS"
40
+ : statement.name.text.startsWith("__error") ? "MEEK_ERROR_PROPS" : "MEEK_PAGE_PROPS";
41
+ const sidecar = kind === "MEEK_PAGE_PROPS" ? literal(check.typeArguments?.[3]) : undefined;
42
+ aliases.set(ast.getLineAndCharacterOfPosition(statement.getStart(ast)).line + 1, {
43
+ file, kind, ...(sidecar === undefined ? {} : { sidecar }),
44
+ });
45
+ }
46
+ return aliases;
47
+ }
48
+ function samePath(left, right) {
49
+ const a = resolve(left);
50
+ const b = resolve(right);
51
+ return process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
52
+ }
53
+ /** Checks generated route/component contracts after a saved project change. */
54
+ export async function checkFrameworkPageContracts(projectRoot) {
55
+ const root = resolve(projectRoot);
56
+ await prepareFrameworkTypeDeclarations(root);
57
+ const generated = join(root, ".meeko", "generated", "page-contracts.server.ts");
58
+ const aliases = contractAliases(await readFile(generated, "utf8"), generated);
59
+ // The editor's isolated consumer may not install the optional Vite peer or
60
+ // @types/node. The contract itself only needs component declarations.
61
+ const config = join(root, ".meeko", "tsconfig.page-contracts.json");
62
+ const configSource = `${JSON.stringify({
63
+ compilerOptions: {
64
+ target: "ES2022", module: "NodeNext", moduleResolution: "NodeNext", lib: ["ES2022", "DOM"],
65
+ types: [], strict: true, noEmit: true, skipLibCheck: true, allowArbitraryExtensions: true,
66
+ rootDirs: ["..", "./types"],
67
+ },
68
+ include: ["generated/page-contracts.server.ts", "types/**/*.d.meeko.ts", "types/routes.d.ts"],
69
+ }, null, 2)}\n`;
70
+ try {
71
+ if (await readFile(config, "utf8") !== configSource)
72
+ await writeFile(config, configSource);
73
+ }
74
+ catch (error) {
75
+ if (error.code === "ENOENT")
76
+ await writeFile(config, configSource);
77
+ else
78
+ throw error;
79
+ }
80
+ const compiler = join(dirname(typescriptPackage), "bin", "tsc");
81
+ let output = "";
82
+ try {
83
+ await execute(process.execPath, [compiler, "-p", config, "--noEmit", "--pretty", "false"]);
84
+ }
85
+ catch (error) {
86
+ const failure = error;
87
+ output = `${failure.stdout ?? ""}${failure.stderr ?? ""}`;
88
+ if (!output.trim())
89
+ throw error;
90
+ }
91
+ const diagnostics = [];
92
+ for (const line of output.split(/\r?\n/)) {
93
+ const match = /^(.*page-contracts\.server\.ts)\((\d+),\d+\): error TS2344: (.*)$/.exec(line);
94
+ if (!match || !samePath(match[1], generated))
95
+ continue;
96
+ const alias = aliases.get(Number(match[2]));
97
+ if (!alias)
98
+ continue;
99
+ const filename = resolve(root, alias.file);
100
+ const source = await readFile(filename, "utf8");
101
+ const unsafe = match[3].includes("props cannot be any, never or unknown");
102
+ const title = alias.kind === "MEEK_PAGE_PROPS" ? "Page" : alias.kind === "MEEK_LAYOUT_PROPS" ? "Layout" : "Error page";
103
+ const message = unsafe ? `${title} ${alias.file} props cannot be any, never or unknown`
104
+ : alias.kind === "MEEK_PAGE_PROPS" ? `Page ${alias.file} props do not match loader ${alias.sidecar ?? "(no sidecar)"}`
105
+ : `${title} ${alias.file} props do not match the values supplied by Meeko`;
106
+ diagnostics.push({ filename, offset: contractSourceOffset(source, filename), code: alias.kind, message });
107
+ }
108
+ return diagnostics;
109
+ }
110
+ //# sourceMappingURL=page-contracts-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-contracts-check.js","sourceRoot":"","sources":["../../src/framework/page-contracts-check.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFile } from \"node:child_process\";\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { promisify } from \"node:util\";\nimport ts from \"@typescript/typescript6\";\nimport { parseSfc } from \"../compiler/sfc.js\";\nimport { prepareFrameworkTypeDeclarations } from \"./type-declarations.js\";\n\nconst execute = promisify(execFile);\nconst typescriptPackage = fileURLToPath(import.meta.resolve(\"typescript/package.json\"));\n\nexport interface PageContractDiagnostic {\n readonly filename: string;\n readonly offset: number;\n readonly code: \"MEEK_PAGE_PROPS\" | \"MEEK_LAYOUT_PROPS\" | \"MEEK_ERROR_PROPS\";\n readonly message: string;\n}\n\nfunction contractSourceOffset(source: string, filename: string): number {\n const script = parseSfc(source, filename).script;\n const ast = ts.createSourceFile(filename, script.content, ts.ScriptTarget.Latest, true);\n for (const statement of ast.statements) {\n if (!ts.isVariableStatement(statement)) continue;\n for (const declaration of statement.declarationList.declarations) {\n const call = declaration.initializer;\n if (call && ts.isCallExpression(call) && ts.isIdentifier(call.expression) && call.expression.text === \"defineProps\") {\n return script.contentStart + (call.typeArguments?.[0]?.getStart(ast) ?? call.getStart(ast));\n }\n }\n }\n return script.contentStart;\n}\n\nfunction contractAliases(source: string, filename: string): Map<number, { file: string; kind: PageContractDiagnostic[\"code\"]; sidecar?: string }> {\n const ast = ts.createSourceFile(filename, source, ts.ScriptTarget.Latest, true);\n const aliases = new Map<number, { file: string; kind: PageContractDiagnostic[\"code\"]; sidecar?: string }>();\n const literal = (node: ts.TypeNode | undefined): string | undefined =>\n node && ts.isLiteralTypeNode(node) && ts.isStringLiteral(node.literal) ? node.literal.text : undefined;\n for (const statement of ast.statements) {\n if (!ts.isTypeAliasDeclaration(statement) || !ts.isTypeReferenceNode(statement.type)) continue;\n const check = statement.type.typeArguments?.[0];\n if (!check || !ts.isTypeReferenceNode(check)) continue;\n const file = literal(check.typeArguments?.[2]);\n if (!file) continue;\n const kind = statement.name.text.startsWith(\"__layout\") ? \"MEEK_LAYOUT_PROPS\"\n : statement.name.text.startsWith(\"__error\") ? \"MEEK_ERROR_PROPS\" : \"MEEK_PAGE_PROPS\";\n const sidecar = kind === \"MEEK_PAGE_PROPS\" ? literal(check.typeArguments?.[3]) : undefined;\n aliases.set(ast.getLineAndCharacterOfPosition(statement.getStart(ast)).line + 1, {\n file, kind, ...(sidecar === undefined ? {} : { sidecar }),\n });\n }\n return aliases;\n}\n\nfunction samePath(left: string, right: string): boolean {\n const a = resolve(left);\n const b = resolve(right);\n return process.platform === \"win32\" ? a.toLowerCase() === b.toLowerCase() : a === b;\n}\n\n/** Checks generated route/component contracts after a saved project change. */\nexport async function checkFrameworkPageContracts(projectRoot: string): Promise<PageContractDiagnostic[]> {\n const root = resolve(projectRoot);\n await prepareFrameworkTypeDeclarations(root);\n const generated = join(root, \".meeko\", \"generated\", \"page-contracts.server.ts\");\n const aliases = contractAliases(await readFile(generated, \"utf8\"), generated);\n // The editor's isolated consumer may not install the optional Vite peer or\n // @types/node. The contract itself only needs component declarations.\n const config = join(root, \".meeko\", \"tsconfig.page-contracts.json\");\n const configSource = `${JSON.stringify({\n compilerOptions: {\n target: \"ES2022\", module: \"NodeNext\", moduleResolution: \"NodeNext\", lib: [\"ES2022\", \"DOM\"],\n types: [], strict: true, noEmit: true, skipLibCheck: true, allowArbitraryExtensions: true,\n rootDirs: [\"..\", \"./types\"],\n },\n include: [\"generated/page-contracts.server.ts\", \"types/**/*.d.meeko.ts\", \"types/routes.d.ts\"],\n }, null, 2)}\\n`;\n try { if (await readFile(config, \"utf8\") !== configSource) await writeFile(config, configSource); }\n catch (error) { if ((error as NodeJS.ErrnoException).code === \"ENOENT\") await writeFile(config, configSource); else throw error; }\n const compiler = join(dirname(typescriptPackage), \"bin\", \"tsc\");\n let output = \"\";\n try { await execute(process.execPath, [compiler, \"-p\", config, \"--noEmit\", \"--pretty\", \"false\"]); }\n catch (error) {\n const failure = error as Error & { stdout?: string; stderr?: string };\n output = `${failure.stdout ?? \"\"}${failure.stderr ?? \"\"}`;\n if (!output.trim()) throw error;\n }\n const diagnostics: PageContractDiagnostic[] = [];\n for (const line of output.split(/\\r?\\n/)) {\n const match = /^(.*page-contracts\\.server\\.ts)\\((\\d+),\\d+\\): error TS2344: (.*)$/.exec(line);\n if (!match || !samePath(match[1]!, generated)) continue;\n const alias = aliases.get(Number(match[2]));\n if (!alias) continue;\n const filename = resolve(root, alias.file);\n const source = await readFile(filename, \"utf8\");\n const unsafe = match[3]!.includes(\"props cannot be any, never or unknown\");\n const title = alias.kind === \"MEEK_PAGE_PROPS\" ? \"Page\" : alias.kind === \"MEEK_LAYOUT_PROPS\" ? \"Layout\" : \"Error page\";\n const message = unsafe ? `${title} ${alias.file} props cannot be any, never or unknown`\n : alias.kind === \"MEEK_PAGE_PROPS\" ? `Page ${alias.file} props do not match loader ${alias.sidecar ?? \"(no sidecar)\"}`\n : `${title} ${alias.file} props do not match the values supplied by Meeko`;\n diagnostics.push({ filename, offset: contractSourceOffset(source, filename), code: alias.kind, message });\n }\n return diagnostics;\n}\n"]}
@@ -0,0 +1,2 @@
1
+ /** Checks that each page accepts its route params, search and loader data. */
2
+ export declare function generatePageContracts(projectRoot: string): Promise<string>;
@@ -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":"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","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"]}
@@ -1,6 +1,5 @@
1
1
  /** Browser-safe subset of Standard Schema v1, implemented by Valibot schemas. */
2
2
  import type { StateStore } from "./state.js";
3
- import type { PageRoute } from "./server.js";
4
3
  export interface PageSchema<I = unknown, O = I> {
5
4
  readonly "~standard": {
6
5
  readonly version: 1;
@@ -35,7 +34,7 @@ export interface PageLoadContext<P extends PageSchema | undefined, S extends Pag
35
34
  export interface PageDefinition<P extends PageSchema | undefined, S extends PageSchema | undefined, D, C = unknown> {
36
35
  readonly params?: P;
37
36
  readonly search?: S;
38
- readonly render?: "ssr" | "prerender" | "client";
37
+ readonly render?: "ssr" | "prerender";
39
38
  readonly prerenderEntries?: readonly Readonly<Record<string, string>>[] | (() => Promise<readonly Readonly<Record<string, string>>[]>);
40
39
  readonly load?: (args: PageLoadContext<P, S, C>) => Promise<D> | D;
41
40
  readonly head?: (args: PageLoadContext<P, S, C> & {
@@ -45,7 +44,12 @@ export interface PageDefinition<P extends PageSchema | undefined, S extends Page
45
44
  };
46
45
  }
47
46
  /** Captures route schemas and keeps loader/head inference in one declaration. */
48
- 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> & PageRoute;
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>;
49
53
  export declare class PageInputError extends Error {
50
54
  readonly code: "INVALID_PARAMS" | "INVALID_SEARCH";
51
55
  constructor(code: "INVALID_PARAMS" | "INVALID_SEARCH");
@@ -1,5 +1,7 @@
1
- /** Captures route schemas and keeps loader/head inference in one declaration. */
2
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
+ }
3
5
  return definition;
4
6
  }
5
7
  export class PageInputError extends Error {
@@ -1 +1 @@
1
- {"version":3,"file":"page.js","sourceRoot":"","sources":["../../src/framework/page.ts"],"names":[],"mappings":"AAqCA,iFAAiF;AACjF,MAAM,UAAU,UAAU,CAKxB,UAAsC;IACtC,OAAO,UAAoD,CAAC;AAC9D,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\";\nimport type { PageRoute } from \"./server.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\" | \"client\";\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>): PageDefinition<P, S, D, C> & PageRoute {\n return definition as PageDefinition<P, S, D, C> & PageRoute;\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"]}
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"]}
@@ -5,6 +5,7 @@ import { serializeJson } from "../server.js";
5
5
  import { href, matchRoute } from "./router.js";
6
6
  import { createFrameworkHandler } from "./server.js";
7
7
  import { discoverConfiguredRoutes } from "./config.js";
8
+ import { stylesheetLinks } from "./styles.js";
8
9
  function hasParams(id) {
9
10
  return /\[(?:\.\.\.)?[A-Za-z_$][\w$]*\]/.test(id);
10
11
  }
@@ -65,16 +66,19 @@ export async function prerenderProject(projectRoot) {
65
66
  catch {
66
67
  throw new Error("Client build manifest is missing; run the client build before prerender");
67
68
  }
68
- const entry = manifest["app/entry-client.ts"]?.file;
69
+ const entry = manifest[".meeko/generated/entry-client.ts"]?.file
70
+ ?? manifest["app/entry-client.ts"]?.file;
69
71
  if (!entry)
70
- throw new Error("Client build manifest has no app/entry-client.ts 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));
71
75
  const serverBundle = resolve(root, ".meeko/output/server/entry.js");
72
76
  let server;
73
77
  try {
74
78
  server = await import(pathToFileURL(serverBundle).href);
75
79
  }
76
- catch {
77
- throw new Error("Server build is missing; run the server build before prerender");
80
+ catch (error) {
81
+ throw new Error(`Cannot load the server build at ${serverBundle}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
78
82
  }
79
83
  if (typeof server.loadPage !== "function" || typeof server.loadRoute !== "function") {
80
84
  throw new Error("Server build does not expose the Meeko prerender loaders");
@@ -92,7 +96,9 @@ export async function prerenderProject(projectRoot) {
92
96
  return await prerenderRoutes({
93
97
  routes, handler, loadRoute,
94
98
  outputRoot: resolve(root, ".meeko/output/prerender"),
95
- transformHtml: (html) => html.replace("</body>", `<script id="meeko-routes" type="application/json">${serializeJson(portable)}</script></body>`),
99
+ transformHtml: (html) => html
100
+ .replace("</head>", `${globalStyles}</head>`)
101
+ .replace("</body>", `<script id="meeko-routes" type="application/json">${serializeJson(portable)}</script></body>`),
96
102
  });
97
103
  }
98
104
  function fileRelative(root, file) {
@@ -1 +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 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 }>;\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[\"app/entry-client.ts\"]?.file;\n if (!entry) throw new Error(\"Client build manifest has no app/entry-client.ts entry\");\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 {\n throw new Error(\"Server build is missing; run the server build before prerender\");\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.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"]}
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"]}