@solidjs/router 0.10.0-beta.9 → 0.10.1

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 CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  # Solid Router [![npm Version](https://img.shields.io/npm/v/@solidjs/router.svg?style=flat-square)](https://www.npmjs.org/package/@solidjs/router)
6
6
 
7
+ **Version 0.10.0 requires Solid v1.8.4 or later.**
8
+
7
9
  A router lets you change your view based on the URL in the browser. This allows your "single-page" application to simulate a traditional multipage site. To use Solid Router, you specify components called Routes that depend on the value of the URL (the "path"), and the router handles the mechanism of swapping them in and out.
8
10
 
9
11
  Solid Router is a universal router for SolidJS - it works whether you're rendering on the client or on the server. It was inspired by and combines paradigms of React Router and the Ember Router. Routes can be defined directly in your app's template using JSX, but you can also pass your route configuration directly as an object. It also supports nested routing, so navigation can change a part of a component, rather than completely replacing it.
@@ -376,7 +378,9 @@ getUser.key // returns "users"
376
378
  getUser.keyFor(id) // returns "users[5]"
377
379
  ```
378
380
 
379
- This cache can be defined anywhere and then used inside your components with:
381
+ You can revalidate the cache using the `revalidate` method or you can set `revalidate` keys on your response from your actions. If you pass the whole key it will invalidate all the entries for the cache (ie "users" in the example above). You can also invalidate a single entry by using `keyFor`.
382
+
383
+ `cache` can be defined anywhere and then used inside your components with:
380
384
 
381
385
  ### `createAsync`
382
386
 
@@ -390,7 +394,7 @@ Using `cache` in `createResource` directly won't work properly as the fetcher is
390
394
 
391
395
  ### `action`
392
396
 
393
- Actions are data mutations that can trigger invalidations and further routing. A list of prebuilt response builders can be found below(TODO).
397
+ Actions are data mutations that can trigger invalidations and further routing. A list of prebuilt response helpers can be found below.
394
398
  ```jsx
395
399
  import { action, revalidate, redirect } from "@solidjs/router"
396
400
 
@@ -450,7 +454,7 @@ const submit = useAction(myAction)
450
454
  submit(...args)
451
455
  ```
452
456
 
453
- The outside of a form context you can use custom data instead of formData, and these helpers preserve types.
457
+ The outside of a form context you can use custom data instead of formData, and these helpers preserve types. However, even when used with server functions (in projects like SolidStart) this requires client side javascript and is not Progressive Enhancible like forms are.
454
458
 
455
459
  ### `useSubmission`/`useSubmissions`
456
460
 
@@ -470,6 +474,36 @@ const submissions = useSubmissions(action, (input) => filter(input));
470
474
  const submission = useSubmission(action, (input) => filter(input));
471
475
  ```
472
476
 
477
+ ### Response Helpers
478
+
479
+ These are used to communicate router navigations from cache/actions, and can include invalidation hints. Generally these are thrown to not interfere the with the types and make it clear that function ends execution at that point.
480
+
481
+ #### `redirect(path, options)`
482
+
483
+ Redirects to the next route
484
+ ```js
485
+ const getUser = cache(() => {
486
+ const user = await api.getCurrentUser()
487
+ if (!user) throw redirect("/login");
488
+ return user;
489
+ })
490
+ ```
491
+
492
+ #### `reload(options)`
493
+
494
+ Reloads the data on the current page
495
+ ```js
496
+ const getTodo = cache(async (id: number) => {
497
+ const todo = await fetchTodo(id);
498
+ return todo;
499
+ }, "todo")
500
+
501
+ const updateTodo = action(async (todo: Todo) => {
502
+ await updateTodo(todo.id, todo);
503
+ reload({ revalidate: getTodo.keyFor(id) })
504
+ })
505
+ ```
506
+
473
507
  ### Load Functions
474
508
 
475
509
  Even with the cache API it is possible that we have waterfalls both with view logic and with lazy loaded code. With load functions, we can instead start fetching the data parallel to loading the route, so we can use the data as soon as possible.
