@solidjs/router 0.17.0-next.4 → 0.17.0-next.6

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.
@@ -1,7 +1,8 @@
1
1
  /*@refresh skip*/
2
2
  import { children, createMemo, createRoot, getOwner, merge, onCleanup, runWithOwner, untrack } from "solid-js";
3
3
  import { getRequestEvent, isServer } from "@solidjs/web";
4
- import { createBranches, createRouteContext, createRouterContext, getIntent, getRouteMatches, RouteContextObj, RouterContextObj, setInPreloadFn } from "../routing.js";
4
+ import { createBranches, createRouteContext, createRouterContext, getIntent, getRouteMatches, resolveRouteDefinitions, RouteContextObj, RouterContextObj, setInPreloadFn } from "../routing.js";
5
+ import { setupFlightDataConsumer } from "../data/action.js";
5
6
  export const createRouterComponent = (router) => function IntegratedRouter(props) {
6
7
  const { base, singleFlight, transformUrl, root, rootPreload, routeChildren } = untrack(() => ({
7
8
  base: props.base,
@@ -11,7 +12,7 @@ export const createRouterComponent = (router) => function IntegratedRouter(props
11
12
  rootPreload: props.rootPreload,
12
13
  routeChildren: props.children
13
14
  }));
14
- const routeDefs = children(() => routeChildren);
15
+ const routeDefs = resolveRouteDefinitions(routeChildren);
15
16
  const branches = createMemo(() => createBranches(routeDefs(), base || ""));
16
17
  let context;
17
18
  const routerState = createRouterContext(router, branches, () => context, {
@@ -20,6 +21,12 @@ export const createRouterComponent = (router) => function IntegratedRouter(props
20
21
  transformUrl,
21
22
  });
22
23
  router.create && router.create(routerState);
24
+ if (!isServer && routerState.singleFlight) {
25
+ // Registering the consumer IS the single-flight opt-in: the transport
26
+ // sends the request header while one is subscribed. `singleFlight={false}`
27
+ // simply never subscribes, so the server is never asked to collect.
28
+ onCleanup(setupFlightDataConsumer(routerState));
29
+ }
23
30
  return (<RouterContextObj value={routerState}>
24
31
  <Root routerState={routerState} root={root} preload={rootPreload}>
25
32
  {(context = getOwner()) && null}
package/dist/routing.d.ts CHANGED
@@ -162,8 +162,17 @@ export declare const useSearchParams: <T extends SearchParams>() => [Partial<T>,
162
162
  export declare const useBeforeLeave: (listener: (e: BeforeLeaveEventArgs) => void) => void;
163
163
  export declare function createRoutes(routeDef: RouteDefinition, base?: string): RouteDescription[];
164
164
  export declare function createBranch(routes: RouteDescription[], index?: number): Branch;
165
+ /**
166
+ * Resolves the route tree an app hands the router — JSX `<Route>` children
167
+ * (`Route` is a data-holder component, evaluating to its merged props),
168
+ * config objects, arrays, or thunks producing either — into route
169
+ * definitions. Shared by the `<Router>` component (which keeps the
170
+ * resolution live) and the server integration's preload runner (one-shot).
171
+ */
172
+ export declare function resolveRouteDefinitions(routes: JSX.Element | RouteDefinition | RouteDefinition[]): () => RouteDefinition | RouteDefinition[];
165
173
  export declare function createBranches(routeDef: RouteDefinition | RouteDefinition[], base?: string, stack?: RouteDescription[], branches?: Branch[]): Branch[];
166
174
  export declare function getRouteMatches(branches: Branch[], location: string): RouteMatch[];
175
+ export declare function mergeParams(matches: RouteMatch[]): Params;
167
176
  export declare function getIntent(): Intent | undefined;
168
177
  export declare function getInPreloadFn(): boolean;
169
178
  export declare function setInPreloadFn(value: boolean): void;
package/dist/routing.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { flush, runWithOwner } from "solid-js";
2
- import { createComponent, createContext, createMemo, createSignal, onCleanup, untrack, useContext } from "solid-js";
2
+ import { children, createComponent, createContext, createMemo, createSignal, onCleanup, untrack, useContext } from "solid-js";
3
3
  import { isServer, getRequestEvent } from "@solidjs/web";
4
4
  import { createBeforeLeave } from "./lifecycle.js";
5
5
  import { mockBase, createMemoObject, extractSearchParams, invariant, resolvePath, createMatcher, joinPaths, scoreRoute, mergeSearchString, expandOptionals } from "./utils.js";
6
+ import { clearFlashCookie, decodeFlashCookie, hasFlashCookie } from "./data/flash.js";
6
7
  const MAX_REDIRECTS = 100;
7
8
  /** Consider this API opaque and internal. It is likely to change in the future. */
8
9
  export const RouterContextObj = createContext();
@@ -283,6 +284,16 @@ export function createBranch(routes, index = 0) {
283
284
  function asArray(value) {
284
285
  return Array.isArray(value) ? value : [value];
285
286
  }
287
+ /**
288
+ * Resolves the route tree an app hands the router — JSX `<Route>` children
289
+ * (`Route` is a data-holder component, evaluating to its merged props),
290
+ * config objects, arrays, or thunks producing either — into route
291
+ * definitions. Shared by the `<Router>` component (which keeps the
292
+ * resolution live) and the server integration's preload runner (one-shot).
293
+ */
294
+ export function resolveRouteDefinitions(routes) {
295
+ return children(() => routes);
296
+ }
286
297
  export function createBranches(routeDef, base = "", stack = [], branches = []) {
287
298
  const routeDefs = asArray(routeDef);
288
299
  for (let i = 0, len = routeDefs.length; i < len; i++) {
@@ -317,7 +328,7 @@ export function getRouteMatches(branches, location) {
317
328
  }
318
329
  return [];
319
330
  }
320
- function mergeParams(matches) {
331
+ export function mergeParams(matches) {
321
332
  const params = {};
322
333
  for (let i = 0; i < matches.length; i++) {
323
334
  Object.assign(params, matches[i].params);
@@ -552,13 +563,29 @@ export function createRouterContext(integration, branches, getContext, options =
552
563
  }
553
564
  intent = prevIntent;
554
565
  }
566
+ // Seeds the initial submission from a no-JS form post: the server
567
+ // function handler redirected back with the outcome in a one-shot flash
568
+ // cookie (see src/server.ts's handleNoJS), which is read here — and
569
+ // cleared — so the post-redirect SSR renders useSubmission() state
570
+ // exactly as a scripted submission would. An explicitly pre-seeded
571
+ // `event.router.submission` (framework integrations) takes precedence.
555
572
  function initFromFlash() {
556
573
  const e = getRequestEvent();
557
- if (!(e && e.router && e.router.submission))
574
+ if (!e)
575
+ return [];
576
+ let submission = e.router && e.router.submission;
577
+ if (!submission) {
578
+ const cookieHeader = e.request.headers.get("cookie");
579
+ submission = decodeFlashCookie(cookieHeader);
580
+ // one-shot: clear it even when unreadable so it can't haunt later renders
581
+ if (hasFlashCookie(cookieHeader) && e.response && e.response.headers)
582
+ e.response.headers.append("Set-Cookie", clearFlashCookie());
583
+ }
584
+ if (!submission)
558
585
  return [];
559
586
  return [
560
587
  {
561
- ...e.router.submission,
588
+ ...submission,
562
589
  clear() { },
563
590
  retry() { }
564
591
  }
@@ -0,0 +1,65 @@
1
+ import type { JSX } from "@solidjs/web";
2
+ import type { CollectFlightDataHook, ServerFunctionOutcome } from "@solidjs/web/server-functions/server";
3
+ import type { RouteDefinition, RoutePreloadFunc } from "./types.js";
4
+ export type { FlashSubmission } from "./data/flash.js";
5
+ export type { CollectFlightDataHook, ServerFunctionOutcome };
6
+ export interface FlightDataCollectorOptions {
7
+ /**
8
+ * The app's route tree — the same thing the `<Router>` receives as
9
+ * children: JSX `<Route>` trees, config objects, arrays, or a thunk
10
+ * producing either (for lazily/per-request built trees). Normalized to
11
+ * route definitions the same way the `<Router>` normalizes its children
12
+ * (on the server `Route` evaluates to its props, so no render pass is
13
+ * needed). Flight data is produced by the pure preload runner: the target
14
+ * URL is matched against the tree and the matched routes' `preload`
15
+ * functions run in data-only mode.
16
+ */
17
+ routes: JSX.Element | RouteDefinition | RouteDefinition[] | (() => JSX.Element | RouteDefinition | RouteDefinition[]);
18
+ /**
19
+ * The root layout's preload — the same function the app passes to
20
+ * `<Router rootPreload>`. Runs before the matched routes' preloads with
21
+ * the semantics the root gets during a real server render: the merged
22
+ * params of every match and `intent: "initial"`.
23
+ */
24
+ rootPreload?: RoutePreloadFunc;
25
+ /** The app's base path, for resolving redirect `Location`s and matching. */
26
+ base?: string;
27
+ }
28
+ /**
29
+ * Produces the `collectFlightData` implementation for
30
+ * `configureServerFunctionsServer` (or `handleServerFunctionRequest`
31
+ * options). Strategy: rerun the route data for the URL the client will show
32
+ * after the mutation — the redirect `Location` when the outcome carries
33
+ * one, the referring page otherwise — collecting each `query` result under
34
+ * its cache key, scoped to the outcome's `X-Revalidate` keys when present
35
+ * (routes newly entered via redirect always collect fully). The returned
36
+ * payload seeds the client router's cache through its registered
37
+ * flight-data consumer.
38
+ */
39
+ export declare function createFlightDataCollector(options: FlightDataCollectorOptions): CollectFlightDataHook;
40
+ /**
41
+ * The request headers for the flight-data collection pass: the source
42
+ * request's headers with the mutation's `Set-Cookie` mutations folded into
43
+ * the `Cookie` header, so preloads observe the post-mutation cookie state
44
+ * (deletions honored via Max-Age/Expires).
45
+ */
46
+ export declare function createSingleFlightHeaders(sourceEvent: {
47
+ request: Request;
48
+ response?: {
49
+ headers?: Headers;
50
+ };
51
+ }): Headers;
52
+ export interface NoJSHandlerOptions {
53
+ /** The app's base path, for resolving redirect `Location`s. */
54
+ base?: string;
55
+ }
56
+ /**
57
+ * Produces the `handleNoJS` implementation for
58
+ * `handleServerFunctionRequest` options: form posts made without the client
59
+ * runtime redirect back to the referring page (or to the result's
60
+ * `Location`) with the outcome riding a one-shot `flash` cookie. The
61
+ * router's SSR initialization reads the cookie into submission state, so
62
+ * `useSubmission()` renders the result exactly as a scripted submission
63
+ * would — progressive enhancement with no app code.
64
+ */
65
+ export declare function createNoJSHandler(options?: NoJSHandlerOptions): (result: unknown, request: Request, args: unknown[], thrown?: boolean) => Response;
package/dist/server.js ADDED
@@ -0,0 +1,228 @@
1
+ // The router's server-side integration with the server function runtime
2
+ // (@solidjs/web/server-functions). Server-only entry (`@solidjs/router/server`):
3
+ // it reaches for node async context via @solidjs/web/storage — keep it out of
4
+ // client bundles.
5
+ //
6
+ // Two policies live here, both previously implemented inside SolidStart and
7
+ // now owned by the router (the vocabulary — query keys, submissions — is the
8
+ // router's):
9
+ //
10
+ // - single-flight mutations: `createFlightDataCollector` produces the
11
+ // `collectFlightData` hook the app hands to
12
+ // `configureServerFunctionsServer` (or the handler options). After a
13
+ // mutation it produces the route data the mutation invalidated for the
14
+ // page the client is on (or is being redirected to), which the handler
15
+ // folds into the response — mutation and fresh data in one round trip.
16
+ // - the no-JS form convention: `createNoJSHandler` produces the
17
+ // `handleNoJS` hook — form posts made without the client runtime redirect
18
+ // back (303) carrying the outcome in a one-shot flash cookie that the
19
+ // router's SSR initialization reads into submission state.
20
+ import { provideRequestEvent } from "@solidjs/web/storage";
21
+ import { createBranches, getRouteMatches, mergeParams, resolveRouteDefinitions } from "./routing.js";
22
+ import { extractSearchParams } from "./utils.js";
23
+ import { encodeFlashCookie } from "./data/flash.js";
24
+ /**
25
+ * Produces the `collectFlightData` implementation for
26
+ * `configureServerFunctionsServer` (or `handleServerFunctionRequest`
27
+ * options). Strategy: rerun the route data for the URL the client will show
28
+ * after the mutation — the redirect `Location` when the outcome carries
29
+ * one, the referring page otherwise — collecting each `query` result under
30
+ * its cache key, scoped to the outcome's `X-Revalidate` keys when present
31
+ * (routes newly entered via redirect always collect fully). The returned
32
+ * payload seeds the client router's cache through its registered
33
+ * flight-data consumer.
34
+ */
35
+ export function createFlightDataCollector(options) {
36
+ const { routes, rootPreload, base = "" } = options;
37
+ if (!routes)
38
+ throw new Error("createFlightDataCollector requires `routes`");
39
+ let branches;
40
+ // resolveRouteDefinitions handles every accepted shape — thunks are called
41
+ // during resolution, JSX <Route> elements evaluate to their definitions
42
+ const resolveBranches = () => branches ||
43
+ (branches = createBranches(resolveRouteDefinitions(routes)(), base));
44
+ return async (sourceEvent, outcome) => {
45
+ // no referrer, nothing to produce data for (e.g. non-browser callers)
46
+ const referrer = outcome.request.headers.get("referer");
47
+ if (!referrer)
48
+ return undefined;
49
+ // a raw body-carrying Response is the caller's verbatim payload — there
50
+ // is no envelope to fold data into
51
+ if (outcome.value instanceof Response && outcome.value.body)
52
+ return undefined;
53
+ const origin = new URL(outcome.request.url).origin;
54
+ let revalidate;
55
+ let url = new URL(referrer).toString();
56
+ if (outcome.response) {
57
+ if (outcome.response.headers.has("X-Revalidate"))
58
+ revalidate = outcome.response.headers.get("X-Revalidate").split(",");
59
+ if (outcome.response.headers.has("Location"))
60
+ url = new URL(outcome.response.headers.get("Location"), origin + base).toString();
61
+ }
62
+ // redirects leaving the app can't be collected for
63
+ if (new URL(url).origin !== origin)
64
+ return undefined;
65
+ // The flight event: the source event (its `response` rides along, so
66
+ // cookies/headers set during preloads still reach the real response)
67
+ // pointed at the target URL, with the mutation's own cookie mutations
68
+ // folded into the request, in data-only router mode.
69
+ const event = { ...sourceEvent };
70
+ event.request = new Request(url, { headers: createSingleFlightHeaders(sourceEvent) });
71
+ event.router = {
72
+ dataOnly: revalidate || true,
73
+ previousUrl: referrer,
74
+ data: {}
75
+ };
76
+ return provideRequestEvent(event, async () => {
77
+ try {
78
+ runPreloads(event, resolveBranches(), url, referrer, rootPreload);
79
+ }
80
+ catch (error) {
81
+ console.error(error);
82
+ }
83
+ const data = event.router.data;
84
+ if (!data)
85
+ return undefined;
86
+ let containsKey = false;
87
+ for (const key in data) {
88
+ if (data[key] === undefined)
89
+ delete data[key];
90
+ else
91
+ containsKey = true;
92
+ }
93
+ return containsKey ? data : undefined;
94
+ });
95
+ };
96
+ }
97
+ // The pure preload runner: the same collection loop the router's <Routes>
98
+ // component runs when it detects a data-only render (routers/components.tsx),
99
+ // driven directly off the route tree — matched routes' preloads run and
100
+ // their `query` calls store results on `event.router.data`. Routes not
101
+ // matched by the previous URL flip collection from the revalidation-key
102
+ // filter to everything (their queries have no client cache yet).
103
+ function runPreloads(event, branches, url, previousUrl, rootPreload) {
104
+ const target = new URL(url);
105
+ const prevMatches = getRouteMatches(branches, new URL(previousUrl, target).pathname);
106
+ const matches = getRouteMatches(branches, target.pathname);
107
+ const location = {
108
+ pathname: target.pathname,
109
+ search: target.search,
110
+ hash: target.hash,
111
+ query: extractSearchParams(target),
112
+ state: null,
113
+ key: ""
114
+ };
115
+ // the root layout preloads first, exactly as <Root> would during a real
116
+ // server render: merged params of every match, intent "initial" (there is
117
+ // no navigation intent on the server), before any dataOnly-filter flip
118
+ rootPreload && rootPreload({ params: mergeParams(matches), location, intent: "initial" });
119
+ for (let match = 0; match < matches.length; match++) {
120
+ if (!prevMatches[match] || matches[match].route !== prevMatches[match].route)
121
+ event.router.dataOnly = true;
122
+ const { route, params } = matches[match];
123
+ route.preload &&
124
+ route.preload({
125
+ params,
126
+ location,
127
+ intent: "preload"
128
+ });
129
+ }
130
+ }
131
+ /**
132
+ * The request headers for the flight-data collection pass: the source
133
+ * request's headers with the mutation's `Set-Cookie` mutations folded into
134
+ * the `Cookie` header, so preloads observe the post-mutation cookie state
135
+ * (deletions honored via Max-Age/Expires).
136
+ */
137
+ export function createSingleFlightHeaders(sourceEvent) {
138
+ const headers = new Headers(sourceEvent.request.headers);
139
+ const setCookies = sourceEvent.response?.headers?.getSetCookie() ?? [];
140
+ if (!setCookies.length)
141
+ return headers;
142
+ const cookies = {};
143
+ for (const pair of headers.get("cookie")?.split(";") ?? []) {
144
+ const eq = pair.indexOf("=");
145
+ if (eq > -1)
146
+ cookies[pair.slice(0, eq).trim()] = pair.slice(eq + 1).trim();
147
+ }
148
+ for (const setCookie of setCookies) {
149
+ const parsed = parseSetCookie(setCookie);
150
+ if (!parsed)
151
+ continue;
152
+ if ((parsed.maxAge != null && parsed.maxAge <= 0) ||
153
+ (parsed.expires != null && parsed.expires.getTime() <= Date.now())) {
154
+ delete cookies[parsed.name];
155
+ }
156
+ else {
157
+ cookies[parsed.name] = parsed.value;
158
+ }
159
+ }
160
+ headers.delete("cookie");
161
+ const serialized = Object.entries(cookies)
162
+ .map(([name, value]) => `${name}=${value}`)
163
+ .join("; ");
164
+ if (serialized)
165
+ headers.set("cookie", serialized);
166
+ return headers;
167
+ }
168
+ function parseSetCookie(setCookie) {
169
+ const [pair, ...attributes] = setCookie.split(";");
170
+ const eq = pair.indexOf("=");
171
+ if (eq < 0)
172
+ return undefined;
173
+ const parsed = {
174
+ name: pair.slice(0, eq).trim(),
175
+ value: pair.slice(eq + 1).trim()
176
+ };
177
+ for (const attribute of attributes) {
178
+ const attrEq = attribute.indexOf("=");
179
+ const key = (attrEq < 0 ? attribute : attribute.slice(0, attrEq)).trim().toLowerCase();
180
+ const value = attrEq < 0 ? "" : attribute.slice(attrEq + 1).trim();
181
+ if (key === "max-age")
182
+ parsed.maxAge = Number(value);
183
+ else if (key === "expires")
184
+ parsed.expires = new Date(value);
185
+ }
186
+ return parsed;
187
+ }
188
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#redirection_messages
189
+ const validRedirectStatuses = new Set([301, 302, 303, 307, 308]);
190
+ /**
191
+ * Produces the `handleNoJS` implementation for
192
+ * `handleServerFunctionRequest` options: form posts made without the client
193
+ * runtime redirect back to the referring page (or to the result's
194
+ * `Location`) with the outcome riding a one-shot `flash` cookie. The
195
+ * router's SSR initialization reads the cookie into submission state, so
196
+ * `useSubmission()` renders the result exactly as a scripted submission
197
+ * would — progressive enhancement with no app code.
198
+ */
199
+ export function createNoJSHandler(options = {}) {
200
+ const { base = "" } = options;
201
+ return function handleNoJS(result, request, args, thrown) {
202
+ const url = new URL(request.url);
203
+ const back = request.headers.get("referer") || url.origin + base;
204
+ // form post -> GET: 303 See Other unless the result names a redirect status
205
+ let status = 303;
206
+ let headers;
207
+ if (result instanceof Response) {
208
+ headers = new Headers(result.headers);
209
+ if (result.headers.has("Location")) {
210
+ headers.set("Location", new URL(result.headers.get("Location"), url.origin + base).toString());
211
+ if (validRedirectStatuses.has(result.status))
212
+ status = result.status;
213
+ }
214
+ else {
215
+ headers.set("Location", back);
216
+ }
217
+ }
218
+ else {
219
+ headers = new Headers({ Location: back });
220
+ }
221
+ // Responses carry their meaning in their metadata; anything else flashes
222
+ // the outcome for the next render's submission state.
223
+ if (result && !(result instanceof Response)) {
224
+ headers.append("Set-Cookie", encodeFlashCookie(url.pathname + url.search, result, args, thrown));
225
+ }
226
+ return new Response(null, { status, headers });
227
+ };
228
+ }
package/dist/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Component, Signal } from "solid-js";
2
- import type { JSX } from "@solidjs/web";
2
+ import type { JSX, ResponseEnvelope } from "@solidjs/web";
3
3
  declare module "@solidjs/web" {
4
4
  interface RequestEvent {
5
5
  response: {
@@ -12,7 +12,7 @@ declare module "@solidjs/web" {
12
12
  cache?: Map<string, CacheEntry>;
13
13
  submission?: {
14
14
  input: any;
15
- result: any;
15
+ result?: any;
16
16
  error?: any;
17
17
  url: string;
18
18
  };
@@ -181,11 +181,4 @@ export interface MaybePreloadableComponent extends Component {
181
181
  export type CacheEntry = [number, Promise<any>, any, Intent | undefined, Signal<number> & {
182
182
  count: number;
183
183
  }];
184
- export type NarrowResponse<T> = T extends CustomResponse<infer U> ? U : Exclude<T, Response>;
185
- export type RouterResponseInit = Omit<ResponseInit, "body"> & {
186
- revalidate?: string | string[];
187
- };
188
- export type CustomResponse<T> = Omit<Response, "clone"> & {
189
- customBody: () => T;
190
- clone(...args: readonly unknown[]): CustomResponse<T>;
191
- };
184
+ export type NarrowResponse<T> = T extends ResponseEnvelope<infer U> ? U : Exclude<T, Response>;
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "Ryan Turnquist"
7
7
  ],
8
8
  "license": "MIT",
9
- "version": "0.17.0-next.4",
9
+ "version": "0.17.0-next.6",
10
10
  "homepage": "https://github.com/solidjs/solid-router#readme",
11
11
  "repository": {
12
12
  "type": "git",
@@ -22,14 +22,15 @@
22
22
  ".": {
23
23
  "solid": "./dist/index.jsx",
24
24
  "default": "./dist/index.js"
25
- }
25
+ },
26
+ "./server": "./dist/server.js"
26
27
  },
27
28
  "files": [
28
29
  "dist"
29
30
  ],
30
31
  "sideEffects": false,
31
32
  "devDependencies": {
32
- "@solidjs/web": "2.0.0-beta.18",
33
+ "@solidjs/web": "2.0.0-beta.21",
33
34
  "@babel/core": "^7.26.0",
34
35
  "@babel/preset-typescript": "^7.26.0",
35
36
  "@changesets/cli": "^2.27.10",
@@ -38,24 +39,25 @@
38
39
  "@rollup/plugin-terser": "0.4.4",
39
40
  "@types/jest": "^29.5.14",
40
41
  "@types/node": "^22.10.0",
41
- "babel-preset-solid": "2.0.0-beta.18",
42
+ "babel-preset-solid": "2.0.0-beta.21",
42
43
  "jsdom": "^25.0.1",
43
44
  "prettier": "^3.4.1",
44
45
  "rollup": "^4.27.4",
45
- "solid-js": "2.0.0-beta.18",
46
+ "solid-js": "2.0.0-beta.21",
46
47
  "typescript": "^5.7.2",
47
48
  "vite": "^6.0.0",
48
49
  "vite-plugin-solid": "3.0.0-next.10",
49
50
  "vitest": "^2.1.6"
50
51
  },
51
52
  "peerDependencies": {
52
- "@solidjs/web": ">=2.0.0-beta.18 <2.0.0-experimental.0",
53
- "solid-js": ">=2.0.0-beta.18 <2.0.0-experimental.0"
53
+ "@solidjs/web": ">=2.0.0-beta.21 <2.0.0-experimental.0",
54
+ "solid-js": ">=2.0.0-beta.21 <2.0.0-experimental.0"
54
55
  },
55
56
  "scripts": {
56
57
  "build": "rm -rf dist && tsc && rollup -c",
57
- "test": "vitest run && npm run test:types",
58
+ "test": "vitest run && vitest run --config vitest.config.server.ts && npm run test:types",
58
59
  "test:watch": "vitest",
60
+ "test:server": "vitest run --config vitest.config.server.ts",
59
61
  "test:types": "tsc --project tsconfig.test.json",
60
62
  "pretty": "prettier --write \"{src,test}/**/*.{ts,tsx}\"",
61
63
  "release": "pnpm build && changeset publish"
@@ -1,4 +0,0 @@
1
- import type { RouterResponseInit, CustomResponse } from "../types.js";
2
- export declare function redirect(url: string, init?: number | RouterResponseInit): CustomResponse<never>;
3
- export declare function reload(init?: RouterResponseInit): CustomResponse<never>;
4
- export declare function json<T>(data: T, init?: RouterResponseInit): CustomResponse<T>;
@@ -1,42 +0,0 @@
1
- export function redirect(url, init = 302) {
2
- let responseInit;
3
- let revalidate;
4
- if (typeof init === "number") {
5
- responseInit = { status: init };
6
- }
7
- else {
8
- ({ revalidate, ...responseInit } = init);
9
- if (typeof responseInit.status === "undefined") {
10
- responseInit.status = 302;
11
- }
12
- }
13
- const headers = new Headers(responseInit.headers);
14
- headers.set("Location", url);
15
- revalidate !== undefined && headers.set("X-Revalidate", revalidate.toString());
16
- const response = new Response(null, {
17
- ...responseInit,
18
- headers: headers
19
- });
20
- return response;
21
- }
22
- export function reload(init = {}) {
23
- const { revalidate, ...responseInit } = init;
24
- const headers = new Headers(responseInit.headers);
25
- revalidate !== undefined && headers.set("X-Revalidate", revalidate.toString());
26
- return new Response(null, {
27
- ...responseInit,
28
- headers
29
- });
30
- }
31
- export function json(data, init = {}) {
32
- const { revalidate, ...responseInit } = init;
33
- const headers = new Headers(responseInit.headers);
34
- revalidate !== undefined && headers.set("X-Revalidate", revalidate.toString());
35
- headers.set("Content-Type", "application/json");
36
- const response = new Response(JSON.stringify(data), {
37
- ...responseInit,
38
- headers
39
- });
40
- response.customBody = () => data;
41
- return response;
42
- }