@cmj/juice 0.0.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.
Files changed (91) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +88 -0
  3. package/dist/cli/commands.d.ts +29 -0
  4. package/dist/cli/commands.d.ts.map +1 -0
  5. package/dist/cli/commands.js +102 -0
  6. package/dist/cli/commands.js.map +1 -0
  7. package/dist/cli/create.d.ts +35 -0
  8. package/dist/cli/create.d.ts.map +1 -0
  9. package/dist/cli/create.js +108 -0
  10. package/dist/cli/create.js.map +1 -0
  11. package/dist/cli/index.d.ts +3 -0
  12. package/dist/cli/index.d.ts.map +1 -0
  13. package/dist/cli/index.js +97 -0
  14. package/dist/cli/index.js.map +1 -0
  15. package/dist/cli/templates.d.ts +14 -0
  16. package/dist/cli/templates.d.ts.map +1 -0
  17. package/dist/cli/templates.js +154 -0
  18. package/dist/cli/templates.js.map +1 -0
  19. package/dist/compiler/errors.d.ts +91 -0
  20. package/dist/compiler/errors.d.ts.map +1 -0
  21. package/dist/compiler/errors.js +110 -0
  22. package/dist/compiler/errors.js.map +1 -0
  23. package/dist/compiler/manifest.d.ts +39 -0
  24. package/dist/compiler/manifest.d.ts.map +1 -0
  25. package/dist/compiler/manifest.js +78 -0
  26. package/dist/compiler/manifest.js.map +1 -0
  27. package/dist/compiler/parse.d.ts +126 -0
  28. package/dist/compiler/parse.d.ts.map +1 -0
  29. package/dist/compiler/parse.js +246 -0
  30. package/dist/compiler/parse.js.map +1 -0
  31. package/dist/compiler/plugin.d.ts +43 -0
  32. package/dist/compiler/plugin.d.ts.map +1 -0
  33. package/dist/compiler/plugin.js +281 -0
  34. package/dist/compiler/plugin.js.map +1 -0
  35. package/dist/compiler/proxy.d.ts +42 -0
  36. package/dist/compiler/proxy.d.ts.map +1 -0
  37. package/dist/compiler/proxy.js +80 -0
  38. package/dist/compiler/proxy.js.map +1 -0
  39. package/dist/compiler/registry.d.ts +58 -0
  40. package/dist/compiler/registry.d.ts.map +1 -0
  41. package/dist/compiler/registry.js +79 -0
  42. package/dist/compiler/registry.js.map +1 -0
  43. package/dist/compiler/server-action-registry.d.ts +57 -0
  44. package/dist/compiler/server-action-registry.d.ts.map +1 -0
  45. package/dist/compiler/server-action-registry.js +76 -0
  46. package/dist/compiler/server-action-registry.js.map +1 -0
  47. package/dist/compiler/server-actions.d.ts +49 -0
  48. package/dist/compiler/server-actions.d.ts.map +1 -0
  49. package/dist/compiler/server-actions.js +89 -0
  50. package/dist/compiler/server-actions.js.map +1 -0
  51. package/dist/compiler/types.d.ts +188 -0
  52. package/dist/compiler/types.d.ts.map +1 -0
  53. package/dist/compiler/types.js +9 -0
  54. package/dist/compiler/types.js.map +1 -0
  55. package/dist/runtime/actions.d.ts +37 -0
  56. package/dist/runtime/actions.d.ts.map +1 -0
  57. package/dist/runtime/actions.js +167 -0
  58. package/dist/runtime/actions.js.map +1 -0
  59. package/dist/runtime/dev.d.ts +43 -0
  60. package/dist/runtime/dev.d.ts.map +1 -0
  61. package/dist/runtime/dev.js +260 -0
  62. package/dist/runtime/dev.js.map +1 -0
  63. package/dist/runtime/errors.d.ts +98 -0
  64. package/dist/runtime/errors.d.ts.map +1 -0
  65. package/dist/runtime/errors.js +124 -0
  66. package/dist/runtime/errors.js.map +1 -0
  67. package/dist/runtime/index.d.ts +3 -0
  68. package/dist/runtime/index.d.ts.map +1 -0
  69. package/dist/runtime/index.js +5 -0
  70. package/dist/runtime/index.js.map +1 -0
  71. package/dist/runtime/matcher.d.ts +44 -0
  72. package/dist/runtime/matcher.d.ts.map +1 -0
  73. package/dist/runtime/matcher.js +83 -0
  74. package/dist/runtime/matcher.js.map +1 -0
  75. package/dist/runtime/render.d.ts +25 -0
  76. package/dist/runtime/render.d.ts.map +1 -0
  77. package/dist/runtime/render.js +141 -0
  78. package/dist/runtime/render.js.map +1 -0
  79. package/dist/runtime/resolve.d.ts +24 -0
  80. package/dist/runtime/resolve.d.ts.map +1 -0
  81. package/dist/runtime/resolve.js +41 -0
  82. package/dist/runtime/resolve.js.map +1 -0
  83. package/dist/runtime/router.d.ts +74 -0
  84. package/dist/runtime/router.d.ts.map +1 -0
  85. package/dist/runtime/router.js +367 -0
  86. package/dist/runtime/router.js.map +1 -0
  87. package/dist/runtime/types.d.ts +245 -0
  88. package/dist/runtime/types.d.ts.map +1 -0
  89. package/dist/runtime/types.js +5 -0
  90. package/dist/runtime/types.js.map +1 -0
  91. package/package.json +92 -0
