@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.
- package/LICENSE +21 -0
- package/README.md +88 -0
- package/dist/cli/commands.d.ts +29 -0
- package/dist/cli/commands.d.ts.map +1 -0
- package/dist/cli/commands.js +102 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/create.d.ts +35 -0
- package/dist/cli/create.d.ts.map +1 -0
- package/dist/cli/create.js +108 -0
- package/dist/cli/create.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +97 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/templates.d.ts +14 -0
- package/dist/cli/templates.d.ts.map +1 -0
- package/dist/cli/templates.js +154 -0
- package/dist/cli/templates.js.map +1 -0
- package/dist/compiler/errors.d.ts +91 -0
- package/dist/compiler/errors.d.ts.map +1 -0
- package/dist/compiler/errors.js +110 -0
- package/dist/compiler/errors.js.map +1 -0
- package/dist/compiler/manifest.d.ts +39 -0
- package/dist/compiler/manifest.d.ts.map +1 -0
- package/dist/compiler/manifest.js +78 -0
- package/dist/compiler/manifest.js.map +1 -0
- package/dist/compiler/parse.d.ts +126 -0
- package/dist/compiler/parse.d.ts.map +1 -0
- package/dist/compiler/parse.js +246 -0
- package/dist/compiler/parse.js.map +1 -0
- package/dist/compiler/plugin.d.ts +43 -0
- package/dist/compiler/plugin.d.ts.map +1 -0
- package/dist/compiler/plugin.js +281 -0
- package/dist/compiler/plugin.js.map +1 -0
- package/dist/compiler/proxy.d.ts +42 -0
- package/dist/compiler/proxy.d.ts.map +1 -0
- package/dist/compiler/proxy.js +80 -0
- package/dist/compiler/proxy.js.map +1 -0
- package/dist/compiler/registry.d.ts +58 -0
- package/dist/compiler/registry.d.ts.map +1 -0
- package/dist/compiler/registry.js +79 -0
- package/dist/compiler/registry.js.map +1 -0
- package/dist/compiler/server-action-registry.d.ts +57 -0
- package/dist/compiler/server-action-registry.d.ts.map +1 -0
- package/dist/compiler/server-action-registry.js +76 -0
- package/dist/compiler/server-action-registry.js.map +1 -0
- package/dist/compiler/server-actions.d.ts +49 -0
- package/dist/compiler/server-actions.d.ts.map +1 -0
- package/dist/compiler/server-actions.js +89 -0
- package/dist/compiler/server-actions.js.map +1 -0
- package/dist/compiler/types.d.ts +188 -0
- package/dist/compiler/types.d.ts.map +1 -0
- package/dist/compiler/types.js +9 -0
- package/dist/compiler/types.js.map +1 -0
- package/dist/runtime/actions.d.ts +37 -0
- package/dist/runtime/actions.d.ts.map +1 -0
- package/dist/runtime/actions.js +167 -0
- package/dist/runtime/actions.js.map +1 -0
- package/dist/runtime/dev.d.ts +43 -0
- package/dist/runtime/dev.d.ts.map +1 -0
- package/dist/runtime/dev.js +260 -0
- package/dist/runtime/dev.js.map +1 -0
- package/dist/runtime/errors.d.ts +98 -0
- package/dist/runtime/errors.d.ts.map +1 -0
- package/dist/runtime/errors.js +124 -0
- package/dist/runtime/errors.js.map +1 -0
- package/dist/runtime/index.d.ts +3 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +5 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/matcher.d.ts +44 -0
- package/dist/runtime/matcher.d.ts.map +1 -0
- package/dist/runtime/matcher.js +83 -0
- package/dist/runtime/matcher.js.map +1 -0
- package/dist/runtime/render.d.ts +25 -0
- package/dist/runtime/render.d.ts.map +1 -0
- package/dist/runtime/render.js +141 -0
- package/dist/runtime/render.js.map +1 -0
- package/dist/runtime/resolve.d.ts +24 -0
- package/dist/runtime/resolve.d.ts.map +1 -0
- package/dist/runtime/resolve.js +41 -0
- package/dist/runtime/resolve.js.map +1 -0
- package/dist/runtime/router.d.ts +74 -0
- package/dist/runtime/router.d.ts.map +1 -0
- package/dist/runtime/router.js +367 -0
- package/dist/runtime/router.js.map +1 -0
- package/dist/runtime/types.d.ts +245 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/runtime/types.js +5 -0
- package/dist/runtime/types.js.map +1 -0
- package/package.json +92 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
// ── juice/runtime router ───────────────────────────────────────────
|
|
2
|
+
// The sole public entry point. Creates a WinterCG-compatible fetch
|
|
3
|
+
// handler from a flight manifest.
|
|
4
|
+
//
|
|
5
|
+
// WinterCG only. No Node.js APIs. No bundler awareness.
|
|
6
|
+
// ────────────────────────────────────────────────────────────────────
|
|
7
|
+
import { ManifestValidationError, HookExecutionError } from './errors.js';
|
|
8
|
+
import { _compileRoutes, _matchRoute } from './matcher.js';
|
|
9
|
+
import { _renderPipeline } from './render.js';
|
|
10
|
+
import { _serverActionPipeline } from './actions.js';
|
|
11
|
+
import { _generateDevErrorOverlay } from './dev.js';
|
|
12
|
+
// ── Manifest Normalization ────────────────────────────────────────
|
|
13
|
+
/**
|
|
14
|
+
* Normalizes a compiler-emitted manifest into the runtime’s expected shape.
|
|
15
|
+
*
|
|
16
|
+
* The Juice compiler emits `{ $schema, version, clientModules, ssrModules, serverActions }`
|
|
17
|
+
* where serverActions use `module` and `name` fields. The runtime expects
|
|
18
|
+
* `{ routes, clientModules, serverActions }` with `moduleId` and `exportName` fields.
|
|
19
|
+
*
|
|
20
|
+
* This function bridges the gap so `createRouter()` accepts both shapes.
|
|
21
|
+
*
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
function _normalizeManifest(raw) {
|
|
25
|
+
// Non-objects pass through to _validateManifest which will reject them
|
|
26
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
27
|
+
return raw;
|
|
28
|
+
}
|
|
29
|
+
const obj = raw;
|
|
30
|
+
// If the manifest already has the runtime shape, return as-is
|
|
31
|
+
// (but still normalize serverActions field names if needed)
|
|
32
|
+
if (obj.routes && typeof obj.routes === 'object') {
|
|
33
|
+
const sa = obj.serverActions;
|
|
34
|
+
if (sa && typeof sa === 'object') {
|
|
35
|
+
const normalized = _normalizeServerActions(sa);
|
|
36
|
+
if (normalized !== sa) {
|
|
37
|
+
return { ...obj, serverActions: normalized };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return obj;
|
|
41
|
+
}
|
|
42
|
+
// Detect compiler-shaped manifest by the presence of $schema or version.
|
|
43
|
+
// Only compiler output has these markers. Without them, a random object
|
|
44
|
+
// missing "routes" should still fail validation — not be silently patched.
|
|
45
|
+
const isCompilerManifest = '$schema' in obj || 'version' in obj;
|
|
46
|
+
if (!isCompilerManifest) {
|
|
47
|
+
return obj; // let _validateManifest catch the missing "routes" field
|
|
48
|
+
}
|
|
49
|
+
// Compiler-shaped manifest: provide default empty routes and normalize
|
|
50
|
+
const result = {
|
|
51
|
+
routes: obj.routes ?? {},
|
|
52
|
+
clientModules: obj.clientModules ?? {},
|
|
53
|
+
serverActions: {},
|
|
54
|
+
};
|
|
55
|
+
const sa = obj.serverActions;
|
|
56
|
+
if (sa && typeof sa === 'object') {
|
|
57
|
+
result.serverActions = _normalizeServerActions(sa);
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Normalizes server action entries from the compiler’s format
|
|
63
|
+
* (`{ module, name }`) to the runtime’s format (`{ moduleId, exportName }`).
|
|
64
|
+
*
|
|
65
|
+
* If an entry already has `moduleId`, it’s left as-is.
|
|
66
|
+
*
|
|
67
|
+
* @internal
|
|
68
|
+
*/
|
|
69
|
+
function _normalizeServerActions(actions) {
|
|
70
|
+
const result = {};
|
|
71
|
+
let changed = false;
|
|
72
|
+
for (const [id, entry] of Object.entries(actions)) {
|
|
73
|
+
// Compiler uses `module` + `name`; runtime uses `moduleId` + `exportName`
|
|
74
|
+
if (entry.module && !entry.moduleId) {
|
|
75
|
+
result[id] = {
|
|
76
|
+
moduleId: entry.module,
|
|
77
|
+
exportName: entry.name ?? 'default',
|
|
78
|
+
};
|
|
79
|
+
changed = true;
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
result[id] = entry;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return changed ? result : actions;
|
|
86
|
+
}
|
|
87
|
+
// ── Manifest Validation ────────────────────────────────────────────
|
|
88
|
+
/**
|
|
89
|
+
* Validates the flight manifest at router initialization time.
|
|
90
|
+
* Fails fast with actionable error messages.
|
|
91
|
+
*
|
|
92
|
+
* @internal
|
|
93
|
+
*/
|
|
94
|
+
function _validateManifest(manifest) {
|
|
95
|
+
if (manifest === null) {
|
|
96
|
+
throw new ManifestValidationError('expected an object but received null');
|
|
97
|
+
}
|
|
98
|
+
if (manifest === undefined) {
|
|
99
|
+
throw new ManifestValidationError('expected an object but received undefined. ' +
|
|
100
|
+
'Did you forget to pass the manifest to createRouter()?');
|
|
101
|
+
}
|
|
102
|
+
if (typeof manifest !== 'object' || Array.isArray(manifest)) {
|
|
103
|
+
throw new ManifestValidationError('expected an object but received ' +
|
|
104
|
+
(Array.isArray(manifest) ? 'an array' : typeof manifest));
|
|
105
|
+
}
|
|
106
|
+
const m = manifest;
|
|
107
|
+
if (!m.routes || typeof m.routes !== 'object') {
|
|
108
|
+
throw new ManifestValidationError('missing "routes" field. The manifest must have a "routes" object ' +
|
|
109
|
+
'mapping URL patterns to route entries');
|
|
110
|
+
}
|
|
111
|
+
if (!m.clientModules || typeof m.clientModules !== 'object') {
|
|
112
|
+
throw new ManifestValidationError('missing "clientModules" field. The manifest must have a "clientModules" ' +
|
|
113
|
+
'object mapping client component IDs to chunk info');
|
|
114
|
+
}
|
|
115
|
+
if (!m.serverActions || typeof m.serverActions !== 'object') {
|
|
116
|
+
throw new ManifestValidationError('missing "serverActions" field. The manifest must have a "serverActions" ' +
|
|
117
|
+
'object mapping action IDs to handler modules');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// ── Default Hooks ──────────────────────────────────────────────────
|
|
121
|
+
/** @internal */
|
|
122
|
+
function _defaultOnNotFound(req) {
|
|
123
|
+
const url = new URL(req.url);
|
|
124
|
+
return new Response(`Not Found: ${url.pathname}`, { status: 404 });
|
|
125
|
+
}
|
|
126
|
+
/** @internal */
|
|
127
|
+
function _defaultOnError(error, _req) {
|
|
128
|
+
console.error('[juice/runtime] Unhandled error:', error);
|
|
129
|
+
return new Response('Internal Server Error', { status: 500 });
|
|
130
|
+
}
|
|
131
|
+
// ── Hook Safety Wrappers ───────────────────────────────────────────
|
|
132
|
+
/**
|
|
133
|
+
* Safely executes a user-provided hook. If the hook throws,
|
|
134
|
+
* catches the error and falls back gracefully instead of
|
|
135
|
+
* crashing the server.
|
|
136
|
+
*
|
|
137
|
+
* @internal
|
|
138
|
+
*/
|
|
139
|
+
async function _safeCallHook(hookName, hookFn, fallback) {
|
|
140
|
+
try {
|
|
141
|
+
return await hookFn();
|
|
142
|
+
}
|
|
143
|
+
catch (cause) {
|
|
144
|
+
// If the hook threw a Response, honor it — it's intentional.
|
|
145
|
+
if (cause instanceof Response) {
|
|
146
|
+
return cause;
|
|
147
|
+
}
|
|
148
|
+
console.error(new HookExecutionError(hookName, cause));
|
|
149
|
+
return fallback;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Creates a promise that rejects after `ms` milliseconds with a
|
|
154
|
+
* 504 Gateway Timeout response. Used for per-request timeout.
|
|
155
|
+
*
|
|
156
|
+
* @internal
|
|
157
|
+
*/
|
|
158
|
+
function _createTimeout(ms) {
|
|
159
|
+
let timeoutId;
|
|
160
|
+
const promise = new Promise((_, reject) => {
|
|
161
|
+
timeoutId = setTimeout(() => {
|
|
162
|
+
reject(new Response('Gateway Timeout', {
|
|
163
|
+
status: 504,
|
|
164
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
165
|
+
}));
|
|
166
|
+
}, ms);
|
|
167
|
+
});
|
|
168
|
+
return {
|
|
169
|
+
promise,
|
|
170
|
+
clear: () => clearTimeout(timeoutId),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
// ── Public API ─────────────────────────────────────────────────────
|
|
174
|
+
/**
|
|
175
|
+
* Creates a WinterCG-compatible fetch handler for the Juice edge runtime.
|
|
176
|
+
*
|
|
177
|
+
* The returned function has the signature `(req: Request) => Promise<Response>`,
|
|
178
|
+
* which is the universal handler interface for Cloudflare Workers, Bun,
|
|
179
|
+
* Deno, and any WinterCG-compliant runtime.
|
|
180
|
+
*
|
|
181
|
+
* **Architecture:**
|
|
182
|
+
* - `GET` requests are routed to React 19's `renderToReadableStream`.
|
|
183
|
+
* - `POST` requests execute server actions resolved from the manifest.
|
|
184
|
+
* - All other methods return `405 Method Not Allowed`.
|
|
185
|
+
*
|
|
186
|
+
* **Thrown Response pattern:**
|
|
187
|
+
* User components can `throw new Response(body, { status })` to signal
|
|
188
|
+
* non-200 outcomes (404, redirect, etc.). The router catches these,
|
|
189
|
+
* aborts the React stream, and returns the thrown `Response` directly.
|
|
190
|
+
*
|
|
191
|
+
* **Hardened hooks:**
|
|
192
|
+
* User-provided hooks (`onBeforeRequest`, `onNotFound`, `onError`) are
|
|
193
|
+
* wrapped in try/catch. If a hook throws, the error is logged and a
|
|
194
|
+
* safe fallback response is returned — the server never crashes.
|
|
195
|
+
*
|
|
196
|
+
* @param manifest - The flight manifest generated by the Juice compiler.
|
|
197
|
+
* This is the sole bridge between the build step and the runtime.
|
|
198
|
+
* @param options - Optional runtime configuration (hooks, base path, timeout).
|
|
199
|
+
* @returns A `(req: Request) => Promise<Response>` fetch handler.
|
|
200
|
+
*
|
|
201
|
+
* @throws {ManifestValidationError} If the manifest is structurally invalid.
|
|
202
|
+
*
|
|
203
|
+
* @example
|
|
204
|
+
* ```ts
|
|
205
|
+
* // ── Cloudflare Workers ──────────────────────────────
|
|
206
|
+
* import manifest from './flight-manifest.json';
|
|
207
|
+
* import { createRouter } from '@cmj/juice/runtime';
|
|
208
|
+
*
|
|
209
|
+
* export default { fetch: createRouter(manifest) };
|
|
210
|
+
* ```
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```ts
|
|
214
|
+
* // ── Bun ─────────────────────────────────────────────
|
|
215
|
+
* import manifest from './flight-manifest.json';
|
|
216
|
+
* import { createRouter } from '@cmj/juice/runtime';
|
|
217
|
+
*
|
|
218
|
+
* Bun.serve({ fetch: createRouter(manifest) });
|
|
219
|
+
* ```
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* ```ts
|
|
223
|
+
* // ── With options ────────────────────────────────────
|
|
224
|
+
* import manifest from './flight-manifest.json';
|
|
225
|
+
* import { createRouter } from '@cmj/juice/runtime';
|
|
226
|
+
*
|
|
227
|
+
* export default {
|
|
228
|
+
* fetch: createRouter(manifest, {
|
|
229
|
+
* basePath: '/app',
|
|
230
|
+
* requestTimeout: 10_000, // 10 seconds
|
|
231
|
+
* onBeforeRequest: (req) => {
|
|
232
|
+
* if (!getSession(req)) {
|
|
233
|
+
* return Response.redirect('/login', 302);
|
|
234
|
+
* }
|
|
235
|
+
* },
|
|
236
|
+
* onNotFound: () => new Response('Custom 404', { status: 404 }),
|
|
237
|
+
* onError: (err) => {
|
|
238
|
+
* reportToSentry(err);
|
|
239
|
+
* return new Response('Oops', { status: 500 });
|
|
240
|
+
* },
|
|
241
|
+
* }),
|
|
242
|
+
* };
|
|
243
|
+
* ```
|
|
244
|
+
*/
|
|
245
|
+
export function createRouter(manifest, options) {
|
|
246
|
+
// ── Normalize compiler-shaped manifest to runtime shape ───────
|
|
247
|
+
const normalized = _normalizeManifest(manifest);
|
|
248
|
+
// ── Fail fast: validate at initialization, not per-request ───
|
|
249
|
+
_validateManifest(normalized);
|
|
250
|
+
const resolvedManifest = normalized;
|
|
251
|
+
// ── Resolve options with defaults ────────────────────────────
|
|
252
|
+
// Read _meta from the manifest (injected by the Vite plugin) as
|
|
253
|
+
// defaults. User-provided options always take precedence.
|
|
254
|
+
const meta = resolvedManifest._meta;
|
|
255
|
+
const basePath = options?.basePath ?? '/';
|
|
256
|
+
const requestTimeout = options?.requestTimeout ?? 0;
|
|
257
|
+
const root = options?.root ?? meta?.root;
|
|
258
|
+
const isDev = (options?.mode ?? meta?.mode) === 'development';
|
|
259
|
+
const hmrUrl = options?.hmrUrl ?? meta?.hmrUrl;
|
|
260
|
+
const assetPrefix = options?.assetPrefix ?? meta?.assetPrefix ?? '/';
|
|
261
|
+
const clientEntry = options?.clientEntry ?? meta?.clientEntry;
|
|
262
|
+
const resolvedOptions = {
|
|
263
|
+
...options,
|
|
264
|
+
root,
|
|
265
|
+
isDev,
|
|
266
|
+
hmrUrl,
|
|
267
|
+
assetPrefix,
|
|
268
|
+
clientEntry,
|
|
269
|
+
onNotFound: options?.onNotFound ?? _defaultOnNotFound,
|
|
270
|
+
onError: options?.onError ?? _defaultOnError,
|
|
271
|
+
};
|
|
272
|
+
if (isDev) {
|
|
273
|
+
console.log('[juice/dev] \u{1F9C3} Dev mode active \u2014 error overlays, module cache-busting, HMR enabled');
|
|
274
|
+
}
|
|
275
|
+
// ── Compile routes once ──────────────────────────────────────
|
|
276
|
+
const compiledRoutes = _compileRoutes(resolvedManifest.routes, basePath);
|
|
277
|
+
// ── Return the fetch handler ─────────────────────────────────
|
|
278
|
+
return async function handleRequest(req) {
|
|
279
|
+
// ── Request timeout race ─────────────────────────────────────
|
|
280
|
+
if (requestTimeout > 0) {
|
|
281
|
+
const timeout = _createTimeout(requestTimeout);
|
|
282
|
+
try {
|
|
283
|
+
return await Promise.race([
|
|
284
|
+
_handleRequestInner(req, compiledRoutes, resolvedManifest, resolvedOptions),
|
|
285
|
+
timeout.promise,
|
|
286
|
+
]);
|
|
287
|
+
}
|
|
288
|
+
catch (thrown) {
|
|
289
|
+
// Timeout rejects with a Response object
|
|
290
|
+
if (thrown instanceof Response) {
|
|
291
|
+
return thrown;
|
|
292
|
+
}
|
|
293
|
+
return resolvedOptions.onError(thrown, req);
|
|
294
|
+
}
|
|
295
|
+
finally {
|
|
296
|
+
timeout.clear();
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return _handleRequestInner(req, compiledRoutes, resolvedManifest, resolvedOptions);
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Inner request handler — separated so timeout can race against it.
|
|
304
|
+
* @internal
|
|
305
|
+
*/
|
|
306
|
+
async function _handleRequestInner(req, compiledRoutes, manifest, resolvedOptions) {
|
|
307
|
+
try {
|
|
308
|
+
// ── onBeforeRequest hook (guarded) ───────────────────────────
|
|
309
|
+
if (resolvedOptions.onBeforeRequest) {
|
|
310
|
+
const hookFn = resolvedOptions.onBeforeRequest;
|
|
311
|
+
const earlyResponse = await _safeCallHook('onBeforeRequest', () => hookFn(req), undefined);
|
|
312
|
+
if (earlyResponse instanceof Response) {
|
|
313
|
+
return earlyResponse;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
// ── Method dispatch ────────────────────────────────────────
|
|
317
|
+
const method = req.method.toUpperCase();
|
|
318
|
+
switch (method) {
|
|
319
|
+
case 'GET':
|
|
320
|
+
case 'HEAD': {
|
|
321
|
+
// ── Route matching ─────────────────────────────────────
|
|
322
|
+
const match = _matchRoute(req.url, compiledRoutes);
|
|
323
|
+
if (!match) {
|
|
324
|
+
return _safeCallHook('onNotFound', () => resolvedOptions.onNotFound(req), new Response('Not Found', { status: 404 }));
|
|
325
|
+
}
|
|
326
|
+
// ── Render pipeline ────────────────────────────────────
|
|
327
|
+
const response = await _renderPipeline(req, match, manifest, resolvedOptions);
|
|
328
|
+
// HEAD requests: return headers only, strip body
|
|
329
|
+
if (method === 'HEAD') {
|
|
330
|
+
return new Response(null, {
|
|
331
|
+
status: response.status,
|
|
332
|
+
statusText: response.statusText,
|
|
333
|
+
headers: response.headers,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
return response;
|
|
337
|
+
}
|
|
338
|
+
case 'POST': {
|
|
339
|
+
return _serverActionPipeline(req, manifest, resolvedOptions);
|
|
340
|
+
}
|
|
341
|
+
default: {
|
|
342
|
+
return new Response('Method Not Allowed', {
|
|
343
|
+
status: 405,
|
|
344
|
+
headers: { Allow: 'GET, HEAD, POST' },
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
catch (thrown) {
|
|
350
|
+
// ── Top-level safety net ─────────────────────────────────
|
|
351
|
+
// Thrown Responses that somehow escaped the pipelines.
|
|
352
|
+
if (thrown instanceof Response) {
|
|
353
|
+
return thrown;
|
|
354
|
+
}
|
|
355
|
+
// ── Dev mode: rich error overlay ─────────────────────────
|
|
356
|
+
if (resolvedOptions.isDev) {
|
|
357
|
+
console.error('[juice/dev]', thrown);
|
|
358
|
+
return new Response(_generateDevErrorOverlay(thrown, req), {
|
|
359
|
+
status: 500,
|
|
360
|
+
headers: { 'Content-Type': 'text/html; charset=utf-8' },
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
// Guard the onError hook itself — if it throws, don't crash.
|
|
364
|
+
return _safeCallHook('onError', () => resolvedOptions.onError(thrown, req), new Response('Internal Server Error', { status: 500 }));
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
//# sourceMappingURL=router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/runtime/router.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A single route entry mapping a URL pattern to its server component module.
|
|
3
|
+
*/
|
|
4
|
+
export interface RouteEntry {
|
|
5
|
+
/** The module ID for dynamic import (relative to runtime root). */
|
|
6
|
+
readonly moduleId: string;
|
|
7
|
+
/**
|
|
8
|
+
* Named export to use as the page component.
|
|
9
|
+
* @default 'default'
|
|
10
|
+
*/
|
|
11
|
+
readonly exportName?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Resolution info for a client component chunk, used by React Flight
|
|
15
|
+
* to load client components on the browser.
|
|
16
|
+
*/
|
|
17
|
+
export interface ClientModuleRef {
|
|
18
|
+
/** Chunk filenames for the client bundle. */
|
|
19
|
+
readonly chunks: readonly string[];
|
|
20
|
+
/** The named export within the chunk. */
|
|
21
|
+
readonly name: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Resolution info for a server action, mapping an action ID to its
|
|
25
|
+
* handler module and export.
|
|
26
|
+
*/
|
|
27
|
+
export interface ServerActionRef {
|
|
28
|
+
/** The module ID containing the action handler. */
|
|
29
|
+
readonly moduleId: string;
|
|
30
|
+
/** The named export for the action function. */
|
|
31
|
+
readonly exportName: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The manifest generated by the Juice Vite plugin at build time.
|
|
35
|
+
* Maps server component paths to their client-side chunk references,
|
|
36
|
+
* route patterns to page modules, and action IDs to handlers.
|
|
37
|
+
*
|
|
38
|
+
* This is the **sole bridge** between the compiler and the runtime.
|
|
39
|
+
* The runtime never reads the filesystem — it receives this object
|
|
40
|
+
* from the caller.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* import manifest from './flight-manifest.json';
|
|
45
|
+
* import { createRouter } from '@cmj/juice/runtime';
|
|
46
|
+
*
|
|
47
|
+
* export default { fetch: createRouter(manifest) };
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export interface FlightManifest {
|
|
51
|
+
/** Map of URL pathname patterns to their server component modules. */
|
|
52
|
+
readonly routes: Readonly<Record<string, RouteEntry>>;
|
|
53
|
+
/** Map of client component IDs to their chunk resolution info. */
|
|
54
|
+
readonly clientModules: Readonly<Record<string, ClientModuleRef>>;
|
|
55
|
+
/** Map of server action IDs to their handler modules. */
|
|
56
|
+
readonly serverActions: Readonly<Record<string, ServerActionRef>>;
|
|
57
|
+
/**
|
|
58
|
+
* Build-time metadata injected by the Vite plugin.
|
|
59
|
+
* When present, `createRouter` reads these as defaults — no need
|
|
60
|
+
* to pass `root`, `mode`, or `hmrUrl` manually.
|
|
61
|
+
*
|
|
62
|
+
* @see {@link RouterOptions.mode}
|
|
63
|
+
* @see {@link RouterOptions.hmrUrl}
|
|
64
|
+
* @see {@link RouterOptions.root}
|
|
65
|
+
*/
|
|
66
|
+
readonly _meta?: {
|
|
67
|
+
readonly root?: string;
|
|
68
|
+
readonly mode?: 'development' | 'production';
|
|
69
|
+
readonly hmrUrl?: string;
|
|
70
|
+
/** Public URL prefix for client assets (e.g., `'/assets/'`). */
|
|
71
|
+
readonly assetPrefix?: string;
|
|
72
|
+
/** Client entry script that calls `hydrateRoot()`. */
|
|
73
|
+
readonly clientEntry?: string;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Optional configuration for the Juice edge router.
|
|
78
|
+
*
|
|
79
|
+
* All fields are optional and additive — the router works with zero
|
|
80
|
+
* configuration. Use hooks for auth guards, custom error pages,
|
|
81
|
+
* logging, and other cross-cutting concerns.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```ts
|
|
85
|
+
* createRouter(manifest, {
|
|
86
|
+
* basePath: '/app',
|
|
87
|
+
* onBeforeRequest: (req) => {
|
|
88
|
+
* if (!getSession(req)) {
|
|
89
|
+
* return Response.redirect('/login', 302);
|
|
90
|
+
* }
|
|
91
|
+
* },
|
|
92
|
+
* onNotFound: () => new Response('Page not found', { status: 404 }),
|
|
93
|
+
* onError: (err, req) => {
|
|
94
|
+
* console.error('[app]', req.url, err);
|
|
95
|
+
* return new Response('Something broke', { status: 500 });
|
|
96
|
+
* },
|
|
97
|
+
* });
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
export interface RouterOptions {
|
|
101
|
+
/**
|
|
102
|
+
* The root URL for resolving module paths in `import()` calls.
|
|
103
|
+
*
|
|
104
|
+
* **Why this is needed:** The runtime resolves module paths like
|
|
105
|
+
* `'./app/routes/home.tsx'` via dynamic `import()`. Without a root,
|
|
106
|
+
* these paths resolve relative to the **runtime package's** directory
|
|
107
|
+
* (e.g., `node_modules/juice/dist/runtime/`), not your app's root.
|
|
108
|
+
*
|
|
109
|
+
* Pass `import.meta.url` from your server entry point to anchor
|
|
110
|
+
* module resolution to your project directory.
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```ts
|
|
114
|
+
* // server.ts
|
|
115
|
+
* import { createRouter } from '@cmj/juice/runtime';
|
|
116
|
+
* import manifest from './flight-manifest.json';
|
|
117
|
+
*
|
|
118
|
+
* export default {
|
|
119
|
+
* fetch: createRouter(manifest, {
|
|
120
|
+
* root: import.meta.url, // anchors imports to this file's directory
|
|
121
|
+
* }),
|
|
122
|
+
* };
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
readonly root?: string;
|
|
126
|
+
/**
|
|
127
|
+
* Base path prefix for all routes (e.g., `'/app'`).
|
|
128
|
+
* Trailing slashes are normalized automatically.
|
|
129
|
+
* @default '/'
|
|
130
|
+
*/
|
|
131
|
+
readonly basePath?: string;
|
|
132
|
+
/**
|
|
133
|
+
* Hook called before every request is routed.
|
|
134
|
+
*
|
|
135
|
+
* Return a `Response` to short-circuit the pipeline entirely
|
|
136
|
+
* (useful for auth guards, CORS preflight, redirects, etc.).
|
|
137
|
+
* Return `void` / `undefined` to continue normal routing.
|
|
138
|
+
*/
|
|
139
|
+
readonly onBeforeRequest?: (req: Request) => Response | void | Promise<Response | void>;
|
|
140
|
+
/**
|
|
141
|
+
* Hook called when no route matches the request URL.
|
|
142
|
+
*
|
|
143
|
+
* @default Returns `new Response('Not Found', { status: 404 })`
|
|
144
|
+
*/
|
|
145
|
+
readonly onNotFound?: (req: Request) => Response | Promise<Response>;
|
|
146
|
+
/**
|
|
147
|
+
* Hook called on unhandled errors (NOT thrown `Response` objects).
|
|
148
|
+
*
|
|
149
|
+
* Thrown `Response` objects are always returned directly — they
|
|
150
|
+
* never reach this hook.
|
|
151
|
+
*
|
|
152
|
+
* @default Returns `new Response('Internal Server Error', { status: 500 })`
|
|
153
|
+
*/
|
|
154
|
+
readonly onError?: (error: unknown, req: Request) => Response | Promise<Response>;
|
|
155
|
+
/**
|
|
156
|
+
* Maximum time (in milliseconds) allowed for a single request.
|
|
157
|
+
* If a render or action exceeds this, the request is aborted and
|
|
158
|
+
* a `504 Gateway Timeout` response is returned.
|
|
159
|
+
*
|
|
160
|
+
* Set to `0` or `undefined` to disable (no timeout).
|
|
161
|
+
*
|
|
162
|
+
* @default undefined (no timeout)
|
|
163
|
+
*/
|
|
164
|
+
readonly requestTimeout?: number;
|
|
165
|
+
/**
|
|
166
|
+
* Runtime mode. Controls error verbosity, module caching, and
|
|
167
|
+
* HMR integration.
|
|
168
|
+
*
|
|
169
|
+
* - `'development'`: Rich HTML error overlays with stack traces,
|
|
170
|
+
* module cache-busting on every request (edits are picked up
|
|
171
|
+
* without restart), and Vite HMR client script injection.
|
|
172
|
+
* - `'production'`: Minimal error responses, modules cached
|
|
173
|
+
* normally, no dev tooling injected.
|
|
174
|
+
*
|
|
175
|
+
* @default 'production'
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* ```ts
|
|
179
|
+
* createRouter(manifest, {
|
|
180
|
+
* mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
|
|
181
|
+
* root: import.meta.url,
|
|
182
|
+
* });
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
readonly mode?: 'development' | 'production';
|
|
186
|
+
/**
|
|
187
|
+
* Vite HMR WebSocket URL to inject into SSR-rendered pages.
|
|
188
|
+
* Only used when `mode` is `'development'`.
|
|
189
|
+
*
|
|
190
|
+
* When set, a `<script type="module" src="/@vite/client"></script>`
|
|
191
|
+
* tag is injected into every HTML response, enabling Vite's
|
|
192
|
+
* hot module replacement and fast refresh in the browser.
|
|
193
|
+
*
|
|
194
|
+
* @default '/@vite/client'
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* ```ts
|
|
198
|
+
* createRouter(manifest, {
|
|
199
|
+
* mode: 'development',
|
|
200
|
+
* hmrUrl: '/@vite/client',
|
|
201
|
+
* });
|
|
202
|
+
* ```
|
|
203
|
+
*/
|
|
204
|
+
readonly hmrUrl?: string;
|
|
205
|
+
/**
|
|
206
|
+
* Public URL prefix prepended to client chunk paths from the
|
|
207
|
+
* manifest. Ensures `bootstrapModules` generates valid browser
|
|
208
|
+
* URLs (e.g., `'/assets/'` → `'/assets/Counter.CxK3d.js'`).
|
|
209
|
+
*
|
|
210
|
+
* Auto-detected from manifest `_meta` when the Vite plugin
|
|
211
|
+
* injects it. Only set manually for non-standard deployments.
|
|
212
|
+
*
|
|
213
|
+
* @default '/' in production, Vite dev server base in development
|
|
214
|
+
*/
|
|
215
|
+
readonly assetPrefix?: string;
|
|
216
|
+
/**
|
|
217
|
+
* Path to the client entry module that calls `hydrateRoot()`.
|
|
218
|
+
* Injected into `bootstrapModules` so React knows how to
|
|
219
|
+
* hydrate the page on the client.
|
|
220
|
+
*
|
|
221
|
+
* Auto-detected from manifest `_meta` when the Vite plugin
|
|
222
|
+
* injects it.
|
|
223
|
+
*
|
|
224
|
+
* @example `'/src/entry-client.tsx'`
|
|
225
|
+
*/
|
|
226
|
+
readonly clientEntry?: string;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* A compiled route pairs a `URLPattern` with its manifest entry.
|
|
230
|
+
* Created once at startup inside `createRouter`.
|
|
231
|
+
* @internal
|
|
232
|
+
*/
|
|
233
|
+
export interface _CompiledRoute {
|
|
234
|
+
readonly pattern: URLPattern;
|
|
235
|
+
readonly entry: RouteEntry;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Result of a successful route match.
|
|
239
|
+
* @internal
|
|
240
|
+
*/
|
|
241
|
+
export interface _RouteMatch {
|
|
242
|
+
readonly entry: RouteEntry;
|
|
243
|
+
readonly params: Readonly<Record<string, string>>;
|
|
244
|
+
}
|
|
245
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/runtime/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/runtime/types.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,wDAAwD;AACxD,uEAAuE"}
|