@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.
- package/README.md +100 -7
- package/dist/data/action.js +34 -25
- package/dist/fs.d.ts +94 -0
- package/dist/fs.js +51 -0
- package/dist/index.d.ts +20 -1
- package/dist/index.js +233 -116
- package/dist/routers/factory.d.ts +81 -5
- package/dist/routers/factory.jsx +25 -14
- package/dist/routers/history.d.ts +2 -0
- package/dist/routers/history.js +2 -2
- package/dist/routers/index.d.ts +3 -2
- package/dist/routers/index.js +1 -1
- package/dist/routers/scrollRestoration.d.ts +41 -0
- package/dist/routers/scrollRestoration.js +134 -0
- package/dist/routing.d.ts +11 -3
- package/dist/routing.js +25 -8
- package/dist/server.d.ts +7 -35
- package/dist/server.js +35 -143
- package/dist/types.d.ts +66 -12
- package/package.json +8 -7
- package/dist/data/flash.d.ts +0 -21
- package/dist/data/flash.js +0 -78
- package/dist/data/flashCookie.d.ts +0 -7
- package/dist/data/flashCookie.js +0 -20
package/dist/server.js
CHANGED
|
@@ -3,24 +3,27 @@
|
|
|
3
3
|
// it reaches for node async context via @solidjs/web/storage — keep it out of
|
|
4
4
|
// client bundles.
|
|
5
5
|
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// router's):
|
|
6
|
+
// One policy lives here — the vocabulary (query keys, route trees, preloads)
|
|
7
|
+
// is the router's:
|
|
9
8
|
//
|
|
10
9
|
// - single-flight mutations: `createFlightDataCollector` produces the
|
|
11
10
|
// `collectFlightData` hook the app hands to
|
|
12
|
-
// `configureServerFunctionsServer` (or the handler options).
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
11
|
+
// `configureServerFunctionsServer` (or the handler options). The runtime
|
|
12
|
+
// pre-digests the generic halves onto the outcome (target URL,
|
|
13
|
+
// revalidation keys, post-mutation cookie headers); the collector runs
|
|
14
|
+
// the matched routes' preloads for the target and returns their `query`
|
|
15
|
+
// results, which the handler folds into the response — mutation and
|
|
16
|
+
// fresh data in one round trip.
|
|
17
|
+
//
|
|
18
|
+
// The no-JS form convention used to live here too; the runtime now owns it
|
|
19
|
+
// outright — it applies to browser form posts by default (redirect back with
|
|
20
|
+
// the outcome in a one-shot flash cookie), and `createNoJSHandler` is
|
|
21
|
+
// configured through @solidjs/web/server-functions/server. The router keeps
|
|
22
|
+
// only its read side: SSR initialization seeds the decoded cookie into
|
|
23
|
+
// submission state (routing.ts).
|
|
20
24
|
import { provideRequestEvent } from "@solidjs/web/storage";
|
|
21
25
|
import { createBranches, getRouteMatches, mergeParams, peekLazySubtrees, resolveLazySubtree } from "./routing.js";
|
|
22
26
|
import { extractSearchParams } from "./utils.js";
|
|
23
|
-
import { encodeFlashCookie } from "./data/flash.js";
|
|
24
27
|
// the instance is the provider component, so it (unlike an options object) is a function
|
|
25
28
|
function isRouterInstance(options) {
|
|
26
29
|
return typeof options === "function";
|
|
@@ -32,13 +35,12 @@ function isRouterInstance(options) {
|
|
|
32
35
|
* and `preload` are the single source of truth — or an options object for
|
|
33
36
|
* trees not created through the factory.
|
|
34
37
|
*
|
|
35
|
-
* Strategy: rerun the route data for the URL the client will show
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* flight-data consumer.
|
|
38
|
+
* Strategy: rerun the route data for the URL the client will show after
|
|
39
|
+
* the mutation (the outcome's pre-digested `targetUrl`), collecting each
|
|
40
|
+
* `query` result under its cache key, scoped to the outcome's
|
|
41
|
+
* `revalidateKeys` when present (routes newly entered via redirect always
|
|
42
|
+
* collect fully). The returned payload seeds the client router's cache
|
|
43
|
+
* through its registered flight-data consumer.
|
|
42
44
|
*/
|
|
43
45
|
export function createFlightDataCollector(options) {
|
|
44
46
|
const { routes, rootPreload, base = "" } = isRouterInstance(options)
|
|
@@ -59,41 +61,29 @@ export function createFlightDataCollector(options) {
|
|
|
59
61
|
return branches;
|
|
60
62
|
};
|
|
61
63
|
return async (sourceEvent, outcome) => {
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
// is no envelope to fold data into
|
|
68
|
-
if (outcome.value instanceof Response && outcome.value.body)
|
|
69
|
-
return undefined;
|
|
70
|
-
const origin = new URL(outcome.request.url).origin;
|
|
71
|
-
let revalidate;
|
|
72
|
-
let url = new URL(referrer).toString();
|
|
73
|
-
if (outcome.response) {
|
|
74
|
-
if (outcome.response.headers.has("X-Revalidate"))
|
|
75
|
-
revalidate = outcome.response.headers.get("X-Revalidate").split(",");
|
|
76
|
-
if (outcome.response.headers.has("Location"))
|
|
77
|
-
url = new URL(outcome.response.headers.get("Location"), origin + base).toString();
|
|
78
|
-
}
|
|
79
|
-
// redirects leaving the app can't be collected for
|
|
80
|
-
if (new URL(url).origin !== origin)
|
|
64
|
+
// The generic halves arrive pre-digested on the outcome: no target
|
|
65
|
+
// (non-browser caller without a referer, or a redirect leaving the
|
|
66
|
+
// app's origin) means nothing to produce data for.
|
|
67
|
+
const { targetUrl, revalidateKeys } = outcome;
|
|
68
|
+
if (!targetUrl)
|
|
81
69
|
return undefined;
|
|
70
|
+
// a target implies a referer — it is the fallback target
|
|
71
|
+
const previousUrl = outcome.request.headers.get("referer");
|
|
82
72
|
// The flight event: the source event (its `response` rides along, so
|
|
83
73
|
// cookies/headers set during preloads still reach the real response)
|
|
84
|
-
// pointed at the target URL, with the mutation's
|
|
85
|
-
// folded
|
|
74
|
+
// pointed at the target URL, with the mutation's cookie effects already
|
|
75
|
+
// folded in (the outcome's `foldedHeaders`), in data-only router mode.
|
|
86
76
|
const event = { ...sourceEvent };
|
|
87
|
-
event.request = new Request(
|
|
77
|
+
event.request = new Request(targetUrl, { headers: outcome.foldedHeaders });
|
|
88
78
|
event.router = {
|
|
89
|
-
dataOnly:
|
|
90
|
-
previousUrl
|
|
79
|
+
dataOnly: revalidateKeys || true,
|
|
80
|
+
previousUrl,
|
|
91
81
|
data: {}
|
|
92
82
|
};
|
|
93
83
|
return provideRequestEvent(event, async () => {
|
|
94
84
|
try {
|
|
95
|
-
await resolveLazyMatches(resolveBranches,
|
|
96
|
-
runPreloads(event, resolveBranches(),
|
|
85
|
+
await resolveLazyMatches(resolveBranches, targetUrl, previousUrl);
|
|
86
|
+
runPreloads(event, resolveBranches(), targetUrl, previousUrl, rootPreload);
|
|
97
87
|
}
|
|
98
88
|
catch (error) {
|
|
99
89
|
console.error(error);
|
|
@@ -164,101 +154,3 @@ function runPreloads(event, branches, url, previousUrl, rootPreload) {
|
|
|
164
154
|
});
|
|
165
155
|
}
|
|
166
156
|
}
|
|
167
|
-
/**
|
|
168
|
-
* The request headers for the flight-data collection pass: the source
|
|
169
|
-
* request's headers with the mutation's `Set-Cookie` mutations folded into
|
|
170
|
-
* the `Cookie` header, so preloads observe the post-mutation cookie state
|
|
171
|
-
* (deletions honored via Max-Age/Expires).
|
|
172
|
-
*/
|
|
173
|
-
export function createSingleFlightHeaders(sourceEvent) {
|
|
174
|
-
const headers = new Headers(sourceEvent.request.headers);
|
|
175
|
-
const setCookies = sourceEvent.response?.headers?.getSetCookie() ?? [];
|
|
176
|
-
if (!setCookies.length)
|
|
177
|
-
return headers;
|
|
178
|
-
const cookies = {};
|
|
179
|
-
for (const pair of headers.get("cookie")?.split(";") ?? []) {
|
|
180
|
-
const eq = pair.indexOf("=");
|
|
181
|
-
if (eq > -1)
|
|
182
|
-
cookies[pair.slice(0, eq).trim()] = pair.slice(eq + 1).trim();
|
|
183
|
-
}
|
|
184
|
-
for (const setCookie of setCookies) {
|
|
185
|
-
const parsed = parseSetCookie(setCookie);
|
|
186
|
-
if (!parsed)
|
|
187
|
-
continue;
|
|
188
|
-
if ((parsed.maxAge != null && parsed.maxAge <= 0) ||
|
|
189
|
-
(parsed.expires != null && parsed.expires.getTime() <= Date.now())) {
|
|
190
|
-
delete cookies[parsed.name];
|
|
191
|
-
}
|
|
192
|
-
else {
|
|
193
|
-
cookies[parsed.name] = parsed.value;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
headers.delete("cookie");
|
|
197
|
-
const serialized = Object.entries(cookies)
|
|
198
|
-
.map(([name, value]) => `${name}=${value}`)
|
|
199
|
-
.join("; ");
|
|
200
|
-
if (serialized)
|
|
201
|
-
headers.set("cookie", serialized);
|
|
202
|
-
return headers;
|
|
203
|
-
}
|
|
204
|
-
function parseSetCookie(setCookie) {
|
|
205
|
-
const [pair, ...attributes] = setCookie.split(";");
|
|
206
|
-
const eq = pair.indexOf("=");
|
|
207
|
-
if (eq < 0)
|
|
208
|
-
return undefined;
|
|
209
|
-
const parsed = {
|
|
210
|
-
name: pair.slice(0, eq).trim(),
|
|
211
|
-
value: pair.slice(eq + 1).trim()
|
|
212
|
-
};
|
|
213
|
-
for (const attribute of attributes) {
|
|
214
|
-
const attrEq = attribute.indexOf("=");
|
|
215
|
-
const key = (attrEq < 0 ? attribute : attribute.slice(0, attrEq)).trim().toLowerCase();
|
|
216
|
-
const value = attrEq < 0 ? "" : attribute.slice(attrEq + 1).trim();
|
|
217
|
-
if (key === "max-age")
|
|
218
|
-
parsed.maxAge = Number(value);
|
|
219
|
-
else if (key === "expires")
|
|
220
|
-
parsed.expires = new Date(value);
|
|
221
|
-
}
|
|
222
|
-
return parsed;
|
|
223
|
-
}
|
|
224
|
-
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#redirection_messages
|
|
225
|
-
const validRedirectStatuses = new Set([301, 302, 303, 307, 308]);
|
|
226
|
-
/**
|
|
227
|
-
* Produces the `handleNoJS` implementation for
|
|
228
|
-
* `handleServerFunctionRequest` options: form posts made without the client
|
|
229
|
-
* runtime redirect back to the referring page (or to the result's
|
|
230
|
-
* `Location`) with the outcome riding a one-shot `flash` cookie. The
|
|
231
|
-
* router's SSR initialization reads the cookie into submission state, so
|
|
232
|
-
* `useSubmission()` renders the result exactly as a scripted submission
|
|
233
|
-
* would — progressive enhancement with no app code.
|
|
234
|
-
*/
|
|
235
|
-
export function createNoJSHandler(options = {}) {
|
|
236
|
-
const { base = "" } = options;
|
|
237
|
-
return function handleNoJS(result, request, args, thrown) {
|
|
238
|
-
const url = new URL(request.url);
|
|
239
|
-
const back = request.headers.get("referer") || url.origin + base;
|
|
240
|
-
// form post -> GET: 303 See Other unless the result names a redirect status
|
|
241
|
-
let status = 303;
|
|
242
|
-
let headers;
|
|
243
|
-
if (result instanceof Response) {
|
|
244
|
-
headers = new Headers(result.headers);
|
|
245
|
-
if (result.headers.has("Location")) {
|
|
246
|
-
headers.set("Location", new URL(result.headers.get("Location"), url.origin + base).toString());
|
|
247
|
-
if (validRedirectStatuses.has(result.status))
|
|
248
|
-
status = result.status;
|
|
249
|
-
}
|
|
250
|
-
else {
|
|
251
|
-
headers.set("Location", back);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
else {
|
|
255
|
-
headers = new Headers({ Location: back });
|
|
256
|
-
}
|
|
257
|
-
// Responses carry their meaning in their metadata; anything else flashes
|
|
258
|
-
// the outcome for the next render's submission state.
|
|
259
|
-
if (result && !(result instanceof Response)) {
|
|
260
|
-
headers.append("Set-Cookie", encodeFlashCookie(url.pathname + url.search, result, args, thrown));
|
|
261
|
-
}
|
|
262
|
-
return new Response(null, { status, headers });
|
|
263
|
-
};
|
|
264
|
-
}
|
package/dist/types.d.ts
CHANGED
|
@@ -96,19 +96,48 @@ export interface RouterIntegration {
|
|
|
96
96
|
utils?: Partial<RouterUtils>;
|
|
97
97
|
}
|
|
98
98
|
export type Intent = "initial" | "native" | "navigate" | "preload";
|
|
99
|
-
export interface RoutePreloadFuncArgs {
|
|
100
|
-
params:
|
|
99
|
+
export interface RoutePreloadFuncArgs<P extends Params = Params> {
|
|
100
|
+
params: P;
|
|
101
101
|
location: Location;
|
|
102
102
|
intent: Intent;
|
|
103
103
|
}
|
|
104
|
-
export type RoutePreloadFunc<T = unknown> = (args: RoutePreloadFuncArgs) => T;
|
|
105
|
-
export interface RouteSectionProps<T = unknown> {
|
|
106
|
-
params:
|
|
104
|
+
export type RoutePreloadFunc<T = unknown, P extends Params = Params> = (args: RoutePreloadFuncArgs<P>) => T;
|
|
105
|
+
export interface RouteSectionProps<T = unknown, P extends Params = Params> {
|
|
106
|
+
params: P;
|
|
107
107
|
location: Location;
|
|
108
108
|
data: T;
|
|
109
109
|
children?: JSX.Element;
|
|
110
110
|
}
|
|
111
|
-
export type RouteSectionComponent<T = unknown> = Component<RouteSectionProps<T>> | Component<Omit<RouteSectionProps<T>, "children">> | Component<{}>;
|
|
111
|
+
export type RouteSectionComponent<T = unknown, P extends Params = Params> = Component<RouteSectionProps<T, P>> | Component<Omit<RouteSectionProps<T, P>, "children">> | Component<{}>;
|
|
112
|
+
declare const ROUTE_PATTERN: unique symbol;
|
|
113
|
+
export interface TypedRouteConfig<S extends string = string> {
|
|
114
|
+
readonly [ROUTE_PATTERN]: S;
|
|
115
|
+
}
|
|
116
|
+
type RouteDataOf<Path> = Path extends TypedRouteConfig & {
|
|
117
|
+
preload?: RoutePreloadFunc<infer T>;
|
|
118
|
+
} ? T : unknown;
|
|
119
|
+
/**
|
|
120
|
+
* Props for a route component declared away from its definition, typed by a
|
|
121
|
+
* path witness — the same `paths` node you navigate with, the pattern string
|
|
122
|
+
* when no instance is in scope, or a `defineFileRoute` config (which also
|
|
123
|
+
* types `data` from its `preload`):
|
|
124
|
+
*
|
|
125
|
+
* ```ts
|
|
126
|
+
* function Story(props: RouteProps<typeof Router.paths.stories>) {}
|
|
127
|
+
* function Story(props: RouteProps<"/stories/:id", StoryData>) {}
|
|
128
|
+
* function Post(props: RouteProps<typeof route>) {} // file-system route files
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
export type RouteProps<Path, T = RouteDataOf<Path>> = RouteSectionProps<T, Path extends TypedPath<infer P> ? SimplifyRecord<P> & Params : Path extends TypedRouteConfig<infer S> ? RouteParams<S> : RouteParams<Path>>;
|
|
132
|
+
/**
|
|
133
|
+
* Component-type counterpart of `RouteProps` — annotate the component itself
|
|
134
|
+
* and the props parameter infers contextually:
|
|
135
|
+
*
|
|
136
|
+
* ```ts
|
|
137
|
+
* const Post: RouteComponent<typeof route> = props => props.params.id;
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
export type RouteComponent<Path, T = RouteDataOf<Path>> = Component<RouteProps<Path, T>>;
|
|
112
141
|
/**
|
|
113
142
|
* A lazy route subtree: a thunk producing the child definitions — typically
|
|
114
143
|
* `() => import("./feature/routes")` (the module's `default` or `routes`
|
|
@@ -131,15 +160,41 @@ export type RouteDefinition<S extends string | string[] = any, T = any> = {
|
|
|
131
160
|
component?: RouteSectionComponent<T>;
|
|
132
161
|
/** Standard Schema validator for this route's search params; its input type flows into the typed path proxy. */
|
|
133
162
|
search?: StandardSchemaV1<any, any>;
|
|
134
|
-
info?:
|
|
163
|
+
info?: RouteInfo;
|
|
135
164
|
};
|
|
165
|
+
import type { RouteInfo } from "./index.jsx";
|
|
166
|
+
export type { RouteInfo };
|
|
136
167
|
export type MatchFilter = readonly string[] | RegExp | ((s: string) => boolean);
|
|
137
168
|
export type PathParams<P extends string | readonly string[]> = P extends `${infer Head}/${infer Tail}` ? [...PathParams<Head>, ...PathParams<Tail>] : P extends `:${infer S}?` ? [S] : P extends `:${infer S}` ? [S] : P extends `*${infer S}` ? [S] : [];
|
|
138
169
|
export type MatchFilters<P extends string | readonly string[] = any> = P extends string ? {
|
|
139
170
|
[K in PathParams<P>[number]]?: MatchFilter;
|
|
140
171
|
} : Record<string, MatchFilter>;
|
|
141
|
-
export
|
|
142
|
-
|
|
172
|
+
export type DefinedRouteFilters<S> = S extends readonly (infer Member extends string)[] ? MatchFilters<Member> : S extends string | readonly string[] ? MatchFilters<S> : MatchFilters;
|
|
173
|
+
type FilterKeysOf<S> = S extends readonly (infer Member extends string)[] ? PathParams<Member>[number] : S extends string ? PathParams<S>[number] : string;
|
|
174
|
+
export type ValidFilters<F, S> = {
|
|
175
|
+
[K in keyof F]: string extends K ? F[K] : K extends FilterKeysOf<S> ? MatchFilter : never;
|
|
176
|
+
};
|
|
177
|
+
type PatternParams<P extends string> = P extends `${infer Head}/${infer Tail}` ? PatternParams<Head> & PatternParams<Tail> : P extends `:${infer Name}?` ? {
|
|
178
|
+
[K in Name]?: string;
|
|
179
|
+
} : P extends `:${infer Name}` ? {
|
|
180
|
+
[K in Name]: string;
|
|
181
|
+
} : P extends `*${infer Name}` ? {
|
|
182
|
+
[K in Name]: string;
|
|
183
|
+
} : {};
|
|
184
|
+
type SimplifyRecord<T> = {
|
|
185
|
+
[K in keyof T]: T[K];
|
|
186
|
+
} & {};
|
|
187
|
+
/**
|
|
188
|
+
* The params a route pattern guarantees, as a precise record — `:id` is
|
|
189
|
+
* `string`, `:tab?` is `string | undefined`, `*rest` is `string`. The record
|
|
190
|
+
* is intersected with the open `Params` shape so params inherited from
|
|
191
|
+
* parent routes stay accessible (as `string | undefined`). Array patterns
|
|
192
|
+
* yield the union of their members' records; non-literal patterns degrade
|
|
193
|
+
* to plain `Params`.
|
|
194
|
+
*/
|
|
195
|
+
export type RouteParams<S> = (S extends readonly (infer Member extends string)[] ? SimplifyRecord<PatternParams<Member>> : S extends string ? string extends S ? {} : SimplifyRecord<PatternParams<S>> : {}) & Params;
|
|
196
|
+
export interface PathMatch<P extends Params = Params> {
|
|
197
|
+
params: P;
|
|
143
198
|
path: string;
|
|
144
199
|
}
|
|
145
200
|
export interface RouteMatch extends PathMatch {
|
|
@@ -150,7 +205,7 @@ export interface OutputMatch {
|
|
|
150
205
|
pattern: string;
|
|
151
206
|
match: string;
|
|
152
207
|
params: Params;
|
|
153
|
-
info?:
|
|
208
|
+
info?: RouteInfo;
|
|
154
209
|
}
|
|
155
210
|
export interface RouteDescription {
|
|
156
211
|
key: unknown;
|
|
@@ -160,7 +215,7 @@ export interface RouteDescription {
|
|
|
160
215
|
preload?: RoutePreloadFunc;
|
|
161
216
|
matcher: (location: string) => PathMatch | null;
|
|
162
217
|
matchFilters?: MatchFilters;
|
|
163
|
-
info?:
|
|
218
|
+
info?: RouteInfo;
|
|
164
219
|
/** Present on the placeholder that stands in for an unresolved lazy subtree. */
|
|
165
220
|
lazy?: LazyBoundary;
|
|
166
221
|
}
|
|
@@ -250,4 +305,3 @@ export type CacheEntry = [number, Promise<any>, any, Intent | undefined, Signal<
|
|
|
250
305
|
count: number;
|
|
251
306
|
}];
|
|
252
307
|
export type NarrowResponse<T> = T extends ResponseEnvelope<infer U> ? U : Exclude<T, Response>;
|
|
253
|
-
export {};
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"Ryan Turnquist"
|
|
7
7
|
],
|
|
8
8
|
"license": "MIT",
|
|
9
|
-
"version": "
|
|
9
|
+
"version": "2.0.0-next.12",
|
|
10
10
|
"homepage": "https://github.com/solidjs/solid-router#readme",
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
@@ -23,14 +23,15 @@
|
|
|
23
23
|
"solid": "./dist/index.jsx",
|
|
24
24
|
"default": "./dist/index.js"
|
|
25
25
|
},
|
|
26
|
-
"./server": "./dist/server.js"
|
|
26
|
+
"./server": "./dist/server.js",
|
|
27
|
+
"./fs": "./dist/fs.js"
|
|
27
28
|
},
|
|
28
29
|
"files": [
|
|
29
30
|
"dist"
|
|
30
31
|
],
|
|
31
32
|
"sideEffects": false,
|
|
32
33
|
"devDependencies": {
|
|
33
|
-
"@solidjs/web": "2.0.0-beta.
|
|
34
|
+
"@solidjs/web": "2.0.0-beta.28",
|
|
34
35
|
"@babel/core": "^7.26.0",
|
|
35
36
|
"@babel/preset-typescript": "^7.26.0",
|
|
36
37
|
"@changesets/cli": "^2.27.10",
|
|
@@ -39,19 +40,19 @@
|
|
|
39
40
|
"@rollup/plugin-terser": "0.4.4",
|
|
40
41
|
"@types/jest": "^29.5.14",
|
|
41
42
|
"@types/node": "^22.10.0",
|
|
42
|
-
"babel-preset-solid": "2.0.0-beta.
|
|
43
|
+
"babel-preset-solid": "2.0.0-beta.28",
|
|
43
44
|
"jsdom": "^25.0.1",
|
|
44
45
|
"prettier": "^3.4.1",
|
|
45
46
|
"rollup": "^4.27.4",
|
|
46
|
-
"solid-js": "2.0.0-beta.
|
|
47
|
+
"solid-js": "2.0.0-beta.28",
|
|
47
48
|
"typescript": "^5.7.2",
|
|
48
49
|
"vite": "^6.0.0",
|
|
49
50
|
"vite-plugin-solid": "3.0.0-next.10",
|
|
50
51
|
"vitest": "^2.1.6"
|
|
51
52
|
},
|
|
52
53
|
"peerDependencies": {
|
|
53
|
-
"@solidjs/web": ">=2.0.0-beta.
|
|
54
|
-
"solid-js": ">=2.0.0-beta.
|
|
54
|
+
"@solidjs/web": ">=2.0.0-beta.28 <2.0.0-experimental.0",
|
|
55
|
+
"solid-js": ">=2.0.0-beta.28 <2.0.0-experimental.0"
|
|
55
56
|
},
|
|
56
57
|
"scripts": {
|
|
57
58
|
"build": "rm -rf dist && tsc && rollup -c",
|
package/dist/data/flash.d.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export { FLASH_COOKIE, hasFlashCookie, clearFlashCookie } from "./flashCookie.js";
|
|
2
|
-
/** What rides the cookie: a Submission minus its lifecycle methods. */
|
|
3
|
-
export interface FlashSubmission {
|
|
4
|
-
input: any[];
|
|
5
|
-
url: string;
|
|
6
|
-
result?: any;
|
|
7
|
-
error?: any;
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
10
|
-
* Encodes the outcome of a no-JS submission as a Set-Cookie value. `url` is
|
|
11
|
-
* the action url (pathname + search of the server function call) so the
|
|
12
|
-
* seeded submission matches `useSubmission` filters; `thrown` errors land on
|
|
13
|
-
* `error`, returned values on `result` (mirroring the scripted split).
|
|
14
|
-
*/
|
|
15
|
-
export declare function encodeFlashCookie(url: string, result: any, input: any[], thrown?: boolean): string;
|
|
16
|
-
/**
|
|
17
|
-
* Decodes the flash cookie out of a request's Cookie header. Returns
|
|
18
|
-
* undefined when absent or unreadable (a malformed cookie must never take
|
|
19
|
-
* down SSR — it is cleared either way).
|
|
20
|
-
*/
|
|
21
|
-
export declare function decodeFlashCookie(cookieHeader: string | null): FlashSubmission | undefined;
|
package/dist/data/flash.js
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
// The no-JS form convention's cookie codec. When a form posts to a server
|
|
2
|
-
// function without the client runtime (no instance header), the server
|
|
3
|
-
// handler redirects back carrying the outcome in a one-shot "flash" cookie;
|
|
4
|
-
// the next SSR pass reads it and seeds the router's submission state so
|
|
5
|
-
// useSubmission() renders the result exactly as a scripted submission would.
|
|
6
|
-
// Both codec halves live in this module so the write (src/server.ts, the
|
|
7
|
-
// handler's handleNoJS) and the read (provided to the router core by
|
|
8
|
-
// data/action.ts) can never drift apart. The cookie's name/clearing live in
|
|
9
|
-
// flashCookie.ts — the only piece the router core itself consumes — so this
|
|
10
|
-
// codec stays out of bundles that never load the action layer.
|
|
11
|
-
import { FLASH_COOKIE, matchFlashCookie } from "./flashCookie.js";
|
|
12
|
-
export { FLASH_COOKIE, hasFlashCookie, clearFlashCookie } from "./flashCookie.js";
|
|
13
|
-
// Form payloads have no JSON encoding, so entries are captured as pair
|
|
14
|
-
// arrays under a marker key ($f / $u) and revived to real FormData /
|
|
15
|
-
// URLSearchParams on the way out. File entries cannot ride a cookie and are
|
|
16
|
-
// dropped.
|
|
17
|
-
function encodeInputValue(value) {
|
|
18
|
-
if (value instanceof FormData)
|
|
19
|
-
return { $f: [...value.entries()].filter(([, v]) => typeof v === "string") };
|
|
20
|
-
if (value instanceof URLSearchParams)
|
|
21
|
-
return { $u: [...value.entries()] };
|
|
22
|
-
return value;
|
|
23
|
-
}
|
|
24
|
-
function decodeInputValue(value) {
|
|
25
|
-
if (value && typeof value === "object") {
|
|
26
|
-
if (Array.isArray(value.$f)) {
|
|
27
|
-
const form = new FormData();
|
|
28
|
-
for (const [k, v] of value.$f)
|
|
29
|
-
form.append(k, v);
|
|
30
|
-
return form;
|
|
31
|
-
}
|
|
32
|
-
if (Array.isArray(value.$u))
|
|
33
|
-
return new URLSearchParams(value.$u);
|
|
34
|
-
}
|
|
35
|
-
return value;
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Encodes the outcome of a no-JS submission as a Set-Cookie value. `url` is
|
|
39
|
-
* the action url (pathname + search of the server function call) so the
|
|
40
|
-
* seeded submission matches `useSubmission` filters; `thrown` errors land on
|
|
41
|
-
* `error`, returned values on `result` (mirroring the scripted split).
|
|
42
|
-
*/
|
|
43
|
-
export function encodeFlashCookie(url, result, input, thrown) {
|
|
44
|
-
const isError = result instanceof Error;
|
|
45
|
-
const payload = {
|
|
46
|
-
url,
|
|
47
|
-
result: isError ? result.message : result,
|
|
48
|
-
error: isError,
|
|
49
|
-
thrown: !!thrown,
|
|
50
|
-
input: input.map(encodeInputValue)
|
|
51
|
-
};
|
|
52
|
-
return `${FLASH_COOKIE}=${encodeURIComponent(JSON.stringify(payload))}; Secure; HttpOnly; Path=/`;
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Decodes the flash cookie out of a request's Cookie header. Returns
|
|
56
|
-
* undefined when absent or unreadable (a malformed cookie must never take
|
|
57
|
-
* down SSR — it is cleared either way).
|
|
58
|
-
*/
|
|
59
|
-
export function decodeFlashCookie(cookieHeader) {
|
|
60
|
-
const match = matchFlashCookie(cookieHeader);
|
|
61
|
-
if (!match)
|
|
62
|
-
return;
|
|
63
|
-
try {
|
|
64
|
-
const payload = JSON.parse(decodeURIComponent(match));
|
|
65
|
-
if (!payload || !payload.result)
|
|
66
|
-
return;
|
|
67
|
-
const result = payload.error ? new Error(payload.result) : payload.result;
|
|
68
|
-
return {
|
|
69
|
-
input: Array.isArray(payload.input) ? payload.input.map(decodeInputValue) : [],
|
|
70
|
-
url: payload.url,
|
|
71
|
-
result: payload.thrown ? undefined : result,
|
|
72
|
-
error: payload.thrown ? result : undefined
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
catch (error) {
|
|
76
|
-
console.error(error);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export declare const FLASH_COOKIE = "flash";
|
|
2
|
-
/** Whether a Cookie header carries a flash cookie (readable or not). */
|
|
3
|
-
export declare function hasFlashCookie(cookieHeader: string | null): boolean;
|
|
4
|
-
/** The raw encoded flash payload out of a Cookie header, if present. */
|
|
5
|
-
export declare function matchFlashCookie(cookieHeader: string | null): string | undefined;
|
|
6
|
-
/** The Set-Cookie value clearing the flash cookie after it has been read. */
|
|
7
|
-
export declare function clearFlashCookie(): string;
|
package/dist/data/flashCookie.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
// The flash cookie's name and one-shot clearing — split from the codec in
|
|
2
|
-
// flash.ts so the router core can consume the cookie eagerly (the clear must
|
|
3
|
-
// be appended before streaming flushes the response headers, and an unread
|
|
4
|
-
// outcome must not haunt a later request) without carrying the encode/decode
|
|
5
|
-
// machinery into client bundles that never load the action layer.
|
|
6
|
-
export const FLASH_COOKIE = "flash";
|
|
7
|
-
const FLASH_MATCHER = new RegExp(`(?:^|;\\s*)${FLASH_COOKIE}=([^;]+)`);
|
|
8
|
-
/** Whether a Cookie header carries a flash cookie (readable or not). */
|
|
9
|
-
export function hasFlashCookie(cookieHeader) {
|
|
10
|
-
return !!cookieHeader && FLASH_MATCHER.test(cookieHeader);
|
|
11
|
-
}
|
|
12
|
-
/** The raw encoded flash payload out of a Cookie header, if present. */
|
|
13
|
-
export function matchFlashCookie(cookieHeader) {
|
|
14
|
-
const match = cookieHeader && cookieHeader.match(FLASH_MATCHER);
|
|
15
|
-
return match ? match[1] : undefined;
|
|
16
|
-
}
|
|
17
|
-
/** The Set-Cookie value clearing the flash cookie after it has been read. */
|
|
18
|
-
export function clearFlashCookie() {
|
|
19
|
-
return `${FLASH_COOKIE}=; Max-Age=0; Path=/`;
|
|
20
|
-
}
|