@solidjs/router 1.0.0-next.9 → 2.0.0-next.12

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.
@@ -8,6 +8,7 @@ import { createBranches, createRouterContext, getRouteMatches, registerFlightRou
8
8
  import { mockBase } from "../utils.js";
9
9
  import { Root, Routes } from "./components.jsx";
10
10
  import { browserHistory } from "./history.js";
11
+ import { createScrollRestoration, withScrollRestoration } from "./scrollRestoration.js";
11
12
  /**
12
13
  * Identity helper that preserves literal types when the route tree is
13
14
  * declared as a separate variable. `createRouter` infers literally from
@@ -18,6 +19,9 @@ import { browserHistory } from "./history.js";
18
19
  export function defineRoutes(routes) {
19
20
  return routes;
20
21
  }
22
+ export function defineRoute(route) {
23
+ return route;
24
+ }
21
25
  /** Wraps a history adapter in the integration signal the router core consumes. Must run under a reactive owner. */
22
26
  function createIntegration(history) {
23
27
  let ignore = false;
@@ -45,22 +49,22 @@ function createIntegration(history) {
45
49
  }
46
50
  /**
47
51
  * Server default: a static view of the request URL — no signal machinery, a
48
- * server render never navigates. Without a request event (SSG scripts,
49
- * server-side tests) the configured history adapter provides the location,
50
- * so e.g. `memoryHistory("/page")` works isomorphically.
52
+ * server render never navigates. The request event (when the harness scopes
53
+ * one) wins; the provider's `url` prop is the fallback for renders outside a
54
+ * request scope (SSG scripts, server-side tests, runtimes without
55
+ * `node:async_hooks`). History adapters are a client navigation concern and
56
+ * play no part in locating a server render.
51
57
  */
52
- function staticIntegration(history) {
58
+ function staticIntegration(url, utils) {
53
59
  const e = getRequestEvent();
60
+ const source = e ? e.request.url : url;
54
61
  let value = "";
55
- if (e) {
56
- const url = new URL(e.request.url);
57
- value = url.pathname + url.search;
58
- }
59
- else if (history) {
60
- value = history.get();
62
+ if (source) {
63
+ const u = new URL(source, mockBase);
64
+ value = u.pathname + u.search;
61
65
  }
62
- const obj = typeof value === "string" ? { value } : { ...value };
63
- return { signal: [() => obj, next => Object.assign(obj, next)], utils: history && history.utils };
66
+ const obj = { value };
67
+ return { signal: [() => obj, next => Object.assign(obj, next)], utils };
64
68
  }
65
69
  export function createRouter(config) {
66
70
  const basePath = config.base || "";
@@ -90,9 +94,15 @@ export function createRouter(config) {
90
94
  "Compose route trees in one createRouter config instead.");
91
95
  }
92
96
  const root = untrack(() => props.children);
97
+ let restoration;
98
+ let history = config.history;
99
+ if (!isServer && (config.scrollRestoration ?? !history)) {
100
+ restoration = createScrollRestoration();
101
+ history = withScrollRestoration(history || browserHistory(), restoration);
102
+ }
93
103
  const integration = isServer
94
- ? staticIntegration(config.history)
95
- : createIntegration(config.history || browserHistory());
104
+ ? staticIntegration(props.url, config.history && config.history.utils)
105
+ : createIntegration(history || browserHistory());
96
106
  let context;
97
107
  const routerState = createRouterContext(integration, branches, () => context, {
98
108
  base: basePath,
@@ -109,6 +119,7 @@ export function createRouter(config) {
109
119
  setupLinkClaims(routerState, config.explicitLinks);
110
120
  if (routerState.singleFlight)
111
121
  onCleanup(registerFlightRouter(routerState));
122
+ restoration && restoration.create(routerState);
112
123
  }
113
124
  return (<RouterContextObj value={routerState}>
114
125
  <Root routerState={routerState} root={root} preload={config.preload}>
@@ -1,4 +1,6 @@
1
1
  import type { LocationChange, RouterUtils } from "../types.js";
2
+ export declare function bindEvent(target: EventTarget, type: string, handler: EventListener): () => void;
3
+ export declare function saveCurrentDepth(): void;
2
4
  /**
3
5
  * A history adapter: the source of truth for the current URL and how
4
6
  * navigations write back to it. Adapters are plain imported values so
@@ -1,5 +1,5 @@
1
1
  import { isServer } from "@solidjs/web";
2
- function bindEvent(target, type, handler) {
2
+ export function bindEvent(target, type, handler) {
3
3
  target.addEventListener(type, handler);
4
4
  return () => target.removeEventListener(type, handler);
5
5
  }
@@ -8,7 +8,7 @@ function bindEvent(target, type, handler) {
8
8
  // so blocking stays exact no matter when the first guard subscribes, while the
9
9
  // guard machinery itself lives behind the lazy `beforeLeave` slot.
10
10
  let depth;
11
- function saveCurrentDepth() {
11
+ export function saveCurrentDepth() {
12
12
  if (!window.history.state || window.history.state._depth == null) {
13
13
  window.history.replaceState({ ...window.history.state, _depth: window.history.length - 1 }, "");
14
14
  }
@@ -1,4 +1,5 @@
1
- export { createRouter, defineRoutes } from "./factory.jsx";
2
- export type { RouterConfig, RouterInstance } from "./factory.jsx";
1
+ export { createRouter, defineRoute, defineRoutes } from "./factory.jsx";
2
+ export type { DefinedRoute } from "./factory.jsx";
3
+ export type { RouterConfig, RouterInstance, RouterProps } from "./factory.jsx";
3
4
  export { browserHistory, hashHistory, memoryHistory } from "./history.js";
4
5
  export type { RouterHistory, MemoryHistoryAdapter } from "./history.js";
@@ -1,2 +1,2 @@
1
- export { createRouter, defineRoutes } from "./factory.jsx";
1
+ export { createRouter, defineRoute, defineRoutes } from "./factory.jsx";
2
2
  export { browserHistory, hashHistory, memoryHistory } from "./history.js";
@@ -0,0 +1,41 @@
1
+ import type { RouterContext } from "../types.js";
2
+ import type { RouterHistory } from "./history.js";
3
+ /**
4
+ * Explicit scroll restoration for back/forward navigation. The browser's
5
+ * native same-document heuristic is unreliable for suspense-driven rendering:
6
+ * if the destination route forces a layout while the document is still short,
7
+ * the saved offset for the previous entry is clamped and lost (#577).
8
+ *
9
+ * Positions are captured continuously from the scroll event, keyed by the
10
+ * `_depth` the router already stamps on every history entry — capturing at
11
+ * scroll time (rather than at exit) stays correct through `useBeforeLeave`
12
+ * blocked/reverted traversals. The map persists to sessionStorage on pagehide
13
+ * so restoration survives reloads, which `scrollRestoration = "manual"`
14
+ * otherwise disables.
15
+ *
16
+ * Restoration is a single scroll once routing settles — the same strategy
17
+ * SvelteKit, TanStack Router and React Router use. Settling after the
18
+ * transition commits is what makes the offset reachable; chasing a still-
19
+ * growing document afterwards (a ResizeObserver re-asserting the offset as
20
+ * content arrives) was tried and removed: no peer router does it, an
21
+ * unbounded observer re-clamps the viewport to the bottom when the target is
22
+ * never reachable (a list that is genuinely shorter now), and scroll-induced
23
+ * layout changes can feed it back into itself. Content that commits after the
24
+ * transition settles — an image without reserved space, a boundary below the
25
+ * fold — keeps whatever offset the document can hold.
26
+ */
27
+ export declare function createScrollRestoration(): {
28
+ /** When the adapter notifies a traversal: mark the target for restoration. */
29
+ onPop(): void;
30
+ /** After a push: forward entries died, and this depth may be reused. */
31
+ onPush(): void;
32
+ create(router: RouterContext): void;
33
+ };
34
+ export type ScrollRestoration = ReturnType<typeof createScrollRestoration>;
35
+ /**
36
+ * Threads restoration through a history adapter: pushes prune dead forward
37
+ * entries, and adapter notifications (unblocked pops) mark the traversal
38
+ * target. Notification runs after the adapter's depth bookkeeping, so the
39
+ * marked depth is the entry being restored to.
40
+ */
41
+ export declare function withScrollRestoration(history: RouterHistory, restoration: ScrollRestoration): RouterHistory;
@@ -0,0 +1,134 @@
1
+ import { createEffect, onCleanup } from "solid-js";
2
+ import { bindEvent, saveCurrentDepth } from "./history.js";
3
+ const STORAGE_KEY = "solid-router:scroll";
4
+ /**
5
+ * Explicit scroll restoration for back/forward navigation. The browser's
6
+ * native same-document heuristic is unreliable for suspense-driven rendering:
7
+ * if the destination route forces a layout while the document is still short,
8
+ * the saved offset for the previous entry is clamped and lost (#577).
9
+ *
10
+ * Positions are captured continuously from the scroll event, keyed by the
11
+ * `_depth` the router already stamps on every history entry — capturing at
12
+ * scroll time (rather than at exit) stays correct through `useBeforeLeave`
13
+ * blocked/reverted traversals. The map persists to sessionStorage on pagehide
14
+ * so restoration survives reloads, which `scrollRestoration = "manual"`
15
+ * otherwise disables.
16
+ *
17
+ * Restoration is a single scroll once routing settles — the same strategy
18
+ * SvelteKit, TanStack Router and React Router use. Settling after the
19
+ * transition commits is what makes the offset reachable; chasing a still-
20
+ * growing document afterwards (a ResizeObserver re-asserting the offset as
21
+ * content arrives) was tried and removed: no peer router does it, an
22
+ * unbounded observer re-clamps the viewport to the bottom when the target is
23
+ * never reachable (a list that is genuinely shorter now), and scroll-induced
24
+ * layout changes can feed it back into itself. Content that commits after the
25
+ * transition settles — an image without reserved space, a boundary below the
26
+ * fold — keeps whatever offset the document can hold.
27
+ */
28
+ export function createScrollRestoration() {
29
+ window.history.scrollRestoration = "manual";
30
+ // the current entry needs its depth stamp for captures to have a key, even
31
+ // if something replaced history.state after the adapter stamped it
32
+ saveCurrentDepth();
33
+ let positions = {};
34
+ try {
35
+ positions = JSON.parse(sessionStorage.getItem(STORAGE_KEY)) || {};
36
+ }
37
+ catch { }
38
+ const depth = () => window.history.state && window.history.state._depth;
39
+ let programmatic = false;
40
+ let pending;
41
+ const unbind = [
42
+ bindEvent(window, "scroll", () => {
43
+ const d = depth();
44
+ if (d != null)
45
+ positions[d] = window.scrollY;
46
+ // the user took over — a pending restore would yank them
47
+ if (!programmatic)
48
+ pending = undefined;
49
+ }),
50
+ bindEvent(window, "pagehide", () => {
51
+ try {
52
+ sessionStorage.setItem(STORAGE_KEY, JSON.stringify(positions));
53
+ }
54
+ catch { }
55
+ })
56
+ ];
57
+ const restore = () => {
58
+ if (pending == null)
59
+ return;
60
+ const y = positions[pending];
61
+ pending = undefined;
62
+ if (y == null)
63
+ return;
64
+ // flagged so the resulting scroll event is not mistaken for the user
65
+ // taking over (which cancels a pending restore)
66
+ programmatic = true;
67
+ window.scrollTo(0, y);
68
+ programmatic = false;
69
+ };
70
+ return {
71
+ /** When the adapter notifies a traversal: mark the target for restoration. */
72
+ onPop() {
73
+ pending = depth();
74
+ },
75
+ /** After a push: forward entries died, and this depth may be reused. */
76
+ onPush() {
77
+ const d = depth();
78
+ if (d != null)
79
+ for (const k in positions)
80
+ +k >= d && delete positions[k];
81
+ },
82
+ create(router) {
83
+ // Restore once the traversal has settled: key on the location (a fully
84
+ // synchronous pop commits without isRouting ever flipping) and on
85
+ // isRouting, which reports in-flight transitions — native pops
86
+ // included — and holds the restore until they commit. restore() no-ops
87
+ // unless a traversal marked a target, so push navigations are inert.
88
+ // `transparent` keeps the effect invisible to the hydration id scheme —
89
+ // same reasoning as the link-claims effect (claims.ts): this setup is
90
+ // client-only, so an id-consuming node here has no server counterpart
91
+ // and every hydration id allocated after it shifts by one child slot.
92
+ // The visible failure is any <Loading> content that settled before the
93
+ // shell flush (a cache hit, a preloaded query): its serialized value and
94
+ // inlined markup are keyed under the server's ids, the shifted client
95
+ // misses both, recomputes, and re-renders the route fresh — duplicating
96
+ // the server DOM and leaving it inert.
97
+ createEffect(() => ({
98
+ url: router.location.pathname + router.location.search + router.location.hash,
99
+ routing: router.isRouting()
100
+ }), current => {
101
+ if (!current.routing)
102
+ restore();
103
+ }, { transparent: true });
104
+ onCleanup(() => unbind.forEach(u => u()));
105
+ // reload/back_forward document loads land on an existing entry (a fresh
106
+ // navigation starts a new one and belongs at the top); the effect's
107
+ // initial run performs the restore after first render
108
+ const [nav] = (performance.getEntriesByType &&
109
+ performance.getEntriesByType("navigation"));
110
+ if (nav && nav.type !== "navigate")
111
+ pending = depth();
112
+ }
113
+ };
114
+ }
115
+ /**
116
+ * Threads restoration through a history adapter: pushes prune dead forward
117
+ * entries, and adapter notifications (unblocked pops) mark the traversal
118
+ * target. Notification runs after the adapter's depth bookkeeping, so the
119
+ * marked depth is the entry being restored to.
120
+ */
121
+ export function withScrollRestoration(history, restoration) {
122
+ return {
123
+ ...history,
124
+ set(next) {
125
+ history.set(next);
126
+ next.replace || restoration.onPush();
127
+ },
128
+ init: history.init &&
129
+ (notify => history.init(value => {
130
+ restoration.onPop();
131
+ notify(value);
132
+ }))
133
+ };
134
+ }
package/dist/routing.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { JSX } from "@solidjs/web";
2
- import type { Branch, Intent, LazyBoundary, Location, MatchFilters, NavigateOptions, Navigator, Params, RouteDescription, RouteContext, RouteDefinition, RouteMatch, RouterContext, RouterIntegration, SearchParams, SetSearchParams, TypedPath, TypedSearchPath } from "./types.js";
3
- import type { FlashSubmission } from "./data/flash.js";
2
+ import type { Branch, Intent, LazyBoundary, Location, MatchFilters, NavigateOptions, Navigator, Params, PathMatch, RouteDescription, RouteContext, RouteDefinition, RouteMatch, RouteParams, RouterContext, RouterIntegration, SearchParams, SetSearchParams, TypedPath, TypedSearchPath } from "./types.js";
3
+ import type { FlashSubmission } from "@solidjs/web/server-functions/server";
4
4
  /** Consider this API opaque and internal. It is likely to change in the future. */
5
5
  export declare const RouterContextObj: import("solid-js").Context<RouterContext>;
6
6
  export declare const RouteContextObj: import("solid-js").Context<RouteContext>;
@@ -62,14 +62,22 @@ export declare const useIsRouting: () => () => boolean;
62
62
  * `useMatch` takes an accessor that returns the path and creates a `Memo` that returns match information if the current path matches the provided path.
63
63
  * Useful for determining if a given path matches the current route.
64
64
  *
65
+ * Accepts a pattern string — the match's `params` are typed from it — or a
66
+ * typed path node (a concrete URL, so no params to speak of):
67
+ *
65
68
  * @example
66
69
  * ```js
67
70
  * const match = useMatch(() => props.href);
68
71
  *
69
72
  * return <div classList={{ active: Boolean(match()) }} />;
73
+ *
74
+ * const section = useMatch(() => "/docs/:page");
75
+ * section()?.params.page; // string
76
+ *
77
+ * const here = useMatch(() => paths.users(2));
70
78
  * ```
71
79
  */
72
- export declare const useMatch: <S extends string>(path: () => S, matchFilters?: MatchFilters<S>) => import("solid-js").SourceAccessor<import("./types.js").PathMatch | undefined>;
80
+ export declare const useMatch: <S extends string | TypedPath>(path: () => S, matchFilters?: MatchFilters<S extends string ? S : string>) => (() => PathMatch<S extends string ? RouteParams<S> : Params> | undefined);
73
81
  /**
74
82
  * `useRouteMatches` returns an accessor of the router's resolved matches for
75
83
  * the current location — the chain of route definitions producing the current
package/dist/routing.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { flush, runWithOwner } from "solid-js";
2
- import { createComponent, createContext, createMemo, createSignal, NotReadyError, untrack, useContext } from "solid-js";
2
+ import { createComponent, createContext, createMemo, createSignal, isPending, NotReadyError, untrack, useContext } from "solid-js";
3
3
  import { isServer, getRequestEvent } from "@solidjs/web";
4
4
  import { mockBase, comparablePath, createMemoObject, extractSearchParams, invariant, resolvePath, createMatcher, joinPaths, scoreRoute, mergeSearchString, expandOptionals } from "./utils.js";
5
- import { clearFlashCookie, hasFlashCookie } from "./data/flashCookie.js";
5
+ import { clearFlashCookie, hasFlashCookie } from "@solidjs/web/server-functions";
6
6
  const MAX_REDIRECTS = 100;
7
7
  /** Consider this API opaque and internal. It is likely to change in the future. */
8
8
  export const RouterContextObj = createContext();
@@ -80,16 +80,26 @@ export const useIsRouting = () => useRouter().isRouting;
80
80
  * `useMatch` takes an accessor that returns the path and creates a `Memo` that returns match information if the current path matches the provided path.
81
81
  * Useful for determining if a given path matches the current route.
82
82
  *
83
+ * Accepts a pattern string — the match's `params` are typed from it — or a
84
+ * typed path node (a concrete URL, so no params to speak of):
85
+ *
83
86
  * @example
84
87
  * ```js
85
88
  * const match = useMatch(() => props.href);
86
89
  *
87
90
  * return <div classList={{ active: Boolean(match()) }} />;
91
+ *
92
+ * const section = useMatch(() => "/docs/:page");
93
+ * section()?.params.page; // string
94
+ *
95
+ * const here = useMatch(() => paths.users(2));
88
96
  * ```
89
97
  */
90
98
  export const useMatch = (path, matchFilters) => {
91
99
  const location = useLocation();
92
- const matchers = createMemo(() => expandOptionals(path()).map(path => createMatcher(path, undefined, matchFilters)));
100
+ const matchers = createMemo(() =>
101
+ // `String()` folds typed path nodes to their URL
102
+ expandOptionals(String(path())).map(path => createMatcher(path, undefined, matchFilters)));
93
103
  return createMemo(() => {
94
104
  for (const matcher of matchers()) {
95
105
  const match = matcher(location.pathname);
@@ -496,7 +506,7 @@ export function provideFlightConsumer(factory) {
496
506
  /**
497
507
  * The flash-cookie codec, provided by the action side (data/action.ts) so
498
508
  * the router core never carries it: the core consumes the cookie eagerly
499
- * per request (detection + one-shot clear via the tiny flashCookie.ts half)
509
+ * per request (detection + one-shot clear via the runtime's isomorphic half)
500
510
  * but defers decoding to this slot, read when the submissions signal
501
511
  * initializes. Actions are created at module scope, so on the server the
502
512
  * decoder is always installed before useSubmission can read — and a
@@ -532,7 +542,7 @@ export function createRouterContext(integration, branches, getContext, options =
532
542
  else if (basePath && !initialSource.value) {
533
543
  setSource({ value: basePath, replace: true, scroll: false });
534
544
  }
535
- const [isRouting, setIsRouting] = createSignal(false, { ownedWrite: true });
545
+ const [isNavigating, setIsRouting] = createSignal(false, { ownedWrite: true });
536
546
  // Navigate override written from event handlers.
537
547
  const [navigateTarget, setNavigateTarget] = createSignal(undefined, {
538
548
  ownedWrite: true
@@ -547,7 +557,7 @@ export function createRouterContext(integration, branches, getContext, options =
547
557
  // The flash cookie is consumed eagerly: its one-shot clear (Set-Cookie)
548
558
  // must be appended before streaming flushes the response headers, and an
549
559
  // unread outcome must not haunt a later request's render. Only detection
550
- // and clearing happen here (the tiny flashCookie.ts half); the raw header
560
+ // and clearing happen here (the runtime's isomorphic half); the raw header
551
561
  // is stashed and decoding waits for the action-provided codec, read when
552
562
  // the lazily allocated submissions signal below first initializes.
553
563
  let flashCookieHeader;
@@ -581,6 +591,13 @@ export function createRouterContext(integration, branches, getContext, options =
581
591
  throw new NotReadyError(Promise.all(pending.map(resolveLazySubtree)));
582
592
  return m;
583
593
  });
594
+ // Every write is a transition in Solid 2, so a native history pop forks the
595
+ // source signal exactly like programmatic navigation does. isRouting is
596
+ // therefore derived: the manual flag covers navigateFromRoute's explicit
597
+ // window, and isPending over the location/matches read reports any
598
+ // in-flight fork — including popstate traversals and the lazy-subtree
599
+ // resolution matches() parks on.
600
+ const isRouting = createMemo(() => isNavigating() || isPending(() => (matches(), location.search, location.hash)));
584
601
  const buildParams = () => mergeParams(matches());
585
602
  const wrapParams = utils.paramsWrapper
586
603
  ? (getParams) => utils.paramsWrapper(getParams, branches)
@@ -739,8 +756,8 @@ export function createRouterContext(integration, branches, getContext, options =
739
756
  intent = prevIntent;
740
757
  }
741
758
  // Seeds the initial submission from a no-JS form post: the server
742
- // function handler redirected back with the outcome in a one-shot flash
743
- // cookie (see src/server.ts's handleNoJS), consumed eagerly above and
759
+ // function runtime redirected back with the outcome in a one-shot flash
760
+ // cookie (its default no-JS convention), consumed eagerly above and
744
761
  // decoded here — so the post-redirect SSR renders useSubmission() state
745
762
  // exactly as a scripted submission would. An explicitly pre-seeded
746
763
  // `event.router.submission` (framework integrations) takes precedence.
package/dist/server.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import type { JSX } from "@solidjs/web";
2
2
  import type { CollectFlightDataHook, ServerFunctionOutcome } from "@solidjs/web/server-functions/server";
3
3
  import type { RouteDefinition, RoutePreloadFunc } from "./types.js";
4
- export type { FlashSubmission } from "./data/flash.js";
5
4
  export type { CollectFlightDataHook, ServerFunctionOutcome };
6
5
  export interface FlightDataCollectorOptions {
7
6
  /**
@@ -19,7 +18,7 @@ export interface FlightDataCollectorOptions {
19
18
  * render: the merged params of every match and `intent: "initial"`.
20
19
  */
21
20
  rootPreload?: RoutePreloadFunc;
22
- /** The app's base path, for resolving redirect `Location`s and matching. */
21
+ /** The app's base path, for compiling the route tree's matchers. */
23
22
  base?: string;
24
23
  }
25
24
  /** A `createRouter` instance carries everything the collector needs. */
@@ -38,38 +37,11 @@ interface RouterInstanceLike {
38
37
  * and `preload` are the single source of truth — or an options object for
39
38
  * trees not created through the factory.
40
39
  *
41
- * Strategy: rerun the route data for the URL the client will show
42
- * after the mutation — the redirect `Location` when the outcome carries
43
- * one, the referring page otherwise — collecting each `query` result under
44
- * its cache key, scoped to the outcome's `X-Revalidate` keys when present
45
- * (routes newly entered via redirect always collect fully). The returned
46
- * payload seeds the client router's cache through its registered
47
- * flight-data consumer.
40
+ * Strategy: rerun the route data for the URL the client will show after
41
+ * the mutation (the outcome's pre-digested `targetUrl`), collecting each
42
+ * `query` result under its cache key, scoped to the outcome's
43
+ * `revalidateKeys` when present (routes newly entered via redirect always
44
+ * collect fully). The returned payload seeds the client router's cache
45
+ * through its registered flight-data consumer.
48
46
  */
49
47
  export declare function createFlightDataCollector(options: FlightDataCollectorOptions | RouterInstanceLike): CollectFlightDataHook;
50
- /**
51
- * The request headers for the flight-data collection pass: the source
52
- * request's headers with the mutation's `Set-Cookie` mutations folded into
53
- * the `Cookie` header, so preloads observe the post-mutation cookie state
54
- * (deletions honored via Max-Age/Expires).
55
- */
56
- export declare function createSingleFlightHeaders(sourceEvent: {
57
- request: Request;
58
- response?: {
59
- headers?: Headers;
60
- };
61
- }): Headers;
62
- export interface NoJSHandlerOptions {
63
- /** The app's base path, for resolving redirect `Location`s. */
64
- base?: string;
65
- }
66
- /**
67
- * Produces the `handleNoJS` implementation for
68
- * `handleServerFunctionRequest` options: form posts made without the client
69
- * runtime redirect back to the referring page (or to the result's
70
- * `Location`) with the outcome riding a one-shot `flash` cookie. The
71
- * router's SSR initialization reads the cookie into submission state, so
72
- * `useSubmission()` renders the result exactly as a scripted submission
73
- * would — progressive enhancement with no app code.
74
- */
75
- export declare function createNoJSHandler(options?: NoJSHandlerOptions): (result: unknown, request: Request, args: unknown[], thrown?: boolean) => Response;