@timber-js/app 0.1.10 → 0.1.11
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/_chunks/request-context-BzES06i1.js.map +1 -1
- package/dist/_chunks/use-query-states-wEXY2JQB.js +109 -0
- package/dist/_chunks/use-query-states-wEXY2JQB.js.map +1 -0
- package/dist/client/index.js +1 -83
- package/dist/client/index.js.map +1 -1
- package/dist/client/use-query-states.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/plugins/routing.d.ts.map +1 -1
- package/dist/plugins/server-bundle.d.ts.map +1 -1
- package/dist/routing/status-file-lint.d.ts.map +1 -1
- package/dist/search-params/create.d.ts.map +1 -1
- package/dist/search-params/index.js +13 -4
- package/dist/search-params/index.js.map +1 -1
- package/dist/server/fallback-error.d.ts +28 -0
- package/dist/server/fallback-error.d.ts.map +1 -0
- package/dist/server/html-injectors.d.ts.map +1 -1
- package/dist/server/index.js +4 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/pipeline.d.ts +12 -0
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/route-matcher.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/slot-resolver.d.ts +1 -1
- package/dist/server/slot-resolver.d.ts.map +1 -1
- package/dist/server/ssr-entry.d.ts.map +1 -1
- package/dist/server/tree-builder.d.ts.map +1 -1
- package/package.json +23 -23
- package/src/client/browser-entry.ts +1 -1
- package/src/client/use-query-states.ts +13 -1
- package/src/index.ts +16 -16
- package/src/plugins/dev-server.ts +3 -1
- package/src/plugins/entries.ts +2 -1
- package/src/plugins/routing.ts +5 -4
- package/src/plugins/server-bundle.ts +15 -6
- package/src/routing/status-file-lint.ts +1 -3
- package/src/search-params/create.ts +15 -8
- package/src/server/error-formatter.ts +12 -0
- package/src/server/fallback-error.ts +159 -0
- package/src/server/html-injectors.ts +9 -4
- package/src/server/pipeline.ts +24 -0
- package/src/server/request-context.ts +0 -1
- package/src/server/route-matcher.ts +1 -4
- package/src/server/rsc-entry/index.ts +88 -36
- package/src/server/slot-resolver.ts +38 -5
- package/src/server/ssr-entry.ts +4 -1
- package/src/server/tree-builder.ts +4 -1
- package/src/shims/server-only-noop.js +1 -0
- package/dist/_chunks/registry-BfPM41ri.js +0 -20
- package/dist/_chunks/registry-BfPM41ri.js.map +0 -1
|
@@ -53,7 +53,6 @@ export const requestContextAls = new AsyncLocalStorage<RequestContextStore>();
|
|
|
53
53
|
// the ALS context persists for the entire request lifecycle including
|
|
54
54
|
// async stream consumption by React's renderToReadableStream.
|
|
55
55
|
|
|
56
|
-
|
|
57
56
|
// ─── Cookie Signing Secrets ──────────────────────────────────────────────
|
|
58
57
|
|
|
59
58
|
/**
|
|
@@ -10,10 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import type { RouteMatch } from './pipeline.js';
|
|
12
12
|
import type { MiddlewareFn } from './middleware-runner.js';
|
|
13
|
-
import {
|
|
14
|
-
METADATA_ROUTE_CONVENTIONS,
|
|
15
|
-
type MetadataRouteType,
|
|
16
|
-
} from './metadata-routes.js';
|
|
13
|
+
import { METADATA_ROUTE_CONVENTIONS, type MetadataRouteType } from './metadata-routes.js';
|
|
17
14
|
|
|
18
15
|
// ─── Manifest Types ───────────────────────────────────────────────────────
|
|
19
16
|
// The virtual module manifest has a slightly different shape than SegmentNode:
|
|
@@ -24,49 +24,49 @@ import buildManifest from 'virtual:timber-build-manifest';
|
|
|
24
24
|
|
|
25
25
|
import { renderToReadableStream } from '@vitejs/plugin-rsc/rsc';
|
|
26
26
|
|
|
27
|
-
import
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import type {
|
|
31
|
-
import { logRenderError } from '#/server/logger.js';
|
|
32
|
-
import { resolveLogMode } from '#/server/dev-logger.js';
|
|
33
|
-
import { createRouteMatcher, createMetadataRouteMatcher } from '#/server/route-matcher.js';
|
|
34
|
-
import type { ManifestSegmentNode } from '#/server/route-matcher.js';
|
|
35
|
-
import { DenySignal, RedirectSignal, RenderError, SsrStreamError } from '#/server/primitives.js';
|
|
36
|
-
import { buildClientScripts } from '#/server/html-injectors.js';
|
|
37
|
-
import type { ClientBootstrapConfig } from '#/server/html-injectors.js';
|
|
38
|
-
import { renderDenyPage, renderDenyPageAsRsc } from '#/server/deny-renderer.js';
|
|
39
|
-
import type { LayoutEntry } from '#/server/deny-renderer.js';
|
|
27
|
+
import type { FormRerender } from '#/server/action-handler.js';
|
|
28
|
+
import { handleActionRequest, isActionRequest } from '#/server/action-handler.js';
|
|
29
|
+
import type { BodyLimitsConfig } from '#/server/body-limits.js';
|
|
30
|
+
import type { BuildManifest } from '#/server/build-manifest.js';
|
|
40
31
|
import {
|
|
41
|
-
collectRouteCss,
|
|
42
|
-
collectRouteFonts,
|
|
43
|
-
collectRouteModulepreloads,
|
|
44
32
|
buildCssLinkTags,
|
|
45
33
|
buildFontPreloadTags,
|
|
46
34
|
buildModulepreloadTags,
|
|
35
|
+
collectRouteCss,
|
|
36
|
+
collectRouteFonts,
|
|
37
|
+
collectRouteModulepreloads,
|
|
47
38
|
} from '#/server/build-manifest.js';
|
|
48
|
-
import type {
|
|
49
|
-
import {
|
|
39
|
+
import type { LayoutEntry } from '#/server/deny-renderer.js';
|
|
40
|
+
import { renderDenyPage, renderDenyPageAsRsc } from '#/server/deny-renderer.js';
|
|
41
|
+
import { resolveLogMode } from '#/server/dev-logger.js';
|
|
50
42
|
import { sendEarlyHints103 } from '#/server/early-hints-sender.js';
|
|
51
|
-
import
|
|
52
|
-
import { buildRouteElement, RouteSignalWithContext } from '#/server/route-element-builder.js';
|
|
53
|
-
import { isActionRequest, handleActionRequest } from '#/server/action-handler.js';
|
|
54
|
-
import type { FormRerender } from '#/server/action-handler.js';
|
|
55
|
-
import type { BodyLimitsConfig } from '#/server/body-limits.js';
|
|
43
|
+
import { collectEarlyHintHeaders } from '#/server/early-hints.js';
|
|
56
44
|
import { runWithFormFlash } from '#/server/form-flash.js';
|
|
45
|
+
import type { ClientBootstrapConfig } from '#/server/html-injectors.js';
|
|
46
|
+
import { buildClientScripts } from '#/server/html-injectors.js';
|
|
47
|
+
import { logRenderError } from '#/server/logger.js';
|
|
48
|
+
import type { InterceptionContext, PipelineConfig, RouteMatch } from '#/server/pipeline.js';
|
|
49
|
+
import { createPipeline } from '#/server/pipeline.js';
|
|
50
|
+
import { DenySignal, RedirectSignal, RenderError, SsrStreamError } from '#/server/primitives.js';
|
|
51
|
+
import { buildRouteElement, RouteSignalWithContext } from '#/server/route-element-builder.js';
|
|
52
|
+
import type { ManifestSegmentNode } from '#/server/route-matcher.js';
|
|
53
|
+
import { createMetadataRouteMatcher, createRouteMatcher } from '#/server/route-matcher.js';
|
|
54
|
+
import type { NavContext } from '#/server/ssr-entry.js';
|
|
55
|
+
import { initDevTracing } from '#/server/tracing.js';
|
|
57
56
|
|
|
57
|
+
import { renderFallbackError as renderFallback } from '#/server/fallback-error.js';
|
|
58
|
+
import { handleApiRoute } from './api-handler.js';
|
|
59
|
+
import { renderErrorPage, renderNoMatchPage } from './error-renderer.js';
|
|
58
60
|
import {
|
|
59
|
-
createDebugChannelSink,
|
|
60
|
-
buildSegmentInfo,
|
|
61
|
-
isRscPayloadRequest,
|
|
62
61
|
buildRedirectResponse,
|
|
62
|
+
buildSegmentInfo,
|
|
63
|
+
createDebugChannelSink,
|
|
63
64
|
escapeHtml,
|
|
64
65
|
isAbortError,
|
|
66
|
+
isRscPayloadRequest,
|
|
65
67
|
parseCookiesFromHeader,
|
|
66
68
|
RSC_CONTENT_TYPE,
|
|
67
69
|
} from './helpers.js';
|
|
68
|
-
import { handleApiRoute } from './api-handler.js';
|
|
69
|
-
import { renderErrorPage, renderNoMatchPage } from './error-renderer.js';
|
|
70
70
|
import { callSsr } from './ssr-bridge.js';
|
|
71
71
|
|
|
72
72
|
// Dev-only pipeline error handler, set by the dev server after import.
|
|
@@ -183,6 +183,8 @@ async function createRequestHandler(manifest: typeof routeManifest, runtimeConfi
|
|
|
183
183
|
if (_devPipelineErrorHandler) _devPipelineErrorHandler(error, phase);
|
|
184
184
|
}
|
|
185
185
|
: undefined,
|
|
186
|
+
renderFallbackError: (error, req, responseHeaders) =>
|
|
187
|
+
renderFallback(error, req, responseHeaders, isDev, manifest.root, clientBootstrap),
|
|
186
188
|
};
|
|
187
189
|
|
|
188
190
|
const pipeline = createPipeline(pipelineConfig);
|
|
@@ -409,6 +411,30 @@ async function renderRoute(
|
|
|
409
411
|
status: error.status,
|
|
410
412
|
});
|
|
411
413
|
}
|
|
414
|
+
// Dev diagnostic: detect "Invalid hook call" errors which indicate
|
|
415
|
+
// a 'use client' component is being executed during RSC rendering
|
|
416
|
+
// instead of being serialized as a client reference. This happens when
|
|
417
|
+
// the RSC plugin's transform doesn't detect the directive — e.g., the
|
|
418
|
+
// directive isn't at the very top of the file, or the component is
|
|
419
|
+
// re-exported through a barrel file without 'use client'.
|
|
420
|
+
// See LOCAL-297.
|
|
421
|
+
if (
|
|
422
|
+
process.env.NODE_ENV !== 'production' &&
|
|
423
|
+
error instanceof Error &&
|
|
424
|
+
error.message.includes('Invalid hook call')
|
|
425
|
+
) {
|
|
426
|
+
console.error(
|
|
427
|
+
'[timber] A React hook was called during RSC rendering. This usually means a ' +
|
|
428
|
+
"'use client' component is being executed as a server component instead of " +
|
|
429
|
+
'being serialized as a client reference.\n\n' +
|
|
430
|
+
'Common causes:\n' +
|
|
431
|
+
" 1. The 'use client' directive is not the FIRST statement in the file (before any imports)\n" +
|
|
432
|
+
" 2. The component is re-exported through a barrel file (index.ts) that lacks 'use client'\n" +
|
|
433
|
+
' 3. @vitejs/plugin-rsc is not loaded or is misconfigured\n\n' +
|
|
434
|
+
`Request: ${_req.method} ${new URL(_req.url).pathname}`
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
|
|
412
438
|
// Track unhandled errors for pre-flush handling (500 status)
|
|
413
439
|
if (!renderError) {
|
|
414
440
|
renderError = { error, status: 500 };
|
|
@@ -640,15 +666,28 @@ async function renderRoute(
|
|
|
640
666
|
if (sig) return buildRedirectResponse(_req, sig, responseHeaders);
|
|
641
667
|
if (denySignal) {
|
|
642
668
|
return renderDenyPage(
|
|
643
|
-
denySignal,
|
|
644
|
-
|
|
669
|
+
denySignal,
|
|
670
|
+
segments,
|
|
671
|
+
layoutComponents as LayoutEntry[],
|
|
672
|
+
_req,
|
|
673
|
+
match,
|
|
674
|
+
responseHeaders,
|
|
675
|
+
clientBootstrap,
|
|
676
|
+
createDebugChannelSink,
|
|
677
|
+
callSsr
|
|
645
678
|
);
|
|
646
679
|
}
|
|
647
680
|
const err = renderError as { error: unknown; status: number } | null;
|
|
648
681
|
if (err) {
|
|
649
682
|
return renderErrorPage(
|
|
650
|
-
err.error,
|
|
651
|
-
|
|
683
|
+
err.error,
|
|
684
|
+
err.status,
|
|
685
|
+
segments,
|
|
686
|
+
layoutComponents as LayoutEntry[],
|
|
687
|
+
_req,
|
|
688
|
+
match,
|
|
689
|
+
responseHeaders,
|
|
690
|
+
clientBootstrap
|
|
652
691
|
);
|
|
653
692
|
}
|
|
654
693
|
return null;
|
|
@@ -688,15 +727,28 @@ async function renderRoute(
|
|
|
688
727
|
if (denySignal) {
|
|
689
728
|
// Render deny page without layouts — pass empty layout list
|
|
690
729
|
return renderDenyPage(
|
|
691
|
-
denySignal,
|
|
692
|
-
|
|
730
|
+
denySignal,
|
|
731
|
+
segments,
|
|
732
|
+
[] as LayoutEntry[],
|
|
733
|
+
_req,
|
|
734
|
+
match,
|
|
735
|
+
responseHeaders,
|
|
736
|
+
clientBootstrap,
|
|
737
|
+
createDebugChannelSink,
|
|
738
|
+
callSsr
|
|
693
739
|
);
|
|
694
740
|
}
|
|
695
741
|
const err = renderError as { error: unknown; status: number } | null;
|
|
696
742
|
if (err) {
|
|
697
743
|
return renderErrorPage(
|
|
698
|
-
err.error,
|
|
699
|
-
|
|
744
|
+
err.error,
|
|
745
|
+
err.status,
|
|
746
|
+
segments,
|
|
747
|
+
[] as LayoutEntry[],
|
|
748
|
+
_req,
|
|
749
|
+
match,
|
|
750
|
+
responseHeaders,
|
|
751
|
+
clientBootstrap
|
|
700
752
|
);
|
|
701
753
|
}
|
|
702
754
|
// No captured signal — return bare 500
|
|
@@ -16,12 +16,13 @@
|
|
|
16
16
|
* See design/02-rendering-pipeline.md §"Parallel Slots"
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import type { ManifestSegmentNode } from './route-matcher.js';
|
|
20
|
-
import type { RouteMatch, InterceptionContext } from './pipeline.js';
|
|
21
|
-
import { SlotAccessGate } from './access-gate.js';
|
|
22
|
-
import { wrapSegmentWithErrorBoundaries } from './error-boundary-wrapper.js';
|
|
23
19
|
import { TimberErrorBoundary } from '#/client/error-boundary.js';
|
|
24
20
|
import SlotErrorFallback from '#/client/slot-error-fallback.js';
|
|
21
|
+
import { SlotAccessGate } from './access-gate.js';
|
|
22
|
+
import { wrapSegmentWithErrorBoundaries } from './error-boundary-wrapper.js';
|
|
23
|
+
import type { InterceptionContext, RouteMatch } from './pipeline.js';
|
|
24
|
+
import { DenySignal } from './primitives.js';
|
|
25
|
+
import type { ManifestSegmentNode } from './route-matcher.js';
|
|
25
26
|
|
|
26
27
|
type CreateElementFn = (...args: unknown[]) => React.ReactElement;
|
|
27
28
|
|
|
@@ -56,7 +57,39 @@ export async function resolveSlotElement(
|
|
|
56
57
|
const mod = (await slotMatch.page.load()) as Record<string, unknown>;
|
|
57
58
|
if (mod.default) {
|
|
58
59
|
const SlotPage = mod.default as (...args: unknown[]) => unknown;
|
|
59
|
-
|
|
60
|
+
|
|
61
|
+
// Load default.tsx fallback for notFound() handling in the slot page.
|
|
62
|
+
// When a slot page calls notFound() or deny(), it should gracefully
|
|
63
|
+
// degrade to default.tsx or null — not crash the page. This matches
|
|
64
|
+
// Next.js behavior. See design/02-rendering-pipeline.md
|
|
65
|
+
// §"Slot Access Failure = Graceful Degradation"
|
|
66
|
+
let denyFallback: React.ReactElement | null = null;
|
|
67
|
+
if (slotNode.default) {
|
|
68
|
+
const defaultMod = (await slotNode.default.load()) as Record<string, unknown>;
|
|
69
|
+
const DefaultComp = defaultMod.default as ((...args: unknown[]) => unknown) | undefined;
|
|
70
|
+
if (DefaultComp) {
|
|
71
|
+
denyFallback = h(DefaultComp, { params: paramsPromise, searchParams: {} });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Wrap the slot page to catch DenySignal (from notFound() or deny())
|
|
76
|
+
// at the component level. This prevents the signal from reaching the
|
|
77
|
+
// RSC onError callback and being tracked as a page-level denial, which
|
|
78
|
+
// would cause the pipeline to replace the entire successful SSR response
|
|
79
|
+
// with a deny page. Instead, the slot gracefully degrades.
|
|
80
|
+
const denyFallbackCapture = denyFallback;
|
|
81
|
+
const SafeSlotPage = async (props: Record<string, unknown>) => {
|
|
82
|
+
try {
|
|
83
|
+
return await (SlotPage as (props: Record<string, unknown>) => unknown)(props);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (error instanceof DenySignal) {
|
|
86
|
+
return denyFallbackCapture;
|
|
87
|
+
}
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
let element: React.ReactElement = h(SafeSlotPage, {
|
|
60
93
|
params: paramsPromise,
|
|
61
94
|
searchParams: {},
|
|
62
95
|
});
|
package/src/server/ssr-entry.ts
CHANGED
|
@@ -156,7 +156,10 @@ export async function handleSsr(
|
|
|
156
156
|
// Wrap in SsrStreamError so the RSC entry can handle it without
|
|
157
157
|
// re-executing server components via renderDenyPage.
|
|
158
158
|
// See LOCAL-293.
|
|
159
|
-
console.error(
|
|
159
|
+
console.error(
|
|
160
|
+
'[timber] SSR shell failed from RSC stream error:',
|
|
161
|
+
formatSsrError(renderError)
|
|
162
|
+
);
|
|
160
163
|
throw new SsrStreamError(
|
|
161
164
|
'SSR renderToReadableStream failed due to RSC stream error',
|
|
162
165
|
renderError
|
|
@@ -79,7 +79,10 @@ export interface AccessGateProps {
|
|
|
79
79
|
* - 'pass': render children
|
|
80
80
|
* - DenySignal/RedirectSignal: throw synchronously
|
|
81
81
|
*/
|
|
82
|
-
verdict?:
|
|
82
|
+
verdict?:
|
|
83
|
+
| 'pass'
|
|
84
|
+
| import('./primitives.js').DenySignal
|
|
85
|
+
| import('./primitives.js').RedirectSignal;
|
|
83
86
|
children: ReactElement;
|
|
84
87
|
}
|
|
85
88
|
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
//#region src/search-params/registry.ts
|
|
2
|
-
var registry = /* @__PURE__ */ new Map();
|
|
3
|
-
/**
|
|
4
|
-
* Register a route's search params definition.
|
|
5
|
-
* Called by the generated route manifest loader when a route's modules load.
|
|
6
|
-
*/
|
|
7
|
-
function registerSearchParams(route, definition) {
|
|
8
|
-
registry.set(route, definition);
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Look up a route's search params definition.
|
|
12
|
-
* Returns undefined if the route hasn't been loaded yet.
|
|
13
|
-
*/
|
|
14
|
-
function getSearchParams(route) {
|
|
15
|
-
return registry.get(route);
|
|
16
|
-
}
|
|
17
|
-
//#endregion
|
|
18
|
-
export { registerSearchParams as n, getSearchParams as t };
|
|
19
|
-
|
|
20
|
-
//# sourceMappingURL=registry-BfPM41ri.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"registry-BfPM41ri.js","names":[],"sources":["../../src/search-params/registry.ts"],"sourcesContent":["/**\n * Runtime registry for route-scoped search params definitions.\n *\n * When a route's modules load, the framework registers its search-params\n * definition here. useQueryStates('/route') resolves codecs from this map.\n *\n * Design doc: design/23-search-params.md §\"Runtime: Registration at Route Load\"\n */\n\nimport type { SearchParamsDefinition } from './create.js';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst registry = new Map<string, SearchParamsDefinition<any>>();\n\n/**\n * Register a route's search params definition.\n * Called by the generated route manifest loader when a route's modules load.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function registerSearchParams(route: string, definition: SearchParamsDefinition<any>): void {\n registry.set(route, definition);\n}\n\n/**\n * Look up a route's search params definition.\n * Returns undefined if the route hasn't been loaded yet.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function getSearchParams(route: string): SearchParamsDefinition<any> | undefined {\n return registry.get(route);\n}\n"],"mappings":";AAYA,IAAM,2BAAW,IAAI,KAA0C;;;;;AAO/D,SAAgB,qBAAqB,OAAe,YAA+C;AACjG,UAAS,IAAI,OAAO,WAAW;;;;;;AAQjC,SAAgB,gBAAgB,OAAwD;AACtF,QAAO,SAAS,IAAI,MAAM"}
|