@sigil-dev/grimoire 0.8.4 → 0.8.6
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/package.json +5 -5
- package/server.ts +1 -0
- package/src/rendering/index.ts +4 -2
- package/src/routing/transform-routes.ts +2 -2
- package/src/server/coordinator.ts +233 -266
- package/src/server/index.ts +622 -616
- package/src/server/interactive.ts +54 -0
- package/src/types.ts +180 -178
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"module": "index.ts",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
|
-
"version": "0.8.
|
|
6
|
+
"version": "0.8.6",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./index.ts",
|
|
9
9
|
"./server": "./server.ts",
|
|
@@ -33,13 +33,13 @@
|
|
|
33
33
|
"vite": "^8.0.16"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"@sigil-dev/compiler": "0.8.
|
|
37
|
-
"@sigil-dev/runtime": "0.8.
|
|
36
|
+
"@sigil-dev/compiler": "0.8.6",
|
|
37
|
+
"@sigil-dev/runtime": "0.8.6",
|
|
38
38
|
"typescript": "^5"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/bun": "latest",
|
|
42
|
-
"@sigil-dev/compiler": "0.8.
|
|
43
|
-
"@sigil-dev/runtime": "0.8.
|
|
42
|
+
"@sigil-dev/compiler": "0.8.6",
|
|
43
|
+
"@sigil-dev/runtime": "0.8.6"
|
|
44
44
|
}
|
|
45
45
|
}
|
package/server.ts
CHANGED
package/src/rendering/index.ts
CHANGED
|
@@ -36,14 +36,16 @@ export async function renderRoute(
|
|
|
36
36
|
locals: Record<string, any> = {},
|
|
37
37
|
plugins: GrimoirePlugin[] = [],
|
|
38
38
|
cspNonce?: string,
|
|
39
|
-
|
|
39
|
+
dev?: boolean,
|
|
40
|
+
patchedFetch
|
|
40
41
|
): Promise<Response> {
|
|
41
42
|
return runWithContext(async () => {
|
|
42
43
|
const context: LoadContext = {
|
|
43
44
|
request: req,
|
|
44
45
|
params: matched.params,
|
|
45
46
|
url: new URL(req.url),
|
|
46
|
-
|
|
47
|
+
locals,
|
|
48
|
+
fetch: patchedFetch ?? globalThis.fetch
|
|
47
49
|
};
|
|
48
50
|
|
|
49
51
|
initHead(cspNonce);
|
|
@@ -76,8 +76,8 @@ export async function transformRoutes(
|
|
|
76
76
|
plugins: ["typescript", "jsx"], // no isTSX
|
|
77
77
|
},
|
|
78
78
|
plugins: [
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
[sigilPlugin, { hash, mode }],
|
|
80
|
+
//@ts-expect-error it fills what it needs
|
|
81
81
|
rewriteRelativeImports(route.filePath),
|
|
82
82
|
],
|
|
83
83
|
filename: route.filePath,
|
|
@@ -3,303 +3,270 @@ import { type Subprocess, spawn } from "bun";
|
|
|
3
3
|
import { matchRoute } from "../routing/router";
|
|
4
4
|
import { type RouteFile, type RouteTree, scanRoutes } from "../routing/scanner";
|
|
5
5
|
import type {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
CoordinatorContext,
|
|
7
|
+
GrimoireConfig,
|
|
8
|
+
GrimoirePlugin,
|
|
9
|
+
WorkerDescriptor,
|
|
10
|
+
WorkerMode,
|
|
11
11
|
} from "../types";
|
|
12
12
|
import { buildProject } from "./build";
|
|
13
13
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
runHook,
|
|
15
|
+
runRouteRequest,
|
|
16
|
+
runSerializeLocals,
|
|
17
|
+
runWorkerSpawn,
|
|
18
18
|
} from "./plugins";
|
|
19
|
+
import { log } from "../logger/instance";
|
|
20
|
+
|
|
21
|
+
const logger = log.scope("loom")
|
|
19
22
|
|
|
20
23
|
async function waitForWorkers(
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
workers: LiveWorker[],
|
|
25
|
+
timeoutMs = 10_000,
|
|
23
26
|
): Promise<void> {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (ready.has(w.descriptor.globalIndex)) continue;
|
|
42
|
-
if (w.descriptor.pid != null) {
|
|
43
|
-
ready.add(w.descriptor.globalIndex);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
await Bun.sleep(50);
|
|
47
|
-
}
|
|
27
|
+
const ready = new Set<number>();
|
|
28
|
+
const start = Date.now();
|
|
29
|
+
while (ready.size < workers.length) {
|
|
30
|
+
if (Date.now() - start > timeoutMs) {
|
|
31
|
+
const pending = workers
|
|
32
|
+
.filter((w) => !ready.has(w.descriptor.globalIndex))
|
|
33
|
+
.map((w) => w.descriptor.name ?? `${w.descriptor.mode}-${w.descriptor.index}`)
|
|
34
|
+
.join(", ");
|
|
35
|
+
throw new Error(`Grimoire: workers timed out: ${pending}`);
|
|
36
|
+
}
|
|
37
|
+
for (const w of workers) {
|
|
38
|
+
if (!ready.has(w.descriptor.globalIndex) && w.descriptor.pid != null) {
|
|
39
|
+
ready.add(w.descriptor.globalIndex);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
await Bun.sleep(50);
|
|
43
|
+
}
|
|
48
44
|
}
|
|
49
45
|
|
|
50
46
|
function roundRobin(workers: WorkerDescriptor[]): WorkerDescriptor {
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
const idx = roundRobinCounter++ % workers.length;
|
|
48
|
+
return workers[idx];
|
|
53
49
|
}
|
|
54
50
|
let roundRobinCounter = 0;
|
|
55
51
|
|
|
56
|
-
function consistentHash(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
let hash = 0;
|
|
62
|
-
for (let i = 0; i < key.length; i++) {
|
|
63
|
-
hash = (hash * 31 + key.charCodeAt(i)) >>> 0;
|
|
64
|
-
}
|
|
65
|
-
return workers[hash % workers.length];
|
|
52
|
+
function consistentHash(req: Request, workers: WorkerDescriptor[]): WorkerDescriptor {
|
|
53
|
+
const key = req.headers.get("cookie") ?? req.url;
|
|
54
|
+
let hash = 0;
|
|
55
|
+
for (let i = 0; i < key.length; i++) hash = (hash * 31 + key.charCodeAt(i)) >>> 0;
|
|
56
|
+
return workers[hash % workers.length];
|
|
66
57
|
}
|
|
67
58
|
|
|
68
|
-
function defaultRoute(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const matched = matchRoute(tree, url);
|
|
84
|
-
|
|
85
|
-
if (matched?.route.type === "server") {
|
|
86
|
-
const apiWorkers = workers.filter(
|
|
87
|
-
(w) => w.mode === "api" || w.mode === "full",
|
|
88
|
-
);
|
|
89
|
-
if (apiWorkers.length) return roundRobin(apiWorkers);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const frontendWorkers = workers.filter(
|
|
93
|
-
(w) => w.mode === "frontend" || w.mode === "full",
|
|
94
|
-
);
|
|
95
|
-
if (frontendWorkers.length) return roundRobin(frontendWorkers);
|
|
96
|
-
|
|
97
|
-
// fallback — should not happen if scale spec is valid
|
|
98
|
-
return workers[0];
|
|
59
|
+
function defaultRoute(req: Request, workers: WorkerDescriptor[], tree: RouteTree): WorkerDescriptor {
|
|
60
|
+
const isWs = req.headers.get("upgrade")?.toLowerCase() === "websocket";
|
|
61
|
+
if (isWs) {
|
|
62
|
+
const ws = workers.filter((w) => w.mode === "ws" || w.mode === "full");
|
|
63
|
+
if (ws.length) return consistentHash(req, ws);
|
|
64
|
+
}
|
|
65
|
+
const url = new URL(req.url);
|
|
66
|
+
const matched = matchRoute(tree, url);
|
|
67
|
+
if (matched?.route.type === "server") {
|
|
68
|
+
const api = workers.filter((w) => w.mode === "api" || w.mode === "full");
|
|
69
|
+
if (api.length) return roundRobin(api);
|
|
70
|
+
}
|
|
71
|
+
const frontend = workers.filter((w) => w.mode === "frontend" || w.mode === "full");
|
|
72
|
+
if (frontend.length) return roundRobin(frontend);
|
|
73
|
+
return workers[0];
|
|
99
74
|
}
|
|
100
75
|
|
|
101
76
|
export interface ScaleSpec {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
77
|
+
api?: number;
|
|
78
|
+
frontend?: number;
|
|
79
|
+
ws?: number;
|
|
80
|
+
[mode: string]: number | undefined;
|
|
106
81
|
}
|
|
107
82
|
|
|
108
83
|
export interface CoordinatorOptions {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
84
|
+
config: GrimoireConfig;
|
|
85
|
+
plugins: GrimoirePlugin[];
|
|
86
|
+
scale: ScaleSpec;
|
|
87
|
+
noBuild: boolean;
|
|
113
88
|
}
|
|
114
89
|
|
|
115
90
|
interface LiveWorker {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
91
|
+
descriptor: WorkerDescriptor;
|
|
92
|
+
process: Subprocess;
|
|
93
|
+
port: number;
|
|
94
|
+
env: Record<string, string>;
|
|
95
|
+
intentional: boolean; // ← add
|
|
119
96
|
}
|
|
120
97
|
|
|
121
98
|
export async function startCoordinator(options: CoordinatorOptions) {
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
name: w.descriptor.name,
|
|
283
|
-
mode: w.descriptor.mode,
|
|
284
|
-
port: w.port,
|
|
285
|
-
index: w.descriptor.index,
|
|
286
|
-
})),
|
|
287
|
-
};
|
|
99
|
+
const { config, plugins, scale, noBuild } = options;
|
|
100
|
+
const { port = 3000, host = "localhost", routes = "src/routes" } = config;
|
|
101
|
+
let stopping = false;
|
|
102
|
+
|
|
103
|
+
const workerEntry = join(import.meta.dir, "./worker.ts");
|
|
104
|
+
|
|
105
|
+
// ── inner helpers (close over plugins/stopping/workerEntry) ──
|
|
106
|
+
|
|
107
|
+
function spawnWorker(lw: LiveWorker): void {
|
|
108
|
+
const proc = spawn(["bun", "run", workerEntry], {
|
|
109
|
+
env: lw.env,
|
|
110
|
+
ipc(message) {
|
|
111
|
+
if (message?.ready) {
|
|
112
|
+
lw.descriptor.pid = proc.pid;
|
|
113
|
+
runHook(plugins, "onWorkerReady", lw.descriptor);
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
onExit(_, exitCode) {
|
|
117
|
+
if (lw.intentional) {
|
|
118
|
+
lw.intentional = false;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
runHook(plugins, "onWorkerDeath", lw.descriptor, stopping ? "intentional" : "crash");
|
|
122
|
+
if (!stopping) {
|
|
123
|
+
logger.error(`worker ${lw.descriptor.name ?? `${lw.descriptor.mode}-${lw.descriptor.index}`} crashed (exit ${exitCode}), respawning...`);
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
lw.process = proc;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function respawnWorker(lw: LiveWorker): Promise<void> {
|
|
131
|
+
lw.intentional = true;
|
|
132
|
+
lw.process.kill();
|
|
133
|
+
lw.descriptor.pid = undefined;
|
|
134
|
+
spawnWorker(lw);
|
|
135
|
+
await waitForWorkers([lw]);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// 1. build
|
|
139
|
+
if (!noBuild) {
|
|
140
|
+
const { result } = await buildProject(config, plugins);
|
|
141
|
+
if (!result.success) throw new Error("Grimoire: build failed");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// 2. ephemeral secret
|
|
145
|
+
const secret = crypto.randomUUID() + crypto.randomUUID();
|
|
146
|
+
|
|
147
|
+
// 3. scan routes
|
|
148
|
+
const tree = await scanRoutes(routes, process.cwd());
|
|
149
|
+
|
|
150
|
+
// 4. route slices
|
|
151
|
+
const slices: Record<string, RouteFile[]> = {
|
|
152
|
+
api: tree.routes.filter((r) => r.type === "server"),
|
|
153
|
+
ws: tree.routes.filter((r) => r.type === "server"),
|
|
154
|
+
frontend: tree.routes.filter((r) => r.type === "page" || r.type === "simple"),
|
|
155
|
+
full: [...tree.routes],
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// 5. spawn workers
|
|
159
|
+
const liveWorkers: LiveWorker[] = [];
|
|
160
|
+
let globalIndex = 0;
|
|
161
|
+
let nextPort = port + 1;
|
|
162
|
+
|
|
163
|
+
for (const [mode, count = 0] of Object.entries(scale)) {
|
|
164
|
+
for (let i = 0; i < count; i++) {
|
|
165
|
+
const workerPort = nextPort++;
|
|
166
|
+
const descriptor: WorkerDescriptor = {
|
|
167
|
+
mode: mode as WorkerMode,
|
|
168
|
+
index: i + 1,
|
|
169
|
+
globalIndex: globalIndex++,
|
|
170
|
+
internalUrl: `http://127.0.0.1:${workerPort}`,
|
|
171
|
+
routes: slices[mode] ?? slices.full,
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const publicEnvVars: Record<string, string> = {};
|
|
175
|
+
for (const [k, v] of Object.entries(process.env)) {
|
|
176
|
+
if (k.startsWith("PUBLIC_") && v !== undefined) publicEnvVars[k] = v;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const workerEnv = await runWorkerSpawn(plugins, descriptor);
|
|
180
|
+
if (workerEnv.name) descriptor.name = workerEnv.name;
|
|
181
|
+
|
|
182
|
+
const env = {
|
|
183
|
+
...process.env,
|
|
184
|
+
...workerEnv.env,
|
|
185
|
+
...publicEnvVars,
|
|
186
|
+
GRIMOIRE_MODE: mode,
|
|
187
|
+
GRIMOIRE_WORKER_INDEX: String(i),
|
|
188
|
+
GRIMOIRE_WORKER_PORT: String(workerPort),
|
|
189
|
+
GRIMOIRE_INTERNAL_SECRET: secret,
|
|
190
|
+
GRIMOIRE_WORKER_NAME: descriptor.name ?? `${mode}-${i}`,
|
|
191
|
+
} as Record<string, string>;
|
|
192
|
+
|
|
193
|
+
const lw: LiveWorker = { descriptor, process: null as any, port: workerPort, env, intentional: false };
|
|
194
|
+
spawnWorker(lw);
|
|
195
|
+
liveWorkers.push(lw);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// 6. wait for ready
|
|
200
|
+
await waitForWorkers(liveWorkers);
|
|
201
|
+
|
|
202
|
+
// 7. onCoordinatorStart
|
|
203
|
+
const ctx: CoordinatorContext = {
|
|
204
|
+
workers: liveWorkers.map((w) => w.descriptor),
|
|
205
|
+
port,
|
|
206
|
+
secret,
|
|
207
|
+
routes: tree,
|
|
208
|
+
};
|
|
209
|
+
await runHook(plugins, "onCoordinatorStart", ctx);
|
|
210
|
+
|
|
211
|
+
// 8. coordinator server
|
|
212
|
+
const server = Bun.serve({
|
|
213
|
+
port,
|
|
214
|
+
hostname: host,
|
|
215
|
+
fetch: async (req) => {
|
|
216
|
+
const locals: App.Locals = {};
|
|
217
|
+
const descriptors = liveWorkers.map((w) => w.descriptor);
|
|
218
|
+
const chosen =
|
|
219
|
+
(await runRouteRequest(plugins, req, descriptors, tree)) ??
|
|
220
|
+
defaultRoute(req, descriptors, tree);
|
|
221
|
+
const serialized =
|
|
222
|
+
(await runSerializeLocals(plugins, locals)) ??
|
|
223
|
+
defaultSerialize(locals, secret);
|
|
224
|
+
return fetch(chosen.internalUrl + new URL(req.url).pathname + new URL(req.url).search, {
|
|
225
|
+
method: req.method,
|
|
226
|
+
headers: {
|
|
227
|
+
//@ts-expect-error shut up
|
|
228
|
+
...Object.fromEntries(req.headers),
|
|
229
|
+
"X-Grimoire-Locals": serialized,
|
|
230
|
+
"X-Grimoire-Internal": secret,
|
|
231
|
+
},
|
|
232
|
+
body: req.body,
|
|
233
|
+
});
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// 9. expose stop + restartAll — CLI owns process lifetime
|
|
238
|
+
const stop = async () => {
|
|
239
|
+
if (stopping) return;
|
|
240
|
+
stopping = true;
|
|
241
|
+
await runHook(plugins, "onStop", "shutdown");
|
|
242
|
+
for (const w of liveWorkers) w.process.kill();
|
|
243
|
+
server.stop();
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const restartAll = () => Promise.all(liveWorkers.map((w) => respawnWorker(w)));
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
server,
|
|
250
|
+
stop,
|
|
251
|
+
restartAll,
|
|
252
|
+
workers: liveWorkers.map((w) => ({
|
|
253
|
+
name: w.descriptor.name,
|
|
254
|
+
mode: w.descriptor.mode,
|
|
255
|
+
port: w.port,
|
|
256
|
+
index: w.descriptor.index,
|
|
257
|
+
})),
|
|
258
|
+
};
|
|
288
259
|
}
|
|
289
260
|
|
|
290
261
|
function defaultSerialize(locals: App.Locals, secret: string): string {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
.digest("hex");
|
|
295
|
-
return Buffer.from(JSON.stringify({ payload, sig })).toString("base64");
|
|
262
|
+
const payload = JSON.stringify(locals);
|
|
263
|
+
const sig = new Bun.CryptoHasher("sha256").update(secret + payload).digest("hex");
|
|
264
|
+
return Buffer.from(JSON.stringify({ payload, sig })).toString("base64");
|
|
296
265
|
}
|
|
297
266
|
|
|
298
267
|
function _defaultDeserialize(raw: string, secret: string): App.Locals {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
if (sig !== expected) throw new Error("Grimoire: locals signature invalid");
|
|
304
|
-
return JSON.parse(payload);
|
|
268
|
+
const { payload, sig } = JSON.parse(Buffer.from(raw, "base64").toString());
|
|
269
|
+
const expected = new Bun.CryptoHasher("sha256").update(secret + payload).digest("hex");
|
|
270
|
+
if (sig !== expected) throw new Error("Grimoire: locals signature invalid");
|
|
271
|
+
return JSON.parse(payload);
|
|
305
272
|
}
|