@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,25 @@
1
+ import type { FlightManifest, RouterOptions, _RouteMatch } from './types.js';
2
+ /**
3
+ * The core GET render pipeline.
4
+ *
5
+ * 1. Dynamically imports the matched route's server component module.
6
+ * 2. Creates a React element with extracted `params` and `searchParams`.
7
+ * 3. Streams it via `renderToReadableStream`.
8
+ * 4. **Critical:** If the user component throws a `Response` object,
9
+ * the stream is aborted and that `Response` is returned directly.
10
+ *
11
+ * @param req - The incoming `Request`.
12
+ * @param match - The matched route entry and extracted URL params.
13
+ * @param manifest - The flight manifest for client module resolution.
14
+ * @param options - Router options (hooks).
15
+ * @returns A streaming `Response` (200) or a thrown `Response` (any status).
16
+ *
17
+ * @internal
18
+ */
19
+ export declare function _renderPipeline(req: Request, match: _RouteMatch, manifest: FlightManifest, options: Required<Pick<RouterOptions, 'onError'>> & RouterOptions & {
20
+ isDev?: boolean;
21
+ hmrUrl?: string;
22
+ assetPrefix?: string;
23
+ clientEntry?: string;
24
+ }): Promise<Response>;
25
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/runtime/render.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,cAAc,EAEd,aAAa,EACb,WAAW,EACZ,MAAM,YAAY,CAAC;AA2BpB;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,cAAc,EACxB,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC,GAAG,aAAa,GAAG;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GACnJ,OAAO,CAAC,QAAQ,CAAC,CAwHnB"}
@@ -0,0 +1,141 @@
1
+ // ── juice/runtime render pipeline ──────────────────────────────────
2
+ // Handles GET requests: React 19 renderToReadableStream with
3
+ // AbortController for the thrown Response catch/abort pattern.
4
+ // ────────────────────────────────────────────────────────────────────
5
+ import { createElement } from 'react';
6
+ import { renderToReadableStream } from 'react-dom/server';
7
+ import { ModuleLoadError } from './errors.js';
8
+ import { _resolveModulePath } from './resolve.js';
9
+ import { _bustModuleCache, _generateHmrClientScript, _generateDevErrorOverlay } from './dev.js';
10
+ /**
11
+ * Resolves the client bootstrap module paths from the manifest.
12
+ * These are injected as `<script>` tags by React's streaming renderer
13
+ * to hydrate client components on the browser.
14
+ *
15
+ * @internal
16
+ */
17
+ function _getClientBootstrap(manifest, assetPrefix = '/') {
18
+ const prefix = assetPrefix.endsWith('/') ? assetPrefix : `${assetPrefix}/`;
19
+ const chunks = new Set();
20
+ for (const ref of Object.values(manifest.clientModules)) {
21
+ for (const chunk of ref.chunks) {
22
+ // Prepend assetPrefix so the browser can load the chunk
23
+ chunks.add(`${prefix}${chunk}`);
24
+ }
25
+ }
26
+ return Array.from(chunks);
27
+ }
28
+ /**
29
+ * The core GET render pipeline.
30
+ *
31
+ * 1. Dynamically imports the matched route's server component module.
32
+ * 2. Creates a React element with extracted `params` and `searchParams`.
33
+ * 3. Streams it via `renderToReadableStream`.
34
+ * 4. **Critical:** If the user component throws a `Response` object,
35
+ * the stream is aborted and that `Response` is returned directly.
36
+ *
37
+ * @param req - The incoming `Request`.
38
+ * @param match - The matched route entry and extracted URL params.
39
+ * @param manifest - The flight manifest for client module resolution.
40
+ * @param options - Router options (hooks).
41
+ * @returns A streaming `Response` (200) or a thrown `Response` (any status).
42
+ *
43
+ * @internal
44
+ */
45
+ export async function _renderPipeline(req, match, manifest, options) {
46
+ const { entry, params } = match;
47
+ // ── 1. Dynamic import of the page module ─────────────────────
48
+ let importPath = _resolveModulePath(entry.moduleId, options.root);
49
+ if (options.isDev) {
50
+ importPath = _bustModuleCache(importPath);
51
+ }
52
+ let mod;
53
+ try {
54
+ mod = await import(importPath);
55
+ }
56
+ catch (cause) {
57
+ throw new ModuleLoadError(entry.moduleId, `route "${new URL(req.url).pathname}"` +
58
+ (options.root
59
+ ? ` (resolved to "${importPath}" from root "${options.root}")`
60
+ : ' (no root option set — pass { root: import.meta.url } to createRouter)'), cause);
61
+ }
62
+ const exportName = entry.exportName ?? 'default';
63
+ const Component = mod[exportName];
64
+ if (typeof Component !== 'function') {
65
+ throw new Error(`[juice/runtime] Route module "${entry.moduleId}" does not export ` +
66
+ `a valid component at "${exportName}". ` +
67
+ `Ensure the file has a \`export default function Page()\` or ` +
68
+ `the exportName in flight-manifest.json points to a valid React component.`);
69
+ }
70
+ // ── 2. Build props ───────────────────────────────────────────
71
+ const url = new URL(req.url);
72
+ const searchParams = Object.fromEntries(url.searchParams.entries());
73
+ // ── 3. Create the React element ──────────────────────────────
74
+ const element = createElement(Component, { params, searchParams });
75
+ // ── 4. Stream with AbortController ───────────────────────────
76
+ const abortController = new AbortController();
77
+ // Track thrown Response objects from React's onError callback.
78
+ // React's onError fires asynchronously during streaming — we store
79
+ // the thrown Response and check after awaiting the stream.
80
+ let thrownResponse = null;
81
+ try {
82
+ const stream = await renderToReadableStream(element, {
83
+ signal: abortController.signal,
84
+ bootstrapModules: [
85
+ // 1. Client entry (hydrateRoot) — must load first
86
+ ...(options.clientEntry ? [options.clientEntry] : []),
87
+ // 2. Client component chunks (with asset prefix)
88
+ ..._getClientBootstrap(manifest, options.assetPrefix),
89
+ // 3. HMR client (dev only)
90
+ ...(options.isDev && options.hmrUrl ? [options.hmrUrl] : []),
91
+ ],
92
+ onError(error) {
93
+ // React calls this for errors during streaming.
94
+ // If it's a thrown Response, capture it and abort.
95
+ if (error instanceof Response) {
96
+ thrownResponse = error;
97
+ abortController.abort();
98
+ return;
99
+ }
100
+ // For real errors, log but let React's error boundary handle it.
101
+ console.error('[juice/runtime]', error);
102
+ },
103
+ });
104
+ // If a Response was thrown during initial render (before streaming),
105
+ // React would have called onError synchronously. Check immediately.
106
+ if (thrownResponse) {
107
+ abortController.abort();
108
+ return thrownResponse;
109
+ }
110
+ return new Response(stream, {
111
+ status: 200,
112
+ headers: {
113
+ 'Content-Type': 'text/html; charset=utf-8',
114
+ 'Transfer-Encoding': 'chunked',
115
+ },
116
+ });
117
+ }
118
+ catch (thrown) {
119
+ // ── Thrown Response: user intentionally signaled a non-200 ──
120
+ if (thrown instanceof Response) {
121
+ abortController.abort();
122
+ return thrown;
123
+ }
124
+ // ── Check if onError captured a Response ───────────────────
125
+ if (thrownResponse) {
126
+ abortController.abort();
127
+ return thrownResponse;
128
+ }
129
+ // ── Unexpected error ───────────────────────────────────────
130
+ abortController.abort();
131
+ // Dev mode: return rich error overlay
132
+ if (options.isDev) {
133
+ return new Response(_generateDevErrorOverlay(thrown, req, `route "${entry.moduleId}"`), {
134
+ status: 500,
135
+ headers: { 'Content-Type': 'text/html; charset=utf-8' },
136
+ });
137
+ }
138
+ return options.onError(thrown, req);
139
+ }
140
+ }
141
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/runtime/render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Resolves a module ID to its full import path, relative to the
3
+ * consumer's root URL.
4
+ *
5
+ * When `root` is provided (e.g., `import.meta.url` of the consumer's
6
+ * server entry), relative paths like `'./app/routes/home.tsx'` are
7
+ * resolved against that root. Without it, the path is returned as-is
8
+ * (and will resolve relative to the runtime package — usually wrong).
9
+ *
10
+ * @param moduleId - The module path from the manifest (e.g., `'./app/routes/home.tsx'`).
11
+ * @param root - The consumer's root URL (e.g., `import.meta.url`).
12
+ * @returns The resolved module path for `import()`.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * // root = 'file:///Users/dev/my-app/server.ts'
17
+ * _resolveModulePath('./app/routes/home.tsx', root);
18
+ * // → '/Users/dev/my-app/app/routes/home.tsx'
19
+ * ```
20
+ *
21
+ * @internal
22
+ */
23
+ export declare function _resolveModulePath(moduleId: string, root: string | undefined): string;
24
+ //# sourceMappingURL=resolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/runtime/resolve.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GAAG,SAAS,GACvB,MAAM,CAaR"}
@@ -0,0 +1,41 @@
1
+ // ── juice/runtime module resolution ───────────────────────────────
2
+ // Shared utility for resolving module paths against a root URL.
3
+ // Separated to avoid circular dependencies (router ↔ render/actions).
4
+ // ────────────────────────────────────────────────────────────────────
5
+ /**
6
+ * Resolves a module ID to its full import path, relative to the
7
+ * consumer's root URL.
8
+ *
9
+ * When `root` is provided (e.g., `import.meta.url` of the consumer's
10
+ * server entry), relative paths like `'./app/routes/home.tsx'` are
11
+ * resolved against that root. Without it, the path is returned as-is
12
+ * (and will resolve relative to the runtime package — usually wrong).
13
+ *
14
+ * @param moduleId - The module path from the manifest (e.g., `'./app/routes/home.tsx'`).
15
+ * @param root - The consumer's root URL (e.g., `import.meta.url`).
16
+ * @returns The resolved module path for `import()`.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * // root = 'file:///Users/dev/my-app/server.ts'
21
+ * _resolveModulePath('./app/routes/home.tsx', root);
22
+ * // → '/Users/dev/my-app/app/routes/home.tsx'
23
+ * ```
24
+ *
25
+ * @internal
26
+ */
27
+ export function _resolveModulePath(moduleId, root) {
28
+ if (!root) {
29
+ return moduleId;
30
+ }
31
+ // new URL('./app/page.tsx', 'file:///Users/dev/my-app/server.ts')
32
+ // → 'file:///Users/dev/my-app/app/page.tsx'
33
+ try {
34
+ return new URL(moduleId, root).href;
35
+ }
36
+ catch {
37
+ // If URL construction fails (e.g., non-URL root), fall back
38
+ return moduleId;
39
+ }
40
+ }
41
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/runtime/resolve.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,gEAAgE;AAChE,sEAAsE;AACtE,uEAAuE;AAEvE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,kBAAkB,CAChC,QAAgB,EAChB,IAAwB;IAExB,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,kEAAkE;IAClE,4CAA4C;IAC5C,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,4DAA4D;QAC5D,OAAO,QAAQ,CAAC;IAClB,CAAC;AACH,CAAC"}
@@ -0,0 +1,74 @@
1
+ import type { FlightManifest, RouterOptions } from './types.js';
2
+ /**
3
+ * Creates a WinterCG-compatible fetch handler for the Juice edge runtime.
4
+ *
5
+ * The returned function has the signature `(req: Request) => Promise<Response>`,
6
+ * which is the universal handler interface for Cloudflare Workers, Bun,
7
+ * Deno, and any WinterCG-compliant runtime.
8
+ *
9
+ * **Architecture:**
10
+ * - `GET` requests are routed to React 19's `renderToReadableStream`.
11
+ * - `POST` requests execute server actions resolved from the manifest.
12
+ * - All other methods return `405 Method Not Allowed`.
13
+ *
14
+ * **Thrown Response pattern:**
15
+ * User components can `throw new Response(body, { status })` to signal
16
+ * non-200 outcomes (404, redirect, etc.). The router catches these,
17
+ * aborts the React stream, and returns the thrown `Response` directly.
18
+ *
19
+ * **Hardened hooks:**
20
+ * User-provided hooks (`onBeforeRequest`, `onNotFound`, `onError`) are
21
+ * wrapped in try/catch. If a hook throws, the error is logged and a
22
+ * safe fallback response is returned — the server never crashes.
23
+ *
24
+ * @param manifest - The flight manifest generated by the Juice compiler.
25
+ * This is the sole bridge between the build step and the runtime.
26
+ * @param options - Optional runtime configuration (hooks, base path, timeout).
27
+ * @returns A `(req: Request) => Promise<Response>` fetch handler.
28
+ *
29
+ * @throws {ManifestValidationError} If the manifest is structurally invalid.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * // ── Cloudflare Workers ──────────────────────────────
34
+ * import manifest from './flight-manifest.json';
35
+ * import { createRouter } from '@cmj/juice/runtime';
36
+ *
37
+ * export default { fetch: createRouter(manifest) };
38
+ * ```
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * // ── Bun ─────────────────────────────────────────────
43
+ * import manifest from './flight-manifest.json';
44
+ * import { createRouter } from '@cmj/juice/runtime';
45
+ *
46
+ * Bun.serve({ fetch: createRouter(manifest) });
47
+ * ```
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * // ── With options ────────────────────────────────────
52
+ * import manifest from './flight-manifest.json';
53
+ * import { createRouter } from '@cmj/juice/runtime';
54
+ *
55
+ * export default {
56
+ * fetch: createRouter(manifest, {
57
+ * basePath: '/app',
58
+ * requestTimeout: 10_000, // 10 seconds
59
+ * onBeforeRequest: (req) => {
60
+ * if (!getSession(req)) {
61
+ * return Response.redirect('/login', 302);
62
+ * }
63
+ * },
64
+ * onNotFound: () => new Response('Custom 404', { status: 404 }),
65
+ * onError: (err) => {
66
+ * reportToSentry(err);
67
+ * return new Response('Oops', { status: 500 });
68
+ * },
69
+ * }),
70
+ * };
71
+ * ```
72
+ */
73
+ export declare function createRouter(manifest: FlightManifest, options?: RouterOptions): (req: Request) => Promise<Response>;
74
+ //# sourceMappingURL=router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/runtime/router.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EAEd,MAAM,YAAY,CAAC;AAwNpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsEG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,cAAc,EACxB,OAAO,CAAC,EAAE,aAAa,GACtB,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAiErC"}