@sigil-dev/grimoire 0.7.5 → 0.7.7
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/.grimoire/_routes.dom.js +8 -0
- package/.grimoire/_routes.hydrate.js +8 -0
- package/.grimoire/tsconfig.generated.json +11 -0
- package/.grimoire/types/ambient.d.ts +59 -0
- package/.grimoire/types/api/hello/$types.d.ts +50 -0
- package/.grimoire/types/api/items/$types.d.ts +50 -0
- package/.grimoire/types/echo/$types.d.ts +50 -0
- package/.grimoire/types/env-private.d.ts +5 -0
- package/.grimoire/types/env-public.d.ts +5 -0
- package/.grimoire/types/mixed/$types.d.ts +50 -0
- package/.grimoire/types/params/[docId]/$types.d.ts +52 -0
- package/.grimoire/types/reject/$types.d.ts +50 -0
- package/index.ts +21 -20
- package/package.json +13 -7
- package/preload.js +3 -0
- package/public/__grimoire__/hydrate.js +585 -0
- package/public/__grimoire__/index.js +490 -0
- package/server.ts +13 -13
- package/src/client/head.ts +29 -0
- package/src/client/router.ts +254 -40
- package/src/dev/compile-module.ts +173 -0
- package/src/dev/effect-registry.ts +23 -0
- package/src/dev/graph.ts +114 -0
- package/src/dev/hmr-client.ts +158 -0
- package/src/dev/hmr-server.ts +187 -0
- package/src/dev/loader.ts +47 -0
- package/src/dev/paths.ts +14 -0
- package/src/dev/runtime-bundle.ts +49 -0
- package/src/dev/watcher.ts +44 -0
- package/src/env/index.ts +25 -0
- package/src/env/plugin.ts +13 -0
- package/src/env/private.ts +5 -0
- package/src/env/public.ts +7 -0
- package/src/env/typegen.ts +51 -0
- package/src/integrations/vite.ts +1 -0
- package/src/rendering/head.ts +22 -2
- package/src/rendering/hydrate.ts +111 -18
- package/src/rendering/index.ts +263 -153
- package/src/rendering/ssrPlugin.ts +59 -39
- package/src/routing/manifest-gen.ts +18 -2
- package/src/routing/router.ts +94 -83
- package/src/routing/scanner.ts +26 -14
- package/src/routing/transform-routes.ts +68 -68
- package/src/server/build.ts +225 -76
- package/src/server/coordinator.ts +9 -0
- package/src/server/hooks.ts +24 -3
- package/src/server/index.ts +388 -104
- package/src/typegen/index.ts +30 -14
- package/src/types.ts +12 -2
- package/test/middleware.test.ts +6 -4
- package/test/rendering.test.ts +510 -356
- package/test/routing.test.ts +36 -0
- package/test/scanning.test.ts +39 -8
- package/test/scope.test.ts +24 -8
- package/test/server.test.ts +27 -7
- package/test/streaming.test.ts +117 -98
- package/test/typegen.test.ts +52 -24
- package/tsconfig.json +1 -0
package/src/server/index.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { mkdirSync } from "node:fs";
|
|
2
|
+
import { dirname, isAbsolute, join } from "node:path";
|
|
3
|
+
import { plugin as bunPlugin, type ServerWebSocket } from "bun";
|
|
4
|
+
import { DevGraph } from "../dev/graph.ts";
|
|
5
|
+
import { HMR_CLIENT_SOURCE } from "../dev/hmr-client.ts";
|
|
6
|
+
import { HmrServer, handleChange } from "../dev/hmr-server.ts";
|
|
7
|
+
import { makeDevLoader } from "../dev/loader.ts";
|
|
8
|
+
import { startWatcher } from "../dev/watcher.ts";
|
|
2
9
|
import { renderRoute } from "../rendering";
|
|
3
10
|
import { registerSSRPlugin } from "../rendering/ssrPlugin";
|
|
4
11
|
import { findClosestError, matchRoute } from "../routing/router";
|
|
@@ -6,10 +13,12 @@ import { isErrorResult } from "../sentinels/error.ts";
|
|
|
6
13
|
import { isFailResult } from "../sentinels/fail.ts";
|
|
7
14
|
import { isRedirectResult } from "../sentinels/redirect.ts";
|
|
8
15
|
import type { GrimoireConfig, WsRouteHandler } from "../types";
|
|
9
|
-
import { buildProject } from "./build";
|
|
16
|
+
import { buildProject, rebuildForChange } from "./build";
|
|
10
17
|
import { createCookies } from "./cookie-utils";
|
|
11
18
|
import type {
|
|
12
19
|
Handle,
|
|
20
|
+
HandleError,
|
|
21
|
+
HandleFetch,
|
|
13
22
|
InitFunction,
|
|
14
23
|
RequestEvent,
|
|
15
24
|
ResolveFunction,
|
|
@@ -17,15 +26,23 @@ import type {
|
|
|
17
26
|
import { runDeserializeLocals, runHook, runRequestHooks } from "./plugins";
|
|
18
27
|
|
|
19
28
|
/**
|
|
20
|
-
* Try to load hooks.
|
|
29
|
+
* Try to load hooks.server.ts from the project root.
|
|
21
30
|
*/
|
|
22
|
-
async function loadHooks(
|
|
23
|
-
|
|
24
|
-
|
|
31
|
+
async function loadHooks(projectRoot: string): Promise<{
|
|
32
|
+
handle?: Handle;
|
|
33
|
+
init?: InitFunction;
|
|
34
|
+
handleError?: HandleError;
|
|
35
|
+
handleFetch?: HandleFetch;
|
|
36
|
+
}> {
|
|
25
37
|
const hooksPath = `${projectRoot}/hooks.server.ts`;
|
|
26
38
|
try {
|
|
27
39
|
const mod = await import(hooksPath);
|
|
28
|
-
return {
|
|
40
|
+
return {
|
|
41
|
+
handle: mod.handle,
|
|
42
|
+
init: mod.init,
|
|
43
|
+
handleError: mod.handleError,
|
|
44
|
+
handleFetch: mod.handleFetch,
|
|
45
|
+
};
|
|
29
46
|
} catch {
|
|
30
47
|
// no hooks file — that's fine
|
|
31
48
|
return {};
|
|
@@ -48,12 +65,15 @@ export async function createServer(config: GrimoireConfig = {}) {
|
|
|
48
65
|
finalConfig = plugin.config?.(finalConfig) ?? finalConfig;
|
|
49
66
|
}
|
|
50
67
|
|
|
51
|
-
//w timings????
|
|
52
68
|
const {
|
|
53
69
|
port = 3000,
|
|
54
70
|
host = "localhost",
|
|
55
71
|
plugins = [],
|
|
72
|
+
dev = false,
|
|
56
73
|
routes = "src/routes",
|
|
74
|
+
cspNonce,
|
|
75
|
+
//biome-ignore lint: not implemented yet
|
|
76
|
+
devEditor = "code",
|
|
57
77
|
_skipBuild = false,
|
|
58
78
|
} = finalConfig;
|
|
59
79
|
|
|
@@ -64,9 +84,8 @@ export async function createServer(config: GrimoireConfig = {}) {
|
|
|
64
84
|
tree = _tree;
|
|
65
85
|
} else {
|
|
66
86
|
// worker — build already done by loom, just scan routes
|
|
67
|
-
const { isAbsolute, join } = await import("node:path");
|
|
68
|
-
const { scanRoutes } = await import("../routing/scanner");
|
|
69
87
|
const routesDir = isAbsolute(routes) ? routes : join(process.cwd(), routes);
|
|
88
|
+
const { scanRoutes } = await import("../routing/scanner");
|
|
70
89
|
tree = await scanRoutes(routesDir, process.cwd());
|
|
71
90
|
}
|
|
72
91
|
|
|
@@ -74,91 +93,234 @@ export async function createServer(config: GrimoireConfig = {}) {
|
|
|
74
93
|
// WHAT ARE WE DOINGGGGGGGGG
|
|
75
94
|
registerSSRPlugin(plugins);
|
|
76
95
|
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
)
|
|
96
|
+
const graph = new DevGraph();
|
|
97
|
+
const hmr = dev ? new HmrServer() : null;
|
|
98
|
+
|
|
99
|
+
if (dev) {
|
|
100
|
+
const { ensureRuntimeBundle } = await import("../dev/runtime-bundle.ts");
|
|
101
|
+
await ensureRuntimeBundle(process.cwd());
|
|
102
|
+
|
|
103
|
+
// clean dev-modules cache from previous session
|
|
104
|
+
const { rmSync, mkdirSync } = await import("node:fs");
|
|
105
|
+
const devModulesDir = join(process.cwd(), ".grimoire/dev-modules");
|
|
106
|
+
try {
|
|
107
|
+
rmSync(devModulesDir, { recursive: true, force: true });
|
|
108
|
+
} catch {}
|
|
109
|
+
mkdirSync(devModulesDir, { recursive: true });
|
|
110
|
+
|
|
111
|
+
// register route files and scan their imports for the graph
|
|
112
|
+
const srcDir = isAbsolute(routes) ? routes : join(process.cwd(), routes);
|
|
113
|
+
const allFiles = [
|
|
114
|
+
...tree.routes,
|
|
115
|
+
...tree.layouts,
|
|
116
|
+
...tree.servers,
|
|
117
|
+
...tree.errors,
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
await Promise.all(
|
|
121
|
+
allFiles.map(async (rf) => {
|
|
122
|
+
graph.getOrCreate(rf.filePath, rf.filePath);
|
|
123
|
+
graph.markRoute(rf.filePath, rf);
|
|
124
|
+
try {
|
|
125
|
+
const source = await Bun.file(rf.filePath).text();
|
|
126
|
+
const imports = new Bun.Transpiler({ loader: "tsx" }).scanImports(
|
|
127
|
+
source,
|
|
128
|
+
);
|
|
129
|
+
for (const imp of imports) {
|
|
130
|
+
if (imp.path.startsWith(".") || imp.path.startsWith("$lib")) {
|
|
131
|
+
const resolved = imp.path.startsWith("$lib")
|
|
132
|
+
? join(process.cwd(), "src/lib", imp.path.slice(5))
|
|
133
|
+
: join(dirname(rf.filePath), imp.path);
|
|
134
|
+
graph.addEdge(rf.filePath, resolved);
|
|
135
|
+
// also register lib files as graph nodes
|
|
136
|
+
graph.getOrCreate(resolved, resolved);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
} catch {}
|
|
140
|
+
}),
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
startWatcher(srcDir, (f) =>
|
|
144
|
+
handleChange(f, graph, hmr!, srcDir, process.cwd(), tree),
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const loader = dev
|
|
149
|
+
? makeDevLoader(graph, process.cwd())
|
|
150
|
+
: (p: string) => import(p);
|
|
151
|
+
// Load hooks.server.ts
|
|
152
|
+
const {
|
|
153
|
+
handle: hooksHandle,
|
|
154
|
+
init: hooksInit,
|
|
155
|
+
handleError: hooksHandleError,
|
|
156
|
+
handleFetch: hooksHandleFetch,
|
|
157
|
+
} = await loadHooks(process.cwd());
|
|
81
158
|
|
|
82
159
|
// Run init hook if present
|
|
83
160
|
await hooksInit?.();
|
|
84
161
|
|
|
162
|
+
// Register alias plugin so dynamically-imported +server.ts routes can resolve custom aliases
|
|
163
|
+
const aliases = finalConfig.alias ?? {};
|
|
164
|
+
if (Object.keys(aliases).length > 0) {
|
|
165
|
+
bunPlugin({
|
|
166
|
+
name: "grimoire-alias",
|
|
167
|
+
setup(build) {
|
|
168
|
+
for (const [prefix, target] of Object.entries(aliases)) {
|
|
169
|
+
const escaped = prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
170
|
+
build.onResolve(
|
|
171
|
+
{ filter: new RegExp(`^${escaped}(/|$)`) },
|
|
172
|
+
(args) => ({
|
|
173
|
+
path: args.path.replace(prefix, join(process.cwd(), target)),
|
|
174
|
+
}),
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
}
|
|
85
180
|
const server = Bun.serve({
|
|
86
181
|
port,
|
|
87
182
|
hostname: host,
|
|
88
183
|
fetch: async (req) => {
|
|
89
|
-
// Shared locals object for this request — same reference across all route handlers.
|
|
90
|
-
// hooks.index.ts handle() runs via resolve() for page routes only.
|
|
91
|
-
// For server/WebSocket routes, auth must use cookies/headers in upgrade().
|
|
92
|
-
// If this request came from a coordinator, deserialize its locals
|
|
93
184
|
const rawLocals = req.headers.get("X-Grimoire-Locals");
|
|
94
185
|
const locals: App.Locals = rawLocals
|
|
95
186
|
? ((await runDeserializeLocals(plugins, rawLocals)) ?? {})
|
|
96
187
|
: {};
|
|
188
|
+
const url = new URL(req.url);
|
|
97
189
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
190
|
+
if (dev && url.pathname === "/__grimoire__/hmr") {
|
|
191
|
+
//@ts-expect-error holy shut up
|
|
192
|
+
if (server.upgrade(req, { data: { __hmrClient: true } })) {
|
|
193
|
+
return undefined as unknown as Response;
|
|
194
|
+
}
|
|
195
|
+
return new Response("Upgrade Required", { status: 426 });
|
|
196
|
+
}
|
|
197
|
+
if (dev && url.pathname === "/__grimoire__/hmr-client.js") {
|
|
198
|
+
return new Response(HMR_CLIENT_SOURCE, {
|
|
199
|
+
headers: { "Content-Type": "application/javascript" },
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
if (dev && url.pathname === "/__grimoire__/open") {
|
|
203
|
+
const file = url.searchParams.get("file") ?? "";
|
|
204
|
+
const line = url.searchParams.get("line") ?? "1";
|
|
205
|
+
const col = url.searchParams.get("col") ?? "1";
|
|
206
|
+
const editor = finalConfig.devEditor ?? "code";
|
|
207
|
+
//@ts-expect-error Bun spawn has werird types just shut up
|
|
208
|
+
Bun.spawn([editor, "--goto", `${file}:${line}:${col}`]);
|
|
209
|
+
return new Response(null, { status: 204 });
|
|
210
|
+
}
|
|
211
|
+
if (dev && url.pathname.startsWith("/__grimoire__/dep/")) {
|
|
212
|
+
const depName = decodeURIComponent(
|
|
213
|
+
url.pathname.slice("/__grimoire__/dep/".length).replace(/\.js$/, ""),
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
// grimoire shim
|
|
217
|
+
if (depName.includes("@sigil-dev__grimoire")) {
|
|
218
|
+
return Response.redirect("/__grimoire__/grimoire-client.js", 302);
|
|
104
219
|
}
|
|
105
220
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
221
|
+
// convert URL-safe name back to package name
|
|
222
|
+
// @codex__shared → @codex/shared
|
|
223
|
+
const pkgName = depName.startsWith("@")
|
|
224
|
+
? depName.replace("__", "/")
|
|
225
|
+
: depName;
|
|
226
|
+
|
|
227
|
+
const depsDir = join(process.cwd(), ".grimoire/deps");
|
|
228
|
+
mkdirSync(depsDir, { recursive: true });
|
|
229
|
+
const outFile = join(depsDir, depName + ".js");
|
|
230
|
+
|
|
231
|
+
if (!(await Bun.file(outFile).exists())) {
|
|
232
|
+
console.log("[sigil hmr] bundling dep:", pkgName);
|
|
233
|
+
try {
|
|
234
|
+
const entry = Bun.resolveSync(pkgName, process.cwd());
|
|
235
|
+
const result = await Bun.build({
|
|
236
|
+
entrypoints: [entry],
|
|
237
|
+
outdir: depsDir,
|
|
238
|
+
naming: depName + ".js",
|
|
239
|
+
target: "browser",
|
|
240
|
+
format: "esm",
|
|
241
|
+
minify: false,
|
|
242
|
+
external: ["@sigil-dev/runtime"],
|
|
116
243
|
});
|
|
244
|
+
if (!result.success) {
|
|
245
|
+
console.error(
|
|
246
|
+
"[sigil hmr] dep bundle failed:",
|
|
247
|
+
pkgName,
|
|
248
|
+
result.logs,
|
|
249
|
+
);
|
|
250
|
+
return new Response(
|
|
251
|
+
`throw new Error("failed to bundle dep: ${pkgName}")`,
|
|
252
|
+
{ headers: { "Content-Type": "application/javascript" } },
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
console.log("[sigil hmr] bundled dep:", pkgName);
|
|
256
|
+
} catch (e: any) {
|
|
257
|
+
return new Response(
|
|
258
|
+
`throw new Error("dep not found: ${pkgName}: ${e.message}")`,
|
|
259
|
+
{ headers: { "Content-Type": "application/javascript" } },
|
|
260
|
+
);
|
|
117
261
|
}
|
|
118
|
-
return new Response("Not Found", { status: 404 });
|
|
119
262
|
}
|
|
120
263
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
264
|
+
return new Response(Bun.file(outFile), {
|
|
265
|
+
headers: {
|
|
266
|
+
"Content-Type": "application/javascript",
|
|
267
|
+
"Cache-Control": "no-store",
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (dev && url.pathname.startsWith("/__grimoire__/m/")) {
|
|
273
|
+
const clientPath = decodeURIComponent(
|
|
274
|
+
url.pathname.slice("/__grimoire__/m/".length),
|
|
275
|
+
);
|
|
276
|
+
const v = url.searchParams.get("v");
|
|
277
|
+
|
|
278
|
+
if (v) {
|
|
279
|
+
// versioned request — serve from dev-modules cache
|
|
280
|
+
// find the cache file for this version
|
|
281
|
+
const { createHash } = await import("node:crypto");
|
|
282
|
+
const absPath = join(process.cwd(), clientPath.replace(/\.js$/, ""));
|
|
283
|
+
const fileHash = createHash("md5")
|
|
284
|
+
.update(absPath)
|
|
285
|
+
.digest("hex")
|
|
286
|
+
.slice(0, 8);
|
|
287
|
+
const cachePath = join(
|
|
288
|
+
process.cwd(),
|
|
289
|
+
`.grimoire/dev-modules/${fileHash}_v${v}.js`,
|
|
290
|
+
);
|
|
291
|
+
const file = Bun.file(cachePath);
|
|
292
|
+
if (await file.exists()) {
|
|
293
|
+
return new Response(file, {
|
|
294
|
+
headers: {
|
|
295
|
+
"Content-Type": "application/javascript",
|
|
296
|
+
"Cache-Control": "no-store",
|
|
297
|
+
},
|
|
298
|
+
});
|
|
154
299
|
}
|
|
300
|
+
}
|
|
155
301
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
return
|
|
302
|
+
// unversioned or cache miss — compile on demand
|
|
303
|
+
const absPath = join(process.cwd(), clientPath.replace(/\.js$/, ""));
|
|
304
|
+
try {
|
|
305
|
+
const { compileForBrowser } = await import("../dev/compile-module");
|
|
306
|
+
const result = await compileForBrowser(absPath, process.cwd());
|
|
307
|
+
return new Response(result.js, {
|
|
308
|
+
headers: {
|
|
309
|
+
"Content-Type": "application/javascript",
|
|
310
|
+
"Cache-Control": "no-store",
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
} catch (e: any) {
|
|
314
|
+
return new Response(`throw new Error(${JSON.stringify(e.message)})`, {
|
|
315
|
+
headers: { "Content-Type": "application/javascript" },
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return runRequestHooks(plugins, req, async () => {
|
|
321
|
+
const publicFile = Bun.file(`${process.cwd()}/public${url.pathname}`);
|
|
322
|
+
if (await publicFile.exists()) {
|
|
323
|
+
return new Response(publicFile);
|
|
162
324
|
}
|
|
163
325
|
|
|
164
326
|
const HTTP_METHODS = [
|
|
@@ -171,7 +333,7 @@ export async function createServer(config: GrimoireConfig = {}) {
|
|
|
171
333
|
"OPTIONS",
|
|
172
334
|
] as const;
|
|
173
335
|
|
|
174
|
-
// Build
|
|
336
|
+
// Build event BEFORE matching — handle() runs pre-match like SvelteKit
|
|
175
337
|
const cookieHeader = req.headers.get("cookie") ?? "";
|
|
176
338
|
const cookies = createCookies(cookieHeader);
|
|
177
339
|
const setHeadersMap: Record<string, string> = {};
|
|
@@ -179,20 +341,112 @@ export async function createServer(config: GrimoireConfig = {}) {
|
|
|
179
341
|
const event: RequestEvent = {
|
|
180
342
|
request: req,
|
|
181
343
|
url,
|
|
182
|
-
params:
|
|
344
|
+
params: {},
|
|
183
345
|
locals,
|
|
184
346
|
cookies,
|
|
347
|
+
route: { id: "" },
|
|
348
|
+
fetch: globalThis.fetch,
|
|
185
349
|
setHeaders: (headers) => Object.assign(setHeadersMap, headers),
|
|
186
350
|
};
|
|
187
351
|
|
|
188
|
-
|
|
352
|
+
if (hooksHandleFetch) {
|
|
353
|
+
event.fetch = (reqInfo: RequestInfo | URL, init?: RequestInit) => {
|
|
354
|
+
const r = new Request(reqInfo, init);
|
|
355
|
+
return Promise.resolve(
|
|
356
|
+
hooksHandleFetch({ request: r, fetch: globalThis.fetch, event }),
|
|
357
|
+
);
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
189
361
|
const resolve: ResolveFunction = async (evt) => {
|
|
190
|
-
//
|
|
362
|
+
// matching happens here — after handle() has run
|
|
363
|
+
const matched = matchRoute(tree, evt.url);
|
|
364
|
+
|
|
365
|
+
if (!matched) {
|
|
366
|
+
const error = findClosestError(tree.errors, evt.url.pathname);
|
|
367
|
+
if (error) {
|
|
368
|
+
const mod = await loader(error.filePath);
|
|
369
|
+
const html = mod.default({ status: 404, message: "Not Found" });
|
|
370
|
+
return new Response(html, {
|
|
371
|
+
status: 404,
|
|
372
|
+
headers: { "Content-Type": "text/html" },
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
return new Response("Not Found", { status: 404 });
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// patch event with match results so handle() epilogue can read them
|
|
379
|
+
evt.params = matched.params;
|
|
380
|
+
evt.route = { id: matched.route.path };
|
|
381
|
+
|
|
382
|
+
// API routes (+server.ts)
|
|
383
|
+
if (matched.route.type === "server") {
|
|
384
|
+
const mod = await loader(matched.route.filePath);
|
|
385
|
+
|
|
386
|
+
const isWsUpgrade =
|
|
387
|
+
evt.request.headers.get("upgrade")?.toLowerCase() === "websocket";
|
|
388
|
+
if (isWsUpgrade && mod.websocket) {
|
|
389
|
+
let extraData: Record<string, unknown> = {};
|
|
390
|
+
if (mod.upgrade) {
|
|
391
|
+
try {
|
|
392
|
+
const result = await mod.upgrade({
|
|
393
|
+
request: evt.request,
|
|
394
|
+
params: matched.params,
|
|
395
|
+
url: evt.url,
|
|
396
|
+
locals: evt.locals,
|
|
397
|
+
});
|
|
398
|
+
if (result && typeof result === "object") extraData = result;
|
|
399
|
+
} catch {
|
|
400
|
+
return new Response("Upgrade Required", { status: 426 });
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
const wsData: _WsInternalData = {
|
|
404
|
+
params: matched.params,
|
|
405
|
+
__handler: mod.websocket,
|
|
406
|
+
...extraData,
|
|
407
|
+
};
|
|
408
|
+
//@ts-expect-error i dont know what you are talking about please
|
|
409
|
+
if (server.upgrade(req, { data: wsData })) {
|
|
410
|
+
return undefined as unknown as Response;
|
|
411
|
+
}
|
|
412
|
+
return new Response("Upgrade Required", { status: 426 });
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const handler = mod[evt.request.method];
|
|
416
|
+
if (!handler) {
|
|
417
|
+
return new Response("Method Not Allowed", { status: 405 });
|
|
418
|
+
}
|
|
419
|
+
return handler({
|
|
420
|
+
request: evt.request,
|
|
421
|
+
params: matched.params,
|
|
422
|
+
url: evt.url,
|
|
423
|
+
locals: evt.locals,
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// form actions (+page.server.ts)
|
|
191
428
|
if (
|
|
192
429
|
matched.pageServer &&
|
|
193
430
|
HTTP_METHODS.includes(evt.request.method as any)
|
|
194
431
|
) {
|
|
195
|
-
const mod = await
|
|
432
|
+
const mod = await loader(matched.pageServer.filePath);
|
|
433
|
+
|
|
434
|
+
if (
|
|
435
|
+
mod.csrf !== false &&
|
|
436
|
+
evt.request.method !== "GET" &&
|
|
437
|
+
evt.request.method !== "HEAD"
|
|
438
|
+
) {
|
|
439
|
+
const cookieToken = cookies.get("_csrf");
|
|
440
|
+
const formData = await evt.request
|
|
441
|
+
.clone()
|
|
442
|
+
.formData()
|
|
443
|
+
.catch(() => null);
|
|
444
|
+
const bodyToken = formData?.get("_csrf");
|
|
445
|
+
if (!cookieToken || cookieToken !== String(bodyToken)) {
|
|
446
|
+
return new Response("Forbidden", { status: 403 });
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
196
450
|
const handler = mod[evt.request.method];
|
|
197
451
|
if (handler) {
|
|
198
452
|
let result: any;
|
|
@@ -225,7 +479,7 @@ export async function createServer(config: GrimoireConfig = {}) {
|
|
|
225
479
|
evt.url.pathname,
|
|
226
480
|
);
|
|
227
481
|
if (errorPage) {
|
|
228
|
-
const errMod = await
|
|
482
|
+
const errMod = await loader(errorPage.filePath);
|
|
229
483
|
const html = errMod.default({
|
|
230
484
|
status: e.status,
|
|
231
485
|
message: e.message,
|
|
@@ -264,16 +518,17 @@ export async function createServer(config: GrimoireConfig = {}) {
|
|
|
264
518
|
matched,
|
|
265
519
|
evt.request,
|
|
266
520
|
tree.errors,
|
|
267
|
-
|
|
521
|
+
loader,
|
|
268
522
|
evt.locals,
|
|
269
523
|
plugins,
|
|
524
|
+
cspNonce,
|
|
525
|
+
dev,
|
|
270
526
|
);
|
|
271
527
|
|
|
272
528
|
if (evt.request.headers.get("x-grimoire-navigate") === "1") {
|
|
273
529
|
return response;
|
|
274
530
|
}
|
|
275
531
|
|
|
276
|
-
// Streaming SSR: if onRouteRender plugins exist, consume stream
|
|
277
532
|
const hasRenderPlugins = plugins.some((p) => p.onRouteRender);
|
|
278
533
|
if (hasRenderPlugins) {
|
|
279
534
|
let html = await response.text();
|
|
@@ -294,18 +549,41 @@ export async function createServer(config: GrimoireConfig = {}) {
|
|
|
294
549
|
return response;
|
|
295
550
|
};
|
|
296
551
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
552
|
+
let response: Response;
|
|
553
|
+
try {
|
|
554
|
+
if (!hooksHandle) {
|
|
555
|
+
response = await resolve(event);
|
|
556
|
+
} else {
|
|
557
|
+
response = await hooksHandle({ event, resolve });
|
|
558
|
+
}
|
|
559
|
+
} catch (err) {
|
|
560
|
+
console.error(
|
|
561
|
+
`[grimoire] ${event.request.method} ${event.url.pathname} failed:`,
|
|
562
|
+
err,
|
|
563
|
+
);
|
|
564
|
+
await hooksHandleError?.({
|
|
565
|
+
error: err,
|
|
566
|
+
event,
|
|
567
|
+
status: 500,
|
|
568
|
+
message: "Internal Server Error",
|
|
569
|
+
});
|
|
570
|
+
const errorHtmlPath = `${process.cwd()}/error.html`;
|
|
571
|
+
try {
|
|
572
|
+
const errorFile = Bun.file(errorHtmlPath);
|
|
573
|
+
if (await errorFile.exists()) {
|
|
574
|
+
return new Response(errorFile, {
|
|
575
|
+
status: 500,
|
|
576
|
+
headers: { "Content-Type": "text/html" },
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
} catch {}
|
|
580
|
+
const message =
|
|
581
|
+
process.env.NODE_ENV === "production"
|
|
582
|
+
? "Internal Server Error"
|
|
583
|
+
: `${err instanceof Error ? `${err.message}\n${err.stack}` : String(err)}`;
|
|
584
|
+
return new Response(message, { status: 500 });
|
|
300
585
|
}
|
|
301
586
|
|
|
302
|
-
// Run through hooks chain
|
|
303
|
-
let response = await hooksHandle({
|
|
304
|
-
event,
|
|
305
|
-
resolve,
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
// Apply setHeaders
|
|
309
587
|
const setCookieHeaders = cookies.toHeaders();
|
|
310
588
|
if (
|
|
311
589
|
setCookieHeaders.length > 0 ||
|
|
@@ -329,21 +607,27 @@ export async function createServer(config: GrimoireConfig = {}) {
|
|
|
329
607
|
});
|
|
330
608
|
},
|
|
331
609
|
websocket: {
|
|
332
|
-
open(ws: ServerWebSocket<
|
|
333
|
-
ws.data.
|
|
610
|
+
open(ws: ServerWebSocket<any>) {
|
|
611
|
+
if (ws.data.__hmrClient) {
|
|
612
|
+
hmr?.addClient(ws);
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
(ws.data as _WsInternalData).__handler?.open?.(ws);
|
|
334
616
|
},
|
|
335
|
-
message(ws: ServerWebSocket<
|
|
336
|
-
ws.data.
|
|
617
|
+
message(ws: ServerWebSocket<any>, data: string | Buffer) {
|
|
618
|
+
if (ws.data.__hmrClient) return;
|
|
619
|
+
(ws.data as _WsInternalData).__handler?.message?.(ws, data);
|
|
337
620
|
},
|
|
338
|
-
close(
|
|
339
|
-
ws
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
ws.data.__handler?.close?.(ws, code, reason);
|
|
621
|
+
close(ws: ServerWebSocket<any>, code: number, reason?: string) {
|
|
622
|
+
if (ws.data.__hmrClient) {
|
|
623
|
+
hmr?.removeClient(ws);
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
(ws.data as _WsInternalData).__handler?.close?.(ws, code, reason);
|
|
344
627
|
},
|
|
345
|
-
drain(ws: ServerWebSocket<
|
|
346
|
-
ws.data.
|
|
628
|
+
drain(ws: ServerWebSocket<any>) {
|
|
629
|
+
if (ws.data.__hmrClient) return;
|
|
630
|
+
(ws.data as _WsInternalData).__handler?.drain?.(ws);
|
|
347
631
|
},
|
|
348
632
|
},
|
|
349
633
|
});
|