@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.
Files changed (58) hide show
  1. package/.grimoire/_routes.dom.js +8 -0
  2. package/.grimoire/_routes.hydrate.js +8 -0
  3. package/.grimoire/tsconfig.generated.json +11 -0
  4. package/.grimoire/types/ambient.d.ts +59 -0
  5. package/.grimoire/types/api/hello/$types.d.ts +50 -0
  6. package/.grimoire/types/api/items/$types.d.ts +50 -0
  7. package/.grimoire/types/echo/$types.d.ts +50 -0
  8. package/.grimoire/types/env-private.d.ts +5 -0
  9. package/.grimoire/types/env-public.d.ts +5 -0
  10. package/.grimoire/types/mixed/$types.d.ts +50 -0
  11. package/.grimoire/types/params/[docId]/$types.d.ts +52 -0
  12. package/.grimoire/types/reject/$types.d.ts +50 -0
  13. package/index.ts +21 -20
  14. package/package.json +13 -7
  15. package/preload.js +3 -0
  16. package/public/__grimoire__/hydrate.js +585 -0
  17. package/public/__grimoire__/index.js +490 -0
  18. package/server.ts +13 -13
  19. package/src/client/head.ts +29 -0
  20. package/src/client/router.ts +254 -40
  21. package/src/dev/compile-module.ts +173 -0
  22. package/src/dev/effect-registry.ts +23 -0
  23. package/src/dev/graph.ts +114 -0
  24. package/src/dev/hmr-client.ts +158 -0
  25. package/src/dev/hmr-server.ts +187 -0
  26. package/src/dev/loader.ts +47 -0
  27. package/src/dev/paths.ts +14 -0
  28. package/src/dev/runtime-bundle.ts +49 -0
  29. package/src/dev/watcher.ts +44 -0
  30. package/src/env/index.ts +25 -0
  31. package/src/env/plugin.ts +13 -0
  32. package/src/env/private.ts +5 -0
  33. package/src/env/public.ts +7 -0
  34. package/src/env/typegen.ts +51 -0
  35. package/src/integrations/vite.ts +1 -0
  36. package/src/rendering/head.ts +22 -2
  37. package/src/rendering/hydrate.ts +111 -18
  38. package/src/rendering/index.ts +263 -153
  39. package/src/rendering/ssrPlugin.ts +59 -39
  40. package/src/routing/manifest-gen.ts +18 -2
  41. package/src/routing/router.ts +94 -83
  42. package/src/routing/scanner.ts +26 -14
  43. package/src/routing/transform-routes.ts +68 -68
  44. package/src/server/build.ts +225 -76
  45. package/src/server/coordinator.ts +9 -0
  46. package/src/server/hooks.ts +24 -3
  47. package/src/server/index.ts +388 -104
  48. package/src/typegen/index.ts +30 -14
  49. package/src/types.ts +12 -2
  50. package/test/middleware.test.ts +6 -4
  51. package/test/rendering.test.ts +510 -356
  52. package/test/routing.test.ts +36 -0
  53. package/test/scanning.test.ts +39 -8
  54. package/test/scope.test.ts +24 -8
  55. package/test/server.test.ts +27 -7
  56. package/test/streaming.test.ts +117 -98
  57. package/test/typegen.test.ts +52 -24
  58. package/tsconfig.json +1 -0
@@ -1,4 +1,5 @@
1
- import { mkdir, rm, writeFile } from "node:fs/promises";
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { mkdir } from "node:fs/promises";
2
3
  import { dirname, isAbsolute, join, relative } from "node:path";
3
4
  import type { RouteFile, RouteTree } from "../routing/scanner.ts";
4
5
 
