@solidjs/router 0.10.0-beta.8 → 0.10.0

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.
@@ -368,7 +370,17 @@ This cache accomplishes the following:
368
370
  3. We have a reactive refetch mechanism based on key. So we can tell routes that aren't new to retrigger on action revalidation.
369
371
  4. It will serve as a back/forward cache for browser navigation up to 5 mins. Any user based navigation or link click bypasses it. Revalidation or new fetch updates the cache.
370
372
 
371
- This cache can be defined anywhere and then used inside your components with:
373
+ Cached function has a few useful methods for getting the key that are useful for invalidation.
374
+ ```ts
375
+ let id = 5;
376
+
377
+ getUser.key // returns "users"
378
+ getUser.keyFor(id) // returns "users[5]"
379
+ ```
380
+
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:
372
384
 
373
385
  ### `createAsync`
374
386
 
@@ -378,9 +390,11 @@ This is light wrapper over `createResource` that aims to serve as stand-in for a
378
390
  const user = createAsync(() => getUser(params.id))
379
391
  ```
380
392
 
393
+ Using `cache` in `createResource` directly won't work properly as the fetcher is not reactive and it won't invalidate properly.
394
+
381
395
  ### `action`
382
396
 
383
- 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.
384
398
  ```jsx
385
399
  import { action, revalidate, redirect } from "@solidjs/router"
386
400
 
@@ -391,12 +405,38 @@ const myAction = action(async (data) => {
391
405
  });
392
406
 
393
407
  // in component
394
- <form action={myAction} />
408
+ <form action={myAction} method="post" />
395
409
 
396
410
  //or
397
411
  <button type="submit" formaction={myAction}></button>
398
412
  ```
399
413
 
414
+ Actions only work with post requests, so make sure to put `method="post"` on your form.
415
+
416
+ Sometimes it might be easier to deal with typed data instead of `FormData` and adding additional hidden fields. For that reason Actions have a with method. That works similar to `bind` which applies the arguments in order.
417
+
418
+ Picture an action that deletes Todo Item:
419
+
420
+ ```js
421
+ const deleteTodo = action(async (formData: FormData) => {
422
+ const id = Number(formData.get("id"))
423
+ await api.deleteTodo(id)
424
+ })
425
+
426
+ <form action={deleteUser} method="post">
427
+ <input type="hidden" name="id" value={todo.id} />
428
+ <button type="submit">Delete</button>
429
+ </form>
430
+ ```
431
+ Instead with `with` you can write this:
432
+ ```js
433
+ const deleteUser = action(api.deleteUser)
434
+
435
+ <form action={deleteUser.with(todo.id)} method="post">
436
+ <button type="submit">Delete</button>
437
+ </form>
438
+ ```
439
+
400
440
  #### Notes of `<form>` implementation and SSR
401
441
  This requires stable references as you can only serialize a string as an attribute, and across SSR they'd need to match. The solution is providing a unique name.
402
442
 
@@ -414,7 +454,7 @@ const submit = useAction(myAction)
414
454
  submit(...args)
415
455
  ```
416
456
 
417
- 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.
418
458
 
419
459
  ### `useSubmission`/`useSubmissions`
420
460
 
@@ -434,6 +474,36 @@ const submissions = useSubmissions(action, (input) => filter(input));
434
474
  const submission = useSubmission(action, (input) => filter(input));
435
475
  ```
436
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
+
437
507
  ### Load Functions
438
508
 
439
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.
@@ -571,9 +641,22 @@ import { Router } from "@solidjs/router";
571
641
 
572
642
  ## Components
573
643
 
644
+ ### `<Router>`
645
+
646
+ This is the main Router component for the browser.
647
+
648
+ | prop | type | description |
649
+ |-----|----|----|
650
+ | children | `JSX.Element` or `RouteDefinition[]` | The route definitions |
651
+ | root | Component | Top level layout component |
652
+ | base | string | Base url to use for matching routes |
653
+ | actionBase | string | Root url for server actions, default: `/_server` |
654
+ | preload | boolean | Enables/disables preloads globally, default: `true` |
655
+ | explicitLinks | boolean | Disables all anchors being intercepted and instead requires `<A>`. default: `false` |
656
+
574
657
  ### `<A>`
575
658
 
576
- Like the `<a>` tag but supports relative paths and active class styling.
659
+ Like the `<a>` tag but supports relative paths and active class styling (requires client side JavaScript).
577
660
 
578
661
  The `<A>` tag has an `active` class if its href matches the current location, and `inactive` otherwise. **Note:** By default matching includes locations that are descendents (eg. href `/users` matches locations `/users` and `/users/123`), use the boolean `end` prop to prevent matching these. This is particularly useful for links to the root route `/` which would match everything.
579
662
 
@@ -608,7 +691,11 @@ The Component for defining Routes:
608
691
 
609
692
  | prop | type | description |
610
693
  |-|-|-|
611
- |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. |
612
699
 
613
700
  ## Router Primitives
614
701
 
@@ -729,15 +816,15 @@ useBeforeLeave((e: BeforeLeaveEventArgs) => {
729
816
  });
730
817
  ```
731
818
 
732
- ## Migrations from 0.8.x
819
+ ## Migrations from 0.9.x
733
820
 
734
- v0.9.0 brings some big changes to support the future of routing including Islands/Partial Hydration hybrid solutions. Most notably there is no Context API available in non-hydrating parts of the application.
821
+ v0.10.0 brings some big changes to support the future of routing including Islands/Partial Hydration hybrid solutions. Most notably there is no Context API available in non-hydrating parts of the application.
735
822
 