@@ -614,7 +648,7 @@ This is the main Router component for the browser.
614
648
  | prop | type | description |
615
649
  |-----|----|----|
616
650
  | children | `JSX.Element` or `RouteDefinition[]` | The route definitions |
617
- | root | Component | Top level layout comoponent |
651
+ | root | Component | Top level layout component |
618
652
  | base | string | Base url to use for matching routes |
619
653
  | actionBase | string | Root url for server actions, default: `/_server` |
620
654
  | preload | boolean | Enables/disables preloads globally, default: `true` |
@@ -657,7 +691,11 @@ The Component for defining Routes:
657
691
 
658
692
  | prop | type | description |
659
693
  |-|-|-|
660
- |TODO
694
+ | path | string | Path partial for defining the route segment |
695
+ | component | `Component` | Component that will be rendered for the matched segment |
696
+ | matchFilters | `MatchFilters` | Additional constraints for matching against the route |
697
+ | children | `JSX.Element` | Nested `<Route>` definitions |
698
+ | load | `RouteLoadFunc` | Function called during preload or when the route is navigated to. |
661
699
 
662
700
  ## Router Primitives
663
701
 
@@ -786,7 +824,7 @@ The biggest changes are around removed APIs that need to be replaced.
786
824
 
787
825
  ### `<Outlet>`, `<Routes>`, `useRoutes`
788
826
 
789
- This is no longer used and instead will use `props.children` passed from into the page components for outlets. Nested Routes inherently cause waterfalls and are Outlets in a sense themselves. We do not want to encourage the pattern and if you must do it you can always nest `<Routers>` with appropriate base path.
827
+ This is no longer used and instead will use `props.children` passed from into the page components for outlets. This keeps the outlet directly passed from its page and avoids oddness of trying to use context across Islands boundaries. Nested `<Routes>` components inherently cause waterfalls and are `<Outlets>` themselves so they have the same concerns. We do not want to encourage the pattern and if you must do it you can always nest `<Router>`s with appropriate base path.
790
828
 
791
829
  ## `element` prop removed from `Route`
792
830
 
@@ -2,7 +2,7 @@ import { $TRACK, createMemo, createSignal, onCleanup, getOwner } from "solid-js"
2
2
  import { isServer } from "solid-js/web";
3
3
  import { useRouter } from "../routing";
4
4
  import { redirectStatusCodes } from "../utils";
5
- import { hashKey, revalidate } from "./cache";
5
+ import { cacheKeyOp, hashKey, revalidate } from "./cache";
6
6
  export const actions = /* #__PURE__ */ new Map();
7
7
  export function useSubmissions(fn, filter) {
8
8
  const router = useRouter();
@@ -65,7 +65,9 @@ export function action(fn, name) {
65
65
  p.then(handler, handler);
66
66
  return p;
67
67
  }
68
- const url = fn.url || (name && `action:${name}`) || (!isServer ? `action:${fn.name}` : "");
68
+ const url = fn.url ||
69
+ (name && `action:${name}`) ||
70
+ (!isServer ? `action:${hashString(fn.toString())}` : "");
69
71
  return toAction(mutate, url);
70
72
  }
71
73
  function toAction(fn, url) {
@@ -80,7 +82,7 @@ function toAction(fn, url) {
80
82
  };
81
83
  const uri = new URL(url, "http://sar");
82
84
  uri.searchParams.set("args", hashKey(args));
83
- return toAction(newFn, uri.pathname + uri.search);
85
+ return toAction(newFn, (uri.protocol === "action:" ? uri.protocol : "") + uri.pathname + uri.search);
84
86
  };
85
87
  fn.url = url;
86
88
  if (!isServer) {
@@ -89,13 +91,13 @@ function toAction(fn, url) {
89
91
  }
90
92
  return fn;
91
93
  }
