@farm.js/plugin 0.1.0-beta.93 → 0.1.0-beta.94

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/dist/index.d.ts CHANGED
@@ -39,5 +39,5 @@
39
39
  * })
40
40
  * ```
41
41
  */
42
- export declare const version = "0.1.0-beta.93";
42
+ export declare const version = "0.1.0-beta.94";
43
43
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -39,7 +39,7 @@
39
39
  * })
40
40
  * ```
41
41
  */
42
- export const version = "0.1.0-beta.93";
42
+ export const version = "0.1.0-beta.94";
43
43
  // Note: Import plugins from their respective subpaths:
44
44
  // - @farm.js/plugin/rsc
45
45
  // - @farm.js/plugin/middleware
@@ -0,0 +1,3 @@
1
+ import type { ServerResponse } from "node:http";
2
+ export declare function sendRscDevelopmentResponse(res: ServerResponse, response: Response): Promise<void>;
3
+ //# sourceMappingURL=dev-response.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-response.d.ts","sourceRoot":"","sources":["../../src/rsc/dev-response.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGhD,wBAAsB,0BAA0B,CAC9C,GAAG,EAAE,cAAc,EACnB,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,IAAI,CAAC,CAYf"}
@@ -0,0 +1,10 @@
1
+ import { sendWebResponse } from "@farm.js/core/server";
2
+ export async function sendRscDevelopmentResponse(res, response) {
3
+ const headers = new Headers(response.headers);
4
+ headers.delete("transfer-encoding");
5
+ await sendWebResponse(res, new Response(response.body, {
6
+ status: response.status,
7
+ statusText: response.statusText,
8
+ headers,
9
+ }));
10
+ }
@@ -0,0 +1,3 @@
1
+ /** Client-owned reset callbacks must not be serialized from the RSC server. */
2
+ export declare function generateErrorFallbackEntry(): string;
3
+ //# sourceMappingURL=error-fallback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-fallback.d.ts","sourceRoot":"","sources":["../../../src/rsc/entries/error-fallback.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,wBAAgB,0BAA0B,IAAI,MAAM,CAUnD"}
@@ -0,0 +1,12 @@
1
+ /** Client-owned reset callbacks must not be serialized from the RSC server. */
2
+ export function generateErrorFallbackEntry() {
3
+ return `"use client";
4
+ import React from 'react';
5
+
6
+ export default function ServerErrorFallback({ Fallback, message, fallbackProps }) {
7
+ const error = new Error(message);
8
+ const reset = () => window.location.reload();
9
+ return React.createElement(Fallback, { ...fallbackProps, error, reset });
10
+ }
11
+ `;
12
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"rsc.d.ts","sourceRoot":"","sources":["../../../src/rsc/entries/rsc.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAw7B1D"}
1
+ {"version":3,"file":"rsc.d.ts","sourceRoot":"","sources":["../../../src/rsc/entries/rsc.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CA06B1D"}
@@ -25,6 +25,8 @@ export function generateRscEntry(ctx) {
25
25
  const debugLog = `// Debug disabled`;
26
26
  let code = `
27
27
  import React from 'react';
28
+ import ServerErrorFallback from '/.farm/rsc-entries/error-fallback.tsx';
29
+ import { registerAPIRouteShape } from '@farm.js/core/api/runtime';
28
30
  import {
29
31
  renderToReadableStream,
30
32
  `;
@@ -49,11 +51,24 @@ import {
49
51
  applyProductionMiddlewareHeaders,
50
52
  createProductionMiddlewareRunner,
51
53
  } from '@farm.js/core/middleware';
52
- import { invokeAPIRouteEndpoint, matchAPIRoute } from '@farm.js/core/api/runtime';
54
+ import {
55
+ getAllowedAPIRouteMethods,
56
+ invokeAPIRouteEndpoint,
57
+ isFarmAPIPathname,
58
+ matchAPIRouteAtBasePath,
59
+ } from '@farm.js/core/api/runtime';
53
60
  import { _runWithAfterRequest } from '@farm.js/core/after';
54
61
  import { _runWithCurrentRequest, searchParamsToObject } from '@farm.js/core/internal/production-runtime';
62
+ import { getFarmRedirectError, isFarmNotFoundError } from '@farm.js/core/internal/production-runtime';
63
+ import {
64
+ parseRoutePath,
65
+ matchFarmPageRoute,
66
+ compareRouteSpecificity,
67
+ getRoutePatternSpecificity,
68
+ } from '@farm.js/core/internal/production-runtime';
55
69
 
56
70
  const farmDeploymentId = ${JSON.stringify(ctx.deploymentId)};
71
+ const farmApiBasePath = ${JSON.stringify(ctx.apiBasePath ?? "/api")};
57
72
  `;
58
73
  if (ctx.actionsEnabled) {
59
74
  code += `import {
@@ -76,6 +91,9 @@ function debug(...args) {
76
91
  }
77
92
 
78
93
  function applyActionResponseHeaders(headers, request) {
94
+ // The same page URL serves HTML or Flight depending on Accept.
95
+ const vary = (headers.get('vary') || '').split(',').map(value => value.trim().toLowerCase());
96
+ if (!vary.includes('*') && !vary.includes('accept')) headers.append('vary', 'Accept');
79
97
  headers.set('x-farm-deployment-id', farmDeploymentId);
80
98
  const accept = request.headers.get('accept') || '';
81
99
  if (request.method === 'GET' && !accept.includes('text/x-component')) {
@@ -165,20 +183,30 @@ const routeDefinitionModules = collectRouteModuleEntries([${routeSourceRoots
165
183
  .map((root, index) => `{ name: ${JSON.stringify(root.name)}, base: ${JSON.stringify(root.sourceGlob)}, modules: routeDefinitionModules${index} }`)
166
184
  .join(", ")}]);
167
185
 
168
- const apiRouteMethods = ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'];
186
+ const apiRouteMethods = ['GET', 'HEAD', 'QUERY', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'];
169
187
  const apiRouteMap = new Map();
188
+ const apiRouteShapes = new Map();
170
189
 
171
- function registerApiEndpoint(routePath, filePath, method, endpoint) {
190
+ function registerApiEndpoint(routePath, filePath, method, endpoint, sourceIndex) {
172
191
  if (!routePath || typeof endpoint !== 'function') return;
192
+ const replacedPath = registerAPIRouteShape(apiRouteShapes, routePath, filePath, sourceIndex);
193
+ if (replacedPath) apiRouteMap.delete(replacedPath);
173
194
  const normalizedMethod = String(method || 'GET').toUpperCase();
174
195
  let route = apiRouteMap.get(routePath);
175
196
  if (!route) {
176
- route = { path: routePath, methods: [], handlers: {}, files: {} };
197
+ route = { path: routePath, methods: [], handlers: {}, files: {}, sources: {} };
177
198
  apiRouteMap.set(routePath, route);
178
199
  }
200
+ if (route.sources[normalizedMethod] === sourceIndex) {
201
+ throw new Error(
202
+ 'Duplicate API route for ' + normalizedMethod + ' ' + routePath + ': ' +
203
+ route.files[normalizedMethod] + ' conflicts with ' + filePath
204
+ );
205
+ }
179
206
  if (!route.methods.includes(normalizedMethod)) route.methods.push(normalizedMethod);
180
207
  route.handlers[normalizedMethod] = endpoint;
181
208
  route.files[normalizedMethod] = filePath;
209
+ route.sources[normalizedMethod] = sourceIndex;
182
210
  }
183
211
 
184
212
  function getProgrammaticApiRoutes(routeModule) {
@@ -206,7 +234,7 @@ function registerApiRouteSources(fileModules, definitionModules, sourceCount) {
206
234
  const routePath = relativePath.replace(/\\/route\\.[tj]sx?$/i, '') || '/api';
207
235
  for (const method of apiRouteMethods) {
208
236
  if (typeof routeModule?.[method] === 'function') {
209
- registerApiEndpoint(routePath, filePath, method, routeModule[method]);
237
+ registerApiEndpoint(routePath, filePath, method, routeModule[method], sourceIndex);
210
238
  }
211
239
  }
212
240
  }
@@ -216,13 +244,13 @@ function registerApiRouteSources(fileModules, definitionModules, sourceCount) {
216
244
  const { filePath, module: routeModule } = entry;
217
245
  for (const endpoint of Object.values(routeModule)) {
218
246
  if (typeof endpoint === 'function' && endpoint.__path) {
219
- registerApiEndpoint(endpoint.__path, filePath, endpoint.__method || 'GET', endpoint);
247
+ registerApiEndpoint(endpoint.__path, filePath, endpoint.__method || 'GET', endpoint, sourceIndex);
220
248
  }
221
249
  }
222
250
 
223
251
  for (const route of getProgrammaticApiRoutes(routeModule)) {
224
252
  for (const [method, endpoint] of Object.entries(route.methods || {})) {
225
- registerApiEndpoint(route.path, filePath, method, endpoint);
253
+ registerApiEndpoint(route.path, filePath, method, endpoint, sourceIndex);
226
254
  }
227
255
  }
228
256
  }
@@ -233,15 +261,19 @@ registerApiRouteSources(apiRouteModules, routeDefinitionModules, ${routeSourceRo
233
261
 
234
262
  async function handleAPIRequest(request) {
235
263
  const url = new URL(request.url);
236
- const match = matchAPIRoute(apiRouteMap, url.pathname);
264
+ const match = matchAPIRouteAtBasePath(apiRouteMap, url.pathname, farmApiBasePath);
237
265
  if (!match) return null;
238
266
 
239
267
  const method = request.method.toUpperCase();
240
- const endpoint = match.route.handlers[method];
268
+ const endpoint = match.route.handlers[method] ??
269
+ (method === 'HEAD' ? match.route.handlers.GET : undefined);
241
270
  if (!endpoint) {
242
271
  return new Response(JSON.stringify({ error: 'Method Not Allowed' }), {
243
272
  status: 405,
244
- headers: { 'Content-Type': 'application/json' },
273
+ headers: {
274
+ 'Allow': getAllowedAPIRouteMethods(match.route).join(', '),
275
+ 'Content-Type': 'application/json',
276
+ },
245
277
  });
246
278
  }
247
279
 
@@ -272,57 +304,6 @@ debug('Discovered errors:', Object.keys(errors));
272
304
  debug('Discovered middlewares:', Object.keys(middlewares));
273
305
  debug('Discovered API routes:', Array.from(apiRouteMap.keys()));
274
306
 
275
- /**
276
- * Convert loading/error file path to route pattern (segment the boundary applies to).
277
- * Inlined to avoid runtime dependency on plugin package; logic is simple and covered by e2e.
278
- */
279
- function boundaryPathToRoute(filePath, globVal, kind) {
280
- const re = kind === 'loading' ? /\\/loading\\.[tj]sx?$/i : /\\/error\\.[tj]sx?$/i;
281
- let route = filePath.replace(globVal, '').replace(re, '').replace(/\\\\/g, '/') || '/';
282
- route = route.replace(/\\[([^\\]]+)\\]/g, ':$1');
283
- return route;
284
- }
285
- function getMatchingLoading(pathname, globVal) {
286
- const normalized = pathname.replace(/\\/$/, '') || '/';
287
- const pathParts = normalized.split('/').filter(Boolean);
288
- let best = null, bestLength = -1;
289
- for (const filePath of Object.keys(loadings)) {
290
- const pattern = boundaryPathToRoute(filePath, globVal, 'loading');
291
- const patternParts = pattern === '/' ? [] : pattern.split('/').filter(Boolean);
292
- if (patternParts.length > pathParts.length) continue;
293
- let matches = true;
294
- for (let i = 0; i < patternParts.length; i++) {
295
- const p = patternParts[i], seg = pathParts[i];
296
- if (!seg || (p.startsWith(':') && p !== ':...') || p === ':...') continue;
297
- if (!p.startsWith(':') && p !== seg) { matches = false; break; }
298
- }
299
- if (matches && patternParts.length > bestLength && loadings[filePath]?.default) {
300
- best = loadings[filePath].default; bestLength = patternParts.length;
301
- }
302
- }
303
- return best;
304
- }
305
- function getMatchingError(pathname, globVal) {
306
- const normalized = pathname.replace(/\\/$/, '') || '/';
307
- const pathParts = normalized.split('/').filter(Boolean);
308
- let best = null, bestLength = -1;
309
- for (const filePath of Object.keys(errors)) {
310
- const pattern = boundaryPathToRoute(filePath, globVal, 'error');
311
- const patternParts = pattern === '/' ? [] : pattern.split('/').filter(Boolean);
312
- if (patternParts.length > pathParts.length) continue;
313
- let matches = true;
314
- for (let i = 0; i < patternParts.length; i++) {
315
- const p = patternParts[i], seg = pathParts[i];
316
- if (!seg || (p.startsWith(':') && p !== ':...') || p === ':...') continue;
317
- if (!p.startsWith(':') && p !== seg) { matches = false; break; }
318
- }
319
- if (matches && patternParts.length > bestLength && errors[filePath]?.default) {
320
- best = errors[filePath].default; bestLength = patternParts.length;
321
- }
322
- }
323
- return best;
324
- }
325
-
326
307
  /**
327
308
  * Convert middleware file path to route path
328
309
  * e.g., '/src/middleware.ts' -> '/'
@@ -345,7 +326,7 @@ async function executeMiddleware(request) {
345
326
  /**
346
327
  * Convert file path to route pattern
347
328
  * e.g., '/src/about/page.tsx' -> '/about'
348
- * e.g., '/src/blog/[slug]/page.tsx' -> '/blog/:slug'
329
+ * e.g., '/blog/[slug]/page.tsx' -> '/blog/[slug]'
349
330
  */
350
331
  function filePathToRoute(filePath) {
351
332
  let route = filePath
@@ -353,9 +334,6 @@ function filePathToRoute(filePath) {
353
334
  .replace(/\\/page\\.[tj]sx?$/, '')
354
335
  .replace(/\\/page$/, '') || '/';
355
336
 
356
- // Convert [param] to :param for matching
357
- route = route.replace(/\\[([^\\]]+)\\]/g, ':$1');
358
-
359
337
  return route;
360
338
  }
361
339
 
@@ -385,52 +363,14 @@ const RouteErrorBoundary = React.Component
385
363
  return children;
386
364
  };
387
365
 
388
- /**
389
- * Match a URL pathname to a route pattern
390
- * Supports dynamic segments like :id and catch-all like *
391
- */
392
- function matchPath(pattern, pathname) {
393
- const patternParts = pattern.split('/').filter(Boolean);
394
- const pathParts = pathname.split('/').filter(Boolean);
395
-
396
- // Special case for root
397
- if (pattern === '/' && pathname === '/') {
398
- return { params: {} };
399
- }
400
-
401
- if (patternParts.length !== pathParts.length) {
402
- // Check for catch-all
403
- const lastPattern = patternParts[patternParts.length - 1];
404
- if (!lastPattern?.startsWith(':...')) {
405
- return null;
406
- }
407
- }
408
-
409
- const params = {};
410
-
411
- for (let i = 0; i < patternParts.length; i++) {
412
- const patternPart = patternParts[i];
413
- const pathPart = pathParts[i];
414
-
415
- if (patternPart.startsWith(':...')) {
416
- // Catch-all segment
417
- const paramName = patternPart.slice(4);
418
- params[paramName] = pathParts.slice(i).join('/');
419
- return { params };
420
- }
421
-
422
- if (patternPart.startsWith(':')) {
423
- // Dynamic segment
424
- const paramName = patternPart.slice(1);
425
- params[paramName] = pathPart;
426
- } else if (patternPart !== pathPart) {
427
- // Static segment mismatch
428
- return null;
429
- }
430
- }
431
-
432
- return { params };
433
- }
366
+ // Parse and rank once per entry initialization, not on every request. Use the
367
+ // same validation, decoding and specificity rules as Farm's ordinary router.
368
+ const pageRoutes = Object.entries(pages).map(([filePath, module]) => ({
369
+ filePath,
370
+ module,
371
+ segments: parseRoutePath(filePath).segments,
372
+ specificity: getRoutePatternSpecificity(filePathToRoute(filePath)),
373
+ })).sort((left, right) => compareRouteSpecificity(left.specificity, right.specificity));
434
374
 
435
375
  /**
436
376
  * Simple file-based router
@@ -439,17 +379,16 @@ function matchPath(pattern, pathname) {
439
379
  function matchRoute(pathname) {
440
380
  const normalized = pathname.replace(/\\/$/, '') || '/';
441
381
 
442
- for (const filePath of Object.keys(pages)) {
443
- const pattern = filePathToRoute(filePath);
444
- const match = matchPath(pattern, normalized);
382
+ for (const { filePath, module, segments } of pageRoutes) {
383
+ const match = matchFarmPageRoute(normalized, segments);
445
384
 
446
- if (match) {
447
- debug('Matched route:', pattern, 'for path:', normalized);
385
+ if (match.matches) {
386
+ debug('Matched route:', filePath, 'for path:', normalized);
448
387
  return {
449
- Page: pages[filePath].default,
388
+ Page: module.default,
450
389
  pattern: filePath,
451
390
  params: match.params,
452
- pageMetadata: pages[filePath].metadata,
391
+ pageMetadata: module.metadata,
453
392
  };
454
393
  }
455
394
  }
@@ -468,14 +407,14 @@ function mergeDocumentMetadata(...sources) {
468
407
  }
469
408
 
470
409
  /**
471
- * Find every applicable layout module from root to the page directory.
472
- * Rendering still uses the nearest layout, while document metadata inherits
473
- * through the complete root -> nested layouts -> page chain.
410
+ * Find modules along the selected page's actual file ancestry, root to leaf.
411
+ * Keep route groups and catch-all folders; matching URL prefixes can pick
412
+ * boundaries from a sibling page that the router did not select.
474
413
  */
475
- function getLayoutModules(pageFilePath) {
414
+ function getRouteModules(pageFilePath, modules, kind) {
476
415
  const tryKeys = (...keys) => {
477
416
  for (const k of keys) {
478
- if (layouts[k]?.default) return layouts[k];
417
+ if (modules[k]?.default) return modules[k];
479
418
  }
480
419
  return null;
481
420
  };
@@ -487,26 +426,41 @@ function getLayoutModules(pageFilePath) {
487
426
  for (let depth = 0; depth <= parts.length; depth++) {
488
427
  const relativeDir = parts.slice(0, depth).join('/');
489
428
  const absoluteDir = relativeDir ? '/' + relativeDir : '';
490
- let matchedLayout = null;
429
+ let matchedModule = null;
491
430
  for (const ext of extensions) {
492
- matchedLayout = tryKeys(
493
- absoluteDir + '/layout.' + ext,
494
- relativeDir ? relativeDir + '/layout.' + ext : 'layout.' + ext,
431
+ matchedModule = tryKeys(
432
+ absoluteDir + '/' + kind + '.' + ext,
433
+ relativeDir ? relativeDir + '/' + kind + '.' + ext : kind + '.' + ext,
495
434
  );
496
- if (matchedLayout) break;
435
+ if (matchedModule) break;
497
436
  }
498
- if (matchedLayout && !matches.includes(matchedLayout)) matches.push(matchedLayout);
437
+ if (matchedModule) matches.push(matchedModule);
499
438
  }
500
439
 
501
440
  return matches;
502
441
  }
503
442
 
443
+ function getLayoutModules(pageFilePath) {
444
+ return getRouteModules(pageFilePath, layouts, 'layout');
445
+ }
446
+
447
+ function getMatchingBoundary(pageFilePath, modules, kind) {
448
+ // Before page selection (for example, middleware failure), only the root
449
+ // boundary applies. Do not guess a route from a partially processed URL.
450
+ const matches = getRouteModules(pageFilePath ?? '/page.tsx', modules, kind);
451
+ return matches.at(-1)?.default ?? null;
452
+ }
453
+
504
454
  /**
505
455
  * Main request handler - entry point for both dev and production (Nitro).
506
456
  * Exported as { fetch: handler } for the RSC/Nitro contract (see vite-plugin-rsc-deploy-example).
507
457
  */
508
458
  async function handleFarmRequest(request) {
509
459
  let url = new URL(request.url);
460
+ let matchedPage = null;
461
+ let errorData = new Map();
462
+ let errorContext = new Map();
463
+ let errorHeaders = new Headers();
510
464
  try {
511
465
  debug('Handling request:', request.method, url.pathname);
512
466
 
@@ -515,9 +469,9 @@ async function handleFarmRequest(request) {
515
469
  return createFarmDeploymentMismatchResponse(deploymentMismatch);
516
470
  }
517
471
 
518
- const initialApiMatch = matchAPIRoute(apiRouteMap, url.pathname);
472
+ const initialApiMatch = matchAPIRouteAtBasePath(apiRouteMap, url.pathname, farmApiBasePath);
519
473
  const isInitialApiRequest = Boolean(initialApiMatch) ||
520
- url.pathname === '/api' || url.pathname.startsWith('/api/');
474
+ isFarmAPIPathname(url.pathname, farmApiBasePath) || isFarmAPIPathname(url.pathname);
521
475
 
522
476
  ${ctx.actionsEnabled
523
477
  ? `if (request.method === 'POST' && !isInitialApiRequest) {
@@ -553,6 +507,9 @@ async function handleFarmRequest(request) {
553
507
  const middlewareData = Object.fromEntries(middlewareResult.data);
554
508
  const middlewareContext = middlewareResult.context;
555
509
  const middlewareHeaders = new Headers(middlewareResult.headers);
510
+ errorData = middlewareResult.data;
511
+ errorContext = middlewareContext;
512
+ errorHeaders = middlewareHeaders;
556
513
  if (middlewareResult.data.size || middlewareContext.size) {
557
514
  middlewareHeaders.set('cache-control', 'private, no-store');
558
515
  }
@@ -568,7 +525,7 @@ async function handleFarmRequest(request) {
568
525
  if (apiResponse) {
569
526
  return applyProductionMiddlewareHeaders(apiResponse, middlewareHeaders);
570
527
  }
571
- if (url.pathname === '/api' || url.pathname.startsWith('/api/')) {
528
+ if (isFarmAPIPathname(url.pathname, farmApiBasePath) || isFarmAPIPathname(url.pathname)) {
572
529
  return applyProductionMiddlewareHeaders(new Response(
573
530
  JSON.stringify({ error: 'API route not found', pathname: url.pathname }),
574
531
  { status: 404, headers: { 'Content-Type': 'application/json' } },
@@ -699,6 +656,7 @@ async function handleFarmRequest(request) {
699
656
  code += `
700
657
  // Match the URL to a page component
701
658
  const matched = matchRoute(url.pathname);
659
+ matchedPage = matched;
702
660
 
703
661
  if (!matched) {
704
662
  debug('404 - No route found for:', url.pathname);
@@ -707,8 +665,6 @@ async function handleFarmRequest(request) {
707
665
 
708
666
  const { Page, pattern, params, pageMetadata } = matched;
709
667
  const LayoutModules = getLayoutModules(pattern);
710
- const LayoutModule = LayoutModules[LayoutModules.length - 1];
711
- const Layout = LayoutModule?.default || (function PassThrough({ children }) { return children; });
712
668
  const metadata = mergeDocumentMetadata(
713
669
  ...LayoutModules.map((layoutModule) => layoutModule.metadata),
714
670
  pageMetadata,
@@ -731,23 +687,25 @@ async function handleFarmRequest(request) {
731
687
  // Helper to create elements without JSX
732
688
  const h = React.createElement;
733
689
 
734
- // Render page content - handle async components
690
+ // Render page content - only native async functions need direct awaiting.
691
+ // Synchronous components must retain React's hook dispatcher. Source text
692
+ // containing the word async does not change how a component executes.
735
693
  let pageContent;
736
- if (Page.constructor.name === 'AsyncFunction' || Page.toString().includes('async')) {
694
+ if (Page.constructor.name === 'AsyncFunction') {
737
695
  pageContent = await Page(pageProps);
738
696
  } else {
739
697
  pageContent = h(Page, pageProps);
740
698
  }
741
699
 
742
700
  // Route-level loading boundary: wrap in Suspense so async content shows fallback
743
- const LoadingComponent = getMatchingLoading(url.pathname, glob);
701
+ const LoadingComponent = getMatchingBoundary(pattern, loadings, 'loading');
744
702
  if (LoadingComponent) {
745
703
  const loadingFallback = h(LoadingComponent, { params, path: url.pathname });
746
704
  pageContent = h(React.Suspense, { fallback: loadingFallback }, pageContent);
747
705
  }
748
706
 
749
707
  // Route-level error boundary (Next.js error.tsx): catches render errors in this segment
750
- const ErrorComponent = getMatchingError(url.pathname, glob);
708
+ const ErrorComponent = getMatchingBoundary(pattern, errors, 'error');
751
709
  if (ErrorComponent) {
752
710
  pageContent = h(RouteErrorBoundary, {
753
711
  Fallback: ErrorComponent,
@@ -756,12 +714,16 @@ async function handleFarmRequest(request) {
756
714
  });
757
715
  }
758
716
 
759
- // Render layout
760
- let layoutContent;
761
- if (Layout.constructor.name === 'AsyncFunction' || Layout.toString().includes('async')) {
762
- layoutContent = await Layout({ children: pageContent });
763
- } else {
764
- layoutContent = h(Layout, null, pageContent);
717
+ // Render layouts inside out so every ancestor wraps its descendants.
718
+ let layoutContent = pageContent;
719
+ for (let index = LayoutModules.length - 1; index >= 0; index--) {
720
+ const Layout = LayoutModules[index].default;
721
+ const layoutProps = { params: pageProps.params, children: layoutContent };
722
+ if (Layout.constructor.name === 'AsyncFunction') {
723
+ layoutContent = await Layout(layoutProps);
724
+ } else {
725
+ layoutContent = h(Layout, layoutProps);
726
+ }
765
727
  }
766
728
 
767
729
  // Single wrapper so #root has exactly one child (avoids duplicate block / "two pages" in DOM).
@@ -861,6 +823,9 @@ async function handleFarmRequest(request) {
861
823
  )
862
824
  );
863
825
  } catch (err) {
826
+ const redirect = getFarmRedirectError(err);
827
+ if (redirect) return new Response(null, { status: redirect.status, headers: { location: redirect.url, 'cache-control': 'no-store' } });
828
+ if (isFarmNotFoundError(err)) return new Response('Not Found', { status: 404, headers: { 'cache-control': 'no-store' } });
864
829
  console.error('[RSC] Handler error:', err);
865
830
  if (request.method === 'POST') {
866
831
  if (request.signal.aborted) {
@@ -875,50 +840,71 @@ async function handleFarmRequest(request) {
875
840
  },
876
841
  });
877
842
  }
878
- // If route has error.tsx, render it (Next.js-style: SSR error goes to route error boundary)
843
+ // Render outside the failed page/layout tree, but retain request context.
879
844
  const pathname = url.pathname.replace(/\\/$/, '') || '/';
880
- const ErrorComponent = getMatchingError(pathname, glob);
845
+ const ErrorComponent = getMatchingBoundary(matchedPage?.pattern, errors, 'error');
881
846
  if (ErrorComponent) {
882
847
  try {
883
- const matched = matchRoute(pathname);
884
- const layoutPattern = matched ? matched.pattern : null;
885
- const LayoutModules = matched ? getLayoutModules(layoutPattern) : [];
886
- const Layout = LayoutModules[LayoutModules.length - 1]?.default;
887
- const LayoutComp = Layout || (function PassThrough({ children }) { return children; });
888
- const errParams = matched ? matched.params : {};
889
- const errSearchParams = searchParamsToObject(url.searchParams);
890
- const errorElement = h(ErrorComponent, {
891
- error: err,
892
- reset: () => {},
893
- params: errParams,
894
- path: pathname,
895
- searchParams: errSearchParams,
896
- });
897
- const layoutContent = LayoutComp.constructor.name === 'AsyncFunction' || LayoutComp.toString().includes('async')
898
- ? await LayoutComp({ children: errorElement })
899
- : h(LayoutComp, null, errorElement);
900
- const rootInner = h('div', { 'data-farm-root': 'true' }, layoutContent);
901
- const doc = h('html', null,
902
- h('head', null,
903
- h('meta', { charSet: 'utf-8' }),
904
- h('meta', { name: 'viewport', content: 'width=device-width, initial-scale=1' }),
905
- h('link', { rel: 'icon', href: 'data:,' }),
906
- h('title', null, 'Error'),
907
- h('link', { rel: 'stylesheet', href: globalsCssPath, as: 'style', precedence: 'default' })
908
- ),
909
- h('body', null, h('div', { id: 'root' }, rootInner))
848
+ return await _runWithCurrentRequest(request, () =>
849
+ _runWithMiddlewareData(errorData, () =>
850
+ _runWithMiddlewareContext(errorContext, async () => {
851
+ const h = React.createElement;
852
+ const errSearchParams = searchParamsToObject(url.searchParams);
853
+ const fallbackProps = {
854
+ params: matchedPage?.params || {},
855
+ path: pathname,
856
+ search: url.search,
857
+ searchParams: errSearchParams,
858
+ middlewareData: Object.fromEntries(errorData),
859
+ };
860
+ // Preserve the original failure in logs, not production payloads.
861
+ const message = ${ctx.development === true ? "true" : "false"} && err instanceof Error
862
+ ? err.message : 'Internal Server Error';
863
+ const isClientBoundary = ErrorComponent.$$typeof === Symbol.for('react.client.reference');
864
+ const errorElement = isClientBoundary
865
+ ? h(ServerErrorFallback, { Fallback: ErrorComponent, message, fallbackProps })
866
+ : h(ErrorComponent, { ...fallbackProps, error: new Error(message), reset: () => {} });
867
+ const rootInner = h('div', { 'data-farm-root': 'true' }, errorElement);
868
+ const payload = {
869
+ root: h('html', null,
870
+ h('head', null, h('meta', { charSet: 'utf-8' }), h('title', null, 'Error')),
871
+ h('body', null, h('div', { id: 'root' }, rootInner))),
872
+ rootContent: rootInner,
873
+ metadata: { title: 'Error' },
874
+ };
875
+ const rscStream = renderToReadableStream(payload);
876
+ const headers = new Headers(errorHeaders);
877
+ headers.set('cache-control', 'private, no-store');
878
+ headers.set('x-content-type-options', 'nosniff');
879
+ applyActionResponseHeaders(headers, request);
880
+ if ((request.headers.get('accept') || '').includes('text/x-component')) {
881
+ headers.set('content-type', 'text/x-component');
882
+ return new Response(rscStream, { status: 500, headers });
883
+ }
884
+ let ssr;
885
+ if (typeof import.meta.viteRsc?.loadModule === 'function') {
886
+ ssr = await import.meta.viteRsc.loadModule('ssr', 'index');
887
+ } else if (typeof globalThis.__VITE_RSC_LOAD_SSR__ === 'function') {
888
+ ssr = await globalThis.__VITE_RSC_LOAD_SSR__();
889
+ }
890
+ if (!ssr) {
891
+ void rscStream.cancel().catch(() => {});
892
+ throw new Error('SSR environment unavailable');
893
+ }
894
+ const html = await ssr.renderHTML({ payload, rscStream });
895
+ headers.set('content-type', 'text/html; charset=utf-8');
896
+ return new Response(html, { status: 500, headers });
897
+ })
898
+ )
910
899
  );
911
- const renderToString = (await import('react-dom/server')).renderToString;
912
- const html = '<!DOCTYPE html>' + renderToString(doc);
913
- return new Response(html, { status: 500, headers: { 'Content-Type': 'text/html; charset=utf-8' } });
914
- } catch (e) {
915
- console.error('[RSC] Error boundary render failed:', e);
900
+ } catch (fallbackError) {
901
+ console.error('[RSC] Error boundary render failed:', fallbackError);
916
902
  }
917
903
  }
918
904
  const message = 'Internal Server Error';
919
905
  return new Response(JSON.stringify({ error: true, url: request.url, status: 500, message }), {
920
906
  status: 500,
921
- headers: { 'Content-Type': 'application/json' },
907
+ headers: { 'Content-Type': 'application/json', 'Cache-Control': 'private, no-store' },
922
908
  });
923
909
  }
924
910
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ssr.d.ts","sourceRoot":"","sources":["../../../src/rsc/entries/ssr.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CA6O1D"}
1
+ {"version":3,"file":"ssr.d.ts","sourceRoot":"","sources":["../../../src/rsc/entries/ssr.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CA+M1D"}
@@ -40,43 +40,57 @@ const cssLinkTag = resolvedCssHref
40
40
  ? '<link rel="stylesheet" href="' + escapeHtmlAttribute(resolvedCssHref) + '" as="style" data-precedence="default">'
41
41
  : '';
42
42
 
43
- function injectIntoHeadStream(tag) {
43
+ // Work on bytes: chunk boundaries need not align with UTF-8 code points.
44
+ function injectBeforeStream(markerText, tag) {
44
45
  const encoder = new TextEncoder();
45
- const decoder = new TextDecoder();
46
- const marker = '</head>';
47
- let buffer = '';
46
+ const marker = encoder.encode(markerText);
47
+ const insertion = encoder.encode(tag);
48
+ let buffer = new Uint8Array(0);
48
49
  let injected = false;
49
50
 
50
51
  return new TransformStream({
51
52
  transform(chunk, controller) {
52
- const str = typeof chunk === "string" ? chunk : decoder.decode(chunk);
53
+ const bytes = typeof chunk === 'string' ? encoder.encode(chunk) : chunk;
53
54
  if (injected || !tag) {
54
- controller.enqueue(typeof chunk === "string" ? encoder.encode(str) : chunk);
55
+ controller.enqueue(bytes);
55
56
  return;
56
57
  }
57
58
 
58
- buffer += str;
59
- const index = buffer.indexOf(marker);
59
+ const pending = new Uint8Array(buffer.length + bytes.length);
60
+ pending.set(buffer);
61
+ pending.set(bytes, buffer.length);
62
+ let index = -1;
63
+ outer: for (let i = 0; i <= pending.length - marker.length; i++) {
64
+ for (let j = 0; j < marker.length; j++) {
65
+ if (pending[i + j] !== marker[j]) continue outer;
66
+ }
67
+ index = i;
68
+ break;
69
+ }
60
70
  if (index >= 0) {
61
- const before = buffer.slice(0, index);
62
- const after = buffer.slice(index + marker.length);
63
- controller.enqueue(encoder.encode(before + tag + marker + after));
64
- buffer = '';
71
+ if (index) controller.enqueue(pending.subarray(0, index));
72
+ controller.enqueue(insertion);
73
+ controller.enqueue(pending.subarray(index));
74
+ buffer = new Uint8Array(0);
65
75
  injected = true;
66
76
  return;
67
77
  }
68
78
 
69
- if (buffer.length > marker.length) {
70
- controller.enqueue(encoder.encode(buffer.slice(0, buffer.length - marker.length)));
71
- buffer = buffer.slice(-marker.length);
72
- }
79
+ // Retain only a possible marker prefix, never the whole response.
80
+ const emitLength = Math.max(0, pending.length - marker.length + 1);
81
+ if (emitLength) controller.enqueue(pending.subarray(0, emitLength));
82
+ buffer = pending.slice(emitLength);
73
83
  },
74
84
  flush(controller) {
75
- if (buffer) controller.enqueue(encoder.encode(buffer));
85
+ if (buffer.length) controller.enqueue(buffer);
76
86
  },
77
87
  });
78
88
  }
79
89
 
90
+ function injectIntoHeadStream(tag) {
91
+ return injectBeforeStream('</head>', tag);
92
+ }
93
+
80
94
  /** Collect Node stream (from renderToPipeableStream) into a single string. Supports Suspense. */
81
95
  function pipeableStreamToString(stream) {
82
96
  return new Promise((resolve, reject) => {
@@ -125,13 +139,19 @@ export async function renderHTML(firstArg, options = {}) {
125
139
  if (!bootstrap && PROD_BOOTSTRAP !== "__FARM_BOOTSTRAP_SCRIPT__") bootstrap = PROD_BOOTSTRAP;
126
140
 
127
141
  // Use renderToPipeableStream so Suspense streams: fallback (loading.tsx) first, then resolved content.
128
- const { pipe } = renderToPipeableStream(React.createElement(Root), {
129
- ...(bootstrap && { bootstrapScriptContent: bootstrap }),
130
- formState: options.formState,
131
- });
132
142
  const { PassThrough, Readable } = await import('stream');
133
143
  const passThrough = new PassThrough();
134
- pipe(passThrough);
144
+ // Do not commit an empty response when the shell cannot render. Reject back
145
+ // to the request handler so it can render error.tsx through RSC and SSR.
146
+ await new Promise((resolve, reject) => {
147
+ const { pipe } = renderToPipeableStream(React.createElement(Root), {
148
+ ...(bootstrap && { bootstrapScriptContent: bootstrap }),
149
+ formState: options.formState,
150
+ onShellReady() { pipe(passThrough); resolve(); },
151
+ onShellError: reject,
152
+ onError(error) { console.error('[RSC] SSR render error:', error); },
153
+ });
154
+ });
135
155
 
136
156
  const CLIENT_ENTRY_HREF = "__FARM_CLIENT_ENTRY_HREF__";
137
157
  const PLACEHOLDER_STR = "__FARM_CLIENT_ENTRY_HREF__";
@@ -141,34 +161,7 @@ export async function renderHTML(firstArg, options = {}) {
141
161
  if (hasPayload) {
142
162
  // Stream HTML to the client while preserving the RSC payload for hydration.
143
163
  debug('Streaming HTML (loading fallback then content)');
144
- const BODY_HTML_END = '</body></html>';
145
- const TAG_LEN = BODY_HTML_END.length;
146
- const encoder = new TextEncoder();
147
- let streamBuf = '';
148
- const streamingClientScriptInjector = new TransformStream({
149
- transform(chunk, controller) {
150
- const str = typeof chunk === "string" ? chunk : new TextDecoder().decode(chunk);
151
- if (!injectClientScriptInStream || !clientScriptTag) {
152
- controller.enqueue(typeof chunk === "string" ? encoder.encode(str) : chunk);
153
- return;
154
- }
155
- streamBuf += str;
156
- while (streamBuf.length > TAG_LEN) {
157
- const tail = streamBuf.slice(-TAG_LEN);
158
- if (tail === BODY_HTML_END) {
159
- const before = streamBuf.slice(0, streamBuf.length - TAG_LEN);
160
- controller.enqueue(encoder.encode(before + clientScriptTag + BODY_HTML_END));
161
- streamBuf = '';
162
- return;
163
- }
164
- controller.enqueue(encoder.encode(streamBuf.slice(0, streamBuf.length - TAG_LEN)));
165
- streamBuf = streamBuf.slice(-TAG_LEN);
166
- }
167
- },
168
- flush(controller) {
169
- if (streamBuf) controller.enqueue(encoder.encode(streamBuf));
170
- }
171
- });
164
+ const streamingClientScriptInjector = injectBeforeStream('</body></html>', clientScriptTag);
172
165
  const webStream = typeof Readable.toWeb === 'function'
173
166
  ? Readable.toWeb(passThrough)
174
167
  : new ReadableStream({
@@ -207,30 +200,7 @@ export async function renderHTML(firstArg, options = {}) {
207
200
  controller.close();
208
201
  }
209
202
  });
210
- let clientBuffer = "";
211
- let clientScriptInjected = false;
212
- const injectClientScriptStream = new TransformStream({
213
- transform(chunk, controller) {
214
- const str = typeof chunk === "string" ? chunk : new TextDecoder().decode(chunk);
215
- if (!injectClientScriptInStream || clientScriptInjected) {
216
- controller.enqueue(chunk);
217
- return;
218
- }
219
- clientBuffer += str;
220
- if (clientBuffer.includes("</body></html>")) {
221
- const href = (typeof CLIENT_ENTRY_HREF === "string" && CLIENT_ENTRY_HREF.length > 0 && CLIENT_ENTRY_HREF.indexOf(PLACEHOLDER_STR) < 0) ? CLIENT_ENTRY_HREF : "";
222
- const out = href ? clientBuffer.replace("</body></html>", '<script type="module" src="' + href + '"></script></body></html>') : clientBuffer;
223
- controller.enqueue(new TextEncoder().encode(out));
224
- clientBuffer = "";
225
- clientScriptInjected = true;
226
- }
227
- },
228
- flush(controller) {
229
- if (clientBuffer) {
230
- controller.enqueue(new TextEncoder().encode(clientBuffer));
231
- }
232
- }
233
- });
203
+ const injectClientScriptStream = injectBeforeStream('</body></html>', clientScriptTag);
234
204
 
235
205
  let out = streamFromHtml.pipeThrough(injectRSCPayload(s2));
236
206
  out = out.pipeThrough(injectClientScriptStream);
@@ -24,6 +24,8 @@
24
24
  import { type Plugin, type UserConfig } from "vite";
25
25
  import type { FarmRscPluginOptions, EntryContext } from "./types.js";
26
26
  import type { FarmServerActionsConfig } from "@farm.js/core/server-action-security";
27
+ import type { FarmAPIConfig } from "@farm.js/core/api";
28
+ import type { FarmServerConfig } from "@farm.js/core/internal/production-runtime";
27
29
  import type { FarmLayerEntry, ResolvedFarmLayer } from "@farm.js/core/server";
28
30
  export type { FarmRscPluginOptions, EntryContext };
29
31
  export { buildRscNitro, waitForRscManifest, waitForRscOutputs } from "./nitro-build.js";
@@ -36,6 +38,8 @@ export interface FarmRscConfig {
36
38
  layers?: readonly ResolvedFarmLayer[];
37
39
  outDir?: string;
38
40
  basePath?: string;
41
+ api?: FarmAPIConfig;
42
+ server?: FarmServerConfig;
39
43
  port?: number;
40
44
  experimental?: {
41
45
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rsc/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAA4B,KAAK,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AAE9E,OAAO,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAC;AACpF,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAiC9E,YAAY,EAAE,oBAAoB,EAAE,YAAY,EAAE,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAExF,YAAY,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAC1D,YAAY,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAGjE,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IACpC,MAAM,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE;QACb;;;WAGG;QACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;QAC3B;;;WAGG;QACH,aAAa,CAAC,EAAE,OAAO,CAAC;QACxB;;;;;;;WAOG;QACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;KAC7B,CAAC;IACF,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACxC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE;QACR,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,gHAAgH;IAChH,OAAO,CAAC,EAAE,GAAG,EAAE,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,GAAE,aAAkB,GAAG,UAAU,CAiDnE;AA8TD;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,OAAO,GAAE,oBAAyB,GAAG,MAAM,EAAE,CAkqC5E"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rsc/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAA4B,KAAK,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AAE9E,OAAO,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAC;AACpF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAClF,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAmD9E,YAAY,EAAE,oBAAoB,EAAE,YAAY,EAAE,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAExF,YAAY,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAC1D,YAAY,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAGjE,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IACpC,MAAM,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,aAAa,CAAC;IACpB,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE;QACb;;;WAGG;QACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;QAC3B;;;WAGG;QACH,aAAa,CAAC,EAAE,OAAO,CAAC;QACxB;;;;;;;WAOG;QACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;KAC7B,CAAC;IACF,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACxC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE;QACR,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,gHAAgH;IAChH,OAAO,CAAC,EAAE,GAAG,EAAE,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,GAAE,aAAkB,GAAG,UAAU,CAuDnE;AA8TD;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,OAAO,GAAE,oBAAyB,GAAG,MAAM,EAAE,CAksC5E"}
package/dist/rsc/index.js CHANGED
@@ -26,12 +26,14 @@ import { init as initModuleLexer, parse as parseModuleImports } from "es-module-
26
26
  import { farmEnvironmentFunctionsPlugin } from "@farm.js/core/environment/vite";
27
27
  import { generateRscEntry } from "./entries/rsc.js";
28
28
  import { generateSsrEntry } from "./entries/ssr.js";
29
+ import { generateErrorFallbackEntry } from "./entries/error-fallback.js";
29
30
  import { generateClientEntry, serverFnTransportErrorClientRuntime } from "./entries/client.js";
30
31
  import { transformFarmRouteActionClients } from "./route-action-transform.js";
31
32
  import { transformFarmServerFns } from "./server-fn-transform.js";
32
33
  import { transformAutomaticOptimizedBoundaries } from "./automatic-optimized-boundary.js";
33
34
  import { resolveRscBuildOutputPath } from "./build-paths.js";
34
35
  import { assertRscPackageCompatibility } from "./compatibility.js";
36
+ import { sendRscDevelopmentResponse } from "./dev-response.js";
35
37
  import fs from "fs/promises";
36
38
  import path from "path";
37
39
  import { devServableFileExists } from "../dev-static.js";
@@ -40,10 +42,13 @@ import { createRequire } from "node:module";
40
42
  // Use API and middleware plugins from @farm.js/core (require so CJS build resolves when ESM .mjs is missing)
41
43
  const require_ = createRequire(import.meta.url);
42
44
  const { farmApiPlugin, farmMiddlewarePlugin } = require_("@farm.js/core");
43
- const { resolveServerActionsConfig } = require_("@farm.js/core/server-action-security");
45
+ const { resolveServerActionsConfig, validateServerActionRequest, createServerActionRequestErrorResponse, } = require_("@farm.js/core/server-action-security");
44
46
  const { normalizeFarmDeploymentId } = require_("@farm.js/core/deployment");
45
47
  const { getFarmLayerAliases, getFarmSourceRoots, resolveFarmLayers } = require_("@farm.js/core/server");
46
- const { searchParamsToObject } = require_("@farm.js/core/internal/production-runtime");
48
+ const { searchParamsToObject, readNodeRequestBody, resolveFarmServerConfig, createFarmRequestBodyErrorResponse, } = require_("@farm.js/core/internal/production-runtime");
49
+ const { isFarmAPIPathname } = require_("@farm.js/core/api/runtime");
50
+ const { resolveFarmAPIConfig, resolveFarmAPIServerBasePath } = require_("@farm.js/core/api");
51
+ const { getResolvedEnv } = require_("@farm.js/core/env");
47
52
  export { buildRscNitro, waitForRscManifest, waitForRscOutputs } from "./nitro-build.js";
48
53
  import { registerRscNitroRuntimePackage } from "./nitro-build.js";
49
54
  export { default as nitro } from "./vite-plugin-nitro.js";
@@ -66,11 +71,13 @@ export function defineConfig(config = {}) {
66
71
  layers: config.layers,
67
72
  outDir: config.outDir ?? "dist",
68
73
  basePath: config.basePath ?? "/",
74
+ api: config.api,
69
75
  serverActions: config.serverActions,
70
76
  deploymentId: config.deploymentId,
71
77
  generateBuildId: config.generateBuildId,
72
78
  // Vite server configuration
73
79
  server: {
80
+ ...config.server,
74
81
  port,
75
82
  strictPort: false,
76
83
  },
@@ -83,7 +90,11 @@ export function defineConfig(config = {}) {
83
90
  },
84
91
  plugins: [
85
92
  farmMiddlewarePlugin({ srcDir: config.srcDir ?? "src", debug }),
86
- farmApiPlugin({ srcDir: config.srcDir ?? "src", debug }),
93
+ farmApiPlugin({
94
+ srcDir: config.srcDir ?? "src",
95
+ debug,
96
+ bodySizeLimit: config.server?.bodySizeLimit,
97
+ }),
87
98
  farmRsc({
88
99
  debug,
89
100
  encryptActions: config.encryptActions,
@@ -375,6 +386,7 @@ export default function farmRsc(options = {}) {
375
386
  let rscEnabled = false;
376
387
  let actionsEnabled = false;
377
388
  let optimizedBoundaryEnabled = false;
389
+ let bodySizeLimit = resolveFarmServerConfig(undefined).bodySizeLimit;
378
390
  // Context passed to entry generators
379
391
  let entryContext;
380
392
  /** Set in config when RSC enabled; used by build plugin to run Nitro after client writeBundle. */
@@ -474,6 +486,7 @@ export default function farmRsc(options = {}) {
474
486
  // Check if user enabled RSC in their config
475
487
  rscEnabled = c.experimental?.serverComponents === true;
476
488
  actionsEnabled = c.experimental?.serverActions === true;
489
+ bodySizeLimit = resolveFarmServerConfig(c.server).bodySizeLimit;
477
490
  optimizedBoundaryEnabled = c.experimental?.optimizedBoundary === true;
478
491
  logInfo(`RSC enabled: ${rscEnabled}`);
479
492
  logInfo(`Actions enabled: ${actionsEnabled}`);
@@ -498,6 +511,11 @@ export default function farmRsc(options = {}) {
498
511
  Object.assign(c, layerResolution.config);
499
512
  }
500
513
  // Read user's directory configuration
514
+ const api = await resolveFarmAPIConfig(c.api, {
515
+ root,
516
+ mode: env.command === "build" ? "production" : "development",
517
+ env: getResolvedEnv(),
518
+ });
501
519
  const srcDir = c.srcDir ?? "src";
502
520
  const outDir = c.outDir ?? "dist";
503
521
  const deploymentId = normalizeFarmDeploymentId(options.deploymentId ||
@@ -538,6 +556,7 @@ export default function farmRsc(options = {}) {
538
556
  srcDir,
539
557
  outDir,
540
558
  basePath: c.basePath ?? "/",
559
+ apiBasePath: resolveFarmAPIServerBasePath(api),
541
560
  routesDir: options.routesDir,
542
561
  globalCssPath,
543
562
  routeRoots,
@@ -549,6 +568,7 @@ export default function farmRsc(options = {}) {
549
568
  }),
550
569
  deploymentId,
551
570
  debug,
571
+ development: env.command === "serve",
552
572
  };
553
573
  logInfo(`srcDir: ${entryContext.srcDir}, outDir: ${entryContext.outDir}`);
554
574
  // Write real entry files so @vitejs/plugin-rsc can use file-based entries.
@@ -559,6 +579,7 @@ export default function farmRsc(options = {}) {
559
579
  await fs.writeFile(entryRscPath, generateRscEntry(entryContext));
560
580
  await fs.writeFile(entrySsrPath, generateSsrEntry(entryContext));
561
581
  await fs.writeFile(entryClientPath, generateClientEntry(entryContext));
582
+ await fs.writeFile(path.join(entriesDir, "error-fallback.tsx"), generateErrorFallbackEntry());
562
583
  logInfo(`Wrote RSC entries to ${entriesDir}`);
563
584
  // User must add rsc({ entries: { rsc, ssr, client } }) to config.plugins so the RSC plugin
564
585
  // runs for every environment and client build can resolve virtual:vite-rsc/client-references.
@@ -585,6 +606,9 @@ export default function farmRsc(options = {}) {
585
606
  return {
586
607
  appType: "custom",
587
608
  builder: { sharedConfigBuild: true },
609
+ define: {
610
+ __FARM_API_BASE_URL__: JSON.stringify(api.baseURL),
611
+ },
588
612
  ssr: {
589
613
  external: [
590
614
  "react",
@@ -1000,19 +1024,26 @@ if (document.readyState === 'loading') {
1000
1024
  globalThis.__FARM_VITE_RSC_LOAD_BOOTSTRAP__ = async () => bootstrapPrefix +
1001
1025
  `import("/@react-refresh").then(m=>{m.default.injectIntoGlobalHook(window);window.$RefreshReg$=()=>{};window.$RefreshSig$=()=>type=>type;window.__vite_plugin_react_preamble_installed__=true;return import("/@vite/client");}).then(()=>import(${JSON.stringify(clientEntryUrl)}));`;
1002
1026
  const base = `http://${req.headers.host || "localhost:3000"}`;
1027
+ const requestUrl = new URL(url, base);
1028
+ const isAction = actionsEnabled &&
1029
+ method === "POST" &&
1030
+ !isFarmAPIPathname(requestUrl.pathname, entryContext.apiBasePath) &&
1031
+ !isFarmAPIPathname(requestUrl.pathname);
1032
+ if (isAction) {
1033
+ validateServerActionRequest(new Request(requestUrl, {
1034
+ method,
1035
+ headers: req.headers,
1036
+ }), entryContext.serverActions);
1037
+ }
1003
1038
  let body;
1004
- if (method === "POST") {
1005
- const chunks = [];
1006
- for await (const chunk of req)
1007
- chunks.push(chunk);
1008
- body = Buffer.concat(chunks);
1009
- // Keep as buffer so request.formData() / request.text() in RSC handler work (multipart must not be UTF-8 decoded)
1039
+ if (method !== "GET" && method !== "HEAD") {
1040
+ body = await readNodeRequestBody(req, isAction ? entryContext.serverActions.bodySizeLimit : bodySizeLimit);
1010
1041
  }
1011
- const request = new Request(new URL(url, base), {
1042
+ const request = new Request(requestUrl, {
1012
1043
  method,
1013
1044
  headers: req.headers,
1014
- body: method === "POST" && body && body.length > 0
1015
- ? body
1045
+ body: body && body.length > 0
1046
+ ? body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength)
1016
1047
  : undefined,
1017
1048
  });
1018
1049
  // Load the RSC entry in the "rsc" environment so @vitejs/plugin-rsc transforms run with this.environment.name === "rsc".
@@ -1046,32 +1077,24 @@ if (document.readyState === 'loading') {
1046
1077
  if (!rscEntry?.default?.fetch)
1047
1078
  throw new Error("[Farm.js] Could not load RSC entry in rsc environment");
1048
1079
  const response = await rscEntry.default.fetch(request);
1049
- res.statusCode = response.status;
1050
- response.headers.forEach((value, key) => {
1051
- if (key.toLowerCase() !== "transfer-encoding")
1052
- res.setHeader(key, value);
1053
- });
1054
- if (response.body) {
1055
- const reader = response.body.getReader();
1056
- const pump = async () => {
1057
- while (true) {
1058
- const { done, value } = await reader.read();
1059
- if (done)
1060
- break;
1061
- res.write(Buffer.from(value));
1062
- }
1063
- res.end();
1064
- };
1065
- await pump();
1066
- }
1067
- else {
1068
- res.end();
1069
- }
1080
+ await sendRscDevelopmentResponse(res, response);
1070
1081
  const duration = Date.now() - startTime;
1071
1082
  logResponse(method, pathname, response.status, duration);
1072
1083
  return;
1073
1084
  }
1074
1085
  catch (rscError) {
1086
+ const rejection = createFarmRequestBodyErrorResponse(rscError) ??
1087
+ createServerActionRequestErrorResponse(rscError);
1088
+ if (rejection && !res.headersSent && !res.writableEnded && !res.destroyed) {
1089
+ await sendRscDevelopmentResponse(res, rejection);
1090
+ return;
1091
+ }
1092
+ if (res.headersSent || res.writableEnded || res.destroyed) {
1093
+ console.error("[Farm.js] RSC dev response error:", rscError);
1094
+ if (!res.destroyed)
1095
+ res.destroy(rscError instanceof Error ? rscError : new Error(String(rscError)));
1096
+ return;
1097
+ }
1075
1098
  // RSC pipeline failed; fall back to legacy SSR for GET only
1076
1099
  if (method !== "GET") {
1077
1100
  const duration = Date.now() - startTime;
@@ -1 +1 @@
1
- {"version":3,"file":"nitro-build.d.ts","sourceRoot":"","sources":["../../src/rsc/nitro-build.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAwBH;;;GAGG;AACH,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,IAAI,CAKN;AAwFD;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACxD,OAAO,CAAC,IAAI,CAAC,CA0Bf;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACxD,OAAO,CAAC,IAAI,CAAC,CAgCf;AA+DD,MAAM,MAAM,oBAAoB,GAAG;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,wBAAsB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CA8MhF"}
1
+ {"version":3,"file":"nitro-build.d.ts","sourceRoot":"","sources":["../../src/rsc/nitro-build.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAwBH;;;GAGG;AACH,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,IAAI,CAKN;AAwFD;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACxD,OAAO,CAAC,IAAI,CAAC,CA0Bf;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACxD,OAAO,CAAC,IAAI,CAAC,CAgCf;AA0ED,MAAM,MAAM,oBAAoB,GAAG;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,wBAAsB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CA8MhF"}
@@ -219,9 +219,20 @@ function createRscRequest(event) {
219
219
  return new Request(event.req, { signal: controller.signal });
220
220
  }
221
221
 
222
- export default defineEventHandler((event) => handler(createRscRequest(event), {
223
- waitUntil: (promise) => event.waitUntil(promise),
224
- }));
222
+ export default defineEventHandler(async (event) => {
223
+ const response = await handler(createRscRequest(event), {
224
+ waitUntil: (promise) => event.waitUntil(promise),
225
+ });
226
+ // H3's prepared headers override Response headers. Keep both the runtime's
227
+ // fields (e.g. Accept-Encoding from static middleware) and the app's fields.
228
+ const vary = [event.res.headers.get('vary'), response.headers.get('vary')]
229
+ .filter(Boolean).join(',').split(',').map(value => value.trim()).filter(Boolean);
230
+ if (vary.length) {
231
+ const fields = new Map(vary.map(value => [value.toLowerCase(), value]));
232
+ event.res.headers.set('vary', fields.has('*') ? '*' : [...fields.values()].join(', '));
233
+ }
234
+ return response;
235
+ });
225
236
  `.trim();
226
237
  const entryPath = path.join(buildDir, "rsc-entry.mjs");
227
238
  mkdirSync(buildDir, { recursive: true });
@@ -54,6 +54,8 @@ export interface EntryContext {
54
54
  outDir: string;
55
55
  /** Base URL path, e.g. '/' */
56
56
  basePath: string;
57
+ /** Resolved same-origin mount for canonical /api routes. */
58
+ apiBasePath?: string;
57
59
  /** Route files subdirectory within srcDir */
58
60
  routesDir?: string;
59
61
  /** Root-relative Vite URL for an existing global stylesheet. */
@@ -72,6 +74,8 @@ export interface EntryContext {
72
74
  deploymentId: string;
73
75
  /** Whether debug mode is enabled */
74
76
  debug: boolean;
77
+ /** True only for the Vite serve command, never for a production build. */
78
+ development?: boolean;
75
79
  }
76
80
  /**
77
81
  * Matched route information from the file-based router.
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/rsc/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,uBAAuB,EACvB,+BAA+B,EAChC,MAAM,sCAAsC,CAAC;AAE9C;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,yDAAyD;IACzD,aAAa,CAAC,EAAE,uBAAuB,CAAC;IAExC,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;OAIG;IACH,OAAO,CAAC,EAAE;QACR,wCAAwC;QACxC,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,wCAAwC;QACxC,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,2CAA2C;QAC3C,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IAEF;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;IAEf,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;IAEf,8BAA8B;IAC9B,QAAQ,EAAE,MAAM,CAAC;IAEjB,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,uFAAuF;IACvF,UAAU,CAAC,EAAE,KAAK,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;IAEH,yCAAyC;IACzC,cAAc,EAAE,OAAO,CAAC;IAExB,wDAAwD;IACxD,aAAa,EAAE,+BAA+B,CAAC;IAE/C,8EAA8E;IAC9E,YAAY,EAAE,MAAM,CAAC;IAErB,oCAAoC;IACpC,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,0CAA0C;IAC1C,IAAI,EAAE,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;IAE/B,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC;IAEhB,yDAAyD;IACzD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,0DAA0D;IAC1D,IAAI,EAAE,KAAK,CAAC,YAAY,CAAC;IAEzB,wFAAwF;IACxF,WAAW,CAAC,EAAE,KAAK,CAAC,YAAY,CAAC;IAEjC,yEAAyE;IACzE,QAAQ,CAAC,EAAE;QACT,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;IAEF,0DAA0D;IAC1D,WAAW,CAAC,EAAE;QACZ,EAAE,EAAE,OAAO,CAAC;QACZ,IAAI,EAAE,OAAO,CAAC;KACf,CAAC;IAEF,6CAA6C;IAC7C,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,UAAU;QAClB,QAAQ,CAAC,OAAO,EAAE;YAChB,qCAAqC;YACrC,OAAO,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;YAEhC,6CAA6C;YAC7C,UAAU,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;YAEhE,kDAAkD;YAClD,0BAA0B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;SAC5D,CAAC;KACH;CACF"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/rsc/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,uBAAuB,EACvB,+BAA+B,EAChC,MAAM,sCAAsC,CAAC;AAE9C;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,yDAAyD;IACzD,aAAa,CAAC,EAAE,uBAAuB,CAAC;IAExC,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;OAIG;IACH,OAAO,CAAC,EAAE;QACR,wCAAwC;QACxC,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,wCAAwC;QACxC,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,2CAA2C;QAC3C,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IAEF;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;IAEf,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;IAEf,8BAA8B;IAC9B,QAAQ,EAAE,MAAM,CAAC;IAEjB,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,uFAAuF;IACvF,UAAU,CAAC,EAAE,KAAK,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;IAEH,yCAAyC;IACzC,cAAc,EAAE,OAAO,CAAC;IAExB,wDAAwD;IACxD,aAAa,EAAE,+BAA+B,CAAC;IAE/C,8EAA8E;IAC9E,YAAY,EAAE,MAAM,CAAC;IAErB,oCAAoC;IACpC,KAAK,EAAE,OAAO,CAAC;IAEf,0EAA0E;IAC1E,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,0CAA0C;IAC1C,IAAI,EAAE,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;IAE/B,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC;IAEhB,yDAAyD;IACzD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,0DAA0D;IAC1D,IAAI,EAAE,KAAK,CAAC,YAAY,CAAC;IAEzB,wFAAwF;IACxF,WAAW,CAAC,EAAE,KAAK,CAAC,YAAY,CAAC;IAEjC,yEAAyE;IACzE,QAAQ,CAAC,EAAE;QACT,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;IAEF,0DAA0D;IAC1D,WAAW,CAAC,EAAE;QACZ,EAAE,EAAE,OAAO,CAAC;QACZ,IAAI,EAAE,OAAO,CAAC;KACf,CAAC;IAEF,6CAA6C;IAC7C,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,UAAU;QAClB,QAAQ,CAAC,OAAO,EAAE;YAChB,qCAAqC;YACrC,OAAO,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;YAEhC,6CAA6C;YAC7C,UAAU,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;YAEhE,kDAAkD;YAClD,0BAA0B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;SAC5D,CAAC;KACH;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farm.js/plugin",
3
- "version": "0.1.0-beta.93",
3
+ "version": "0.1.0-beta.94",
4
4
  "description": "Official plugins for Farm.js framework",
5
5
  "license": "MIT",
6
6
  "author": "Farm.js Team",
@@ -61,7 +61,7 @@
61
61
  "nitro": "3.0.1-alpha.0",
62
62
  "picocolors": "^1.0.0",
63
63
  "rsc-html-stream": "^0.0.4",
64
- "@farm.js/core": "0.1.0-beta.93"
64
+ "@farm.js/core": "0.1.0-beta.94"
65
65
  },
66
66
  "devDependencies": {
67
67
  "@types/node": "^20.19.21",
@@ -82,7 +82,7 @@
82
82
  "react-dom": "19.2.8",
83
83
  "react-server-dom-webpack": "19.2.8",
84
84
  "vite": ">=6.0.0",
85
- "@farm.js/core": "0.1.0-beta.93"
85
+ "@farm.js/core": "0.1.0-beta.94"
86
86
  },
87
87
  "peerDependenciesMeta": {
88
88
  "@vitejs/plugin-rsc": {