@solidjs/router 0.11.3 → 0.11.4

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,8 +1,8 @@
1
1
  import { $TRACK, createMemo, createSignal, onCleanup, getOwner } from "solid-js";
2
2
  import { isServer } from "solid-js/web";
3
3
  import { useRouter } from "../routing.js";
4
- import { redirectStatusCodes, mockBase } from "../utils.js";
5
- import { cacheKeyOp, hashKey, revalidate } from "./cache.js";
4
+ import { mockBase } from "../utils.js";
5
+ import { cacheKeyOp, hashKey, revalidate, cache } from "./cache.js";
6
6
  export const actions = /* #__PURE__ */ new Map();
7
7
  export function useSubmissions(fn, filter) {
8
8
  const router = useRouter();
@@ -31,10 +31,12 @@ export function useAction(action) {
31
31
  }
32
32
  export function action(fn, name) {
33
33
  function mutate(...variables) {
34
- const p = fn(...variables);
34
+ const router = this;
35
+ const p = (router.singleFlight && fn.withOptions
36
+ ? fn.withOptions({ headers: { "X-Single-Flight": "true" } })
37
+ : fn)(...variables);
35
38
  const [result, setResult] = createSignal();
36
39
  let submission;
37
- const router = this;
38
40
  async function handler(res) {
39
41
  const data = await handleResponse(res, router.navigatorFactory());
40
42
  data ? setResult({ data }) : submission.clear();
@@ -95,12 +97,25 @@ const hashString = (s) => s.split("").reduce((a, b) => ((a << 5) - a + b.charCod
95
97
  async function handleResponse(response, navigate) {
96
98
  let data;
97
99
  let keys;
100
+ let invalidateKeys;
98
101
  if (response instanceof Response) {
99
102
  if (response.headers.has("X-Revalidate"))
100
- keys = response.headers.get("X-Revalidate").split(",");
101
- if (response.customBody)
103
+ keys = invalidateKeys = response.headers.get("X-Revalidate").split(",");
104
+ if (response.customBody) {
102
105
  data = await response.customBody();
103
- if (redirectStatusCodes.has(response.status)) {
106
+ if (response.headers.has("X-Single-Flight")) {
107
+ keys || (keys = []);
108
+ invalidateKeys || (invalidateKeys = []);
109
+ Object.keys(data).forEach(key => {
110
+ if (key === "_$value")
111
+ return;
112
+ keys.push(key);
113
+ cache.set(key, data[key]);
114
+ });
115
+ data = data._$value;
116
+ }
117
+ }
118
+ if (response.headers.has("Location")) {
104
119
  const locationUrl = response.headers.get("Location") || "/";
105
120
  if (locationUrl.startsWith("http")) {
106
121
  window.location.href = locationUrl;
@@ -113,7 +128,7 @@ async function handleResponse(response, navigate) {
113
128
  else
114
129
  data = response;
115
130
  // invalidate
116
- cacheKeyOp(keys, entry => (entry[0] = 0));
131
+ cacheKeyOp(invalidateKeys, entry => (entry[0] = 0));
117
132
  // trigger revalidation
118
133
  await revalidate(keys, false);
119
134
  return data;
@@ -2,7 +2,6 @@ import { createSignal, getListener, getOwner, onCleanup, sharedConfig, startTran
2
2
  import { createStore, reconcile } from "solid-js/store";
3
3
  import { getRequestEvent, isServer } from "solid-js/web";
4
4
  import { useNavigate, getIntent } from "../routing.js";
5
- import { redirectStatusCodes } from "../utils.js";
6
5
  const LocationHeader = "Location";
7
6
  const PRELOAD_TIMEOUT = 5000;
8
7
  const CACHE_TIMEOUT = 180000;
@@ -56,6 +55,21 @@ export function cache(fn, name, options) {
56
55
  const key = name + hashKey(args);
57
56
  let cached = cache.get(key);
58
57
  let tracking;
58
+ if (isServer) {
59
+ const e = getRequestEvent();
60
+ if (e) {
61
+ const dataOnly = (e.router || (e.router = {})).dataOnly;
62
+ if (dataOnly) {
63
+ const data = e && (e.router.data || (e.router.data = {}));
64
+ if (data && key in data)
65
+ return data[key];
66
+ if (Array.isArray(dataOnly) && !dataOnly.includes(key)) {
67
+ data[key] = undefined;
68
+ return Promise.resolve();
69
+ }
70
+ }
71
+ }
72
+ }
59
73
  if (getListener() && !isServer) {
60
74
  tracking = true;
61
75
  onCleanup(() => cached[3].count--);
@@ -91,6 +105,7 @@ export function cache(fn, name, options) {
91
105
  sharedConfig.context.async &&
92
106
  !sharedConfig.context.noHydrate) {
93
107
  const e = getRequestEvent();
108
+ e && e.router.dataOnly && (e.router.data[key] = res);
94
109
  (!e || !e.serverOnly) && sharedConfig.context.serialize(key, res);
95
110
  }
96
111
  if (cached) {
@@ -117,7 +132,7 @@ export function cache(fn, name, options) {
117
132
  function handleResponse(error) {
118
133
  return async (v) => {
119
134
  if (v instanceof Response) {
120
- if (redirectStatusCodes.has(v.status)) {
135
+ if (v.headers.has("Location")) {
121
136
  if (navigate) {
122
137
  startTransition(() => {
123
138
  let url = v.headers.get(LocationHeader);
@@ -1,6 +1,6 @@
1
- export type RouterResponseInit = ResponseInit & {
1
+ export type RouterResponseInit = Omit<ResponseInit, "body"> & {
2
2
  revalidate?: string | string[];
3
3
  };
4
4
  export declare function redirect(url: string, init?: number | RouterResponseInit): never;
5
- export declare function reload(init: RouterResponseInit): never;
6
- export declare function json<T>(data: T, init?: Omit<ResponseInit, "body">): T;
5
+ export declare function reload(init?: RouterResponseInit): never;
6
+ export declare function json<T>(data: T, init?: RouterResponseInit): T;
@@ -19,20 +19,22 @@ export function redirect(url, init = 302) {
19
19
  });
20
20
  return response;
21
21
  }
22
- export function reload(init) {
22
+ export function reload(init = {}) {
23
23
  const { revalidate, ...responseInit } = init;
24
+ const headers = new Headers(responseInit.headers);
25
+ revalidate && headers.set("X-Revalidate", revalidate.toString());
24
26
  return new Response(null, {
25
27
  ...responseInit,
26
- ...(revalidate
27
- ? { headers: new Headers(responseInit.headers).set("X-Revalidate", revalidate.toString()) }
28
- : {})
28
+ headers
29
29
  });
30
30
  }
31
- export function json(data, init) {
32
- const headers = new Headers((init || {}).headers);
31
+ export function json(data, init = {}) {
32
+ const { revalidate, ...responseInit } = init;
33
+ const headers = new Headers(responseInit.headers);
34
+ revalidate && headers.set("X-Revalidate", revalidate.toString());
33
35
  headers.set("Content-Type", "application/json");
34
36
  const response = new Response(JSON.stringify(data), {
35
- ...init,
37
+ ...responseInit,
36
38
  headers
37
39
  });
38
40
  response.customBody = () => data;
package/dist/index.js CHANGED
@@ -79,7 +79,6 @@ function notifyIfNotBlocked(notify, block) {
79
79
  const hasSchemeRegex = /^(?:[a-z0-9]+:)?\/\//i;
80
80
  const trimPathRegex = /^\/+|(\/)\/+$/g;
81
81
  const mockBase = "http://sr";
82
- const redirectStatusCodes = new Set([204, 301, 302, 303, 307, 308]);
83
82
  function normalizePath(path, omitSlash = false) {
84
83
  const s = path.replace(trimPathRegex, "$1");
85
84
  return s ? omitSlash || /^[?#]/.test(s) ? s : "/" + s : "";
@@ -477,6 +476,7 @@ function createRouterContext(integration, getBranches, options = {}) {
477
476
  navigatorFactory,
478
477
  beforeLeave,
479
478
  preloadRoute,
479
+ singleFlight: options.singleFlight === undefined ? true : options.singleFlight,
480
480
  submissions
481
481
  };
482
482
  function navigateFromRoute(route, to, options) {
@@ -644,7 +644,8 @@ const createRouterComponent = router => props => {
644
644
  children: routeDefs()
645
645
  } : routeDefs(), props.base || ""));
646
646
  const routerState = createRouterContext(router, branches, {
647
- base
647
+ base,
648
+ singleFlight: props.singleFlight
648
649
  });
649
650
  router.create && router.create(routerState);
650
651
  return createComponent$1(RouterContextObj.Provider, {
@@ -663,6 +664,10 @@ function Routes(props) {
663
664
  const matches = createMemo(() => getRouteMatches(props.branches, props.routerState.location.pathname));
664
665
  if (isServer) {
665
666
  const e = getRequestEvent();
667
+ if (e && e.router && e.router.dataOnly) {
668
+ dataOnly(e, props.branches);
669
+ return;
670
+ }
666
671
  e && ((e.router || (e.router = {})).matches || (e.router.matches = matches().map(({
667
672
  route,
668
673
  path,
@@ -747,6 +752,32 @@ const Route = props => {
747
752
  });
748
753
  };
749
754
 
755
+ // for data only mode with single flight mutations
756
+ function dataOnly(event, branches) {
757
+ const url = new URL(event.request.url);
758
+ const prevMatches = getRouteMatches(branches, new URL(event.router.previousUrl || event.request.url).pathname);
759
+ const matches = getRouteMatches(branches, url.pathname);
760
+ for (let match = 0; match < matches.length; match++) {
761
+ if (!prevMatches[match] || matches[match].route !== prevMatches[match].route) event.router.dataOnly = true;
762
+ const {
763
+ route,
764
+ params
765
+ } = matches[match];
766
+ route.load && route.load({
767
+ params,
768
+ location: {
769
+ pathname: url.pathname,
770
+ search: url.search,
771
+ hash: url.hash,
772
+ query: extractSearchParams(url),
773
+ state: null,
774
+ key: ""
775
+ },
776
+ intent: "preload"
777
+ });
778
+ }
779
+ }
780
+
750
781
  function intercept([value, setValue], get, set) {
751
782
  return [get ? () => get(value()) : value, set ? v => setValue(set(v)) : setValue];
752
783
  }
@@ -860,6 +891,20 @@ function cache(fn, name, options) {
860
891
  const key = name + hashKey(args);
861
892
  let cached = cache.get(key);
862
893
  let tracking;
894
+ if (isServer) {
895
+ const e = getRequestEvent();
896
+ if (e) {
897
+ const dataOnly = (e.router || (e.router = {})).dataOnly;
898
+ if (dataOnly) {
899
+ const data = e && (e.router.data || (e.router.data = {}));
900
+ if (data && key in data) return data[key];
901
+ if (Array.isArray(dataOnly) && !dataOnly.includes(key)) {
902
+ data[key] = undefined;
903
+ return Promise.resolve();
904
+ }
905
+ }
906
+ }
907
+ }
863
908
  if (getListener() && !isServer) {
864
909
  tracking = true;
865
910
  onCleanup(() => cached[3].count--);
@@ -885,6 +930,7 @@ function cache(fn, name, options) {
885
930
  // serialize on server
886
931
  if (isServer && sharedConfig.context && sharedConfig.context.async && !sharedConfig.context.noHydrate) {
887
932
  const e = getRequestEvent();
933
+ e && e.router.dataOnly && (e.router.data[key] = res);
888
934
  (!e || !e.serverOnly) && sharedConfig.context.serialize(key, res);
889
935
  }
890
936
  if (cached) {
@@ -907,7 +953,7 @@ function cache(fn, name, options) {
907
953
  function handleResponse(error) {
908
954
  return async v => {
909
955
  if (v instanceof Response) {
910
- if (redirectStatusCodes.has(v.status)) {
956
+ if (v.headers.has("Location")) {
911
957
  if (navigate) {
912
958
  startTransition(() => {
913
959
  let url = v.headers.get(LocationHeader);
@@ -995,10 +1041,14 @@ function useAction(action) {
995
1041
  }
996
1042
  function action(fn, name) {
997
1043
  function mutate(...variables) {
998
- const p = fn(...variables);
1044
+ const router = this;
1045
+ const p = (router.singleFlight && fn.withOptions ? fn.withOptions({
1046
+ headers: {
1047
+ "X-Single-Flight": "true"
1048
+ }
1049
+ }) : fn)(...variables);
999
1050
  const [result, setResult] = createSignal();
1000
1051
  let submission;
1001
- const router = this;
1002
1052
  async function handler(res) {
1003
1053
  const data = await handleResponse(res, router.navigatorFactory());
1004
1054
  data ? setResult({
@@ -1055,10 +1105,23 @@ const hashString = s => s.split("").reduce((a, b) => (a << 5) - a + b.charCodeAt
1055
1105
  async function handleResponse(response, navigate) {
1056
1106
  let data;
1057
1107
  let keys;
1108
+ let invalidateKeys;
1058
1109
  if (response instanceof Response) {
1059
- if (response.headers.has("X-Revalidate")) keys = response.headers.get("X-Revalidate").split(",");
1060
- if (response.customBody) data = await response.customBody();
1061
- if (redirectStatusCodes.has(response.status)) {
1110
+ if (response.headers.has("X-Revalidate")) keys = invalidateKeys = response.headers.get("X-Revalidate").split(",");
1111
+ if (response.customBody) {
1112
+ data = await response.customBody();
1113
+ if (response.headers.has("X-Single-Flight")) {
1114
+ keys || (keys = []);
1115
+ invalidateKeys || (invalidateKeys = []);
1116
+ Object.keys(data).forEach(key => {
1117
+ if (key === "_$value") return;
1118
+ keys.push(key);
1119
+ cache.set(key, data[key]);
1120
+ });
1121
+ data = data._$value;
1122
+ }
1123
+ }
1124
+ if (response.headers.has("Location")) {
1062
1125
  const locationUrl = response.headers.get("Location") || "/";
1063
1126
  if (locationUrl.startsWith("http")) {
1064
1127
  window.location.href = locationUrl;
@@ -1068,7 +1131,7 @@ async function handleResponse(response, navigate) {
1068
1131
  }
1069
1132
  } else data = response;
1070
1133
  // invalidate
1071
- cacheKeyOp(keys, entry => entry[0] = 0);
1134
+ cacheKeyOp(invalidateKeys, entry => entry[0] = 0);
1072
1135
  // trigger revalidation
1073
1136
  await revalidate(keys, false);
1074
1137
  return data;
@@ -1455,23 +1518,28 @@ function redirect(url, init = 302) {
1455
1518
  });
1456
1519
  return response;
1457
1520
  }
1458
- function reload(init) {
1521
+ function reload(init = {}) {
1459
1522
  const {
1460
1523
  revalidate,
1461
1524
  ...responseInit
1462
1525
  } = init;
1526
+ const headers = new Headers(responseInit.headers);
1527
+ revalidate && headers.set("X-Revalidate", revalidate.toString());
1463
1528
  return new Response(null, {
1464
1529
  ...responseInit,
1465
- ...(revalidate ? {
1466
- headers: new Headers(responseInit.headers).set("X-Revalidate", revalidate.toString())
1467
- } : {})
1530
+ headers
1468
1531
  });
1469
1532
  }
1470
- function json(data, init) {
1471
- const headers = new Headers((init || {}).headers);
1533
+ function json(data, init = {}) {
1534
+ const {
1535
+ revalidate,
1536
+ ...responseInit
1537
+ } = init;
1538
+ const headers = new Headers(responseInit.headers);
1539
+ revalidate && headers.set("X-Revalidate", revalidate.toString());
1472
1540
  headers.set("Content-Type", "application/json");
1473
1541
  const response = new Response(JSON.stringify(data), {
1474
- ...init,
1542
+ ...responseInit,
1475
1543
  headers
1476
1544
  });
1477
1545
  response.customBody = () => data;
@@ -1,11 +1,13 @@
1
1
  import type { Component, JSX } from "solid-js";
2
- import type { MatchFilters, RouteLoadFunc, RouteDefinition, RouterIntegration, RouteSectionProps } from "../types.ts";
2
+ import type { MatchFilters, RouteLoadFunc, RouteDefinition, RouterIntegration, RouteSectionProps } from "../types.js";
3
3
  export type BaseRouterProps = {
4
4
  base?: string;
5
5
  /**
6
6
  * A component that wraps the content of every route.
7
7
  */
8
8
  root?: Component<RouteSectionProps>;
9
+ rootLoad?: RouteLoadFunc;
10
+ singleFlight: boolean;
9
11
  children?: JSX.Element | RouteDefinition | RouteDefinition[];
10
12
  };
11
13
  export declare const createRouterComponent: (router: RouterIntegration) => (props: BaseRouterProps) => JSX.Element;
@@ -2,12 +2,12 @@
2
2
  import { getRequestEvent, isServer } from "solid-js/web";
3
3
  import { children, createMemo, createRoot, mergeProps, on, Show } from "solid-js";
4
4
  import { createBranches, createRouteContext, createRouterContext, getRouteMatches, RouteContextObj, RouterContextObj } from "../routing.js";
5
- import { createMemoObject } from "../utils.js";
5
+ import { createMemoObject, extractSearchParams } from "../utils.js";
6
6
  export const createRouterComponent = (router) => (props) => {
7
7
  const { base } = props;
8
8
  const routeDefs = children(() => props.children);
9
9
  const branches = createMemo(() => createBranches(props.root ? { component: props.root, children: routeDefs() } : routeDefs(), props.base || ""));
10
- const routerState = createRouterContext(router, branches, { base });
10
+ const routerState = createRouterContext(router, branches, { base, singleFlight: props.singleFlight });
11
11
  router.create && router.create(routerState);
12
12
  return (<RouterContextObj.Provider value={routerState}>
13
13
  <Routes routerState={routerState} branches={branches()}/>
@@ -17,6 +17,10 @@ function Routes(props) {
17
17
  const matches = createMemo(() => getRouteMatches(props.branches, props.routerState.location.pathname));
18
18
  if (isServer) {
19
19
  const e = getRequestEvent();
20
+ if (e && e.router && e.router.dataOnly) {
21
+ dataOnly(e, props.branches);
22
+ return;
23
+ }
20
24
  e &&
21
25
  ((e.router || (e.router = {})).matches ||
22
26
  (e.router.matches = matches().map(({ route, path, params }) => ({
@@ -81,3 +85,27 @@ export const Route = (props) => {
81
85
  }
82
86
  });
83
87
  };
88
+ // for data only mode with single flight mutations
89
+ function dataOnly(event, branches) {
90
+ const url = new URL(event.request.url);
91
+ const prevMatches = getRouteMatches(branches, new URL(event.router.previousUrl || event.request.url).pathname);
92
+ const matches = getRouteMatches(branches, url.pathname);
93
+ for (let match = 0; match < matches.length; match++) {
94
+ if (!prevMatches[match] || matches[match].route !== prevMatches[match].route)
95
+ event.router.dataOnly = true;
96
+ const { route, params } = matches[match];
97
+ route.load &&
98
+ route.load({
99
+ params,
100
+ location: {
101
+ pathname: url.pathname,
102
+ search: url.search,
103
+ hash: url.hash,
104
+ query: extractSearchParams(url),
105
+ state: null,
106
+ key: ""
107
+ },
108
+ intent: "preload"
109
+ });
110
+ }
111
+ }
package/dist/routing.d.ts CHANGED
@@ -21,5 +21,6 @@ export declare function createLocation(path: Accessor<string>, state: Accessor<a
21
21
  export declare function getIntent(): Intent | undefined;
22
22
  export declare function createRouterContext(integration: RouterIntegration, getBranches?: () => Branch[], options?: {
23
23
  base?: string;
24
+ singleFlight?: boolean;
24
25
  }): RouterContext;
25
26
  export declare function createRouteContext(router: RouterContext, parent: RouteContext, outlet: () => JSX.Element, match: () => RouteMatch, params: Params): RouteContext;
package/dist/routing.js CHANGED
@@ -245,6 +245,7 @@ export function createRouterContext(integration, getBranches, options = {}) {
245
245
  navigatorFactory,
246
246
  beforeLeave,
247
247
  preloadRoute,
248
+ singleFlight: options.singleFlight === undefined ? true : options.singleFlight,
248
249
  submissions
249
250
  };
250
251
  function navigateFromRoute(route, to, options) {
package/dist/types.d.ts CHANGED
@@ -10,6 +10,9 @@ declare module "solid-js/web" {
10
10
  matches?: OutputMatch[];
11
11
  cache?: Map<string, CacheEntry>;
12
12
  submission?: Submission<any, any>;
13
+ dataOnly?: boolean | string[];
14
+ data?: Record<string, any>;
15
+ previousUrl?: string;
13
16
  };
14
17
  serverOnly?: boolean;
15
18
  }
@@ -127,6 +130,7 @@ export interface RouterContext {
127
130
  parsePath(str: string): string;
128
131
  beforeLeave: BeforeLeaveLifecycle;
129
132
  preloadRoute: (url: URL, preloadData: boolean) => void;
133
+ singleFlight: boolean;
130
134
  submissions: Signal<Submission<any, any>[]>;
131
135
  }
132
136
  export interface BeforeLeaveEventArgs {
package/dist/utils.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import type { MatchFilters, Params, PathMatch, Route, SetParams } from "./types.ts";
2
2
  export declare const mockBase = "http://sr";
3
- export declare const redirectStatusCodes: Set<number>;
4
3
  export declare function normalizePath(path: string, omitSlash?: boolean): string;
5
4
  export declare function resolvePath(base: string, path: string, from?: string): string | undefined;
6
5
  export declare function invariant<T>(value: T | null | undefined, message: string): T;
package/dist/utils.js CHANGED
@@ -2,7 +2,6 @@ import { createMemo, getOwner, runWithOwner } from "solid-js";
2
2
  const hasSchemeRegex = /^(?:[a-z0-9]+:)?\/\//i;
3
3
  const trimPathRegex = /^\/+|(\/)\/+$/g;
4
4
  export const mockBase = "http://sr";
5
- export const redirectStatusCodes = new Set([204, 301, 302, 303, 307, 308]);
6
5
  export function normalizePath(path, omitSlash = false) {
7
6
  const s = path.replace(trimPathRegex, "$1");
8
7
  return s ? (omitSlash || /^[?#]/.test(s) ? s : "/" + s) : "";
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "Ryan Turnquist"
7
7
  ],
8
8
  "license": "MIT",
9
- "version": "0.11.3",
9
+ "version": "0.11.4",
10
10
  "homepage": "https://github.com/solidjs/solid-router#readme",
11
11
  "repository": {
12
12
  "type": "git",