94
+ const hashString = (s) => s.split("").reduce((a, b) => ((a << 5) - a + b.charCodeAt(0)) | 0, 0);
92
95
  async function handleResponse(response, navigate) {
93
96
  let data;
94
97
  let keys;
95
98
  if (response instanceof Response) {
96
- if (response.headers.has("X-Revalidate")) {
99
+ if (response.headers.has("X-Revalidate"))
97
100
  keys = response.headers.get("X-Revalidate").split(",");
98
- }
99
101
  if (response.customBody)
100
102
  data = await response.customBody();
101
103
  if (redirectStatusCodes.has(response.status)) {
@@ -110,6 +112,9 @@ async function handleResponse(response, navigate) {
110
112
  }
111
113
  else
112
114
  data = response;
113
- await revalidate(keys);
115
+ // invalidate
116
+ cacheKeyOp(keys, entry => (entry[0] = 0));
117
+ // trigger revalidation
118
+ await revalidate(keys, false);
114
119
  return data;
115
120
  }
@@ -1,5 +1,8 @@
1
+ import { type Signal } from "solid-js";
1
2
  import { type ReconcileOptions } from "solid-js/store";
2
- export declare function revalidate(key?: string | string[] | void): Promise<void>;
3
+ type CacheEntry = [number, any, string, Set<Signal<number>>];
4
+ export declare function revalidate(key?: string | string[] | void, force?: boolean): Promise<void>;
5
+ export declare function cacheKeyOp(key: string | string[] | void, fn: (cacheEntry: CacheEntry) => void): void;
3
6
  export type CachedFunction<T extends (...args: any) => U | Response, U> = T & {
4
7
  keyFor: (...args: Parameters<T>) => string;
5
8
  key: string;
@@ -9,3 +12,4 @@ export declare namespace cache {
9
12
  var set: (key: string, value: any) => void;
10
13
  }
11
14
  export declare function hashKey<T extends Array<any>>(args: T): string;
15
+ export {};
@@ -26,19 +26,22 @@ function getCache() {
26
26
  throw new Error("Cannot find cache context");
27
27
  return req.routerCache || (req.routerCache = new Map());
28
28
  }
29
- export function revalidate(key) {
30
- key && !Array.isArray(key) && (key = [key]);
29
+ export function revalidate(key, force = true) {
31
30
  return startTransition(() => {
32
31
  const now = Date.now();
33
- for (let k of cacheMap.keys()) {
34
- if (key === undefined || matchKey(k, key)) {
35
- const entry = cacheMap.get(k);
36
- entry[0] = 0; //force cache miss
37
- revalidateSignals(entry[3], now); // retrigger live signals
38
- }
39
- }
32
+ cacheKeyOp(key, entry => {
33
+ force && (entry[0] = 0); //force cache miss
34
+ revalidateSignals(entry[3], now); // retrigger live signals
35
+ });
40
36
  });
41
37
  }
38
+ export function cacheKeyOp(key, fn) {
39
+ key && !Array.isArray(key) && (key = [key]);
40
+ for (let k of cacheMap.keys()) {
41
+ if (key === undefined || matchKey(k, key))
42
+ fn(cacheMap.get(k));
43
+ }
44
+ }
42
45
  function revalidateSignals(set, time) {
43
46
  for (let s of set)
44
47
  s[1](time);
@@ -80,7 +83,7 @@ export function cache(fn, name, options) {
80
83
  ? sharedConfig.load(key) // hydrating
81
84
  : fn(...args);
82
85
  // serialize on server
83
- if (isServer && (sharedConfig.context && !sharedConfig.context.noHydrate)) {
86
+ if (isServer && sharedConfig.context && !sharedConfig.context.noHydrate) {
84
87
  const e = getRequestEvent();
85
88
  (!e || !e.serverOnly) && sharedConfig.context.serialize(key, res);
86
89
  }
@@ -133,7 +136,6 @@ export function cache(fn, name, options) {
133
136
  cachedFn.key = name;
134
137
  return cachedFn;
135
138
  }
136
- ;
137
139
  cache.set = (key, value) => {
138
140
  const cache = getCache();
139
141
  const now = Date.now();
package/dist/index.js CHANGED
@@ -768,20 +768,22 @@ function getCache() {
768
768
  if (!req) throw new Error("Cannot find cache context");
769
769
  return req.routerCache || (req.routerCache = new Map());
770
770
  }
771
- function revalidate(key) {
772
- key && !Array.isArray(key) && (key = [key]);
771
+ function revalidate(key, force = true) {
773
772
  return startTransition(() => {
774
773
  const now = Date.now();
775
- for (let k of cacheMap.keys()) {
776
- if (key === undefined || matchKey(k, key)) {
777
- const entry = cacheMap.get(k);
778
- entry[0] = 0; //force cache miss
779
- revalidateSignals(entry[3], now); // retrigger live signals
780
- }
781
- }
774
+ cacheKeyOp(key, entry => {
775
+ force && (entry[0] = 0); //force cache miss
776
+ revalidateSignals(entry[3], now); // retrigger live signals
777
+ });
782
778
  });
783
779
  }
784
780
 
781
+ function cacheKeyOp(key, fn) {
782
+ key && !Array.isArray(key) && (key = [key]);
783
+ for (let k of cacheMap.keys()) {
784
+ if (key === undefined || matchKey(k, key)) fn(cacheMap.get(k));
785
+ }
786
+ }
785
787
  function revalidateSignals(set, time) {
786
788
  for (let s of set) s[1](time);
787
789
  }
@@ -965,7 +967,7 @@ function action(fn, name) {
965
967
  p.then(handler, handler);
966
968
  return p;
967
969
  }
968
- const url = fn.url || name && `action:${name}` || (!isServer ? `action:${fn.name}` : "");
970
+ const url = fn.url || name && `action:${name}` || (!isServer ? `action:${hashString(fn.toString())}` : "");
969
971
  return toAction(mutate, url);
970
972
  }
971
973
  function toAction(fn, url) {
@@ -979,7 +981,7 @@ function toAction(fn, url) {
979
981
  };
980
982
  const uri = new URL(url, "http://sar");
981
983
  uri.searchParams.set("args", hashKey(args));
982
- return toAction(newFn, uri.pathname + uri.search);
984
+ return toAction(newFn, (uri.protocol === "action:" ? uri.protocol : "") + uri.pathname + uri.search);
983
985
  };
984
986
  fn.url = url;
985
987
  if (!isServer) {
@@ -988,13 +990,12 @@ function toAction(fn, url) {
988
990
  }
989
991
  return fn;
990
992
  }
993
+ const hashString = s => s.split("").reduce((a, b) => (a << 5) - a + b.charCodeAt(0) | 0, 0);
991
994
  async function handleResponse(response, navigate) {
992
995
  let data;
993
996
  let keys;
994
997
  if (response instanceof Response) {
995
- if (response.headers.has("X-Revalidate")) {
996
- keys = response.headers.get("X-Revalidate").split(",");
997
- }
998
+ if (response.headers.has("X-Revalidate")) keys = response.headers.get("X-Revalidate").split(",");
998
999
  if (response.customBody) data = await response.customBody();
999
1000
  if (redirectStatusCodes.has(response.status)) {
1000
1001
  const locationUrl = response.headers.get("Location") || "/";
@@ -1005,7 +1006,10 @@ async function handleResponse(response, navigate) {
1005
1006
  }
1006
1007
  }
1007
1008
  } else data = response;
1008
- await revalidate(keys);
1009
+ // invalidate
1010
+ cacheKeyOp(keys, entry => entry[0] = 0);
1011
+ // trigger revalidation
1012
+ await revalidate(keys, false);
1009
1013
  return data;
1010
1014
  }
1011
1015
 
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "Ryan Turnquist"
7
7
  ],
8
8
  "license": "MIT",
9
- "version": "0.10.0-beta.9",
9
+ "version": "0.10.1",
10
10
  "homepage": "https://github.com/solidjs/solid-router#readme",
11
11
  "repository": {
12
12
  "type": "git",