736
823
  The biggest changes are around removed APIs that need to be replaced.
737
824
 
738
825
  ### `<Outlet>`, `<Routes>`, `useRoutes`
739
826
 
740
- 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.
741
828
 
742
829
  ## `element` prop removed from `Route`
743
830
 
@@ -745,7 +832,7 @@ Related without Outlet component it has to be passed in manually. At which point
745
832
 
746
833
  ### `data` functions & `useRouteData`
747
834
 
748
- These have been replaced by a load mechanism. This allows link hover preloads (as the load function can be run as much as wanted without worry about reactivity). It support deduping/cache APIs which give more control over how things are cached. It also addresses TS issues with getting the right types in the Component without `typeof` checks.
835
+ These have been replaced by a load mechanism. This allows link hover preloads (as the load function can be run as much as wanted without worry about reactivity). It support deduping/cache APIs which give more control over how things are cached. It also addresses TS issues with getting the right types in the Component without `typeof` checks.
749
836
 
750
837
  ## SPAs in Deployed Environments
751
838
 
@@ -7,6 +7,7 @@ declare module "solid-js" {
7
7
  noScroll?: boolean;
8
8
  replace?: boolean;
9
9
  preload?: boolean;
10
+ link?: boolean;
10
11
  }
11
12
  }
12
13
  }
@@ -27,7 +27,7 @@ export function A(props) {
27
27
  [props.inactiveClass]: !isActive(),
28
28
  [props.activeClass]: isActive(),
29
29
  ...rest.classList
30
- }} aria-current={isActive() ? "page" : undefined}/>);
30
+ }} link aria-current={isActive() ? "page" : undefined}/>);
31
31
  }
32
32
  export function Navigate(props) {
33
33
  const navigate = useNavigate();
@@ -1,6 +1,6 @@
1
1
  import { JSX } from "solid-js";
2
2
  import { Submission } from "../types";
3
- export type Action<T extends Array<any>, U> = ((...vars: T) => Promise<U>) & JSX.SerializableAttributeValue & {
3
+ export type Action<T extends Array<any>, U> = (T extends [FormData] | [] ? JSX.SerializableAttributeValue : unknown) & ((...vars: T) => Promise<U>) & {
4
4
  url: string;
5
5
  with<A extends any[], B extends any[]>(this: (this: any, ...args: [...A, ...B]) => U, ...args: A): Action<B, U>;
6
6
  };
@@ -1,8 +1,8 @@
1
- import { $TRACK, createMemo, createSignal, onCleanup, getOwner } from "solid-js";
1
+ 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,12 +91,15 @@ 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
99
  if (response.headers.has("X-Revalidate")) {
97
100
  keys = response.headers.get("X-Revalidate").split(",");
101
+ // invalidate
102
+ cacheKeyOp(keys, entry => (entry[0] = 0));
98
103
  }
99
104
  if (response.customBody)
100
105
  data = await response.customBody();
@@ -110,6 +115,7 @@ async function handleResponse(response, navigate) {
110
115
  }
111
116
  else
112
117
  data = response;
113
- await revalidate(keys);
118
+ // trigger revalidation
119
+ await revalidate(keys, false);
114
120
  return data;
115
121
  }
@@ -1,8 +1,15 @@
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;
6
9
  };
7
10
  export declare function cache<T extends (...args: any) => U | Response, U>(fn: T, name: string, options?: ReconcileOptions): CachedFunction<T, U>;
11
+ export declare namespace cache {
12
+ var set: (key: string, value: any) => void;
13
+ }
8
14
  export declare function hashKey<T extends Array<any>>(args: T): string;
15
+ export {};
@@ -22,21 +22,26 @@ function getCache() {
22
22
  if (!isServer)
23
23
  return cacheMap;
24
24
  const req = getRequestEvent() || sharedConfig.context;
25
+ if (!req)
26
+ throw new Error("Cannot find cache context");
25
27
  return req.routerCache || (req.routerCache = new Map());
26
28
  }
