@sigil-dev/grimoire 0.5.0 → 0.6.1
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 +174 -174
- package/index.ts +34 -16
- package/package.json +34 -34
- package/src/{enhance.ts → client/enhance.ts} +2 -1
- package/src/client/index.ts +5 -0
- package/src/{client-router.ts → client/router.ts} +1 -1
- package/src/{vite-plugin.ts → integrations/vite.ts} +4 -4
- package/src/{hydrate.ts → rendering/hydrate.ts} +2 -2
- package/src/{renderer.ts → rendering/index.ts} +29 -16
- package/src/{ssrPlugin.ts → rendering/ssrPlugin.ts} +3 -2
- package/src/{scanner.ts → routing/scanner.ts} +16 -4
- package/src/{transform-routes.ts → routing/transform-routes.ts} +3 -2
- package/src/{fail.ts → sentinels/fail.ts} +1 -1
- package/src/{build.ts → server/build.ts} +12 -10
- package/src/server/coordinator.ts +297 -0
- package/src/{hooks.ts → server/hooks.ts} +1 -1
- package/src/{server.ts → server/index.ts} +85 -37
- package/src/server/plugins.ts +119 -0
- package/src/server/worker.ts +59 -0
- package/src/{typegen.ts → typegen/index.ts} +5 -2
- package/src/types.ts +134 -3
- package/test/context.test.ts +1 -1
- package/test/fail.test.ts +1 -1
- package/test/headers.test.ts +6 -2
- package/test/hydration.test.ts +1 -1
- package/test/middleware.test.ts +8 -4
- package/test/preload.ts +1 -1
- package/test/redirect-error.test.ts +2 -2
- package/test/rendering.test.ts +15 -6
- package/test/routing.test.ts +2 -2
- package/test/scanning.test.ts +40 -11
- package/test/scope.test.ts +25 -10
- package/test/server.test.ts +39 -10
- package/test/streaming.test.ts +21 -8
- package/test/transform-routes.test.ts +2 -2
- package/test/typegen.test.ts +5 -3
- package/tsconfig.json +3 -1
- package/.grimoire/_routes.ts +0 -4
- package/public/__grimoire__/client.js +0 -55
- package/public/__grimoire__/hydrate.js +0 -63
- package/src/client.ts +0 -4
- package/src/plugins.ts +0 -39
- package/src/sync.ts +0 -18
- /package/src/{scope.ts → client/scope.ts} +0 -0
- /package/src/{head.ts → rendering/head.ts} +0 -0
- /package/src/{manifest-gen.ts → routing/manifest-gen.ts} +0 -0
- /package/src/{router.ts → routing/router.ts} +0 -0
- /package/src/{error.ts → sentinels/error.ts} +0 -0
- /package/src/{redirect.ts → sentinels/redirect.ts} +0 -0
- /package/src/{context.ts → server/context.ts} +0 -0
- /package/src/{cookie-utils.ts → server/cookie-utils.ts} +0 -0
package/test/scope.test.ts
CHANGED
|
@@ -8,8 +8,12 @@
|
|
|
8
8
|
* a fresh empty pool instead of recycling stale DOM nodes.
|
|
9
9
|
*/
|
|
10
10
|
import { describe, expect, test } from "bun:test";
|
|
11
|
-
import {
|
|
12
|
-
|
|
11
|
+
import {
|
|
12
|
+
createEffect,
|
|
13
|
+
createSignal,
|
|
14
|
+
withEffectScope as runtimeScope,
|
|
15
|
+
} from "@sigil-dev/runtime";
|
|
16
|
+
import { withEffectScope } from "../src/client/scope.ts";
|
|
13
17
|
|
|
14
18
|
describe("grimoire withEffectScope", () => {
|
|
15
19
|
test("effects inside scope stop after dispose", () => {
|
|
@@ -38,7 +42,10 @@ describe("grimoire withEffectScope", () => {
|
|
|
38
42
|
|
|
39
43
|
// Mount page A
|
|
40
44
|
let disposeCurrentPage = withEffectScope(() => {
|
|
41
|
-
createEffect(() => {
|
|
45
|
+
createEffect(() => {
|
|
46
|
+
count();
|
|
47
|
+
pageARuns++;
|
|
48
|
+
});
|
|
42
49
|
});
|
|
43
50
|
count.set(1);
|
|
44
51
|
expect(pageARuns).toBe(2);
|
|
@@ -47,7 +54,10 @@ describe("grimoire withEffectScope", () => {
|
|
|
47
54
|
// Navigate to page B — dispose A first
|
|
48
55
|
disposeCurrentPage();
|
|
49
56
|
disposeCurrentPage = withEffectScope(() => {
|
|
50
|
-
createEffect(() => {
|
|
57
|
+
createEffect(() => {
|
|
58
|
+
count();
|
|
59
|
+
pageBRuns++;
|
|
60
|
+
});
|
|
51
61
|
});
|
|
52
62
|
|
|
53
63
|
count.set(2);
|
|
@@ -96,7 +106,10 @@ describe("grimoire withEffectScope", () => {
|
|
|
96
106
|
|
|
97
107
|
// grimoire's local withEffectScope wraps a createEffect from @sigil-dev/runtime
|
|
98
108
|
const dispose = withEffectScope(() => {
|
|
99
|
-
createEffect(() => {
|
|
109
|
+
createEffect(() => {
|
|
110
|
+
count();
|
|
111
|
+
runs++;
|
|
112
|
+
});
|
|
100
113
|
});
|
|
101
114
|
|
|
102
115
|
count.set(1);
|
|
@@ -114,7 +127,10 @@ describe("grimoire withEffectScope", () => {
|
|
|
114
127
|
let runs = 0;
|
|
115
128
|
|
|
116
129
|
const dispose = runtimeScope(() => {
|
|
117
|
-
createEffect(() => {
|
|
130
|
+
createEffect(() => {
|
|
131
|
+
count();
|
|
132
|
+
runs++;
|
|
133
|
+
});
|
|
118
134
|
});
|
|
119
135
|
|
|
120
136
|
count.set(1);
|
|
@@ -127,7 +143,7 @@ describe("grimoire withEffectScope", () => {
|
|
|
127
143
|
|
|
128
144
|
describe("__nodes pool reset on SPA navigation", () => {
|
|
129
145
|
test("navigate sets __nodes to [] before calling Page()", () => {
|
|
130
|
-
// Simulate exactly what
|
|
146
|
+
// Simulate exactly what router.ts's navigate() does:
|
|
131
147
|
// dispose old effects → set __nodes = [] → call Page() → replaceChildren(node)
|
|
132
148
|
// We only care that Page() sees an empty pool.
|
|
133
149
|
let nodesAtCallTime: unknown = "not set";
|
|
@@ -136,9 +152,8 @@ describe("__nodes pool reset on SPA navigation", () => {
|
|
|
136
152
|
nodesAtCallTime = (globalThis as any).__nodes;
|
|
137
153
|
return document.createElement("div"); // satisfy replaceChildren
|
|
138
154
|
};
|
|
139
|
-
|
|
140
155
|
// Mimic the three lines in navigate() that matter
|
|
141
|
-
|
|
156
|
+
(globalThis as any).__nodes = ["stale", "dom", "nodes"]; // leftover from previous page
|
|
142
157
|
(globalThis as any).__nodes = []; // the reset
|
|
143
158
|
mockPage();
|
|
144
159
|
|
|
@@ -154,7 +169,7 @@ describe("__nodes pool reset on SPA navigation", () => {
|
|
|
154
169
|
nodesAtCallTime = (globalThis as any).__nodes;
|
|
155
170
|
};
|
|
156
171
|
|
|
157
|
-
|
|
172
|
+
(globalThis as any).__nodes = [fakeSlotChild]; // hydrate.ts sets real nodes
|
|
158
173
|
mockPage();
|
|
159
174
|
|
|
160
175
|
expect(Array.isArray(nodesAtCallTime)).toBe(true);
|
package/test/server.test.ts
CHANGED
|
@@ -73,7 +73,9 @@ describe("Server — API routes (+server.ts)", () => {
|
|
|
73
73
|
|
|
74
74
|
test("unsupported method returns 405", async () => {
|
|
75
75
|
const server = await createServer({ port: 3004, routes: tmpDir });
|
|
76
|
-
const res = await fetch("http://localhost:3004/api/items", {
|
|
76
|
+
const res = await fetch("http://localhost:3004/api/items", {
|
|
77
|
+
method: "DELETE",
|
|
78
|
+
});
|
|
77
79
|
expect(res.status).toBe(405);
|
|
78
80
|
server.stop();
|
|
79
81
|
});
|
|
@@ -143,15 +145,25 @@ export const websocket = {
|
|
|
143
145
|
return new Promise((resolve, reject) => {
|
|
144
146
|
const ws = new WebSocket(`ws://localhost:3005${path}`);
|
|
145
147
|
ws.onopen = () => resolve(ws);
|
|
146
|
-
ws.onerror = () =>
|
|
148
|
+
ws.onerror = () =>
|
|
149
|
+
reject(new Error(`WebSocket connection failed: ${path}`));
|
|
147
150
|
});
|
|
148
151
|
}
|
|
149
152
|
|
|
150
153
|
function nextMessage(ws: WebSocket): Promise<string> {
|
|
151
154
|
return new Promise((resolve, reject) => {
|
|
152
|
-
const timer = setTimeout(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
+
const timer = setTimeout(
|
|
156
|
+
() => reject(new Error("timeout waiting for message")),
|
|
157
|
+
2000,
|
|
158
|
+
);
|
|
159
|
+
ws.onmessage = (ev) => {
|
|
160
|
+
clearTimeout(timer);
|
|
161
|
+
resolve(String(ev.data));
|
|
162
|
+
};
|
|
163
|
+
ws.onerror = () => {
|
|
164
|
+
clearTimeout(timer);
|
|
165
|
+
reject(new Error("ws error"));
|
|
166
|
+
};
|
|
155
167
|
});
|
|
156
168
|
}
|
|
157
169
|
|
|
@@ -169,8 +181,14 @@ export const websocket = {
|
|
|
169
181
|
// just verify connection opens cleanly (sessionId is in ws.data server-side)
|
|
170
182
|
await new Promise<void>((resolve, reject) => {
|
|
171
183
|
const t = setTimeout(() => reject(new Error("timeout")), 2000);
|
|
172
|
-
ws.onopen = () => {
|
|
173
|
-
|
|
184
|
+
ws.onopen = () => {
|
|
185
|
+
clearTimeout(t);
|
|
186
|
+
resolve();
|
|
187
|
+
};
|
|
188
|
+
ws.onerror = () => {
|
|
189
|
+
clearTimeout(t);
|
|
190
|
+
reject(new Error("connect failed"));
|
|
191
|
+
};
|
|
174
192
|
});
|
|
175
193
|
ws.close();
|
|
176
194
|
});
|
|
@@ -185,9 +203,20 @@ export const websocket = {
|
|
|
185
203
|
await new Promise<void>((resolve, reject) => {
|
|
186
204
|
const ws = new WebSocket("ws://localhost:3005/reject");
|
|
187
205
|
const t = setTimeout(() => reject(new Error("timeout")), 2000);
|
|
188
|
-
ws.onopen = () => {
|
|
189
|
-
|
|
190
|
-
|
|
206
|
+
ws.onopen = () => {
|
|
207
|
+
clearTimeout(t);
|
|
208
|
+
ws.close();
|
|
209
|
+
reject(new Error("should not have opened"));
|
|
210
|
+
};
|
|
211
|
+
ws.onerror = () => {
|
|
212
|
+
clearTimeout(t);
|
|
213
|
+
resolve();
|
|
214
|
+
};
|
|
215
|
+
ws.onclose = (ev) => {
|
|
216
|
+
clearTimeout(t);
|
|
217
|
+
if (ev.code !== 1006) resolve();
|
|
218
|
+
else resolve();
|
|
219
|
+
};
|
|
191
220
|
});
|
|
192
221
|
});
|
|
193
222
|
|
package/test/streaming.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { renderRoute } from "../src/
|
|
2
|
+
import { renderRoute } from "../src/rendering";
|
|
3
3
|
|
|
4
4
|
describe("Streaming SSR", () => {
|
|
5
5
|
test("returns a ReadableStream response", async () => {
|
|
@@ -7,7 +7,8 @@ describe("Streaming SSR", () => {
|
|
|
7
7
|
route: {
|
|
8
8
|
path: "/",
|
|
9
9
|
params: {},
|
|
10
|
-
filePath:
|
|
10
|
+
filePath:
|
|
11
|
+
"data:text/javascript,export default (p) => '<div>hello</div>'",
|
|
11
12
|
},
|
|
12
13
|
params: {},
|
|
13
14
|
} as any;
|
|
@@ -27,7 +28,10 @@ describe("Streaming SSR", () => {
|
|
|
27
28
|
params: {},
|
|
28
29
|
} as any;
|
|
29
30
|
|
|
30
|
-
const res = await renderRoute(
|
|
31
|
+
const res = await renderRoute(
|
|
32
|
+
matched,
|
|
33
|
+
new Request("http://localhost/test"),
|
|
34
|
+
);
|
|
31
35
|
const html = await res.text();
|
|
32
36
|
|
|
33
37
|
expect(html).toContain("<!DOCTYPE html>");
|
|
@@ -69,7 +73,10 @@ describe("Streaming SSR", () => {
|
|
|
69
73
|
params: {},
|
|
70
74
|
} as any;
|
|
71
75
|
|
|
72
|
-
const res = await renderRoute(
|
|
76
|
+
const res = await renderRoute(
|
|
77
|
+
matched,
|
|
78
|
+
new Request("http://localhost/head-test"),
|
|
79
|
+
);
|
|
73
80
|
const html = await res.text();
|
|
74
81
|
|
|
75
82
|
const hydrateScript = html.indexOf("hydrate.js");
|
|
@@ -90,7 +97,10 @@ describe("Streaming SSR", () => {
|
|
|
90
97
|
params: {},
|
|
91
98
|
} as any;
|
|
92
99
|
|
|
93
|
-
const res = await renderRoute(
|
|
100
|
+
const res = await renderRoute(
|
|
101
|
+
matched,
|
|
102
|
+
new Request("http://localhost/chunked"),
|
|
103
|
+
);
|
|
94
104
|
const reader = res.body!.getReader();
|
|
95
105
|
const chunks: string[] = [];
|
|
96
106
|
|
|
@@ -98,7 +108,11 @@ describe("Streaming SSR", () => {
|
|
|
98
108
|
const { done, value } = await reader.read();
|
|
99
109
|
if (done) break;
|
|
100
110
|
// Bun ReadableStream returns Uint8Array
|
|
101
|
-
chunks.push(
|
|
111
|
+
chunks.push(
|
|
112
|
+
typeof value === "string"
|
|
113
|
+
? value
|
|
114
|
+
: new TextDecoder().decode(value.buffer),
|
|
115
|
+
);
|
|
102
116
|
}
|
|
103
117
|
|
|
104
118
|
// Should have multiple chunks (at least DOCTYPE + body)
|
|
@@ -114,8 +128,7 @@ describe("Streaming SSR", () => {
|
|
|
114
128
|
route: {
|
|
115
129
|
path: "/nav",
|
|
116
130
|
params: {},
|
|
117
|
-
filePath:
|
|
118
|
-
"data:text/javascript,export default (p) => '<div>nav</div>'",
|
|
131
|
+
filePath: "data:text/javascript,export default (p) => '<div>nav</div>'",
|
|
119
132
|
},
|
|
120
133
|
params: {},
|
|
121
134
|
} as any;
|
|
@@ -2,8 +2,8 @@ import { describe, expect, test } from "bun:test";
|
|
|
2
2
|
import { mkdir, mkdtemp, readFile, rm, writeFile } from "fs/promises";
|
|
3
3
|
import { tmpdir } from "os";
|
|
4
4
|
import { join } from "path";
|
|
5
|
-
import type { RouteFile } from "../src/scanner";
|
|
6
|
-
import { transformRoutes } from "../src/transform-routes";
|
|
5
|
+
import type { RouteFile } from "../src/routing/scanner";
|
|
6
|
+
import { transformRoutes } from "../src/routing/transform-routes";
|
|
7
7
|
|
|
8
8
|
describe("transformRoutes", () => {
|
|
9
9
|
test("pre-transforms TypeScript TSX routes to unique JavaScript files", async () => {
|
package/test/typegen.test.ts
CHANGED
|
@@ -3,8 +3,8 @@ import { existsSync } from "node:fs";
|
|
|
3
3
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { join, sep } from "node:path";
|
|
6
|
-
import type { RouteFile } from "../src/scanner";
|
|
7
|
-
import { scanRoutes } from "../src/scanner";
|
|
6
|
+
import type { RouteFile } from "../src/routing/scanner";
|
|
7
|
+
import { scanRoutes } from "../src/routing/scanner";
|
|
8
8
|
import {
|
|
9
9
|
buildParams,
|
|
10
10
|
generateTypes,
|
|
@@ -418,7 +418,9 @@ describe("generateTypes — route without +page.server.ts", () => {
|
|
|
418
418
|
|
|
419
419
|
test("PageProps contains data and params", async () => {
|
|
420
420
|
const content = await readGenerated("src/routes/blog");
|
|
421
|
-
expect(content).toContain(
|
|
421
|
+
expect(content).toContain(
|
|
422
|
+
"export type PageProps = { data: PageData; params: Params };",
|
|
423
|
+
);
|
|
422
424
|
});
|
|
423
425
|
});
|
|
424
426
|
|
package/tsconfig.json
CHANGED
package/.grimoire/_routes.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
// .grimoire/_routes.ts
|
|
2
|
-
var routes = {};
|
|
3
|
-
|
|
4
|
-
// src/client-router.ts
|
|
5
|
-
var routeMap = {};
|
|
6
|
-
async function navigate(path) {
|
|
7
|
-
const res = await fetch(path, { headers: { "x-grimoire-navigate": "1" } });
|
|
8
|
-
const json = await res.json();
|
|
9
|
-
console.log("navigate response:", json);
|
|
10
|
-
const { data, params, pattern, head } = json;
|
|
11
|
-
const Page = routeMap[pattern];
|
|
12
|
-
if (!Page) {
|
|
13
|
-
window.location.href = path;
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
const node = Page({ ...data, params });
|
|
17
|
-
const slot = document.getElementById("grimoire-page");
|
|
18
|
-
if (!slot)
|
|
19
|
-
return;
|
|
20
|
-
slot.replaceChildren(node);
|
|
21
|
-
updateHead(head);
|
|
22
|
-
history.pushState({}, "", path);
|
|
23
|
-
window.scrollTo(0, 0);
|
|
24
|
-
}
|
|
25
|
-
function initRouter(routes2) {
|
|
26
|
-
routeMap = routes2;
|
|
27
|
-
document.addEventListener("click", handleClick);
|
|
28
|
-
window.addEventListener("popstate", () => navigate(location.pathname));
|
|
29
|
-
}
|
|
30
|
-
function updateHead(headHtml) {
|
|
31
|
-
document.querySelectorAll("[data-grimoire-head]").forEach((el) => el.remove());
|
|
32
|
-
if (!headHtml)
|
|
33
|
-
return;
|
|
34
|
-
const tmp = document.createElement("head");
|
|
35
|
-
tmp.innerHTML = headHtml;
|
|
36
|
-
for (const el of Array.from(tmp.children)) {
|
|
37
|
-
el.dataset.grimoireHead = "1";
|
|
38
|
-
document.head.appendChild(el);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
function handleClick(e) {
|
|
42
|
-
const a = e.target.closest("a");
|
|
43
|
-
if (!a)
|
|
44
|
-
return;
|
|
45
|
-
const href = a.getAttribute("href");
|
|
46
|
-
if (!href)
|
|
47
|
-
return;
|
|
48
|
-
if (/^(https?:\/\/|\/\/|#|mailto:|tel:)/.test(href))
|
|
49
|
-
return;
|
|
50
|
-
e.preventDefault();
|
|
51
|
-
navigate(href);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// src/client.ts
|
|
55
|
-
initRouter(routes);
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
// .grimoire/_routes.ts
|
|
2
|
-
var routes = {};
|
|
3
|
-
|
|
4
|
-
// src/client-router.ts
|
|
5
|
-
var routeMap = {};
|
|
6
|
-
async function navigate(path) {
|
|
7
|
-
const res = await fetch(path, { headers: { "x-grimoire-navigate": "1" } });
|
|
8
|
-
const json = await res.json();
|
|
9
|
-
console.log("navigate response:", json);
|
|
10
|
-
const { data, params, pattern, head } = json;
|
|
11
|
-
const Page = routeMap[pattern];
|
|
12
|
-
if (!Page) {
|
|
13
|
-
window.location.href = path;
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
const node = Page({ ...data, params });
|
|
17
|
-
const slot = document.getElementById("grimoire-page");
|
|
18
|
-
if (!slot)
|
|
19
|
-
return;
|
|
20
|
-
slot.replaceChildren(node);
|
|
21
|
-
updateHead(head);
|
|
22
|
-
history.pushState({}, "", path);
|
|
23
|
-
window.scrollTo(0, 0);
|
|
24
|
-
}
|
|
25
|
-
function initRouter(routes2) {
|
|
26
|
-
routeMap = routes2;
|
|
27
|
-
document.addEventListener("click", handleClick);
|
|
28
|
-
window.addEventListener("popstate", () => navigate(location.pathname));
|
|
29
|
-
}
|
|
30
|
-
function updateHead(headHtml) {
|
|
31
|
-
document.querySelectorAll("[data-grimoire-head]").forEach((el) => el.remove());
|
|
32
|
-
if (!headHtml)
|
|
33
|
-
return;
|
|
34
|
-
const tmp = document.createElement("head");
|
|
35
|
-
tmp.innerHTML = headHtml;
|
|
36
|
-
for (const el of Array.from(tmp.children)) {
|
|
37
|
-
el.dataset.grimoireHead = "1";
|
|
38
|
-
document.head.appendChild(el);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
function handleClick(e) {
|
|
42
|
-
const a = e.target.closest("a");
|
|
43
|
-
if (!a)
|
|
44
|
-
return;
|
|
45
|
-
const href = a.getAttribute("href");
|
|
46
|
-
if (!href)
|
|
47
|
-
return;
|
|
48
|
-
if (/^(https?:\/\/|\/\/|#|mailto:|tel:)/.test(href))
|
|
49
|
-
return;
|
|
50
|
-
e.preventDefault();
|
|
51
|
-
navigate(href);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// src/hydrate.ts
|
|
55
|
-
var stateEl = document.getElementById("__grimoire_state__");
|
|
56
|
-
if (stateEl) {
|
|
57
|
-
const state = JSON.parse(stateEl.textContent);
|
|
58
|
-
const Page = routes[state.pattern];
|
|
59
|
-
if (Page) {
|
|
60
|
-
Page({ ...state.data, params: state.params });
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
initRouter(routes);
|
package/src/client.ts
DELETED
package/src/plugins.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import type { BuildResult, GrimoirePlugin, LoadContext, Route, Server } from "./types";
|
|
2
|
-
|
|
3
|
-
// Fire-and-forget hooks routed through runHook.
|
|
4
|
-
// onRequest and onRouteRender are intentionally excluded: they have distinct
|
|
5
|
-
// calling conventions (middleware chain / inline transform loop) handled in server.ts.
|
|
6
|
-
// Keep HookArgs in sync with GrimoirePlugin in types.ts.
|
|
7
|
-
type HookArgs = {
|
|
8
|
-
onStart: [server: Server];
|
|
9
|
-
onStop: [reason: "shutdown" | "restart"];
|
|
10
|
-
onBuildStart: [];
|
|
11
|
-
onBuildEnd: [result: BuildResult];
|
|
12
|
-
onRouteLoad: [route: Route, context: LoadContext];
|
|
13
|
-
};
|
|
14
|
-
type FireAndForgetHook = keyof HookArgs;
|
|
15
|
-
|
|
16
|
-
export async function runHook<K extends FireAndForgetHook>(
|
|
17
|
-
plugins: GrimoirePlugin[],
|
|
18
|
-
hook: K,
|
|
19
|
-
...args: HookArgs[K]
|
|
20
|
-
): Promise<void> {
|
|
21
|
-
for (const plugin of plugins) {
|
|
22
|
-
const fn = plugin[hook] as ((...a: any[]) => any) | undefined;
|
|
23
|
-
await fn?.(...args);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export async function runRequestHooks(
|
|
28
|
-
plugins: GrimoirePlugin[],
|
|
29
|
-
req: Request,
|
|
30
|
-
final: () => Promise<Response>,
|
|
31
|
-
): Promise<Response> {
|
|
32
|
-
const chain = plugins
|
|
33
|
-
.filter((p) => p.onRequest)
|
|
34
|
-
.reduceRight(
|
|
35
|
-
(next, plugin) => async () => plugin.onRequest!(req, next),
|
|
36
|
-
final,
|
|
37
|
-
);
|
|
38
|
-
return chain();
|
|
39
|
-
}
|
package/src/sync.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
import { mkdir } from "node:fs/promises";
|
|
3
|
-
import { isAbsolute, join } from "node:path";
|
|
4
|
-
import { scanRoutes } from "./scanner";
|
|
5
|
-
import { generateTypes } from "./typegen";
|
|
6
|
-
|
|
7
|
-
const routes = process.env.GRIMOIRE_ROUTES ?? "src/routes";
|
|
8
|
-
const routesDir = isAbsolute(routes) ? routes : join(process.cwd(), routes);
|
|
9
|
-
|
|
10
|
-
await mkdir(join(process.cwd(), ".grimoire"), { recursive: true });
|
|
11
|
-
const tree = await scanRoutes(routesDir, process.cwd());
|
|
12
|
-
await generateTypes(tree, {
|
|
13
|
-
projectRoot: process.cwd(),
|
|
14
|
-
routesDir,
|
|
15
|
-
outDir: join(process.cwd(), ".grimoire/types"),
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
console.log("Grimoire: types generated");
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|