@meekojs/meeko 0.0.2 → 0.0.4
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 +40 -2
- package/dist/autoimports-ts.d.ts +26 -0
- package/dist/autoimports-ts.js +127 -0
- package/dist/autoimports-ts.js.map +1 -0
- package/dist/autoimports.d.ts +21 -0
- package/dist/autoimports.js +285 -0
- package/dist/autoimports.js.map +1 -0
- package/dist/check.js +35 -23
- package/dist/check.js.map +1 -0
- package/dist/cli.js +59 -19
- package/dist/cli.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/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 +22 -11
- package/dist/compiler.js.map +1 -0
- package/dist/devtools-ui/index.js +56 -6
- package/dist/devtools-ui/index.js.map +1 -0
- package/dist/devtools.d.ts +9 -0
- package/dist/devtools.js +16 -6
- package/dist/devtools.js.map +1 -0
- package/dist/dom.d.ts +17 -6
- package/dist/dom.js +86 -19
- package/dist/dom.js.map +1 -0
- package/dist/framework/check-app.d.ts +5 -0
- package/dist/framework/check-app.js +59 -0
- package/dist/framework/check-app.js.map +1 -0
- package/dist/framework/client.d.ts +45 -0
- package/dist/framework/client.js +146 -0
- package/dist/framework/client.js.map +1 -0
- package/dist/framework/clients.d.ts +82 -0
- package/dist/framework/clients.js +182 -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 +227 -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 +362 -0
- package/dist/framework/init.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.d.ts +60 -0
- package/dist/framework/page.js +42 -0
- package/dist/framework/page.js.map +1 -0
- package/dist/framework/prerender.d.ts +14 -0
- package/dist/framework/prerender.js +104 -0
- package/dist/framework/prerender.js.map +1 -0
- package/dist/framework/router.d.ts +39 -0
- package/dist/framework/router.js +173 -0
- package/dist/framework/router.js.map +1 -0
- package/dist/framework/server.d.ts +51 -0
- package/dist/framework/server.js +192 -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 +159 -0
- package/dist/framework/socket-contracts-generate.js.map +1 -0
- package/dist/framework/socket.d.ts +70 -0
- package/dist/framework/socket.js +150 -0
- package/dist/framework/socket.js.map +1 -0
- package/dist/framework/state.d.ts +15 -0
- package/dist/framework/state.js +72 -0
- package/dist/framework/state.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 +64 -0
- package/dist/framework-server/contracts.js +129 -0
- package/dist/framework-server/contracts.js.map +1 -0
- package/dist/framework-server/discovery.d.ts +40 -0
- package/dist/framework-server/discovery.js +252 -0
- package/dist/framework-server/discovery.js.map +1 -0
- package/dist/framework-server/local.d.ts +19 -0
- package/dist/framework-server/local.js +98 -0
- package/dist/framework-server/local.js.map +1 -0
- package/dist/framework-server/node-upgrade.d.ts +23 -0
- package/dist/framework-server/node-upgrade.js +78 -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 +125 -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 +66 -0
- package/dist/framework-server/session-stores.js +96 -0
- package/dist/framework-server/session-stores.js.map +1 -0
- package/dist/framework-server/sessions.d.ts +64 -0
- package/dist/framework-server/sessions.js +136 -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 +161 -0
- package/dist/framework-server/socket.js.map +1 -0
- package/dist/framework-server.d.ts +21 -0
- package/dist/framework-server.js +12 -0
- package/dist/framework-server.js.map +1 -0
- package/dist/framework.d.ts +29 -0
- package/dist/framework.js +13 -0
- package/dist/framework.js.map +1 -0
- package/dist/hmr.js +1 -0
- package/dist/hmr.js.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +2 -0
- package/dist/init.js +209 -0
- package/dist/init.js.map +1 -0
- package/dist/internal.js +1 -0
- package/dist/internal.js.map +1 -0
- package/dist/node.d.ts +12 -0
- package/dist/node.js +52 -0
- package/dist/node.js.map +1 -0
- package/dist/reactivity.d.ts +25 -1
- package/dist/reactivity.js +69 -10
- package/dist/reactivity.js.map +1 -0
- package/dist/server.js +1 -0
- package/dist/server.js.map +1 -0
- package/dist/types.js +23 -8
- package/dist/types.js.map +1 -0
- package/dist/vite.js +91 -6
- package/dist/vite.js.map +1 -0
- package/package.json +19 -3
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
3
|
+
const allowedEntries = new Set(["package.json", "package-lock.json", "pnpm-lock.yaml", "pnpm-workspace.yaml", "yarn.lock", "node_modules", ".npmrc", ".git"]);
|
|
4
|
+
const files = {
|
|
5
|
+
".gitignore": "node_modules/\n.meeko/\n",
|
|
6
|
+
"public/.gitkeep": "",
|
|
7
|
+
"meeko.config.ts": `import { defineMeekoConfig } from "@meekojs/meeko/framework";
|
|
8
|
+
|
|
9
|
+
export default defineMeekoConfig({
|
|
10
|
+
extends: [],
|
|
11
|
+
modules: [],
|
|
12
|
+
imports: { serverDirs: [] },
|
|
13
|
+
});
|
|
14
|
+
`,
|
|
15
|
+
"vite.config.ts": `import { defineConfig } from "vite";
|
|
16
|
+
import { meeko } from "@meekojs/meeko/vite";
|
|
17
|
+
|
|
18
|
+
export default defineConfig({
|
|
19
|
+
plugins: [meeko()],
|
|
20
|
+
build: {
|
|
21
|
+
outDir: ".meeko/output/client",
|
|
22
|
+
manifest: true,
|
|
23
|
+
rollupOptions: { input: "app/entry-client.ts" },
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
`,
|
|
27
|
+
"vite.server.config.ts": `import { defineConfig } from "vite";
|
|
28
|
+
import { meeko } from "@meekojs/meeko/vite";
|
|
29
|
+
|
|
30
|
+
export default defineConfig({
|
|
31
|
+
plugins: [meeko()],
|
|
32
|
+
build: {
|
|
33
|
+
ssr: "server/entry.ts",
|
|
34
|
+
outDir: ".meeko/output/server",
|
|
35
|
+
rollupOptions: { output: { entryFileNames: "entry.js" } },
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
`,
|
|
39
|
+
"tsconfig.json": `{
|
|
40
|
+
"compilerOptions": {
|
|
41
|
+
"target": "ES2022",
|
|
42
|
+
"module": "NodeNext",
|
|
43
|
+
"moduleResolution": "NodeNext",
|
|
44
|
+
"lib": ["ES2022", "DOM"],
|
|
45
|
+
"types": ["node", "vite/client"],
|
|
46
|
+
"strict": true,
|
|
47
|
+
"noEmit": true,
|
|
48
|
+
"allowArbitraryExtensions": true,
|
|
49
|
+
"skipLibCheck": true
|
|
50
|
+
},
|
|
51
|
+
"include": ["vite.config.ts", "vite.server.config.ts"]
|
|
52
|
+
}
|
|
53
|
+
`,
|
|
54
|
+
"tsconfig.app.json": `{
|
|
55
|
+
"extends": "./tsconfig.json",
|
|
56
|
+
"compilerOptions": { "rootDirs": [".", ".meeko/types"] },
|
|
57
|
+
"include": ["app/**/*.ts", "layers/**/app/**/*.ts", "modules/**/app/**/*.ts", ".meeko/types/**/*.d.meeko.ts", ".meeko/types/routes.d.ts", ".meeko/types/virtual-pages.d.ts", ".meeko/types/server-contracts.d.ts", ".meeko/types/autoimports/client.d.ts"],
|
|
58
|
+
"exclude": ["app/pages/**/*.route.ts", "layers/**/app/pages/**/*.route.ts", "modules/**/app/pages/**/*.route.ts"]
|
|
59
|
+
}
|
|
60
|
+
`,
|
|
61
|
+
"tsconfig.server.json": `{
|
|
62
|
+
"extends": "./tsconfig.json",
|
|
63
|
+
"include": ["server/**/*.ts", "app/pages/**/*.route.ts", "layers/**/app/pages/**/*.route.ts", "modules/**/app/pages/**/*.route.ts", "layers/**/server/**/*.ts", "modules/**/server/**/*.ts", ".meeko/types/virtual-pages.d.ts", ".meeko/types/autoimports/server.d.ts"]
|
|
64
|
+
}
|
|
65
|
+
`,
|
|
66
|
+
"tsconfig.shared.json": `{
|
|
67
|
+
"extends": "./tsconfig.json",
|
|
68
|
+
"include": ["shared/**/*.ts", "layers/**/shared/**/*.ts", "modules/**/shared/**/*.ts", ".meeko/types/autoimports/shared.d.ts"]
|
|
69
|
+
}
|
|
70
|
+
`,
|
|
71
|
+
"app/pages/index.meeko": `<script lang="ts">
|
|
72
|
+
import { signal } from "@meekojs/meeko";
|
|
73
|
+
|
|
74
|
+
export interface Props {
|
|
75
|
+
params: Record<string, string>;
|
|
76
|
+
search: Record<string, string>;
|
|
77
|
+
data?: unknown;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const count = signal(0);
|
|
81
|
+
</script>
|
|
82
|
+
|
|
83
|
+
<template>
|
|
84
|
+
<section>
|
|
85
|
+
<h1>Welcome to Meeko</h1>
|
|
86
|
+
<p>File routes, server rendering, and hydration are ready.</p>
|
|
87
|
+
<button type="button" on:click="() => count.value++">Count: {count.value}</button>
|
|
88
|
+
</section>
|
|
89
|
+
</template>
|
|
90
|
+
`,
|
|
91
|
+
"app/pages/index.route.ts": `import { definePage } from "@meekojs/meeko/framework";
|
|
92
|
+
|
|
93
|
+
export const route = definePage({ render: "prerender" });
|
|
94
|
+
`,
|
|
95
|
+
"app/app.meeko": `<script lang="ts">
|
|
96
|
+
import type { ComponentModule } from "@meekojs/meeko";
|
|
97
|
+
|
|
98
|
+
export interface Props {
|
|
99
|
+
outlet: ComponentModule<Record<string, unknown>>;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const props = defineProps<Props>();
|
|
103
|
+
</script>
|
|
104
|
+
|
|
105
|
+
<template>
|
|
106
|
+
<div class="site-root"><component is="props.outlet"></component></div>
|
|
107
|
+
</template>
|
|
108
|
+
`,
|
|
109
|
+
"app/pages/+layout.meeko": `<script lang="ts">
|
|
110
|
+
import type { ComponentModule } from "@meekojs/meeko";
|
|
111
|
+
|
|
112
|
+
export interface Props {
|
|
113
|
+
outlet: ComponentModule<Record<string, unknown>>;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const props = defineProps<Props>();
|
|
117
|
+
</script>
|
|
118
|
+
|
|
119
|
+
<template>
|
|
120
|
+
<div class="app-shell">
|
|
121
|
+
<header><a href="/">Meeko</a></header>
|
|
122
|
+
<main><component is="props.outlet"></component></main>
|
|
123
|
+
</div>
|
|
124
|
+
</template>
|
|
125
|
+
|
|
126
|
+
<style scoped>
|
|
127
|
+
.app-shell { max-width: 64rem; margin: 0 auto; padding: 2rem; font-family: system-ui, sans-serif; }
|
|
128
|
+
header { margin-bottom: 2rem; }
|
|
129
|
+
</style>
|
|
130
|
+
`,
|
|
131
|
+
"app/entry-client.ts": `import { mountClientApp, type ClientComponent, type RouteRecord } from "@meekojs/meeko/framework";
|
|
132
|
+
import { modules as pages } from "virtual:meeko-client-pages";
|
|
133
|
+
|
|
134
|
+
const root = document.getElementById("app");
|
|
135
|
+
const payload = document.getElementById("meeko-payload")?.textContent;
|
|
136
|
+
const routesText = document.getElementById("meeko-routes")?.textContent;
|
|
137
|
+
if (!root || !payload || !routesText) throw new Error("Missing Meeko SSR data");
|
|
138
|
+
const routes = JSON.parse(routesText) as RouteRecord[];
|
|
139
|
+
|
|
140
|
+
await mountClientApp({
|
|
141
|
+
root, payload, routes, serverData: true,
|
|
142
|
+
async loadPage(file) {
|
|
143
|
+
const load = pages[file];
|
|
144
|
+
if (!load) throw new Error("Page module missing: " + file);
|
|
145
|
+
return await load() as ClientComponent;
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
`,
|
|
149
|
+
"server/entry.ts": `import { createFrameworkHandler, createLocalServerFetch, createRequestContext, createServerDispatcher, createSocketHub, discoverServerManifest, loadSocketChannels, type FrameworkHandlerOptions } from "@meekojs/meeko/server";
|
|
150
|
+
import type { RouteRecord } from "@meekojs/meeko/framework";
|
|
151
|
+
import { modules as pages } from "virtual:meeko-server-pages";
|
|
152
|
+
import { modules as routeModules } from "virtual:meeko-server-routes";
|
|
153
|
+
import { createSessionManager } from "./session.js";
|
|
154
|
+
|
|
155
|
+
const handlers = import.meta.glob(["/server/api/**/*.ts", "/server/rpc/**/*.ts", "/server/socket/**/*.ts"], { eager: true });
|
|
156
|
+
|
|
157
|
+
function keyFor(root: string, file: string): string {
|
|
158
|
+
return "/" + file.slice(root.length).replaceAll("\\\\", "/").replace(/^\\//, "");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export const loadPage = async (root: string, file: string): Promise<Awaited<ReturnType<FrameworkHandlerOptions["loadPage"]>>> => {
|
|
162
|
+
const key = keyFor(root, file);
|
|
163
|
+
const module = pages[key];
|
|
164
|
+
if (!module) throw new Error("Page module missing: " + key);
|
|
165
|
+
return module as Awaited<ReturnType<FrameworkHandlerOptions["loadPage"]>>;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export const loadRoute = async (root: string, file: string): Promise<Awaited<ReturnType<NonNullable<FrameworkHandlerOptions["loadRoute"]>>>> => {
|
|
169
|
+
const key = keyFor(root, file);
|
|
170
|
+
const module = routeModules[key] as Record<string, unknown> | undefined;
|
|
171
|
+
if (!module) throw new Error("Route module missing: " + key);
|
|
172
|
+
return (module.route ?? module.default ?? module) as Awaited<ReturnType<NonNullable<FrameworkHandlerOptions["loadRoute"]>>>;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
export async function createAppRuntime(root: string, routes: readonly RouteRecord[], clientEntry: string, production: boolean) {
|
|
176
|
+
const sessions = createSessionManager(production);
|
|
177
|
+
const manifest = await discoverServerManifest(root);
|
|
178
|
+
const load = async (file: string) => {
|
|
179
|
+
const module = handlers[keyFor(root, file)];
|
|
180
|
+
if (!module) throw new Error("Server handler missing: " + file);
|
|
181
|
+
return module;
|
|
182
|
+
};
|
|
183
|
+
const services = {};
|
|
184
|
+
const pageHandler = createFrameworkHandler({
|
|
185
|
+
routes,
|
|
186
|
+
loadPage: (file) => loadPage(root, file),
|
|
187
|
+
loadRoute: (file) => loadRoute(root, file),
|
|
188
|
+
clientEntry,
|
|
189
|
+
createContext: async (request) => createRequestContext(request, services, await sessions.load(request)),
|
|
190
|
+
serverFetch: createLocalServerFetch({ manifest, sessions, load }),
|
|
191
|
+
});
|
|
192
|
+
const endpointHandler = createServerDispatcher({ manifest, sessions, services, load });
|
|
193
|
+
const channels = await loadSocketChannels(manifest, load);
|
|
194
|
+
const socketHub = createSocketHub({ channels, sessions, services });
|
|
195
|
+
return { pageHandler, endpointHandler, socketHub, sessions, manifest };
|
|
196
|
+
}
|
|
197
|
+
`,
|
|
198
|
+
"server/session.ts": `import { MemorySessionStore, SessionManager } from "@meekojs/meeko/server";
|
|
199
|
+
|
|
200
|
+
export function createSessionManager(production: boolean) {
|
|
201
|
+
if (production) throw new Error("Configure a persistent SessionStore in server/session.ts before production startup");
|
|
202
|
+
return new SessionManager({ store: new MemorySessionStore(), production: false });
|
|
203
|
+
}
|
|
204
|
+
`,
|
|
205
|
+
"server.mjs": `import { createServer } from "node:http";
|
|
206
|
+
import { readFile, realpath } from "node:fs/promises";
|
|
207
|
+
import { dirname, extname, relative, resolve, sep } from "node:path";
|
|
208
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
209
|
+
import { createServer as createViteServer } from "vite";
|
|
210
|
+
import { attachNodeWebSocketServer, discoverConfiguredRoutes, toNodeListener } from "@meekojs/meeko/server";
|
|
211
|
+
import { serializeJson } from "@meekojs/meeko";
|
|
212
|
+
|
|
213
|
+
const root = dirname(fileURLToPath(import.meta.url));
|
|
214
|
+
const production = process.argv.includes("--production");
|
|
215
|
+
const vite = production ? undefined : await createViteServer({
|
|
216
|
+
root, configFile: resolve(root, "vite.config.ts"),
|
|
217
|
+
server: { middlewareMode: true }, appType: "custom",
|
|
218
|
+
});
|
|
219
|
+
const clientRoot = resolve(root, ".meeko/output/client");
|
|
220
|
+
const publicRoot = resolve(root, "public");
|
|
221
|
+
const routes = await discoverConfiguredRoutes(root);
|
|
222
|
+
let clientEntry = "/app/entry-client.ts";
|
|
223
|
+
if (production) {
|
|
224
|
+
const manifest = JSON.parse(await readFile(resolve(clientRoot, ".vite/manifest.json"), "utf8"));
|
|
225
|
+
const entry = manifest["app/entry-client.ts"];
|
|
226
|
+
if (!entry?.file) throw new Error("Missing client entry in Vite manifest");
|
|
227
|
+
clientEntry = "/" + entry.file;
|
|
228
|
+
}
|
|
229
|
+
const serverEntry = production
|
|
230
|
+
? await import(pathToFileURL(resolve(root, ".meeko/output/server/entry.js")).href)
|
|
231
|
+
: await vite.ssrLoadModule("/server/entry.ts");
|
|
232
|
+
const portable = routes.map((route) => ({
|
|
233
|
+
id: route.id,
|
|
234
|
+
file: "/" + relative(root, route.file).replaceAll("\\\\", "/"),
|
|
235
|
+
layouts: route.layouts.map((file) => "/" + relative(root, file).replaceAll("\\\\", "/")),
|
|
236
|
+
...(route.error ? { error: "/" + relative(root, route.error).replaceAll("\\\\", "/") } : {}),
|
|
237
|
+
}));
|
|
238
|
+
const runtime = await serverEntry.createAppRuntime(root, routes, clientEntry, production);
|
|
239
|
+
const handler = runtime.pageHandler;
|
|
240
|
+
const decorated = async (request) => {
|
|
241
|
+
const path = new URL(request.url).pathname;
|
|
242
|
+
if (path.startsWith("/api/") || path.startsWith("/_meeko/rpc/")) return runtime.endpointHandler(request);
|
|
243
|
+
const result = await handler(request);
|
|
244
|
+
if (result.status !== 200 || !(result.headers.get("content-type") || "").includes("text/html")) return result;
|
|
245
|
+
let html = (await result.text()).replace("</body>", '<script id="meeko-routes" type="application/json">' + serializeJson(portable) + "</script></body>");
|
|
246
|
+
if (vite) html = await vite.transformIndexHtml(new URL(request.url).pathname, html);
|
|
247
|
+
return new Response(request.method === "HEAD" ? null : html, { status: result.status, headers: result.headers });
|
|
248
|
+
};
|
|
249
|
+
const pageListener = toNodeListener(decorated);
|
|
250
|
+
const types = { ".js": "text/javascript", ".css": "text/css", ".svg": "image/svg+xml", ".png": "image/png" };
|
|
251
|
+
async function readAsset(assetRoot, path) {
|
|
252
|
+
const file = resolve(assetRoot, "." + path);
|
|
253
|
+
if (!file.startsWith(assetRoot + sep)) return { status: 403 };
|
|
254
|
+
let actual;
|
|
255
|
+
try { actual = await realpath(file); }
|
|
256
|
+
catch (error) { if (error?.code === "ENOENT") return null; throw error; }
|
|
257
|
+
const actualRoot = await realpath(assetRoot);
|
|
258
|
+
if (!actual.startsWith(actualRoot + sep)) return { status: 403 };
|
|
259
|
+
return { status: 200, body: await readFile(actual) };
|
|
260
|
+
}
|
|
261
|
+
const server = createServer((request, response) => {
|
|
262
|
+
const path = new URL(request.url || "/", "http://localhost").pathname;
|
|
263
|
+
if (production && /\\.[a-z0-9]+$/i.test(path)) {
|
|
264
|
+
void (async () => {
|
|
265
|
+
const asset = await readAsset(clientRoot, path) ?? await readAsset(publicRoot, path);
|
|
266
|
+
if (!asset || asset.status !== 200) { response.writeHead(asset?.status ?? 404); response.end(); return; }
|
|
267
|
+
response.writeHead(200, { "content-type": types[extname(path)] || "application/octet-stream" });
|
|
268
|
+
response.end(asset.body);
|
|
269
|
+
})().catch(() => { response.writeHead(404); response.end(); });
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (vite) {
|
|
273
|
+
vite.middlewares(request, response, () => pageListener(request, response));
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
pageListener(request, response);
|
|
277
|
+
});
|
|
278
|
+
const port = Number(process.env.PORT || 4173);
|
|
279
|
+
const socketServer = runtime.manifest.socket?.length ? attachNodeWebSocketServer({
|
|
280
|
+
server, hub: runtime.socketHub, publicOrigin: process.env.PUBLIC_ORIGIN || "http://127.0.0.1:" + port,
|
|
281
|
+
}) : undefined;
|
|
282
|
+
server.listen(port, "127.0.0.1", () => console.log("http://127.0.0.1:" + port));
|
|
283
|
+
process.on("SIGINT", () => { void socketServer?.close(); server.close(); void runtime.sessions.close(); void vite?.close(); });
|
|
284
|
+
`,
|
|
285
|
+
"README.md": `# Meeko framework starter
|
|
286
|
+
|
|
287
|
+
Requires Node 24.
|
|
288
|
+
|
|
289
|
+
\`\`\`sh
|
|
290
|
+
npm install
|
|
291
|
+
npm run dev
|
|
292
|
+
\`\`\`
|
|
293
|
+
|
|
294
|
+
Routes live in \`app/pages\`. \`+layout.meeko\` keeps the shared shell during client navigation. Run \`npm run build\` and \`npm start\` for production SSR. Generated files live under \`.meeko/\`.
|
|
295
|
+
`,
|
|
296
|
+
};
|
|
297
|
+
function record(value) {
|
|
298
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
299
|
+
}
|
|
300
|
+
/** Creates a framework project without overwriting unrelated user files. */
|
|
301
|
+
export async function initFrameworkProject(directory) {
|
|
302
|
+
const target = resolve(directory);
|
|
303
|
+
const name = basename(target).toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[._-]+|[._-]+$/g, "") || "meeko-app";
|
|
304
|
+
const metadata = JSON.parse(await readFile(new URL("../../package.json", import.meta.url), "utf8"));
|
|
305
|
+
if (!record(metadata) || typeof metadata.version !== "string")
|
|
306
|
+
throw new Error("Meeko package version is unavailable");
|
|
307
|
+
let entries = [];
|
|
308
|
+
try {
|
|
309
|
+
if (!(await stat(target)).isDirectory())
|
|
310
|
+
throw new Error(`Destination is not a directory: ${target}`);
|
|
311
|
+
entries = await readdir(target);
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
|
|
315
|
+
throw error;
|
|
316
|
+
}
|
|
317
|
+
const unexpected = entries.filter((entry) => !allowedEntries.has(entry));
|
|
318
|
+
if (unexpected.length || (entries.length && !entries.includes("package.json"))) {
|
|
319
|
+
throw new Error(`Destination is not empty: ${target}`);
|
|
320
|
+
}
|
|
321
|
+
const existing = entries.includes("package.json") ? JSON.parse(await readFile(join(target, "package.json"), "utf8")) : {};
|
|
322
|
+
if (!record(existing) || (existing.scripts !== undefined && !record(existing.scripts))
|
|
323
|
+
|| (existing.dependencies !== undefined && !record(existing.dependencies))
|
|
324
|
+
|| (existing.devDependencies !== undefined && !record(existing.devDependencies))) {
|
|
325
|
+
throw new Error(`Invalid package.json in ${target}`);
|
|
326
|
+
}
|
|
327
|
+
const scripts = {
|
|
328
|
+
dev: "node server.mjs",
|
|
329
|
+
check: "meeko routes . && meeko check-app . && tsc -p tsconfig.json --noEmit && tsc -p tsconfig.shared.json --noEmit && tsc -p tsconfig.app.json --noEmit && tsc -p tsconfig.server.json --noEmit",
|
|
330
|
+
build: "npm run check && vite build --config vite.config.ts && vite build --config vite.server.config.ts && meeko prerender .",
|
|
331
|
+
start: "node server.mjs --production",
|
|
332
|
+
};
|
|
333
|
+
const oldScripts = existing.scripts;
|
|
334
|
+
for (const command of Object.keys(scripts))
|
|
335
|
+
if (oldScripts && command in oldScripts) {
|
|
336
|
+
throw new Error(`Destination already defines the ${command} script: ${target}`);
|
|
337
|
+
}
|
|
338
|
+
const manifest = {
|
|
339
|
+
...existing,
|
|
340
|
+
name: typeof existing.name === "string" ? existing.name : name,
|
|
341
|
+
private: true,
|
|
342
|
+
type: "module",
|
|
343
|
+
engines: { ...(record(existing.engines) ? existing.engines : {}), node: ">=24" },
|
|
344
|
+
scripts: { ...oldScripts, ...scripts },
|
|
345
|
+
dependencies: { ...existing.dependencies, "@meekojs/meeko": metadata.version },
|
|
346
|
+
devDependencies: {
|
|
347
|
+
...existing.devDependencies,
|
|
348
|
+
"@types/node": "^24.0.0", typescript: "^7.0.2", vite: "^8.3.5",
|
|
349
|
+
},
|
|
350
|
+
};
|
|
351
|
+
await mkdir(target, { recursive: true });
|
|
352
|
+
for (const [filename, content] of Object.entries(files)) {
|
|
353
|
+
const output = join(target, filename);
|
|
354
|
+
await mkdir(dirname(output), { recursive: true });
|
|
355
|
+
await writeFile(output, content, { encoding: "utf8", flag: "wx" });
|
|
356
|
+
}
|
|
357
|
+
await writeFile(join(target, "package.json"), JSON.stringify(manifest, null, 2) + "\n", {
|
|
358
|
+
encoding: "utf8", flag: entries.includes("package.json") ? "w" : "wx",
|
|
359
|
+
});
|
|
360
|
+
return target;
|
|
361
|
+
}
|
|
362
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/framework/init.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdir, readFile, readdir, stat, writeFile } from \"node:fs/promises\";\nimport { basename, dirname, join, resolve } from \"node:path\";\n\nconst allowedEntries = new Set([\"package.json\", \"package-lock.json\", \"pnpm-lock.yaml\", \"pnpm-workspace.yaml\", \"yarn.lock\", \"node_modules\", \".npmrc\", \".git\"]);\n\nconst files: Readonly<Record<string, string>> = {\n \".gitignore\": \"node_modules/\\n.meeko/\\n\",\n \"public/.gitkeep\": \"\",\n \"meeko.config.ts\": `import { defineMeekoConfig } from \"@meekojs/meeko/framework\";\n\nexport default defineMeekoConfig({\n extends: [],\n modules: [],\n imports: { serverDirs: [] },\n});\n`,\n \"vite.config.ts\": `import { defineConfig } from \"vite\";\nimport { meeko } from \"@meekojs/meeko/vite\";\n\nexport default defineConfig({\n plugins: [meeko()],\n build: {\n outDir: \".meeko/output/client\",\n manifest: true,\n rollupOptions: { input: \"app/entry-client.ts\" },\n },\n});\n`,\n \"vite.server.config.ts\": `import { defineConfig } from \"vite\";\nimport { meeko } from \"@meekojs/meeko/vite\";\n\nexport default defineConfig({\n plugins: [meeko()],\n build: {\n ssr: \"server/entry.ts\",\n outDir: \".meeko/output/server\",\n rollupOptions: { output: { entryFileNames: \"entry.js\" } },\n },\n});\n`,\n \"tsconfig.json\": `{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"NodeNext\",\n \"moduleResolution\": \"NodeNext\",\n \"lib\": [\"ES2022\", \"DOM\"],\n \"types\": [\"node\", \"vite/client\"],\n \"strict\": true,\n \"noEmit\": true,\n \"allowArbitraryExtensions\": true,\n \"skipLibCheck\": true\n },\n \"include\": [\"vite.config.ts\", \"vite.server.config.ts\"]\n}\n`,\n \"tsconfig.app.json\": `{\n \"extends\": \"./tsconfig.json\",\n \"compilerOptions\": { \"rootDirs\": [\".\", \".meeko/types\"] },\n \"include\": [\"app/**/*.ts\", \"layers/**/app/**/*.ts\", \"modules/**/app/**/*.ts\", \".meeko/types/**/*.d.meeko.ts\", \".meeko/types/routes.d.ts\", \".meeko/types/virtual-pages.d.ts\", \".meeko/types/server-contracts.d.ts\", \".meeko/types/autoimports/client.d.ts\"],\n \"exclude\": [\"app/pages/**/*.route.ts\", \"layers/**/app/pages/**/*.route.ts\", \"modules/**/app/pages/**/*.route.ts\"]\n}\n`,\n \"tsconfig.server.json\": `{\n \"extends\": \"./tsconfig.json\",\n \"include\": [\"server/**/*.ts\", \"app/pages/**/*.route.ts\", \"layers/**/app/pages/**/*.route.ts\", \"modules/**/app/pages/**/*.route.ts\", \"layers/**/server/**/*.ts\", \"modules/**/server/**/*.ts\", \".meeko/types/virtual-pages.d.ts\", \".meeko/types/autoimports/server.d.ts\"]\n}\n`,\n \"tsconfig.shared.json\": `{\n \"extends\": \"./tsconfig.json\",\n \"include\": [\"shared/**/*.ts\", \"layers/**/shared/**/*.ts\", \"modules/**/shared/**/*.ts\", \".meeko/types/autoimports/shared.d.ts\"]\n}\n`,\n \"app/pages/index.meeko\": `<script lang=\"ts\">\nimport { signal } from \"@meekojs/meeko\";\n\nexport interface Props {\n params: Record<string, string>;\n search: Record<string, string>;\n data?: unknown;\n}\n\nconst count = signal(0);\n</script>\n\n<template>\n <section>\n <h1>Welcome to Meeko</h1>\n <p>File routes, server rendering, and hydration are ready.</p>\n <button type=\"button\" on:click=\"() => count.value++\">Count: {count.value}</button>\n </section>\n</template>\n`,\n \"app/pages/index.route.ts\": `import { definePage } from \"@meekojs/meeko/framework\";\n\nexport const route = definePage({ render: \"prerender\" });\n`,\n \"app/app.meeko\": `<script lang=\"ts\">\nimport type { ComponentModule } from \"@meekojs/meeko\";\n\nexport interface Props {\n outlet: ComponentModule<Record<string, unknown>>;\n}\n\nconst props = defineProps<Props>();\n</script>\n\n<template>\n <div class=\"site-root\"><component is=\"props.outlet\"></component></div>\n</template>\n`,\n \"app/pages/+layout.meeko\": `<script lang=\"ts\">\nimport type { ComponentModule } from \"@meekojs/meeko\";\n\nexport interface Props {\n outlet: ComponentModule<Record<string, unknown>>;\n}\n\nconst props = defineProps<Props>();\n</script>\n\n<template>\n <div class=\"app-shell\">\n <header><a href=\"/\">Meeko</a></header>\n <main><component is=\"props.outlet\"></component></main>\n </div>\n</template>\n\n<style scoped>\n.app-shell { max-width: 64rem; margin: 0 auto; padding: 2rem; font-family: system-ui, sans-serif; }\nheader { margin-bottom: 2rem; }\n</style>\n`,\n \"app/entry-client.ts\": `import { mountClientApp, type ClientComponent, type RouteRecord } from \"@meekojs/meeko/framework\";\nimport { modules as pages } from \"virtual:meeko-client-pages\";\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\nawait mountClientApp({\n root, payload, routes, serverData: true,\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});\n`,\n \"server/entry.ts\": `import { createFrameworkHandler, createLocalServerFetch, createRequestContext, createServerDispatcher, createSocketHub, discoverServerManifest, loadSocketChannels, type FrameworkHandlerOptions } from \"@meekojs/meeko/server\";\nimport type { RouteRecord } 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 \"./session.js\";\n\nconst handlers = import.meta.glob([\"/server/api/**/*.ts\", \"/server/rpc/**/*.ts\", \"/server/socket/**/*.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(root: string, routes: readonly RouteRecord[], clientEntry: string, production: boolean) {\n const sessions = createSessionManager(production);\n const manifest = await discoverServerManifest(root);\n const load = async (file: string) => {\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 pageHandler = createFrameworkHandler({\n routes,\n loadPage: (file) => loadPage(root, file),\n loadRoute: (file) => loadRoute(root, file),\n clientEntry,\n createContext: async (request) => createRequestContext(request, services, await sessions.load(request)),\n serverFetch: createLocalServerFetch({ manifest, sessions, load }),\n });\n const endpointHandler = createServerDispatcher({ manifest, sessions, services, load });\n const channels = await loadSocketChannels(manifest, load);\n const socketHub = createSocketHub({ channels, sessions, services });\n return { pageHandler, endpointHandler, socketHub, sessions, manifest };\n}\n`,\n \"server/session.ts\": `import { MemorySessionStore, SessionManager } from \"@meekojs/meeko/server\";\n\nexport function createSessionManager(production: boolean) {\n if (production) throw new Error(\"Configure a persistent SessionStore in server/session.ts before production startup\");\n return new SessionManager({ store: new MemorySessionStore(), production: false });\n}\n`,\n \"server.mjs\": `import { createServer } from \"node:http\";\nimport { readFile, realpath } from \"node:fs/promises\";\nimport { dirname, extname, relative, resolve, sep } from \"node:path\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\nimport { createServer as createViteServer } from \"vite\";\nimport { attachNodeWebSocketServer, discoverConfiguredRoutes, toNodeListener } from \"@meekojs/meeko/server\";\nimport { serializeJson } from \"@meekojs/meeko\";\n\nconst root = dirname(fileURLToPath(import.meta.url));\nconst production = process.argv.includes(\"--production\");\nconst vite = production ? undefined : await createViteServer({\n root, configFile: resolve(root, \"vite.config.ts\"),\n server: { middlewareMode: true }, appType: \"custom\",\n});\nconst clientRoot = resolve(root, \".meeko/output/client\");\nconst publicRoot = resolve(root, \"public\");\nconst routes = await discoverConfiguredRoutes(root);\nlet clientEntry = \"/app/entry-client.ts\";\nif (production) {\n const manifest = JSON.parse(await readFile(resolve(clientRoot, \".vite/manifest.json\"), \"utf8\"));\n const entry = manifest[\"app/entry-client.ts\"];\n if (!entry?.file) throw new Error(\"Missing client entry in Vite manifest\");\n clientEntry = \"/\" + entry.file;\n}\nconst serverEntry = production\n ? await import(pathToFileURL(resolve(root, \".meeko/output/server/entry.js\")).href)\n : await vite.ssrLoadModule(\"/server/entry.ts\");\nconst portable = routes.map((route) => ({\n id: route.id,\n file: \"/\" + relative(root, route.file).replaceAll(\"\\\\\\\\\", \"/\"),\n layouts: route.layouts.map((file) => \"/\" + relative(root, file).replaceAll(\"\\\\\\\\\", \"/\")),\n ...(route.error ? { error: \"/\" + relative(root, route.error).replaceAll(\"\\\\\\\\\", \"/\") } : {}),\n}));\nconst runtime = await serverEntry.createAppRuntime(root, routes, clientEntry, production);\nconst handler = runtime.pageHandler;\nconst decorated = async (request) => {\n const path = new URL(request.url).pathname;\n if (path.startsWith(\"/api/\") || path.startsWith(\"/_meeko/rpc/\")) return runtime.endpointHandler(request);\n const result = await handler(request);\n if (result.status !== 200 || !(result.headers.get(\"content-type\") || \"\").includes(\"text/html\")) return result;\n let html = (await result.text()).replace(\"</body>\", '<script id=\"meeko-routes\" type=\"application/json\">' + serializeJson(portable) + \"</script></body>\");\n if (vite) html = await vite.transformIndexHtml(new URL(request.url).pathname, html);\n return new Response(request.method === \"HEAD\" ? null : html, { status: result.status, headers: result.headers });\n};\nconst pageListener = toNodeListener(decorated);\nconst types = { \".js\": \"text/javascript\", \".css\": \"text/css\", \".svg\": \"image/svg+xml\", \".png\": \"image/png\" };\nasync function readAsset(assetRoot, path) {\n const file = resolve(assetRoot, \".\" + path);\n if (!file.startsWith(assetRoot + sep)) return { status: 403 };\n let actual;\n try { actual = await realpath(file); }\n catch (error) { if (error?.code === \"ENOENT\") return null; throw error; }\n const actualRoot = await realpath(assetRoot);\n if (!actual.startsWith(actualRoot + sep)) return { status: 403 };\n return { status: 200, body: await readFile(actual) };\n}\nconst server = createServer((request, response) => {\n const path = new URL(request.url || \"/\", \"http://localhost\").pathname;\n if (production && /\\\\.[a-z0-9]+$/i.test(path)) {\n void (async () => {\n const asset = await readAsset(clientRoot, path) ?? await readAsset(publicRoot, path);\n if (!asset || asset.status !== 200) { response.writeHead(asset?.status ?? 404); response.end(); return; }\n response.writeHead(200, { \"content-type\": types[extname(path)] || \"application/octet-stream\" });\n response.end(asset.body);\n })().catch(() => { response.writeHead(404); response.end(); });\n return;\n }\n if (vite) {\n vite.middlewares(request, response, () => pageListener(request, response));\n return;\n }\n pageListener(request, response);\n});\nconst port = Number(process.env.PORT || 4173);\nconst socketServer = runtime.manifest.socket?.length ? attachNodeWebSocketServer({\n server, hub: runtime.socketHub, publicOrigin: process.env.PUBLIC_ORIGIN || \"http://127.0.0.1:\" + port,\n}) : undefined;\nserver.listen(port, \"127.0.0.1\", () => console.log(\"http://127.0.0.1:\" + port));\nprocess.on(\"SIGINT\", () => { void socketServer?.close(); server.close(); void runtime.sessions.close(); void vite?.close(); });\n`,\n \"README.md\": `# Meeko framework starter\n\nRequires Node 24.\n\n\\`\\`\\`sh\nnpm install\nnpm run dev\n\\`\\`\\`\n\nRoutes live in \\`app/pages\\`. \\`+layout.meeko\\` keeps the shared shell during client navigation. Run \\`npm run build\\` and \\`npm start\\` for production SSR. Generated files live under \\`.meeko/\\`.\n`,\n};\n\nfunction record(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** Creates a framework project without overwriting unrelated user files. */\nexport async function initFrameworkProject(directory: string): Promise<string> {\n const target = resolve(directory);\n const name = basename(target).toLowerCase().replace(/[^a-z0-9._-]+/g, \"-\").replace(/^[._-]+|[._-]+$/g, \"\") || \"meeko-app\";\n const metadata: unknown = JSON.parse(await readFile(new URL(\"../../package.json\", import.meta.url), \"utf8\"));\n if (!record(metadata) || typeof metadata.version !== \"string\") throw new Error(\"Meeko package version is unavailable\");\n let entries: string[] = [];\n try {\n if (!(await stat(target)).isDirectory()) throw new Error(`Destination is not a directory: ${target}`);\n entries = await readdir(target);\n } catch (error) {\n if (!(error instanceof Error && \"code\" in error && error.code === \"ENOENT\")) throw error;\n }\n const unexpected = entries.filter((entry) => !allowedEntries.has(entry));\n if (unexpected.length || (entries.length && !entries.includes(\"package.json\"))) {\n throw new Error(`Destination is not empty: ${target}`);\n }\n const existing: unknown = entries.includes(\"package.json\") ? JSON.parse(await readFile(join(target, \"package.json\"), \"utf8\")) : {};\n if (!record(existing) || (existing.scripts !== undefined && !record(existing.scripts))\n || (existing.dependencies !== undefined && !record(existing.dependencies))\n || (existing.devDependencies !== undefined && !record(existing.devDependencies))) {\n throw new Error(`Invalid package.json in ${target}`);\n }\n const scripts = {\n dev: \"node server.mjs\",\n check: \"meeko routes . && meeko check-app . && tsc -p tsconfig.json --noEmit && tsc -p tsconfig.shared.json --noEmit && tsc -p tsconfig.app.json --noEmit && tsc -p tsconfig.server.json --noEmit\",\n build: \"npm run check && vite build --config vite.config.ts && vite build --config vite.server.config.ts && meeko prerender .\",\n start: \"node server.mjs --production\",\n };\n const oldScripts = existing.scripts as Record<string, unknown> | undefined;\n for (const command of Object.keys(scripts)) if (oldScripts && command in oldScripts) {\n throw new Error(`Destination already defines the ${command} script: ${target}`);\n }\n const manifest = {\n ...existing,\n name: typeof existing.name === \"string\" ? existing.name : name,\n private: true,\n type: \"module\",\n engines: { ...(record(existing.engines) ? existing.engines : {}), node: \">=24\" },\n scripts: { ...oldScripts, ...scripts },\n dependencies: { ...(existing.dependencies as Record<string, unknown> | undefined), \"@meekojs/meeko\": metadata.version },\n devDependencies: {\n ...(existing.devDependencies as Record<string, unknown> | undefined),\n \"@types/node\": \"^24.0.0\", typescript: \"^7.0.2\", vite: \"^8.3.5\",\n },\n };\n await mkdir(target, { recursive: true });\n for (const [filename, content] of Object.entries(files)) {\n const output = join(target, filename);\n await mkdir(dirname(output), { recursive: true });\n await writeFile(output, content, { encoding: \"utf8\", flag: \"wx\" });\n }\n await writeFile(join(target, \"package.json\"), JSON.stringify(manifest, null, 2) + \"\\n\", {\n encoding: \"utf8\", flag: entries.includes(\"package.json\") ? \"w\" : \"wx\",\n });\n return target;\n}\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { RouteRecord } from "./router.js";
|
|
2
|
+
export interface RouteLayer {
|
|
3
|
+
/** Stable identifier used in diagnostics and generated manifests. */
|
|
4
|
+
readonly name: string;
|
|
5
|
+
/** Directory containing the layer's app/pages tree. */
|
|
6
|
+
readonly root: string;
|
|
7
|
+
}
|
|
8
|
+
export interface RouteModule {
|
|
9
|
+
/** Stable identifier for a module contributing explicit route records. */
|
|
10
|
+
readonly name: string;
|
|
11
|
+
routes(): readonly RouteRecord[] | Promise<readonly RouteRecord[]>;
|
|
12
|
+
}
|
|
13
|
+
export interface ComposeRoutesOptions {
|
|
14
|
+
readonly projectRoot: string;
|
|
15
|
+
readonly layers?: readonly RouteLayer[];
|
|
16
|
+
readonly modules?: readonly RouteModule[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Composes explicit route sources. Every URL collision is an error; no source
|
|
20
|
+
* silently overrides the app, a layer, or another module.
|
|
21
|
+
*/
|
|
22
|
+
export declare function composeFrameworkRoutes(options: ComposeRoutesOptions): Promise<readonly RouteRecord[]>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { createRouteManifest } from "./router.js";
|
|
3
|
+
import { discoverRoutes } from "./server.js";
|
|
4
|
+
/**
|
|
5
|
+
* Composes explicit route sources. Every URL collision is an error; no source
|
|
6
|
+
* silently overrides the app, a layer, or another module.
|
|
7
|
+
*/
|
|
8
|
+
export async function composeFrameworkRoutes(options) {
|
|
9
|
+
const names = new Set(["app"]);
|
|
10
|
+
const roots = new Set([resolve(options.projectRoot).toLowerCase()]);
|
|
11
|
+
for (const source of [...options.layers ?? [], ...options.modules ?? []]) {
|
|
12
|
+
if (!/^[a-z][a-z0-9-]*$/.test(source.name) || names.has(source.name)) {
|
|
13
|
+
throw new Error(`Invalid or duplicate route source name: ${source.name}`);
|
|
14
|
+
}
|
|
15
|
+
names.add(source.name);
|
|
16
|
+
}
|
|
17
|
+
const routes = [...await discoverRoutes(options.projectRoot)];
|
|
18
|
+
for (const layer of options.layers ?? []) {
|
|
19
|
+
const root = resolve(layer.root);
|
|
20
|
+
if (roots.has(root.toLowerCase()))
|
|
21
|
+
throw new Error(`Duplicate route root: ${root}`);
|
|
22
|
+
roots.add(root.toLowerCase());
|
|
23
|
+
routes.push(...await discoverRoutes(root));
|
|
24
|
+
}
|
|
25
|
+
for (const module of options.modules ?? [])
|
|
26
|
+
routes.push(...await module.routes());
|
|
27
|
+
return createRouteManifest(routes);
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=modules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modules.js","sourceRoot":"","sources":["../../src/framework/modules.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAElD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAqB7C;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,OAA6B;IACxE,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;IAC/B,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IACpE,KAAK,MAAM,MAAM,IAAI,CAAC,GAAG,OAAO,CAAC,MAAM,IAAI,EAAE,EAAE,GAAG,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,CAAC;QACzE,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YACrE,MAAM,IAAI,KAAK,CAAC,2CAA2C,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;QAC5E,CAAC;QACD,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IACD,MAAM,MAAM,GAAkB,CAAC,GAAG,MAAM,cAAc,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC;IAC7E,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC;QACzC,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,IAAI,EAAE,CAAC,CAAC;QACpF,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7C,CAAC;IACD,KAAK,MAAM,MAAM,IAAI,OAAO,CAAC,OAAO,IAAI,EAAE;QAAE,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IAClF,OAAO,mBAAmB,CAAC,MAAM,CAAC,CAAC;AACrC,CAAC","sourcesContent":["import { resolve } from \"node:path\";\nimport { createRouteManifest } from \"./router.js\";\nimport type { RouteRecord } from \"./router.js\";\nimport { discoverRoutes } from \"./server.js\";\n\nexport interface RouteLayer {\n /** Stable identifier used in diagnostics and generated manifests. */\n readonly name: string;\n /** Directory containing the layer's app/pages tree. */\n readonly root: string;\n}\n\nexport interface RouteModule {\n /** Stable identifier for a module contributing explicit route records. */\n readonly name: string;\n routes(): readonly RouteRecord[] | Promise<readonly RouteRecord[]>;\n}\n\nexport interface ComposeRoutesOptions {\n readonly projectRoot: string;\n readonly layers?: readonly RouteLayer[];\n readonly modules?: readonly RouteModule[];\n}\n\n/**\n * Composes explicit route sources. Every URL collision is an error; no source\n * silently overrides the app, a layer, or another module.\n */\nexport async function composeFrameworkRoutes(options: ComposeRoutesOptions): Promise<readonly RouteRecord[]> {\n const names = new Set([\"app\"]);\n const roots = new Set([resolve(options.projectRoot).toLowerCase()]);\n for (const source of [...options.layers ?? [], ...options.modules ?? []]) {\n if (!/^[a-z][a-z0-9-]*$/.test(source.name) || names.has(source.name)) {\n throw new Error(`Invalid or duplicate route source name: ${source.name}`);\n }\n names.add(source.name);\n }\n const routes: RouteRecord[] = [...await discoverRoutes(options.projectRoot)];\n for (const layer of options.layers ?? []) {\n const root = resolve(layer.root);\n if (roots.has(root.toLowerCase())) throw new Error(`Duplicate route root: ${root}`);\n roots.add(root.toLowerCase());\n routes.push(...await discoverRoutes(root));\n }\n for (const module of options.modules ?? []) routes.push(...await module.routes());\n return createRouteManifest(routes);\n}\n"]}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/** Browser-safe subset of Standard Schema v1, implemented by Valibot schemas. */
|
|
2
|
+
import type { StateStore } from "./state.js";
|
|
3
|
+
import type { PageRoute } from "./server.js";
|
|
4
|
+
export interface PageSchema<I = unknown, O = I> {
|
|
5
|
+
readonly "~standard": {
|
|
6
|
+
readonly version: 1;
|
|
7
|
+
readonly validate: (value: unknown) => {
|
|
8
|
+
value: O;
|
|
9
|
+
issues?: undefined;
|
|
10
|
+
} | {
|
|
11
|
+
issues: readonly unknown[];
|
|
12
|
+
} | Promise<{
|
|
13
|
+
value: O;
|
|
14
|
+
issues?: undefined;
|
|
15
|
+
} | {
|
|
16
|
+
issues: readonly unknown[];
|
|
17
|
+
}>;
|
|
18
|
+
readonly types?: {
|
|
19
|
+
readonly input: I;
|
|
20
|
+
readonly output: O;
|
|
21
|
+
} | undefined;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
type Output<S> = S extends PageSchema<unknown, infer O> ? O : never;
|
|
25
|
+
type Params<P> = P extends PageSchema ? Output<P> : Readonly<Record<string, string>>;
|
|
26
|
+
type Search<S> = S extends PageSchema ? Output<S> : Readonly<Record<string, string | string[]>>;
|
|
27
|
+
export interface PageLoadContext<P extends PageSchema | undefined, S extends PageSchema | undefined, C = unknown> {
|
|
28
|
+
readonly params: Params<P>;
|
|
29
|
+
readonly search: Search<S>;
|
|
30
|
+
readonly request: Request;
|
|
31
|
+
readonly signal: AbortSignal;
|
|
32
|
+
readonly state: StateStore;
|
|
33
|
+
readonly context?: C;
|
|
34
|
+
}
|
|
35
|
+
export interface PageDefinition<P extends PageSchema | undefined, S extends PageSchema | undefined, D, C = unknown> {
|
|
36
|
+
readonly params?: P;
|
|
37
|
+
readonly search?: S;
|
|
38
|
+
readonly render?: "ssr" | "prerender" | "client";
|
|
39
|
+
readonly prerenderEntries?: readonly Readonly<Record<string, string>>[] | (() => Promise<readonly Readonly<Record<string, string>>[]>);
|
|
40
|
+
readonly load?: (args: PageLoadContext<P, S, C>) => Promise<D> | D;
|
|
41
|
+
readonly head?: (args: PageLoadContext<P, S, C> & {
|
|
42
|
+
data: D;
|
|
43
|
+
}) => {
|
|
44
|
+
title?: string;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** 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;
|
|
49
|
+
export declare class PageInputError extends Error {
|
|
50
|
+
readonly code: "INVALID_PARAMS" | "INVALID_SEARCH";
|
|
51
|
+
constructor(code: "INVALID_PARAMS" | "INVALID_SEARCH");
|
|
52
|
+
}
|
|
53
|
+
/** Validates both inputs before a page loader executes. */
|
|
54
|
+
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<{
|
|
55
|
+
params: Readonly<Record<string, unknown>>;
|
|
56
|
+
search: Readonly<Record<string, unknown>>;
|
|
57
|
+
}>;
|
|
58
|
+
/** Preserves duplicate search keys for schema validation. */
|
|
59
|
+
export declare function pageSearchObject(searchParams: URLSearchParams): Record<string, string | string[]>;
|
|
60
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** Captures route schemas and keeps loader/head inference in one declaration. */
|
|
2
|
+
export function definePage(definition) {
|
|
3
|
+
return definition;
|
|
4
|
+
}
|
|
5
|
+
export class PageInputError extends Error {
|
|
6
|
+
code;
|
|
7
|
+
constructor(code) {
|
|
8
|
+
super(code);
|
|
9
|
+
this.code = code;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function record(value) {
|
|
13
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14
|
+
}
|
|
15
|
+
async function parse(schema, raw, code) {
|
|
16
|
+
if (!schema)
|
|
17
|
+
return raw;
|
|
18
|
+
const result = await schema["~standard"].validate(raw);
|
|
19
|
+
if ("issues" in result && result.issues)
|
|
20
|
+
throw new PageInputError(code);
|
|
21
|
+
const value = result.value;
|
|
22
|
+
if (!record(value))
|
|
23
|
+
throw new PageInputError(code);
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
/** Validates both inputs before a page loader executes. */
|
|
27
|
+
export async function validatePageInputs(definition, params, search) {
|
|
28
|
+
return {
|
|
29
|
+
params: await parse(definition?.params, { ...params }, "INVALID_PARAMS"),
|
|
30
|
+
search: await parse(definition?.search, { ...search }, "INVALID_SEARCH"),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/** Preserves duplicate search keys for schema validation. */
|
|
34
|
+
export function pageSearchObject(searchParams) {
|
|
35
|
+
const result = Object.create(null);
|
|
36
|
+
for (const [key, value] of searchParams) {
|
|
37
|
+
const previous = result[key];
|
|
38
|
+
result[key] = previous === undefined ? value : Array.isArray(previous) ? [...previous, value] : [previous, value];
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=page.js.map
|
|
@@ -0,0 +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"]}
|
|
@@ -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[]>;
|