27
- export function revalidate(key) {
28
- key && !Array.isArray(key) && (key = [key]);
29
+ export function revalidate(key, force = true) {
29
30
  return startTransition(() => {
30
31
  const now = Date.now();
31
- for (let k of cacheMap.keys()) {
32
- if (key === undefined || matchKey(k, key)) {
33
- const entry = cacheMap.get(k);
34
- entry[0] = 0; //force cache miss
35
- revalidateSignals(entry[3], now); // retrigger live signals
36
- }
37
- }
32
+ cacheKeyOp(key, entry => {
33
+ force && (entry[0] = 0); //force cache miss
34
+ revalidateSignals(entry[3], now); // retrigger live signals
35
+ });
38
36
  });
39
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
+ }
40
45
  function revalidateSignals(set, time) {
41
46
  for (let s of set)
42
47
  s[1](time);
@@ -79,7 +84,8 @@ export function cache(fn, name, options) {
79
84
  : fn(...args);
80
85
  // serialize on server
81
86
  if (isServer && sharedConfig.context && !sharedConfig.context.noHydrate) {
82
- sharedConfig.context && sharedConfig.context.serialize(key, res);
87
+ const e = getRequestEvent();
88
+ (!e || !e.serverOnly) && sharedConfig.context.serialize(key, res);
83
89
  }
84
90
  if (cached) {
85
91
  cached[0] = now;
@@ -130,6 +136,26 @@ export function cache(fn, name, options) {
130
136
  cachedFn.key = name;
131
137
  return cachedFn;
132
138
  }
139
+ cache.set = (key, value) => {
140
+ const cache = getCache();
141
+ const now = Date.now();
142
+ let cached = cache.get(key);
143
+ let version;
144
+ if (getOwner()) {
145
+ version = createSignal(now, {
146
+ equals: (p, v) => v - p < 50 // margin of error
147
+ });
148
+ onCleanup(() => cached[3].delete(version));
149
+ }
150
+ if (cached) {
151
+ cached[0] = now;
152
+ cached[1] = value;
153
+ cached[2] = "preload";
154
+ version && cached[3].add(version);
155
+ }
156
+ else
157
+ cache.set(key, (cached = [now, value, , new Set(version ? [version] : [])]));
158
+ };
133
159
  function matchKey(key, keys) {
134
160
  for (let k of keys) {
135
161
  if (key.startsWith(k))
@@ -1,2 +1,2 @@
1
1
  import type { RouterContext } from "../types";
2
- export declare function setupNativeEvents(router: RouterContext): void;
2
+ export declare function setupNativeEvents(preload?: boolean, explicitLinks?: boolean, actionBase?: string): (router: RouterContext) => void;
@@ -1,118 +1,126 @@
1
1
  import { delegateEvents } from "solid-js/web";
2
2
  import { onCleanup } from "solid-js";
3
3
  import { actions } from "./action";
4
- export function setupNativeEvents(router) {
5
- const basePath = router.base.path();
6
- const navigateFromRoute = router.navigatorFactory(router.base);
7
- let preloadTimeout = {};
8
- function isSvg(el) {
9
- return el.namespaceURI === "http://www.w3.org/2000/svg";
10
- }
11
- function handleAnchor(evt) {
12
- if (evt.defaultPrevented ||
13
- evt.button !== 0 ||
14
- evt.metaKey ||
15
- evt.altKey ||
16
- evt.ctrlKey ||
17
- evt.shiftKey)
18
- return;
19
- const a = evt
20
- .composedPath()
21
- .find(el => el instanceof Node && el.nodeName.toUpperCase() === "A");
22
- if (!a)
23
- return;
24
- const svg = isSvg(a);
25
- const href = svg ? a.href.baseVal : a.href;
26
- const target = svg ? a.target.baseVal : a.target;
27
- if (target || (!href && !a.hasAttribute("state")))
28
- return;
29
- const rel = (a.getAttribute("rel") || "").split(/\s+/);
30
- if (a.hasAttribute("download") || (rel && rel.includes("external")))
31
- return;
32
- const url = svg ? new URL(href, document.baseURI) : new URL(href);
33
- if (url.origin !== window.location.origin ||
34
- (basePath && url.pathname && !url.pathname.toLowerCase().startsWith(basePath.toLowerCase())))
35
- return;
36
- return [a, url];
37
- }
38
- function handleAnchorClick(evt) {
39
- const res = handleAnchor(evt);
40
- if (!res)
41
- return;
42
- const [a, url] = res;
43
- const to = router.parsePath(url.pathname + url.search + url.hash);
44
- const state = a.getAttribute("state");
45
- evt.preventDefault();
46
- navigateFromRoute(to, {
47
- resolve: false,
48
- replace: a.hasAttribute("replace"),
49
- scroll: !a.hasAttribute("noscroll"),
50
- state: state && JSON.parse(state)
51
- });
52
- }
53
- function handleAnchorPreload(evt) {
54
- const res = handleAnchor(evt);
55
- if (!res)
56
- return;
57
- const [a, url] = res;
58
- if (!preloadTimeout[url.pathname])
59
- router.preloadRoute(url, a.getAttribute("preload") !== "false");
60
- }
61
- function handleAnchorIn(evt) {
62
- const res = handleAnchor(evt);
63
- if (!res)
64
- return;
65
- const [a, url] = res;
66
- if (preloadTimeout[url.pathname])
67
- return;
68
- preloadTimeout[url.pathname] = setTimeout(() => {
69
- router.preloadRoute(url, a.getAttribute("preload") !== "false");
70
- delete preloadTimeout[url.pathname];
71
- }, 200);
72
- }
73
- function handleAnchorOut(evt) {
74
- const res = handleAnchor(evt);
75
- if (!res)
76
- return;
77
- const [, url] = res;
78
- if (preloadTimeout[url.pathname]) {
79
- clearTimeout(preloadTimeout[url.pathname]);
80
- delete preloadTimeout[url.pathname];
4
+ export function setupNativeEvents(preload = true, explicitLinks = false, actionBase = "/_server") {
5
+ return (router) => {
6
+ const basePath = router.base.path();
7
+ const navigateFromRoute = router.navigatorFactory(router.base);
8
+ let preloadTimeout = {};
9
+ function isSvg(el) {
10
+ return el.namespaceURI === "http://www.w3.org/2000/svg";
81
11
  }
82
- }
83
- function handleFormSubmit(evt) {
84
- let actionRef = evt.submitter && evt.submitter.hasAttribute("formaction")
85
- ? evt.submitter.formAction
86
- : evt.target.action;
87
- if (!actionRef)
88
- return;
89
- if (!actionRef.startsWith("action:")) {
90
- const url = new URL(actionRef);
91
- actionRef = router.parsePath(url.pathname + url.search);
92
- if (!actionRef.startsWith(router.actionBase))
12
+ function handleAnchor(evt) {
13
+ if (evt.defaultPrevented ||
14
+ evt.button !== 0 ||
15
+ evt.metaKey ||
16
+ evt.altKey ||
17
+ evt.ctrlKey ||
18
+ evt.shiftKey)
19
+ return;
20
+ const a = evt
21
+ .composedPath()
22
+ .find(el => el instanceof Node && el.nodeName.toUpperCase() === "A");
23
+ if (!a || (explicitLinks && !a.getAttribute("link")))
24
+ return;
25
+ const svg = isSvg(a);
26
+ const href = svg ? a.href.baseVal : a.href;
27
+ const target = svg ? a.target.baseVal : a.target;
28
+ if (target || (!href && !a.hasAttribute("state")))
29
+ return;
30
+ const rel = (a.getAttribute("rel") || "").split(/\s+/);
31
+ if (a.hasAttribute("download") || (rel && rel.includes("external")))
32
+ return;
33
+ const url = svg ? new URL(href, document.baseURI) : new URL(href);
34
+ if (url.origin !== window.location.origin ||
35
+ (basePath && url.pathname && !url.pathname.toLowerCase().startsWith(basePath.toLowerCase())))
93
36
  return;
37
+ return [a, url];
94
38
  }
95
- const handler = actions.get(actionRef);
96
- if (handler) {
39
+ function handleAnchorClick(evt) {
40
+ const res = handleAnchor(evt);
41
+ if (!res)
42
+ return;
43
+ const [a, url] = res;
44
+ const to = router.parsePath(url.pathname + url.search + url.hash);
45
+ const state = a.getAttribute("state");
97
46
  evt.preventDefault();
98
- const data = new FormData(evt.target);
99
- handler.call(router, data);
47
+ navigateFromRoute(to, {
48
+ resolve: false,
49
+ replace: a.hasAttribute("replace"),
50
+ scroll: !a.hasAttribute("noscroll"),
51
+ state: state && JSON.parse(state)
52
+ });
53
+ }
54
+ function handleAnchorPreload(evt) {
55
+ const res = handleAnchor(evt);
56
+ if (!res)
57
+ return;
58
+ const [a, url] = res;
59
+ if (!preloadTimeout[url.pathname])
60
+ router.preloadRoute(url, a.getAttribute("preload") !== "false");
100
61
  }
101
- }
102
- // ensure delegated event run first
103
- delegateEvents(["click", "submit"]);
104
- document.addEventListener("click", handleAnchorClick);
105
- document.addEventListener("mouseover", handleAnchorIn);
106
- document.addEventListener("mouseout", handleAnchorOut);
107
- document.addEventListener("focusin", handleAnchorPreload);
108
- document.addEventListener("touchstart", handleAnchorPreload);
109
- document.addEventListener("submit", handleFormSubmit);
110
- onCleanup(() => {
111
- document.removeEventListener("click", handleAnchorClick);
112
- document.removeEventListener("mouseover", handleAnchorIn);
113
- document.removeEventListener("mouseout", handleAnchorOut);
114
- document.removeEventListener("focusin", handleAnchorPreload);
115
- document.removeEventListener("touchstart", handleAnchorPreload);
116
- document.removeEventListener("submit", handleFormSubmit);
117
- });
62
+ function handleAnchorIn(evt) {
63
+ const res = handleAnchor(evt);
64
+ if (!res)
65
+ return;
66
+ const [a, url] = res;
67
+ if (preloadTimeout[url.pathname])
68
+ return;
69
+ preloadTimeout[url.pathname] = setTimeout(() => {
70
+ router.preloadRoute(url, a.getAttribute("preload") !== "false");
71
+ delete preloadTimeout[url.pathname];
72
+ }, 200);
73
+ }
74
+ function handleAnchorOut(evt) {
75
+ const res = handleAnchor(evt);
76
+ if (!res)
77
+ return;
78
+ const [, url] = res;
79
+ if (preloadTimeout[url.pathname]) {
80
+ clearTimeout(preloadTimeout[url.pathname]);
81
+ delete preloadTimeout[url.pathname];
82
+ }
83
+ }
84
+ function handleFormSubmit(evt) {
85
+ let actionRef = evt.submitter && evt.submitter.hasAttribute("formaction")
86
+ ? evt.submitter.formAction
87
+ : evt.target.action;
88
+ if (!actionRef)
89
+ return;
90
+ if (!actionRef.startsWith("action:")) {
91
+ const url = new URL(actionRef);
92
+ actionRef = router.parsePath(url.pathname + url.search);
93
+ if (!actionRef.startsWith(actionBase))
94
+ return;
95
+ }
96
+ if (evt.target.method.toUpperCase() !== "POST")
97
+ throw new Error("Only POST forms are supported for Actions");
98
+ const handler = actions.get(actionRef);
99
+ if (handler) {
100
+ evt.preventDefault();
101
+ const data = new FormData(evt.target);
102
+ handler.call(router, data);
103
+ }
104
+ }
105
+ // ensure delegated event run first
106
+ delegateEvents(["click", "submit"]);
107
+ document.addEventListener("click", handleAnchorClick);
108
+ if (preload) {
109
+ document.addEventListener("mouseover", handleAnchorIn);
110
+ document.addEventListener("mouseout", handleAnchorOut);
111
+ document.addEventListener("focusin", handleAnchorPreload);
112
+ document.addEventListener("touchstart", handleAnchorPreload);
113
+ }
114
+ document.addEventListener("submit", handleFormSubmit);
115
+ onCleanup(() => {
116
+ document.removeEventListener("click", handleAnchorClick);
117
+ if (preload) {
118
+ document.removeEventListener("mouseover", handleAnchorIn);
119
+ document.removeEventListener("mouseout", handleAnchorOut);
120
+ document.removeEventListener("focusin", handleAnchorPreload);
121
+ document.removeEventListener("touchstart", handleAnchorPreload);
122
+ }
123
+ document.removeEventListener("submit", handleFormSubmit);
124
+ });
125
+ };
118
126
  }
package/dist/index.js CHANGED
@@ -421,7 +421,6 @@ function createRouterContext(integration, getBranches, options = {}) {
421
421
  });
422
422
  return {
423
423
  base: baseRoute,
424
- actionBase: options.actionBase || "/_server",
425
424
  location,
426
425
  isRouting,
427
426
  renderPath,
@@ -587,8 +586,7 @@ function createRouteContext(router, parent, outlet, match, params) {
587
586
 
588
587
  const createRouterComponent = router => props => {
589
588
  const {
590
- base,
591
- actionBase
589
+ base
592
590
  } = props;
593
591
  const routeDefs = children(() => props.children);
594
592
  const branches = createMemo(() => createBranches(props.root ? {
@@ -596,8 +594,7 @@ const createRouterComponent = router => props => {
596
594
  children: routeDefs()
597
595
  } : routeDefs(), props.base || ""));
598
596
  const routerState = createRouterContext(router, branches, {
599
- base,
600
- actionBase
597
+ base
601
598
  });
602
599
  router.create && router.create(routerState);
603
600
  return createComponent$1(RouterContextObj.Provider, {
@@ -768,22 +765,25 @@ if (!isServer) {
768
765
  function getCache() {
769
766
  if (!isServer) return cacheMap;
770
767
  const req = getRequestEvent() || sharedConfig.context;
768
+ if (!req) throw new Error("Cannot find cache context");
771
769
  return req.routerCache || (req.routerCache = new Map());
772
770
  }
773
- function revalidate(key) {
774
- key && !Array.isArray(key) && (key = [key]);
771
+ function revalidate(key, force = true) {
775
772
  return startTransition(() => {
776
773
  const now = Date.now();
777
- for (let k of cacheMap.keys()) {
778
- if (key === undefined || matchKey(k, key)) {
779
- const entry = cacheMap.get(k);
780
- entry[0] = 0; //force cache miss
781
- revalidateSignals(entry[3], now); // retrigger live signals
782
- }
783
- }
774
+ cacheKeyOp(key, entry => {
775
+ force && (entry[0] = 0); //force cache miss
776
+ revalidateSignals(entry[3], now); // retrigger live signals
777
+ });
784
778
  });
785
779
  }
786
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
+ }
787
787
  function revalidateSignals(set, time) {
788
788
  for (let s of set) s[1](time);
789
789
  }
@@ -825,7 +825,8 @@ function cache(fn, name, options) {
825
825
 
826
826
  // serialize on server
827
827
  if (isServer && sharedConfig.context && !sharedConfig.context.noHydrate) {
828
- sharedConfig.context && sharedConfig.context.serialize(key, res);
828
+ const e = getRequestEvent();
829
+ (!e || !e.serverOnly) && sharedConfig.context.serialize(key, res);
829
830
  }
830
831
  if (cached) {
831
832
  cached[0] = now;
@@ -868,6 +869,25 @@ function cache(fn, name, options) {
868
869
  cachedFn.key = name;
869
870
  return cachedFn;
870
871
  }
872
+ cache.set = (key, value) => {
873
+ const cache = getCache();
874
+ const now = Date.now();
875
+ let cached = cache.get(key);
876
+ let version;
877
+ if (getOwner()) {
878
+ version = createSignal(now, {
879
+ equals: (p, v) => v - p < 50 // margin of error
880
+ });
881
+
882
+ onCleanup(() => cached[3].delete(version));
883
+ }
884
+ if (cached) {
885
+ cached[0] = now;
886
+ cached[1] = value;
887
+ cached[2] = "preload";
888
+ version && cached[3].add(version);
889
+ } else cache.set(key, cached = [now, value,, new Set(version ? [version] : [])]);
890
+ };
871
891
  function matchKey(key, keys) {
872
892
  for (let k of keys) {
873
893
  if (key.startsWith(k)) return true;
@@ -947,7 +967,7 @@ function action(fn, name) {
947
967
  p.then(handler, handler);
948
968
  return p;
949
969
  }
950
- const url = fn.url || name && `action:${name}` || (!isServer ? `action:${fn.name}` : "");
970
+ const url = fn.url || name && `action:${name}` || (!isServer ? `action:${hashString(fn.toString())}` : "");
951
971
  return toAction(mutate, url);
952
972
  }
953
973
  function toAction(fn, url) {
@@ -961,7 +981,7 @@ function toAction(fn, url) {
961
981
  };
962
982
  const uri = new URL(url, "http://sar");
963
983
  uri.searchParams.set("args", hashKey(args));
964
- return toAction(newFn, uri.pathname + uri.search);
984
+ return toAction(newFn, (uri.protocol === "action:" ? uri.protocol : "") + uri.pathname + uri.search);
965
985
  };
966
986
  fn.url = url;
967
987
  if (!isServer) {
@@ -970,12 +990,15 @@ function toAction(fn, url) {
970
990
  }
971
991
  return fn;
972
992
  }
993
+ const hashString = s => s.split("").reduce((a, b) => (a << 5) - a + b.charCodeAt(0) | 0, 0);
973
994
  async function handleResponse(response, navigate) {
974
995
  let data;
975
996
  let keys;
976
997
  if (response instanceof Response) {
977
998
  if (response.headers.has("X-Revalidate")) {
978
999
  keys = response.headers.get("X-Revalidate").split(",");
1000
+ // invalidate
1001
+ cacheKeyOp(keys, entry => entry[0] = 0);
979
1002
  }
980
1003
  if (response.customBody) data = await response.customBody();
981
1004
  if (redirectStatusCodes.has(response.status)) {
@@ -987,102 +1010,110 @@ async function handleResponse(response, navigate) {
987
1010
  }
988
1011
  }
989
1012
  } else data = response;
990
- await revalidate(keys);
1013
+ // trigger revalidation
1014
+ await revalidate(keys, false);
991
1015
  return data;
992
1016
  }
993
1017
 
994
- function setupNativeEvents(router) {
995
- const basePath = router.base.path();
996
- const navigateFromRoute = router.navigatorFactory(router.base);
997
- let preloadTimeout = {};
998
- function isSvg(el) {
999
- return el.namespaceURI === "http://www.w3.org/2000/svg";
1000
- }
1001
- function handleAnchor(evt) {
1002
- if (evt.defaultPrevented || evt.button !== 0 || evt.metaKey || evt.altKey || evt.ctrlKey || evt.shiftKey) return;
1003
- const a = evt.composedPath().find(el => el instanceof Node && el.nodeName.toUpperCase() === "A");
1004
- if (!a) return;
1005
- const svg = isSvg(a);
1006
- const href = svg ? a.href.baseVal : a.href;
1007
- const target = svg ? a.target.baseVal : a.target;
1008
- if (target || !href && !a.hasAttribute("state")) return;
1009
- const rel = (a.getAttribute("rel") || "").split(/\s+/);
1010
- if (a.hasAttribute("download") || rel && rel.includes("external")) return;
1011
- const url = svg ? new URL(href, document.baseURI) : new URL(href);
1012
- if (url.origin !== window.location.origin || basePath && url.pathname && !url.pathname.toLowerCase().startsWith(basePath.toLowerCase())) return;
1013
- return [a, url];
1014
- }
1015
- function handleAnchorClick(evt) {
1016
- const res = handleAnchor(evt);
1017
- if (!res) return;
1018
- const [a, url] = res;
1019
- const to = router.parsePath(url.pathname + url.search + url.hash);
1020
- const state = a.getAttribute("state");
1021
- evt.preventDefault();
1022
- navigateFromRoute(to, {
1023
- resolve: false,
1024
- replace: a.hasAttribute("replace"),
1025
- scroll: !a.hasAttribute("noscroll"),
1026
- state: state && JSON.parse(state)
1027
- });
1028
- }
1029
- function handleAnchorPreload(evt) {
1030
- const res = handleAnchor(evt);
1031
- if (!res) return;
1032
- const [a, url] = res;
1033
- if (!preloadTimeout[url.pathname]) router.preloadRoute(url, a.getAttribute("preload") !== "false");
1034
- }
1035
- function handleAnchorIn(evt) {
1036
- const res = handleAnchor(evt);
1037
- if (!res) return;
1038
- const [a, url] = res;
1039
- if (preloadTimeout[url.pathname]) return;
1040
- preloadTimeout[url.pathname] = setTimeout(() => {
1041
- router.preloadRoute(url, a.getAttribute("preload") !== "false");
1042
- delete preloadTimeout[url.pathname];
1043
- }, 200);
1044
- }
1045
- function handleAnchorOut(evt) {
1046
- const res = handleAnchor(evt);
1047
- if (!res) return;
1048
- const [, url] = res;
1049
- if (preloadTimeout[url.pathname]) {
1050
- clearTimeout(preloadTimeout[url.pathname]);
1051
- delete preloadTimeout[url.pathname];
1018
+ function setupNativeEvents(preload = true, explicitLinks = false, actionBase = "/_server") {
1019
+ return router => {
1020
+ const basePath = router.base.path();
1021
+ const navigateFromRoute = router.navigatorFactory(router.base);
1022
+ let preloadTimeout = {};
1023
+ function isSvg(el) {
1024
+ return el.namespaceURI === "http://www.w3.org/2000/svg";
1052
1025
  }
1053
- }
1054
- function handleFormSubmit(evt) {
1055
- let actionRef = evt.submitter && evt.submitter.hasAttribute("formaction") ? evt.submitter.formAction : evt.target.action;
1056
- if (!actionRef) return;
1057
- if (!actionRef.startsWith("action:")) {
1058
- const url = new URL(actionRef);
1059
- actionRef = router.parsePath(url.pathname + url.search);
1060
- if (!actionRef.startsWith(router.actionBase)) return;
1026
+ function handleAnchor(evt) {
1027
+ if (evt.defaultPrevented || evt.button !== 0 || evt.metaKey || evt.altKey || evt.ctrlKey || evt.shiftKey) return;
1028
+ const a = evt.composedPath().find(el => el instanceof Node && el.nodeName.toUpperCase() === "A");
1029
+ if (!a || explicitLinks && !a.getAttribute("link")) return;
1030
+ const svg = isSvg(a);
1031
+ const href = svg ? a.href.baseVal : a.href;
1032
+ const target = svg ? a.target.baseVal : a.target;
1033
+ if (target || !href && !a.hasAttribute("state")) return;
1034
+ const rel = (a.getAttribute("rel") || "").split(/\s+/);
1035
+ if (a.hasAttribute("download") || rel && rel.includes("external")) return;
1036
+ const url = svg ? new URL(href, document.baseURI) : new URL(href);
1037
+ if (url.origin !== window.location.origin || basePath && url.pathname && !url.pathname.toLowerCase().startsWith(basePath.toLowerCase())) return;
1038
+ return [a, url];
1061
1039
  }
1062
- const handler = actions.get(actionRef);
1063
- if (handler) {
1040
+ function handleAnchorClick(evt) {
1041
+ const res = handleAnchor(evt);
1042
+ if (!res) return;
1043
+ const [a, url] = res;
1044
+ const to = router.parsePath(url.pathname + url.search + url.hash);
1045
+ const state = a.getAttribute("state");
1064
1046
  evt.preventDefault();
1065
- const data = new FormData(evt.target);
1066
- handler.call(router, data);
1047
+ navigateFromRoute(to, {
1048
+ resolve: false,
1049
+ replace: a.hasAttribute("replace"),
1050
+ scroll: !a.hasAttribute("noscroll"),
1051
+ state: state && JSON.parse(state)
1052
+ });
1053
+ }
1054
+ function handleAnchorPreload(evt) {
1055
+ const res = handleAnchor(evt);
1056
+ if (!res) return;
1057
+ const [a, url] = res;
1058
+ if (!preloadTimeout[url.pathname]) router.preloadRoute(url, a.getAttribute("preload") !== "false");
1059
+ }
1060
+ function handleAnchorIn(evt) {
1061
+ const res = handleAnchor(evt);
1062
+ if (!res) return;
1063
+ const [a, url] = res;
1064
+ if (preloadTimeout[url.pathname]) return;
1065
+ preloadTimeout[url.pathname] = setTimeout(() => {
1066
+ router.preloadRoute(url, a.getAttribute("preload") !== "false");
1067
+ delete preloadTimeout[url.pathname];
1068
+ }, 200);
1069
+ }
1070
+ function handleAnchorOut(evt) {
1071
+ const res = handleAnchor(evt);
1072
+ if (!res) return;
1073
+ const [, url] = res;
1074
+ if (preloadTimeout[url.pathname]) {
1075
+ clearTimeout(preloadTimeout[url.pathname]);
1076
+ delete preloadTimeout[url.pathname];
1077
+ }
1078
+ }
1079
+ function handleFormSubmit(evt) {
1080
+ let actionRef = evt.submitter && evt.submitter.hasAttribute("formaction") ? evt.submitter.formAction : evt.target.action;
1081
+ if (!actionRef) return;
1082
+ if (!actionRef.startsWith("action:")) {
1083
+ const url = new URL(actionRef);
1084
+ actionRef = router.parsePath(url.pathname + url.search);
1085
+ if (!actionRef.startsWith(actionBase)) return;
1086
+ }
1087
+ if (evt.target.method.toUpperCase() !== "POST") throw new Error("Only POST forms are supported for Actions");
1088
+ const handler = actions.get(actionRef);
1089
+ if (handler) {
1090
+ evt.preventDefault();
1091
+ const data = new FormData(evt.target);
1092
+ handler.call(router, data);
1093
+ }
1067
1094
  }
1068
- }
1069
1095
 
1070
- // ensure delegated event run first
1071
- delegateEvents(["click", "submit"]);
1072
- document.addEventListener("click", handleAnchorClick);
1073
- document.addEventListener("mouseover", handleAnchorIn);
1074
- document.addEventListener("mouseout", handleAnchorOut);
1075
- document.addEventListener("focusin", handleAnchorPreload);
1076
- document.addEventListener("touchstart", handleAnchorPreload);
1077
- document.addEventListener("submit", handleFormSubmit);
1078
- onCleanup(() => {
1079
- document.removeEventListener("click", handleAnchorClick);
1080
- document.removeEventListener("mouseover", handleAnchorIn);
1081
- document.removeEventListener("mouseout", handleAnchorOut);
1082
- document.removeEventListener("focusin", handleAnchorPreload);
1083
- document.removeEventListener("touchstart", handleAnchorPreload);
1084
- document.removeEventListener("submit", handleFormSubmit);
1085
- });
1096
+ // ensure delegated event run first
1097
+ delegateEvents(["click", "submit"]);
1098
+ document.addEventListener("click", handleAnchorClick);
1099
+ if (preload) {
1100
+ document.addEventListener("mouseover", handleAnchorIn);
1101
+ document.addEventListener("mouseout", handleAnchorOut);
1102
+ document.addEventListener("focusin", handleAnchorPreload);
1103
+ document.addEventListener("touchstart", handleAnchorPreload);
1104
+ }
1105
+ document.addEventListener("submit", handleFormSubmit);
1106
+ onCleanup(() => {
1107
+ document.removeEventListener("click", handleAnchorClick);
1108
+ if (preload) {
1109
+ document.removeEventListener("mouseover", handleAnchorIn);
1110
+ document.removeEventListener("mouseout", handleAnchorOut);
1111
+ document.removeEventListener("focusin", handleAnchorPreload);
1112
+ document.removeEventListener("touchstart", handleAnchorPreload);
1113
+ }
1114
+ document.removeEventListener("submit", handleFormSubmit);
1115
+ });
1116
+ };
1086
1117
  }
1087
1118
 
1088
1119
  function Router(props) {
@@ -1106,7 +1137,7 @@ function Router(props) {
1106
1137
  scrollToHash(window.location.hash.slice(1), scroll);
1107
1138
  },
1108
1139
  init: notify => bindEvent(window, "popstate", () => notify()),
1109
- create: setupNativeEvents,
1140
+ create: setupNativeEvents(props.preload, props.explicitLinks, props.actionBase),
1110
1141
  utils: {
1111
1142
  go: delta => window.history.go(delta)
1112
1143
  }
@@ -1143,7 +1174,7 @@ function HashRouter(props) {
1143
1174
  scrollToHash(hash, scroll);
1144
1175
  },
1145
1176
  init: notify => bindEvent(window, "hashchange", () => notify()),
1146
- create: setupNativeEvents,
1177
+ create: setupNativeEvents(props.preload, props.explicitLinks, props.actionBase),
1147
1178
  utils: {
1148
1179
  go: delta => window.history.go(delta),
1149
1180
  renderPath: path => `#${path}`,
@@ -1243,6 +1274,7 @@ function A(props) {
1243
1274
  ...rest.classList
1244
1275
  };
1245
1276
  },
1277
+ "link": "",
1246
1278
  get ["aria-current"]() {
1247
1279
  return isActive() ? "page" : undefined;
1248
1280
  }
@@ -1,5 +1,9 @@
1
1
  import type { JSX } from "solid-js";
2
2
  import type { BaseRouterProps } from "./components";
3
3
  export declare function hashParser(str: string): string;
4
- export type HashRouterProps = BaseRouterProps;
4
+ export type HashRouterProps = BaseRouterProps & {
5
+ actionBase?: string;
6
+ explicitLinks?: boolean;
7
+ preload?: boolean;
8
+ };
5
9
  export declare function HashRouter(props: HashRouterProps): JSX.Element;
@@ -26,7 +26,7 @@ export function HashRouter(props) {
26
26
  scrollToHash(hash, scroll);
27
27
  },
28
28
  init: notify => bindEvent(window, "hashchange", () => notify()),
29
- create: setupNativeEvents,
29
+ create: setupNativeEvents(props.preload, props.explicitLinks, props.actionBase),
30
30
  utils: {
31
31
  go: delta => window.history.go(delta),
32
32
  renderPath: path => `#${path}`,
@@ -2,5 +2,8 @@ import type { BaseRouterProps } from "./components";
2
2
  import type { JSX } from "solid-js";
3
3
  export type RouterProps = BaseRouterProps & {
4
4
  url?: string;
5
+ actionBase?: string;
6
+ explicitLinks?: boolean;
7
+ preload?: boolean;
5
8
  };
6
9
  export declare function Router(props: RouterProps): JSX.Element;
@@ -20,7 +20,7 @@ export function Router(props) {
20
20
  scrollToHash(window.location.hash.slice(1), scroll);
21
21
  },
22
22
  init: notify => bindEvent(window, "popstate", () => notify()),
23
- create: setupNativeEvents,
23
+ create: setupNativeEvents(props.preload, props.explicitLinks, props.actionBase),
24
24
  utils: {
25
25
  go: delta => window.history.go(delta)
26
26
  }
@@ -2,7 +2,6 @@ import type { Component, JSX } from "solid-js";
2
2
  import type { MatchFilters, RouteLoadFunc, RouterIntegration, RouteSectionProps } from "../types";
3
3
  export type BaseRouterProps = {
4
4
  base?: string;
5
- actionBase?: string;
6
5
  root?: Component<RouteSectionProps>;
7
6
  children?: JSX.Element;
8
7
  };
@@ -3,10 +3,10 @@ import { children, createMemo, createRoot, mergeProps, on, Show } from "solid-js
3
3
  import { createBranches, createRouteContext, createRouterContext, getRouteMatches, RouteContextObj, RouterContextObj } from "../routing";
4
4
  import { createMemoObject } from "../utils";
5
5
  export const createRouterComponent = (router) => (props) => {
6
- const { base, actionBase } = props;
6
+ const { base } = props;
7
7
  const routeDefs = children(() => props.children);
8
8
  const branches = createMemo(() => createBranches(props.root ? { component: props.root, children: routeDefs() } : routeDefs(), props.base || ""));
9
- const routerState = createRouterContext(router, branches, { base, actionBase });
9
+ const routerState = createRouterContext(router, branches, { base });
10
10
  router.create && router.create(routerState);
11
11
  return (<RouterContextObj.Provider value={routerState}>
12
12
  <Routes routerState={routerState} branches={branches()}/>
package/dist/routing.d.ts CHANGED
@@ -21,6 +21,5 @@ 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
- actionBase?: string;
25
24
  }): RouterContext;
26
25
  export declare function createRouteContext(router: RouterContext, parent: RouteContext, outlet: () => JSX.Element, match: () => RouteMatch, params: Params): RouteContext;
package/dist/routing.js CHANGED
@@ -231,7 +231,6 @@ export function createRouterContext(integration, getBranches, options = {}) {
231
231
  });
232
232
  return {
233
233
  base: baseRoute,
234
- actionBase: options.actionBase || "/_server",
235
234
  location,
236
235
  isRouting,
237
236
  renderPath,
package/dist/types.d.ts CHANGED
@@ -4,6 +4,7 @@ declare module "solid-js/web" {
4
4
  response?: Response;
5
5
  routerCache?: Map<any, any>;
6
6
  initialSubmission?: Submission<any, any>;
7
+ serverOnly?: boolean;
7
8
  }
8
9
  }
9
10
  export type Params = Record<string, string>;
@@ -118,7 +119,6 @@ export interface RouterOutput {
118
119
  }
119
120
  export interface RouterContext {
120
121
  base: RouteContext;
121
- actionBase: string;
122
122
  location: Location;
123
123
  navigatorFactory: NavigatorFactory;
124
124
  isRouting: () => boolean;
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "Ryan Turnquist"
7
7
  ],
8
8
  "license": "MIT",
9
- "version": "0.10.0-beta.8",
9
+ "version": "0.10.0",
10
10
  "homepage": "https://github.com/solidjs/solid-router#readme",
11
11
  "repository": {
12
12
  "type": "git",