@@ -11,6 +12,7 @@ export interface TypegenConfig {
11
12
  export interface RouteGroup {
12
13
  dir: string;
13
14
  paramNames: string[];
15
+ restParamNames: string[];
14
16
  page?: RouteFile;
15
17
  pageServer?: RouteFile;
16
18
  layout?: RouteFile;
@@ -45,12 +47,19 @@ export function groupByDirectory(tree: RouteTree): Map<string, RouteGroup> {
45
47
  for (const file of files) {
46
48
  const dir = dirname(file.filePath);
47
49
  if (!groups.has(dir)) {
48
- groups.set(dir, { dir, paramNames: file.paramNames });
50
+ groups.set(dir, {
51
+ dir,
52
+ paramNames: file.paramNames,
53
+ restParamNames: file.restParamNames,
54
+ });
49
55
  }
50
56
  const g = groups.get(dir)!;
51
57
  if (file.paramNames.length > g.paramNames.length) {
52
58
  g.paramNames = file.paramNames;
53
59
  }
60
+ if (file.restParamNames.length > g.restParamNames.length) {
61
+ g.restParamNames = file.restParamNames;
62
+ }
54
63
  switch (file.type) {
55
64
  case "page":
56
65
  g.page = file;
@@ -82,9 +91,13 @@ export function toImportPath(from: string, to: string): string {
82
91
  .replace(/\.tsx?$/, ".js");
83
92
  }
84
93
 
85
- export function buildParams(paramNames: string[]): string {
86
- if (paramNames.length === 0) return "{}";
87
- const props = paramNames.map((p) => ` ${p}: string;`).join("\n");
94
+ export function buildParams(
95
+ paramNames: string[],
96
+ restParamNames: string[] = [],
97
+ ): string {
98
+ const all = [...paramNames, ...restParamNames];
99
+ if (all.length === 0) return "{}";
100
+ const props = all.map((p) => ` ${p}: string;`).join("\n");
88
101
  return `{\n${props}\n}`;
89
102
  }
90
103
 
@@ -94,7 +107,7 @@ function generateTypesForGroup(group: RouteGroup, outFileDir: string): string {
94
107
  "",
95
108
  ];
96
109
 
97
- const paramsType = buildParams(group.paramNames);
110
+ const paramsType = buildParams(group.paramNames, group.restParamNames);
98
111
 
99
112
  // --- Params ---
100
113
  lines.push(`export type Params = ${paramsType};`, "");
@@ -217,7 +230,7 @@ function generateTypesForGroup(group: RouteGroup, outFileDir: string): string {
217
230
  // --- +error.tsx ---
218
231
  if (group.error) {
219
232
  lines.push(
220
- `export type ErrorProps = { status: number; message: string };`,
233
+ `export type ErrorProps = { status: number; message: string; error: unknown; route: string };`,
221
234
  "",
222
235
  );
223
236
  }
@@ -276,7 +289,7 @@ function generateAmbient(): string {
276
289
  " P extends Record<string, string> = Record<string, string>",
277
290
  "> = { data: D; params: P; children?: unknown };",
278
291
  "",
279
- "type ErrorProps = { status: number; message: string };",
292
+ "type ErrorProps = { status: number; message: string; error: unknown; route: string };",
280
293
  "",
281
294
  "type UpgradeContext<",
282
295
  " P extends Record<string, string> = Record<string, string>",
@@ -311,8 +324,9 @@ export async function generateTypes(
311
324
  ): Promise<void> {
312
325
  const { projectRoot, outDir } = config;
313
326
 
314
- await rm(outDir, { recursive: true, force: true });
315
- await mkdir(outDir, { recursive: true });
327
+ // Skip rm on Windows, rm+mkdir in quick succession can race and leave dirs
328
+ // in an unusable state. Stale type files are harmless; we overwrite what we need.
329
+ await mkdir(outDir, { recursive: true }).catch(() => {});
316
330
 
317
331
  const groups = groupByDirectory(tree);
318
332
 
@@ -323,16 +337,18 @@ export async function generateTypes(
323
337
  group.dir,
324
338
  );
325
339
  const outFileDir = join(outDir, routeRelDir);
326
- await mkdir(outFileDir, { recursive: true });
340
+ // Use mkdirSync — async mkdir on Windows/Bun can report success before
341
+ // the directory is actually visible to subsequent fs calls
342
+ mkdirSync(outFileDir, { recursive: true });
327
343
 
328
344
  const content = generateTypesForGroup(group, outFileDir);
329
- await writeFile(join(outFileDir, "$types.d.ts"), content, "utf-8");
345
+ writeFileSync(join(outFileDir, "$types.d.ts"), content, "utf-8");
330
346
  }
331
347
 
332
- await writeFile(join(outDir, "ambient.d.ts"), generateAmbient(), "utf-8");
348
+ writeFileSync(join(outDir, "ambient.d.ts"), generateAmbient(), "utf-8");
333
349
 
334
350
  const grimoireDir = dirname(outDir);
335
- await writeFile(
351
+ writeFileSync(
336
352
  join(grimoireDir, "tsconfig.generated.json"),
337
353
  generateTsConfig(),
338
354
  "utf-8",
package/src/types.ts CHANGED
@@ -4,8 +4,14 @@ import type { RouteFile, RouteTree } from "./routing/scanner";
4
4
  /// <reference types="bun-types">
5
5
  declare global {
6
6
  namespace App {
7
- // augment in your own project
7
+ // augment in your own project via src/app.d.ts
8
8
  interface Locals {}
9
+ interface Error {
10
+ message: string;
11
+ status?: number;
12
+ }
13
+ interface PageData {}
14
+ interface Platform {}
9
15
  }
10
16
  }
11
17
 
@@ -111,9 +117,9 @@ export interface GrimoirePlugin {
111
117
  * Return WorkerEnv to inject env vars or set the worker name.
112
118
  * ALL plugins are called and results are merged.
113
119
  */
114
- // biome-ignore lint/suspicious/noConfusingVoidType: doesn't have to return a thing.
115
120
  onWorkerSpawn?(
116
121
  worker: WorkerDescriptor,
122
+ // biome-ignore lint/suspicious/noConfusingVoidType: doesn't have to return a thing.
117
123
  ): WorkerEnv | void | Promise<WorkerEnv | void>;
118
124
 
119
125
  /**
@@ -168,7 +174,11 @@ export interface GrimoireConfig {
168
174
  plugins?: GrimoirePlugin[];
169
175
  routes?: string; // glob, default 'src/routes/**';
170
176
  dev?: boolean;
177
+ devEditor?: boolean;
171
178
  vitePort?: number;
179
+ alias?: Record<string, string>; // e.g. { "$lib": "src/lib" } — resolved relative to cwd
180
+ cspNonce?: string; // CSP nonce for <script>/<style> tags
181
+ bundleStrategy?: "split" | "single"; // 'split' (default) or 'single' bundle
172
182
  _skipBuild?: boolean;
173
183
  }
174
184
 
@@ -14,6 +14,8 @@ function fakeEvent(overrides?: Partial<RequestEvent>): RequestEvent {
14
14
  set: () => {},
15
15
  delete: () => {},
16
16
  },
17
+ route: { id: "/" },
18
+ fetch: globalThis.fetch,
17
19
  setHeaders: () => {},
18
20
  ...overrides,
19
21
  };
@@ -71,12 +73,12 @@ describe("sequence()", () => {
71
73
 
72
74
  test("can modify event.locals", async () => {
73
75
  const a: Handle = async ({ event, resolve }) => {
74
- event.locals.user = "alice";
76
+ (event.locals as any).user = "alice";
75
77
  return resolve(event);
76
78
  };
77
79
 
78
80
  const b: Handle = async ({ event, resolve }) => {
79
- event.locals.greet = `hello ${event.locals.user}`;
81
+ (event.locals as any).greet = `hello ${(event.locals as any).user}`;
80
82
  return resolve(event);
81
83
  };
82
84
 
@@ -89,8 +91,8 @@ describe("sequence()", () => {
89
91
  },
90
92
  });
91
93
 
92
- expect(event.locals.user).toBe("alice");
93
- expect(event.locals.greet).toBe("hello alice");
94
+ expect((event.locals as any).user).toBe("alice");
95
+ expect((event.locals as any).greet).toBe("hello alice");
94
96
  });
95
97
 
96
98
  test("can modify response", async () => {