@@ -0,0 +1,260 @@
1
+ // ── juice/runtime dev mode ─────────────────────────────────────────
2
+ // Development-only utilities. WinterCG only. No Node.js APIs.
3
+ //
4
+ // These are internal (`_` prefixed) and never exported from the
5
+ // public `juice/runtime` barrel. They are tree-shaken in production
6
+ // builds when the `mode` option is `'production'` (default).
7
+ // ────────────────────────────────────────────────────────────────────
8
+ /**
9
+ * Generates a rich HTML error overlay for development mode.
10
+ *
11
+ * Features:
12
+ * - Dark theme with syntax-highlighted stack trace
13
+ * - Source file + line number parsed from Error stack
14
+ * - Request URL, method, and matched route context
15
+ * - Auto-refresh via HMR EventSource (if hmrUrl provided)
16
+ *
17
+ * @param error - The caught error (may be Error or unknown).
18
+ * @param req - The incoming request (for URL/method display).
19
+ * @param context - Optional context string (e.g., route path, action ID).
20
+ * @returns A full HTML string for the error overlay response.
21
+ *
22
+ * @internal
23
+ */
24
+ export function _generateDevErrorOverlay(error, req, context) {
25
+ const err = error instanceof Error ? error : new Error(String(error));
26
+ const name = err.name || 'Error';
27
+ const message = _escapeHtml(err.message);
28
+ const stack = _formatStack(err.stack ?? '');
29
+ const url = new URL(req.url);
30
+ const contextLine = context ? `<p class="ctx">Context: ${_escapeHtml(context)}</p>` : '';
31
+ return `<!DOCTYPE html>
32
+ <html lang="en">
33
+ <head>
34
+ <meta charset="utf-8" />
35
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
36
+ <title>🧃 Juice — ${name}</title>
37
+ <style>
38
+ *{margin:0;padding:0;box-sizing:border-box}
39
+ body{
40
+ font-family:'SF Mono','Cascadia Code','Fira Code',monospace;
41
+ background:#0a0a0f;
42
+ color:#e4e4e7;
43
+ min-height:100vh;
44
+ display:flex;
45
+ flex-direction:column;
46
+ padding:2rem;
47
+ }
48
+ .bar{
49
+ display:flex;
50
+ align-items:center;
51
+ gap:.75rem;
52
+ padding:.75rem 1.25rem;
53
+ background:linear-gradient(135deg,#1a0a2e,#16082a);
54
+ border:1px solid #2d1b4e;
55
+ border-radius:12px;
56
+ margin-bottom:1.5rem;
57
+ }
58
+ .bar .logo{font-size:1.5rem}
59
+ .bar .label{color:#a78bfa;font-size:.85rem;font-weight:600;letter-spacing:.05em}
60
+ .bar .mode{
61
+ margin-left:auto;
62
+ font-size:.7rem;
63
+ padding:3px 10px;
64
+ border-radius:100px;
65
+ background:#7c3aed22;
66
+ color:#a78bfa;
67
+ border:1px solid #7c3aed44;
68
+ }
69
+ .error-box{
70
+ background:#18181b;
71
+ border:1px solid #ef444488;
72
+ border-left:4px solid #ef4444;
73
+ border-radius:12px;
74
+ padding:1.5rem 2rem;
75
+ margin-bottom:1.5rem;
76
+ }
77
+ .error-box h1{
78
+ color:#ef4444;
79
+ font-size:1.25rem;
80
+ font-weight:700;
81
+ margin-bottom:.5rem;
82
+ }
83
+ .error-box .msg{
84
+ color:#fca5a5;
85
+ font-size:.95rem;
86
+ line-height:1.6;
87
+ word-break:break-word;
88
+ }
89
+ .ctx{
90
+ color:#a1a1aa;
91
+ font-size:.8rem;
92
+ margin-top:.75rem;
93
+ padding:.5rem .75rem;
94
+ background:#27272a;
95
+ border-radius:8px;
96
+ }
97
+ .req{
98
+ display:flex;
99
+ gap:1rem;
100
+ margin-bottom:1.5rem;
101
+ }
102
+ .req .pill{
103
+ font-size:.75rem;
104
+ padding:4px 12px;
105
+ border-radius:100px;
106
+ background:#27272a;
107
+ border:1px solid #3f3f46;
108
+ color:#a1a1aa;
109
+ }
110
+ .req .pill b{color:#e4e4e7}
111
+ .stack-box{
112
+ background:#18181b;
113
+ border:1px solid #27272a;
114
+ border-radius:12px;
115
+ padding:1.25rem 1.5rem;
116
+ overflow-x:auto;
117
+ }
118
+ .stack-box h2{
119
+ font-size:.8rem;
120
+ color:#71717a;
121
+ text-transform:uppercase;
122
+ letter-spacing:.1em;
123
+ margin-bottom:.75rem;
124
+ }
125
+ .stack{
126
+ font-size:.8rem;
127
+ line-height:2;
128
+ color:#a1a1aa;
129
+ }
130
+ .stack .fn{color:#93c5fd}
131
+ .stack .file{color:#fbbf24}
132
+ .stack .line-num{color:#ef4444}
133
+ .hint{
134
+ margin-top:1.5rem;
135
+ padding:1rem 1.25rem;
136
+ background:#1c1917;
137
+ border:1px solid #292524;
138
+ border-radius:8px;
139
+ font-size:.75rem;
140
+ color:#78716c;
141
+ line-height:1.6;
142
+ }
143
+ .hint code{color:#a78bfa;font-size:.8rem}
144
+ </style>
145
+ </head>
146
+ <body>
147
+ <div class="bar">
148
+ <span class="logo">🧃</span>
149
+ <span class="label">JUICE RUNTIME ERROR</span>
150
+ <span class="mode">DEV MODE</span>
151
+ </div>
152
+
153
+ <div class="req">
154
+ <span class="pill"><b>${_escapeHtml(req.method)}</b></span>
155
+ <span class="pill">${_escapeHtml(url.pathname + url.search)}</span>
156
+ </div>
157
+
158
+ <div class="error-box">
159
+ <h1>${_escapeHtml(name)}</h1>
160
+ <p class="msg">${message}</p>
161
+ ${contextLine}
162
+ </div>
163
+
164
+ <div class="stack-box">
165
+ <h2>Stack Trace</h2>
166
+ <div class="stack">${stack || '<em>No stack trace available</em>'}</div>
167
+ </div>
168
+
169
+ <div class="hint">
170
+ 💡 This error overlay is only shown in <code>mode: 'development'</code>.
171
+ In production, a minimal 500 response is returned instead.<br/>
172
+ Save a file to trigger HMR and auto-reload.
173
+ </div>
174
+
175
+ <script type="module">
176
+ // Auto-refresh when Vite signals a full-reload
177
+ if (import.meta.hot) {
178
+ import.meta.hot.on('vite:beforeFullReload', () => location.reload());
179
+ }
180
+ </script>
181
+ </body>
182
+ </html>`;
183
+ }
184
+ /**
185
+ * Generates the Vite HMR client `<script>` tag for SSR injection.
186
+ *
187
+ * @param hmrUrl - The HMR client URL (default: `'/@vite/client'`).
188
+ * @returns A `<script type="module">` string.
189
+ *
190
+ * @internal
191
+ */
192
+ export function _generateHmrClientScript(hmrUrl) {
193
+ const url = hmrUrl || '/@vite/client';
194
+ return `<script type="module" src="${_escapeHtml(url)}"></script>`;
195
+ }
196
+ /**
197
+ * Appends a cache-busting timestamp query parameter to a module ID.
198
+ *
199
+ * In development mode, this forces the JS runtime to re-evaluate
200
+ * the module on every `import()` call, ensuring edits are picked up
201
+ * immediately without a server restart.
202
+ *
203
+ * In production, this function should NOT be called — modules are
204
+ * cached normally for performance.
205
+ *
206
+ * @param moduleId - The original module path (e.g., `'./app/routes/home.tsx'`).
207
+ * @returns The module path with `?t={timestamp}` appended.
208
+ *
209
+ * @internal
210
+ */
211
+ export function _bustModuleCache(moduleId) {
212
+ const separator = moduleId.includes('?') ? '&' : '?';
213
+ return `${moduleId}${separator}t=${Date.now()}`;
214
+ }
215
+ // ── Private Helpers ────────────────────────────────────────────────
216
+ /**
217
+ * Escapes HTML special characters to prevent XSS in the error overlay.
218
+ * @internal
219
+ */
220
+ function _escapeHtml(str) {
221
+ return str
222
+ .replace(/&/g, '&amp;')
223
+ .replace(/</g, '&lt;')
224
+ .replace(/>/g, '&gt;')
225
+ .replace(/"/g, '&quot;')
226
+ .replace(/'/g, '&#039;');
227
+ }
228
+ /**
229
+ * Parses and syntax-highlights a stack trace for the error overlay.
230
+ * Highlights function names in blue, file paths in yellow, and
231
+ * line numbers in red.
232
+ *
233
+ * @internal
234
+ */
235
+ function _formatStack(stack) {
236
+ if (!stack)
237
+ return '';
238
+ return stack
239
+ .split('\n')
240
+ .map((line) => {
241
+ // Match " at FunctionName (file:line:col)"
242
+ const match = line.match(/^\s*at\s+(.+?)\s+\((.+?):(\d+):\d+\)/);
243
+ if (match) {
244
+ const fn = match[1];
245
+ const file = match[2];
246
+ const lineNum = match[3];
247
+ return `at <span class="fn">${_escapeHtml(fn)}</span> (<span class="file">${_escapeHtml(file)}</span>:<span class="line-num">${lineNum}</span>)`;
248
+ }
249
+ // Match " at file:line:col" (no function name)
250
+ const match2 = line.match(/^\s*at\s+(.+?):(\d+):\d+/);
251
+ if (match2) {
252
+ const file = match2[1];
253
+ const lineNum = match2[2];
254
+ return `at <span class="file">${_escapeHtml(file)}</span>:<span class="line-num">${lineNum}</span>`;
255
+ }
256
+ return _escapeHtml(line);
257
+ })
258
+ .join('<br/>');
259
+ }
260
+ //# sourceMappingURL=dev.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev.js","sourceRoot":"","sources":["../../src/runtime/dev.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,8DAA8D;AAC9D,EAAE;AACF,gEAAgE;AAChE,oEAAoE;AACpE,6DAA6D;AAC7D,uEAAuE;AAEvE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,wBAAwB,CACtC,KAAc,EACd,GAAY,EACZ,OAAgB;IAEhB,MAAM,GAAG,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACtE,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,IAAI,OAAO,CAAC;IACjC,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACzC,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;IAC5C,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC7B,MAAM,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,2BAA2B,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IAEzF,OAAO;;;;;oBAKW,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4BAsHI,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC;yBAC1B,WAAW,CAAC,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC;;;;UAIrD,WAAW,CAAC,IAAI,CAAC;qBACN,OAAO;MACtB,WAAW;;;;;yBAKQ,KAAK,IAAI,mCAAmC;;;;;;;;;;;;;;;;QAgB7D,CAAC;AACT,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,wBAAwB,CAAC,MAAe;IACtD,MAAM,GAAG,GAAG,MAAM,IAAI,eAAe,CAAC;IACtC,OAAO,8BAA8B,WAAW,CAAC,GAAG,CAAC,aAAa,CAAC;AACrE,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,MAAM,SAAS,GAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;IACrD,OAAO,GAAG,QAAQ,GAAG,SAAS,KAAK,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;AAClD,CAAC;AAED,sEAAsE;AAEtE;;;GAGG;AACH,SAAS,WAAW,CAAC,GAAW;IAC9B,OAAO,GAAG;SACP,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC7B,CAAC;AAED;;;;;;GAMG;AACH,SAAS,YAAY,CAAC,KAAa;IACjC,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IAEtB,OAAO,KAAK;SACT,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACZ,8CAA8C;QAC9C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CACtB,sCAAsC,CACvC,CAAC;QACF,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;YACrB,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;YACvB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;YAC1B,OAAO,uBAAuB,WAAW,CAAC,EAAE,CAAC,+BAA+B,WAAW,CAAC,IAAI,CAAC,kCAAkC,OAAO,UAAU,CAAC;QACnJ,CAAC;QAED,kDAAkD;QAClD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;QACtD,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAE,CAAC;YACxB,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,CAAE,CAAC;YAC3B,OAAO,yBAAyB,WAAW,CAAC,IAAI,CAAC,kCAAkC,OAAO,SAAS,CAAC;QACtG,CAAC;QAED,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC,CAAC;SACD,IAAI,CAAC,OAAO,CAAC,CAAC;AACnB,CAAC"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Base error class for all Juice runtime errors.
3
+ *
4
+ * Every error includes:
5
+ * - A `[juice/runtime]` prefix for instant identification in logs.
6
+ * - A machine-readable `code` for programmatic error handling.
7
+ * - An actionable message telling the developer exactly how to fix it.
8
+ */
9
+ export declare class JuiceRouterError extends Error {
10
+ /** Machine-readable error code (e.g., `'ROUTE_NOT_FOUND'`). */
11
+ readonly code: string;
12
+ name: string;
13
+ constructor(message: string,
14
+ /** Machine-readable error code (e.g., `'ROUTE_NOT_FOUND'`). */
15
+ code: string);
16
+ }
17
+ /**
18
+ * Thrown when the incoming request URL does not match any route
19
+ * in the flight manifest.
20
+ *
21
+ * @example
22
+ * ```
23
+ * // Error message:
24
+ * // [juice/runtime] No route matches "/dashboard/settings".
25
+ * // Check that your app/ directory has a page.tsx file for this path,
26
+ * // then re-run the Juice compiler to regenerate flight-manifest.json.
27
+ * ```
28
+ */
29
+ export declare class RouteNotFoundError extends JuiceRouterError {
30
+ readonly name = "RouteNotFoundError";
31
+ constructor(pathname: string);
32
+ }
33
+ /**
34
+ * Thrown when a POST request references a server action ID that
35
+ * does not exist in the flight manifest.
36
+ *
37
+ * @example
38
+ * ```
39
+ * // Error message:
40
+ * // [juice/runtime] Server Action "abc123" not found in the manifest.
41
+ * // Ensure the function is exported with 'use server' and the manifest
42
+ * // is up to date.
43
+ * ```
44
+ */
45
+ export declare class ActionNotFoundError extends JuiceRouterError {
46
+ readonly name = "ActionNotFoundError";
47
+ constructor(actionId: string);
48
+ }
49
+ /**
50
+ * Thrown when the flight manifest fails structural validation
51
+ * at router initialization time.
52
+ *
53
+ * @example
54
+ * ```
55
+ * // Error message:
56
+ * // [juice/runtime] Invalid flight-manifest.json: missing "routes" field.
57
+ * // Re-run the Juice compiler to regenerate it.
58
+ * ```
59
+ */
60
+ export declare class ManifestValidationError extends JuiceRouterError {
61
+ readonly name = "ManifestValidationError";
62
+ constructor(detail: string);
63
+ }
64
+ /**
65
+ * Thrown when a dynamic `import()` for a route module or server action
66
+ * module fails at runtime.
67
+ *
68
+ * @example
69
+ * ```
70
+ * // Error message:
71
+ * // [juice/runtime] Failed to load module "./app/routes/missing.tsx"
72
+ * // for route "/missing". The module may have been deleted or the
73
+ * // flight-manifest.json is stale. Re-run the Juice compiler.
74
+ * // Cause: Cannot find module './app/routes/missing.tsx'
75
+ * ```
76
+ */
77
+ export declare class ModuleLoadError extends JuiceRouterError {
78
+ readonly name = "ModuleLoadError";
79
+ constructor(moduleId: string, context: string, cause: unknown);
80
+ }
81
+ /**
82
+ * Thrown internally when a user-provided hook (`onBeforeRequest`,
83
+ * `onNotFound`, `onError`) throws an exception. The router catches
84
+ * this to prevent user bugs from crashing the server.
85
+ *
86
+ * @example
87
+ * ```
88
+ * // Error message:
89
+ * // [juice/runtime] The "onBeforeRequest" hook threw an error.
90
+ * // Your hook function must return a Response or void, not throw.
91
+ * // Cause: Cannot read properties of undefined (reading 'session')
92
+ * ```
93
+ */
94
+ export declare class HookExecutionError extends JuiceRouterError {
95
+ readonly name = "HookExecutionError";
96
+ constructor(hookName: string, cause: unknown);
97
+ }
98
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/runtime/errors.ts"],"names":[],"mappings":"AAKA;;;;;;;GAOG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IAKvC,+DAA+D;aAC/C,IAAI,EAAE,MAAM;IALrB,IAAI,EAAE,MAAM,CAAsB;gBAGzC,OAAO,EAAE,MAAM;IACf,+DAA+D;IAC/C,IAAI,EAAE,MAAM;CAI/B;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,kBAAmB,SAAQ,gBAAgB;IACtD,SAAkB,IAAI,wBAAwB;gBAElC,QAAQ,EAAE,MAAM;CAQ7B;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,mBAAoB,SAAQ,gBAAgB;IACvD,SAAkB,IAAI,yBAAyB;gBAEnC,QAAQ,EAAE,MAAM;CAO7B;AAED;;;;;;;;;;GAUG;AACH,qBAAa,uBAAwB,SAAQ,gBAAgB;IAC3D,SAAkB,IAAI,6BAA6B;gBAEvC,MAAM,EAAE,MAAM;CAO3B;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,eAAgB,SAAQ,gBAAgB;IACnD,SAAkB,IAAI,qBAAqB;gBAGzC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,OAAO;CAWjB;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,kBAAmB,SAAQ,gBAAgB;IACtD,SAAkB,IAAI,wBAAwB;gBAElC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;CAU7C"}
@@ -0,0 +1,124 @@
1
+ // ── juice/runtime errors ───────────────────────────────────────────
2
+ // Empathic error handling: every error tells the developer
3
+ // WHAT went wrong, WHY, and HOW to fix it.
4
+ // ────────────────────────────────────────────────────────────────────
5
+ /**
6
+ * Base error class for all Juice runtime errors.
7
+ *
8
+ * Every error includes:
9
+ * - A `[juice/runtime]` prefix for instant identification in logs.
10
+ * - A machine-readable `code` for programmatic error handling.
11
+ * - An actionable message telling the developer exactly how to fix it.
12
+ */
13
+ export class JuiceRouterError extends Error {
14
+ code;
15
+ name = 'JuiceRouterError';
16
+ constructor(message,
17
+ /** Machine-readable error code (e.g., `'ROUTE_NOT_FOUND'`). */
18
+ code) {
19
+ super(`[juice/runtime] ${message}`);
20
+ this.code = code;
21
+ }
22
+ }
23
+ /**
24
+ * Thrown when the incoming request URL does not match any route
25
+ * in the flight manifest.
26
+ *
27
+ * @example
28
+ * ```
29
+ * // Error message:
30
+ * // [juice/runtime] No route matches "/dashboard/settings".
31
+ * // Check that your app/ directory has a page.tsx file for this path,
32
+ * // then re-run the Juice compiler to regenerate flight-manifest.json.
33
+ * ```
34
+ */
35
+ export class RouteNotFoundError extends JuiceRouterError {
36
+ name = 'RouteNotFoundError';
37
+ constructor(pathname) {
38
+ super(`No route matches "${pathname}". ` +
39
+ `Check that your app/ directory has a page.tsx file for this path, ` +
40
+ `then re-run the Juice compiler to regenerate flight-manifest.json.`, 'ROUTE_NOT_FOUND');
41
+ }
42
+ }
43
+ /**
44
+ * Thrown when a POST request references a server action ID that
45
+ * does not exist in the flight manifest.
46
+ *
47
+ * @example
48
+ * ```
49
+ * // Error message:
50
+ * // [juice/runtime] Server Action "abc123" not found in the manifest.
51
+ * // Ensure the function is exported with 'use server' and the manifest
52
+ * // is up to date.
53
+ * ```
54
+ */
55
+ export class ActionNotFoundError extends JuiceRouterError {
56
+ name = 'ActionNotFoundError';
57
+ constructor(actionId) {
58
+ super(`Server Action "${actionId}" not found in the manifest. ` +
59
+ `Ensure the function is exported with 'use server' and the manifest is up to date.`, 'ACTION_NOT_FOUND');
60
+ }
61
+ }
62
+ /**
63
+ * Thrown when the flight manifest fails structural validation
64
+ * at router initialization time.
65
+ *
66
+ * @example
67
+ * ```
68
+ * // Error message:
69
+ * // [juice/runtime] Invalid flight-manifest.json: missing "routes" field.
70
+ * // Re-run the Juice compiler to regenerate it.
71
+ * ```
72
+ */
73
+ export class ManifestValidationError extends JuiceRouterError {
74
+ name = 'ManifestValidationError';
75
+ constructor(detail) {
76
+ super(`Invalid flight-manifest.json: ${detail}. ` +
77
+ `Re-run the Juice compiler to regenerate it.`, 'INVALID_MANIFEST');
78
+ }
79
+ }
80
+ /**
81
+ * Thrown when a dynamic `import()` for a route module or server action
82
+ * module fails at runtime.
83
+ *
84
+ * @example
85
+ * ```
86
+ * // Error message:
87
+ * // [juice/runtime] Failed to load module "./app/routes/missing.tsx"
88
+ * // for route "/missing". The module may have been deleted or the
89
+ * // flight-manifest.json is stale. Re-run the Juice compiler.
90
+ * // Cause: Cannot find module './app/routes/missing.tsx'
91
+ * ```
92
+ */
93
+ export class ModuleLoadError extends JuiceRouterError {
94
+ name = 'ModuleLoadError';
95
+ constructor(moduleId, context, cause) {
96
+ const causeMessage = cause instanceof Error ? cause.message : String(cause);
97
+ super(`Failed to load module "${moduleId}" for ${context}. ` +
98
+ `The module may have been deleted or the flight-manifest.json is stale. ` +
99
+ `Re-run the Juice compiler.\nCause: ${causeMessage}`, 'MODULE_LOAD_FAILED');
100
+ }
101
+ }
102
+ /**
103
+ * Thrown internally when a user-provided hook (`onBeforeRequest`,
104
+ * `onNotFound`, `onError`) throws an exception. The router catches
105
+ * this to prevent user bugs from crashing the server.
106
+ *
107
+ * @example
108
+ * ```
109
+ * // Error message:
110
+ * // [juice/runtime] The "onBeforeRequest" hook threw an error.
111
+ * // Your hook function must return a Response or void, not throw.
112
+ * // Cause: Cannot read properties of undefined (reading 'session')
113
+ * ```
114
+ */
115
+ export class HookExecutionError extends JuiceRouterError {
116
+ name = 'HookExecutionError';
117
+ constructor(hookName, cause) {
118
+ const causeMessage = cause instanceof Error ? cause.message : String(cause);
119
+ super(`The "${hookName}" hook threw an error. ` +
120
+ `Your hook function must return a Response or void, not throw.\n` +
121
+ `Cause: ${causeMessage}`, 'HOOK_EXECUTION_FAILED');
122
+ }
123
+ }
124
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/runtime/errors.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,2DAA2D;AAC3D,2CAA2C;AAC3C,uEAAuE;AAEvE;;;;;;;GAOG;AACH,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAMvB;IALT,IAAI,GAAW,kBAAkB,CAAC;IAE3C,YACE,OAAe;IACf,+DAA+D;IAC/C,IAAY;QAE5B,KAAK,CAAC,mBAAmB,OAAO,EAAE,CAAC,CAAC;QAFpB,SAAI,GAAJ,IAAI,CAAQ;IAG9B,CAAC;CACF;AAED;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,kBAAmB,SAAQ,gBAAgB;IACpC,IAAI,GAAG,oBAAoB,CAAC;IAE9C,YAAY,QAAgB;QAC1B,KAAK,CACH,qBAAqB,QAAQ,KAAK;YAChC,oEAAoE;YACpE,oEAAoE,EACtE,iBAAiB,CAClB,CAAC;IACJ,CAAC;CACF;AAED;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,mBAAoB,SAAQ,gBAAgB;IACrC,IAAI,GAAG,qBAAqB,CAAC;IAE/C,YAAY,QAAgB;QAC1B,KAAK,CACH,kBAAkB,QAAQ,+BAA+B;YACvD,mFAAmF,EACrF,kBAAkB,CACnB,CAAC;IACJ,CAAC;CACF;AAED;;;;;;;;;;GAUG;AACH,MAAM,OAAO,uBAAwB,SAAQ,gBAAgB;IACzC,IAAI,GAAG,yBAAyB,CAAC;IAEnD,YAAY,MAAc;QACxB,KAAK,CACH,iCAAiC,MAAM,IAAI;YACzC,6CAA6C,EAC/C,kBAAkB,CACnB,CAAC;IACJ,CAAC;CACF;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,eAAgB,SAAQ,gBAAgB;IACjC,IAAI,GAAG,iBAAiB,CAAC;IAE3C,YACE,QAAgB,EAChB,OAAe,EACf,KAAc;QAEd,MAAM,YAAY,GAChB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACzD,KAAK,CACH,0BAA0B,QAAQ,SAAS,OAAO,IAAI;YACpD,yEAAyE;YACzE,sCAAsC,YAAY,EAAE,EACtD,oBAAoB,CACrB,CAAC;IACJ,CAAC;CACF;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,kBAAmB,SAAQ,gBAAgB;IACpC,IAAI,GAAG,oBAAoB,CAAC;IAE9C,YAAY,QAAgB,EAAE,KAAc;QAC1C,MAAM,YAAY,GAChB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACzD,KAAK,CACH,QAAQ,QAAQ,yBAAyB;YACvC,iEAAiE;YACjE,UAAU,YAAY,EAAE,EAC1B,uBAAuB,CACxB,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,3 @@
1
+ export { createRouter } from './router.js';
2
+ export type { FlightManifest, RouterOptions } from './types.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,5 @@
1
+ // ── juice/runtime ──────────────────────────────────────────────────
2
+ // Public API barrel. Three exports. One function, two types.
3
+ // ────────────────────────────────────────────────────────────────────
4
+ export { createRouter } from './router.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,6DAA6D;AAC7D,uEAAuE;AAEvE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,44 @@
1
+ import type { RouteEntry, _CompiledRoute, _RouteMatch } from './types.js';
2
+ /**
3
+ * Compiles the manifest's flat route map into an array of `URLPattern`
4
+ * matchers. Called **once** inside `createRouter` at initialization.
5
+ *
6
+ * Patterns use the URLPattern pathname syntax:
7
+ * - `/blog/:slug` — named parameter
8
+ * - `/docs/*` — wildcard
9
+ * - `/` — exact root match
10
+ *
11
+ * @param routes - The `manifest.routes` record (pattern → RouteEntry).
12
+ * @param basePath - Optional prefix prepended to every pattern.
13
+ * @returns An array of compiled routes, ordered by declaration order.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * const compiled = _compileRoutes(manifest.routes, '/');
18
+ * // compiled[0].pattern matches URLPattern({ pathname: '/blog/:slug' })
19
+ * ```
20
+ *
21
+ * @internal
22
+ */
23
+ export declare function _compileRoutes(routes: Readonly<Record<string, RouteEntry>>, basePath: string): _CompiledRoute[];
24
+ /**
25
+ * Matches a full URL string against the compiled route table.
26
+ * Returns the first match with extracted pathname params, or `null`.
27
+ *
28
+ * @param url - The full request URL (e.g., `'https://example.com/blog/hello'`).
29
+ * @param compiled - The pre-compiled route array from `_compileRoutes`.
30
+ * @returns The matched route entry and extracted params, or `null`.
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * const match = _matchRoute('https://example.com/blog/hello', compiled);
35
+ * if (match) {
36
+ * console.log(match.params.slug); // 'hello'
37
+ * console.log(match.entry.moduleId); // './app/blog/[slug]/page.tsx'
38
+ * }
39
+ * ```
40
+ *
41
+ * @internal
42
+ */
43
+ export declare function _matchRoute(url: string, compiled: readonly _CompiledRoute[]): _RouteMatch | null;
44
+ //# sourceMappingURL=matcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matcher.d.ts","sourceRoot":"","sources":["../../src/runtime/matcher.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAG1E;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,EAC5C,QAAQ,EAAE,MAAM,GACf,cAAc,EAAE,CAmBlB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,SAAS,cAAc,EAAE,GAClC,WAAW,GAAG,IAAI,CAqBpB"}
@@ -0,0 +1,83 @@
1
+ // ── juice/runtime matcher ──────────────────────────────────────────
2
+ // Zero-dependency route matching using the WinterCG URLPattern API.
3
+ // Compiled once at startup, matched per-request.
4
+ // ────────────────────────────────────────────────────────────────────
5
+ import { ManifestValidationError } from './errors.js';
6
+ /**
7
+ * Compiles the manifest's flat route map into an array of `URLPattern`
8
+ * matchers. Called **once** inside `createRouter` at initialization.
9
+ *
10
+ * Patterns use the URLPattern pathname syntax:
11
+ * - `/blog/:slug` — named parameter
12
+ * - `/docs/*` — wildcard
13
+ * - `/` — exact root match
14
+ *
15
+ * @param routes - The `manifest.routes` record (pattern → RouteEntry).
16
+ * @param basePath - Optional prefix prepended to every pattern.
17
+ * @returns An array of compiled routes, ordered by declaration order.
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * const compiled = _compileRoutes(manifest.routes, '/');
22
+ * // compiled[0].pattern matches URLPattern({ pathname: '/blog/:slug' })
23
+ * ```
24
+ *
25
+ * @internal
26
+ */
27
+ export function _compileRoutes(routes, basePath) {
28
+ // Normalize: strip trailing slash from basePath (unless it IS '/')
29
+ const base = basePath === '/' ? '' : basePath.replace(/\/+$/, '');
30
+ return Object.entries(routes).map(([pattern, entry]) => {
31
+ const fullPattern = base + (pattern === '/' ? '/' : pattern);
32
+ try {
33
+ return {
34
+ pattern: new URLPattern({ pathname: fullPattern }),
35
+ entry,
36
+ };
37
+ }
38
+ catch (cause) {
39
+ throw new ManifestValidationError(`invalid route pattern "${pattern}" (resolved to "${fullPattern}"). ` +
40
+ `URLPattern could not parse it. ` +
41
+ `Check the route keys in your manifest`);
42
+ }
43
+ });
44
+ }
45
+ /**
46
+ * Matches a full URL string against the compiled route table.
47
+ * Returns the first match with extracted pathname params, or `null`.
48
+ *
49
+ * @param url - The full request URL (e.g., `'https://example.com/blog/hello'`).
50
+ * @param compiled - The pre-compiled route array from `_compileRoutes`.
51
+ * @returns The matched route entry and extracted params, or `null`.
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * const match = _matchRoute('https://example.com/blog/hello', compiled);
56
+ * if (match) {
57
+ * console.log(match.params.slug); // 'hello'
58
+ * console.log(match.entry.moduleId); // './app/blog/[slug]/page.tsx'
59
+ * }
60
+ * ```
61
+ *
62
+ * @internal
63
+ */
64
+ export function _matchRoute(url, compiled) {
65
+ for (const route of compiled) {
66
+ const result = route.pattern.exec(url);
67
+ if (result) {
68
+ // URLPattern groups are Record<string, string | undefined>.
69
+ // Filter out undefined values for a clean params object.
70
+ const rawGroups = result.pathname.groups;
71
+ const params = {};
72
+ for (const key of Object.keys(rawGroups)) {
73
+ const value = rawGroups[key];
74
+ if (value !== undefined) {
75
+ params[key] = value;
76
+ }
77
+ }
78
+ return { entry: route.entry, params };
79
+ }
80
+ }
81
+ return null;
82
+ }
83
+ //# sourceMappingURL=matcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matcher.js","sourceRoot":"","sources":["../../src/runtime/matcher.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,oEAAoE;AACpE,iDAAiD;AACjD,uEAAuE;AAGvE,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,cAAc,CAC5B,MAA4C,EAC5C,QAAgB;IAEhB,mEAAmE;IACnE,MAAM,IAAI,GAAG,QAAQ,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAElE,OAAO,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,EAAE;QACrD,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QAC7D,IAAI,CAAC;YACH,OAAO;gBACL,OAAO,EAAE,IAAI,UAAU,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;gBAClD,KAAK;aACN,CAAC;QACJ,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,MAAM,IAAI,uBAAuB,CAC/B,0BAA0B,OAAO,mBAAmB,WAAW,MAAM;gBACnE,iCAAiC;gBACjC,uCAAuC,CAC1C,CAAC;QACJ,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,WAAW,CACzB,GAAW,EACX,QAAmC;IAEnC,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACvC,IAAI,MAAM,EAAE,CAAC;YACX,4DAA4D;YAC5D,yDAAyD;YACzD,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;YACzC,MAAM,MAAM,GAA2B,EAAE,CAAC;YAE1C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;gBACzC,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;gBAC7B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;oBACxB,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;gBACtB,CAAC;YACH,CAAC;YAED,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC;QACxC,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}