@solidjs/router 0.10.0-beta.0 → 0.10.0-beta.2

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
@@ -372,14 +372,12 @@ This cache can be defined anywhere and then used inside your components with:
372
372
 
373
373
  ### `createAsync`
374
374
 
375
- This is light wrapper over `createResource` that aims to serve as stand-in for a future primitive we intend to bring to Solid core in 2.0. It is a simpler async primitive where the function tracks like `createMemo` and it expects a promise back that it turns into a Signal. Reading it before it ready causes Suspense/Transitions to trigger.
375
+ This is light wrapper over `createResource` that aims to serve as stand-in for a future primitive we intend to bring to Solid core in 2.0. It is a simpler async primitive where the function tracks like `createMemo` and it expects a promise back that it turns into a Signal. Reading it before it is ready causes Suspense/Transitions to trigger.
376
376
 
377
377
  ```jsx
378
378
  const user = createAsync(() => getUser(params.id))
379
379
  ```
380
380
 
381
- `createAsync` is designed to only work with cached functions otherwise it will over fetch. If not using `cache`, continue using `createResource` instead.
382
-
383
381
  ### `action`
384
382
 
385
383
  Actions are data mutations that can trigger invalidations and further routing. A list of prebuilt response builders can be found below(TODO).
@@ -9,20 +9,19 @@ export const Router = (props) => {
9
9
  const { source, url, base } = props;
10
10
  const integration = source ||
11
11
  (isServer
12
- ? staticIntegration({ value: url || ((e = getRequestEvent()) && e.request.url) || "" })
12
+ ? staticIntegration({ value: url || ((e = getRequestEvent()) && getPath(e.request.url)) || "" })
13
13
  : pathIntegration());
14
- const routeDefs = children(() => props.root
15
- ? {
16
- component: props.root,
17
- children: props.children
18
- }
19
- : props.children);
20
- const branches = createMemo(() => createBranches(routeDefs(), props.base || ""));
14
+ const routeDefs = children(() => props.children);
15
+ const branches = createMemo(() => createBranches(props.root ? { component: props.root, children: routeDefs() } : routeDefs(), props.base || ""));
21
16
  const routerState = createRouterContext(integration, branches, base);
22
17
  return (<RouterContextObj.Provider value={routerState}>
23
18
  <Routes routerState={routerState} branches={branches()}/>
24
19
  </RouterContextObj.Provider>);
25
20
  };
21
+ function getPath(url) {
22
+ const u = new URL(url);
23
+ return u.pathname + u.search;
24
+ }
26
25
  function Routes(props) {
27
26
  const matches = createMemo(() => getRouteMatches(props.branches, props.routerState.location.pathname));
28
27
  const params = createMemoObject(() => {
@@ -49,6 +49,11 @@ export function action(fn, name) {
49
49
  const [result, setResult] = createSignal();
50
50
  let submission;
51
51
  const router = this;
52
+ async function handler(res) {
53
+ const data = await handleResponse(res, router.navigatorFactory());
54
+ data ? setResult({ data }) : submission.clear();
55
+ return data;
56
+ }
52
57
  router.submissions[1](s => [
53
58
  ...s,
54
59
  (submission = {
@@ -66,29 +71,15 @@ export function action(fn, name) {
66
71
  retry() {
67
72
  setResult(undefined);
68
73
  const p = fn(variables);
69
- p.then(async (data) => {
70
- const keys = handleResponse(data, router.navigatorFactory());
71
- await revalidate(keys);
72
- data ? setResult({ data }) : submission.clear();
73
- return data;
74
- }).catch(error => {
75
- setResult({ data: error });
76
- });
74
+ p.then(handler, handler);
77
75
  return p;
78
76
  }
79
77
  })
80
78
  ]);
81
- p.then(async (data) => {
82
- const keys = handleResponse(data, router.navigatorFactory());
83
- await revalidate(keys);
84
- data ? setResult({ data }) : submission.clear();
85
- return data;
86
- }).catch(error => {
87
- setResult({ data: error });
88
- });
79
+ p.then(handler, handler);
89
80
  return p;
90
81
  }
91
- const url = fn.url || `action:${name}` || !isServer ? `action:${fn.name}` : "";
82
+ const url = fn.url || (name && `action:${name}`) || (!isServer ? `action:${fn.name}` : "");
92
83
  mutate.toString = () => {
93
84
  if (!url)
94
85
  throw new Error("Client Actions need explicit names if server rendered");
@@ -98,7 +89,8 @@ export function action(fn, name) {
98
89
  registerAction(url, mutate);
99
90
  return mutate;
100
91
  }
101
- function handleResponse(response, navigate) {
92
+ async function handleResponse(response, navigate) {
93
+ let data;
102
94
  if (response instanceof Response && redirectStatusCodes.has(response.status)) {
103
95
  const locationUrl = response.headers.get("Location") || "/";
104
96
  if (locationUrl.startsWith("http")) {
@@ -108,6 +100,9 @@ function handleResponse(response, navigate) {
108
100
  navigate(locationUrl);
109
101
  }
110
102
  }
111
- // return keys
112
- return;
103
+ else
104
+ data = response;
105
+ // TODO: handle keys
106
+ await revalidate();
107
+ return data;
113
108
  }
@@ -1,3 +1,3 @@
1
1
  import { type ReconcileOptions } from "solid-js/store";
2
2
  export declare function revalidate(key?: string | any[] | void): Promise<void>;
3
- export declare function cache<T extends (...args: any) => U | Response, U>(fn: T, name: string, options: ReconcileOptions): T;
3
+ export declare function cache<T extends (...args: any) => U | Response, U>(fn: T, name: string, options?: ReconcileOptions): T;
@@ -52,7 +52,7 @@ export function cache(fn, name, options) {
52
52
  cached[0] = now;
53
53
  cached[1] =
54
54
  "then" in cached[1]
55
- ? cached[1].then(handleResponse)
55
+ ? cached[1].then(handleResponse, handleResponse)
56
56
  : handleResponse(cached[1]);
57
57
  cached[2] = intent;
58
58
  }
@@ -61,11 +61,17 @@ export function cache(fn, name, options) {
61
61
  }
62
62
  return cached[1];
63
63
  }
64
- let res = fn(...args);
64
+ let res = !isServer && sharedConfig.context && sharedConfig.load
65
+ ? sharedConfig.load(key) // hydrating
66
+ : fn(...args);
67
+ // serialize on server
68
+ if (isServer && sharedConfig.context && !sharedConfig.context.noHydrate) {
69
+ sharedConfig.context && sharedConfig.context.serialize(key, res);
70
+ }
65
71
  if (intent !== "preload") {
66
72
  res =
67
73
  "then" in res
68
- ? res.then(handleResponse)
74
+ ? res.then(handleResponse, handleResponse)
69
75
  : handleResponse(res);
70
76
  }
71
77
  if (cached) {
@@ -80,25 +86,27 @@ export function cache(fn, name, options) {
80
86
  else
81
87
  cache.set(key, (cached = [now, res, intent, new Set(version ? [version] : [])]));
82
88
  return res;
83
- function handleRedirect(response) {
84
- startTransition(() => {
85
- let url = response.headers.get(LocationHeader);
86
- if (url && url.startsWith("/")) {
87
- navigate(url, {
88
- replace: true
89
- });
90
- }
91
- else if (!isServer && url) {
92
- window.location.href = url;
93
- }
94
- });
95
- }
96
89
  function handleResponse(v) {
97
90
  if (v instanceof Response && redirectStatusCodes.has(v.status)) {
98
- if (navigate)
99
- isServer ? handleRedirect(v) : setTimeout(() => handleRedirect(v), 0);
91
+ if (navigate) {
92
+ startTransition(() => {
93
+ let url = v.headers.get(LocationHeader);
94
+ if (url && url.startsWith("/")) {
95
+ navigate(url, {
96
+ replace: true
97
+ });
98
+ }
99
+ else if (!isServer && url) {
100
+ window.location.href = url;
101
+ }
102
+ });
103
+ }
100
104
  return;
101
105
  }
106
+ if (v instanceof Error)
107
+ throw v;
108
+ if (isServer)
109
+ return v;
102
110
  setStore(key, reconcile(v, options));
103
111
  return store[key];
104
112
  }
@@ -1,5 +1,14 @@
1
1
  /**
2
2
  * This is mock of the eventual Solid 2.0 primitive. It is not fully featured.
3
3
  */
4
- import { Accessor } from "solid-js";
5
- export declare function createAsync<T>(fn: () => Promise<T>): Accessor<T>;
4
+ import { type Accessor } from "solid-js";
5
+ export declare function createAsync<T>(fn: () => Promise<T>, options: {
6
+ name?: string;
7
+ initialValue: T;
8
+ deferStream?: boolean;
9
+ }): Accessor<T>;
10
+ export declare function createAsync<T>(fn: () => Promise<T>, options?: {
11
+ name?: string;
12
+ initialValue?: T;
13
+ deferStream?: boolean;
14
+ }): Accessor<T | undefined>;
@@ -1,8 +1,54 @@
1
1
  /**
2
2
  * This is mock of the eventual Solid 2.0 primitive. It is not fully featured.
3
3
  */
4
- import { createResource } from "solid-js";
5
- export function createAsync(fn) {
6
- const [resource] = createResource(() => fn(), v => v);
4
+ import { createResource, sharedConfig } from "solid-js";
5
+ import { isServer } from "solid-js/web";
6
+ export function createAsync(fn, options) {
7
+ const [resource] = createResource(() => subFetch(fn), v => v, options);
7
8
  return () => resource();
8
9
  }
10
+ // mock promise while hydrating to prevent fetching
11
+ class MockPromise {
12
+ static all() {
13
+ return new MockPromise();
14
+ }
15
+ static allSettled() {
16
+ return new MockPromise();
17
+ }
18
+ static any() {
19
+ return new MockPromise();
20
+ }
21
+ static race() {
22
+ return new MockPromise();
23
+ }
24
+ static reject() {
25
+ return new MockPromise();
26
+ }
27
+ static resolve() {
28
+ return new MockPromise();
29
+ }
30
+ catch() {
31
+ return new MockPromise();
32
+ }
33
+ then() {
34
+ return new MockPromise();
35
+ }
36
+ finally() {
37
+ return new MockPromise();
38
+ }
39
+ }
40
+ function subFetch(fn) {
41
+ if (isServer || !sharedConfig.context)
42
+ return fn();
43
+ const ogFetch = fetch;
44
+ const ogPromise = Promise;
45
+ try {
46
+ window.fetch = () => new MockPromise();
47
+ Promise = MockPromise;
48
+ return fn();
49
+ }
50
+ finally {
51
+ window.fetch = ogFetch;
52
+ Promise = ogPromise;
53
+ }
54
+ }
@@ -1,3 +1,4 @@
1
1
  export { createAsync } from "./createAsync";
2
2
  export { action, useSubmission, useSubmissions } from "./action";
3
3
  export { cache, revalidate } from "./cache";
4
+ export { redirect } from "./response";
@@ -1,3 +1,4 @@
1
1
  export { createAsync } from "./createAsync";
2
2
  export { action, useSubmission, useSubmissions } from "./action";
3
3
  export { cache, revalidate } from "./cache";
4
+ export { redirect } from "./response";
@@ -0,0 +1 @@
1
+ export declare function redirect(url: string, init?: number | ResponseInit): Response;
@@ -0,0 +1,16 @@
1
+ export function redirect(url, init = 302) {
2
+ let responseInit = init;
3
+ if (typeof responseInit === "number") {
4
+ responseInit = { status: responseInit };
5
+ }
6
+ else if (typeof responseInit.status === "undefined") {
7
+ responseInit.status = 302;
8
+ }
9
+ const headers = new Headers(responseInit.headers);
10
+ headers.set("Location", url);
11
+ const response = new Response(null, {
12
+ ...responseInit,
13
+ headers: headers
14
+ });
15
+ return response;
16
+ }
package/dist/index.d.ts CHANGED
@@ -4,4 +4,4 @@ export * from "./lifecycle";
4
4
  export { useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, } from "./routing";
5
5
  export { mergeSearchString as _mergeSearchString } from "./utils";
6
6
  export * from "./data";
7
- export type { Location, LocationChange, LocationChangeSignal, NavigateOptions, Navigator, OutputMatch, Params, RouteLoadFunc, RouteLoadFuncArgs, RouteDefinition, RouterIntegration, RouterOutput, RouterUtils, SetParams, BeforeLeaveEventArgs } from "./types";
7
+ export type { Location, LocationChange, LocationChangeSignal, NavigateOptions, Navigator, OutputMatch, Params, RouteSectionProps, RouteLoadFunc, RouteLoadFuncArgs, RouteDefinition, RouterIntegration, RouterOutput, RouterUtils, SetParams, BeforeLeaveEventArgs } from "./types";
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { isServer, delegateEvents, getRequestEvent, createComponent as createComponent$1, spread, mergeProps as mergeProps$1, template } from 'solid-js/web';
2
- import { createSignal, onCleanup, getOwner, runWithOwner, createMemo, createContext, useContext, untrack, createRenderEffect, on, startTransition, createComponent, resetErrorBoundaries, children, createRoot, Show, mergeProps, splitProps, createResource, sharedConfig, $TRACK } from 'solid-js';
2
+ import { createSignal, onCleanup, getOwner, runWithOwner, createMemo, createContext, useContext, untrack, createRenderEffect, on, startTransition, resetErrorBoundaries, createComponent, children, createRoot, Show, mergeProps, splitProps, createResource, sharedConfig, $TRACK } from 'solid-js';
3
3
  import { createStore, reconcile } from 'solid-js/store';
4
4
 
5
5
  function bindEvent(target, type, handler) {
@@ -501,7 +501,7 @@ function createLocation(path, state) {
501
501
  const pathname = createMemo(() => url().pathname);
502
502
  const search = createMemo(() => url().search, true);
503
503
  const hash = createMemo(() => url().hash);
504
- const key = createMemo(() => "");
504
+ const key = () => "";
505
505
  return {
506
506
  get pathname() {
507
507
  return pathname();
@@ -570,6 +570,16 @@ function createRouterContext(integration, getBranches, base = "") {
570
570
  return resolvePath(basePath, to);
571
571
  }
572
572
  };
573
+ const router = {
574
+ base: baseRoute,
575
+ location,
576
+ isRouting,
577
+ renderPath,
578
+ parsePath,
579
+ navigatorFactory,
580
+ beforeLeave,
581
+ submissions: createSignal(submissions)
582
+ };
573
583
  function navigateFromRoute(route, to, options) {
574
584
  // Untrack in case someone navigates in an effect - don't want to track `reference` or route paths
575
585
  untrack(() => {
@@ -602,7 +612,12 @@ function createRouterContext(integration, getBranches, base = "") {
602
612
  if (resolvedTo !== current || nextState !== state()) {
603
613
  if (isServer) {
604
614
  const e = getRequestEvent();
605
- e && (e.response = Response.redirect(resolvedTo, 302));
615
+ e && (e.response = new Response(null, {
616
+ status: 302,
617
+ headers: {
618
+ Location: resolvedTo
619
+ }
620
+ }));
606
621
  setSource({
607
622
  value: resolvedTo,
608
623
  replace,
@@ -703,9 +718,9 @@ function createRouterContext(integration, getBranches, base = "") {
703
718
  state: state && JSON.parse(state)
704
719
  });
705
720
  }
706
- function doPreload(a, path) {
721
+ function doPreload(a, url) {
707
722
  const preload = a.getAttribute("preload") !== "false";
708
- const matches = getRouteMatches(getBranches(), path);
723
+ const matches = getRouteMatches(getBranches(), url.pathname);
709
724
  const prevIntent = intent;
710
725
  intent = "preload";
711
726
  for (let match in matches) {
@@ -716,7 +731,15 @@ function createRouterContext(integration, getBranches, base = "") {
716
731
  route.component && route.component.preload && route.component.preload();
717
732
  preload && route.load && route.load({
718
733
  params,
719
- location
734
+ location: {
735
+ pathname: url.pathname,
736
+ search: url.search,
737
+ hash: url.hash,
738
+ query: extractSearchParams(url),
739
+ state: null,
740
+ key: ""
741
+ },
742
+ intent
720
743
  });
721
744
  }
722
745
  intent = prevIntent;
@@ -725,7 +748,7 @@ function createRouterContext(integration, getBranches, base = "") {
725
748
  const res = handleAnchor(evt);
726
749
  if (!res) return;
727
750
  const [a, url] = res;
728
- if (!preloadTimeout[url.pathname]) doPreload(a, url.pathname);
751
+ if (!preloadTimeout[url.pathname]) doPreload(a, url);
729
752
  }
730
753
  function handleAnchorIn(evt) {
731
754
  const res = handleAnchor(evt);
@@ -733,9 +756,9 @@ function createRouterContext(integration, getBranches, base = "") {
733
756
  const [a, url] = res;
734
757
  if (preloadTimeout[url.pathname]) return;
735
758
  preloadTimeout[url.pathname] = setTimeout(() => {
736
- doPreload(a, url.pathname);
759
+ doPreload(a, url);
737
760
  delete preloadTimeout[url.pathname];
738
- }, 50);
761
+ }, 200);
739
762
  }
740
763
  function handleAnchorOut(evt) {
741
764
  const res = handleAnchor(evt);
@@ -750,7 +773,7 @@ function createRouterContext(integration, getBranches, base = "") {
750
773
  let actionRef = evt.submitter && evt.submitter.getAttribute("formaction") || evt.target.action;
751
774
  if (actionRef && actionRef.startsWith("action:")) {
752
775
  const data = new FormData(evt.target);
753
- actions.get(actionRef.slice(7))(data);
776
+ actions.get(actionRef).call(router, data);
754
777
  evt.preventDefault();
755
778
  }
756
779
  }
@@ -786,16 +809,7 @@ function createRouterContext(integration, getBranches, base = "") {
786
809
  }
787
810
  submissions = initFromFlash(location.query);
788
811
  }
789
- return {
790
- base: baseRoute,
791
- location,
792
- isRouting,
793
- renderPath,
794
- parsePath,
795
- navigatorFactory,
796
- beforeLeave,
797
- submissions: createSignal(submissions)
798
- };
812
+ return router;
799
813
  }
800
814
  function createRouteContext(router, parent, outlet, match, params) {
801
815
  const {
@@ -827,7 +841,8 @@ function createRouteContext(router, parent, outlet, match, params) {
827
841
  component && component.preload && component.preload();
828
842
  load && load({
829
843
  params,
830
- location
844
+ location,
845
+ intent: intent || "navigate"
831
846
  });
832
847
  return route;
833
848
  }
@@ -841,13 +856,13 @@ const Router = props => {
841
856
  base
842
857
  } = props;
843
858
  const integration = source || (isServer ? staticIntegration({
844
- value: url || (e = getRequestEvent()) && e.request.url || ""
859
+ value: url || (e = getRequestEvent()) && getPath(e.request.url) || ""
845
860
  }) : pathIntegration());
846
- const routeDefs = children(() => props.root ? {
861
+ const routeDefs = children(() => props.children);
862
+ const branches = createMemo(() => createBranches(props.root ? {
847
863
  component: props.root,
848
- children: props.children
849
- } : props.children);
850
- const branches = createMemo(() => createBranches(routeDefs(), props.base || ""));
864
+ children: routeDefs()
865
+ } : routeDefs(), props.base || ""));
851
866
  const routerState = createRouterContext(integration, branches, base);
852
867
  return createComponent$1(RouterContextObj.Provider, {
853
868
  value: routerState,
@@ -861,6 +876,10 @@ const Router = props => {
861
876
  }
862
877
  });
863
878
  };
879
+ function getPath(url) {
880
+ const u = new URL(url);
881
+ return u.pathname + u.search;
882
+ }
864
883
  function Routes(props) {
865
884
  const matches = createMemo(() => getRouteMatches(props.branches, props.routerState.location.pathname));
866
885
  const params = createMemoObject(() => {
@@ -997,11 +1016,55 @@ function Navigate(props) {
997
1016
  /**
998
1017
  * This is mock of the eventual Solid 2.0 primitive. It is not fully featured.
999
1018
  */
1000
- function createAsync(fn) {
1001
- const [resource] = createResource(() => fn(), v => v);
1019
+ function createAsync(fn, options) {
1020
+ const [resource] = createResource(() => subFetch(fn), v => v, options);
1002
1021
  return () => resource();
1003
1022
  }
1004
1023
 
1024
+ // mock promise while hydrating to prevent fetching
1025
+ class MockPromise {
1026
+ static all() {
1027
+ return new MockPromise();
1028
+ }
1029
+ static allSettled() {
1030
+ return new MockPromise();
1031
+ }
1032
+ static any() {
1033
+ return new MockPromise();
1034
+ }
1035
+ static race() {
1036
+ return new MockPromise();
1037
+ }
1038
+ static reject() {
1039
+ return new MockPromise();
1040
+ }
1041
+ static resolve() {
1042
+ return new MockPromise();
1043
+ }
1044
+ catch() {
1045
+ return new MockPromise();
1046
+ }
1047
+ then() {
1048
+ return new MockPromise();
1049
+ }
1050
+ finally() {
1051
+ return new MockPromise();
1052
+ }
1053
+ }
1054
+ function subFetch(fn) {
1055
+ if (isServer || !sharedConfig.context) return fn();
1056
+ const ogFetch = fetch;
1057
+ const ogPromise = Promise;
1058
+ try {
1059
+ window.fetch = () => new MockPromise();
1060
+ Promise = MockPromise;
1061
+ return fn();
1062
+ } finally {
1063
+ window.fetch = ogFetch;
1064
+ Promise = ogPromise;
1065
+ }
1066
+ }
1067
+
1005
1068
  const LocationHeader = "Location";
1006
1069
  const PRELOAD_TIMEOUT = 5000;
1007
1070
  let cacheMap = new Map();
@@ -1049,7 +1112,7 @@ function cache(fn, name, options) {
1049
1112
  version && cached[3].add(version);
1050
1113
  if (cached[2] === "preload" && intent !== "preload") {
1051
1114
  cached[0] = now;
1052
- cached[1] = "then" in cached[1] ? cached[1].then(handleResponse) : handleResponse(cached[1]);
1115
+ cached[1] = "then" in cached[1] ? cached[1].then(handleResponse, handleResponse) : handleResponse(cached[1]);
1053
1116
  cached[2] = intent;
1054
1117
  }
1055
1118
  if (!isServer && intent === "navigate") {
@@ -1058,9 +1121,15 @@ function cache(fn, name, options) {
1058
1121
 
1059
1122
  return cached[1];
1060
1123
  }
1061
- let res = fn(...args);
1124
+ let res = !isServer && sharedConfig.context && sharedConfig.load ? sharedConfig.load(key) // hydrating
1125
+ : fn(...args);
1126
+
1127
+ // serialize on server
1128
+ if (isServer && sharedConfig.context && !sharedConfig.context.noHydrate) {
1129
+ sharedConfig.context && sharedConfig.context.serialize(key, res);
1130
+ }
1062
1131
  if (intent !== "preload") {
1063
- res = "then" in res ? res.then(handleResponse) : handleResponse(res);
1132
+ res = "then" in res ? res.then(handleResponse, handleResponse) : handleResponse(res);
1064
1133
  }
1065
1134
  if (cached) {
1066
1135
  cached[0] = now;
@@ -1072,23 +1141,24 @@ function cache(fn, name, options) {
1072
1141
  }
1073
1142
  } else cache.set(key, cached = [now, res, intent, new Set(version ? [version] : [])]);
1074
1143
  return res;
1075
- function handleRedirect(response) {
1076
- startTransition(() => {
1077
- let url = response.headers.get(LocationHeader);
1078
- if (url && url.startsWith("/")) {
1079
- navigate(url, {
1080
- replace: true
1081
- });
1082
- } else if (!isServer && url) {
1083
- window.location.href = url;
1084
- }
1085
- });
1086
- }
1087
1144
  function handleResponse(v) {
1088
1145
  if (v instanceof Response && redirectStatusCodes.has(v.status)) {
1089
- if (navigate) isServer ? handleRedirect(v) : setTimeout(() => handleRedirect(v), 0);
1146
+ if (navigate) {
1147
+ startTransition(() => {
1148
+ let url = v.headers.get(LocationHeader);
1149
+ if (url && url.startsWith("/")) {
1150
+ navigate(url, {
1151
+ replace: true
1152
+ });
1153
+ } else if (!isServer && url) {
1154
+ window.location.href = url;
1155
+ }
1156
+ });
1157
+ }
1090
1158
  return;
1091
1159
  }
1160
+ if (v instanceof Error) throw v;
1161
+ if (isServer) return v;
1092
1162
  setStore(key, reconcile(v, options));
1093
1163
  return store[key];
1094
1164
  }
@@ -1135,6 +1205,13 @@ function action(fn, name) {
1135
1205
  const [result, setResult] = createSignal();
1136
1206
  let submission;
1137
1207
  const router = this;
1208
+ async function handler(res) {
1209
+ const data = await handleResponse(res, router.navigatorFactory());
1210
+ data ? setResult({
1211
+ data
1212
+ }) : submission.clear();
1213
+ return data;
1214
+ }
1138
1215
  router.submissions[1](s => [...s, submission = {
1139
1216
  input: variables,
1140
1217
  url,
@@ -1150,36 +1227,14 @@ function action(fn, name) {
1150
1227
  retry() {
1151
1228
  setResult(undefined);
1152
1229
  const p = fn(variables);
1153
- p.then(async data => {
1154
- const keys = handleResponse(data, router.navigatorFactory());
1155
- await revalidate(keys);
1156
- data ? setResult({
1157
- data
1158
- }) : submission.clear();
1159
- return data;
1160
- }).catch(error => {
1161
- setResult({
1162
- data: error
1163
- });
1164
- });
1230
+ p.then(handler, handler);
1165
1231
  return p;
1166
1232
  }
1167
1233
  }]);
1168
- p.then(async data => {
1169
- const keys = handleResponse(data, router.navigatorFactory());
1170
- await revalidate(keys);
1171
- data ? setResult({
1172
- data
1173
- }) : submission.clear();
1174
- return data;
1175
- }).catch(error => {
1176
- setResult({
1177
- data: error
1178
- });
1179
- });
1234
+ p.then(handler, handler);
1180
1235
  return p;
1181
1236
  }
1182
- const url = fn.url || `action:${name}` || !isServer ? `action:${fn.name}` : "";
1237
+ const url = fn.url || name && `action:${name}` || (!isServer ? `action:${fn.name}` : "");
1183
1238
  mutate.toString = () => {
1184
1239
  if (!url) throw new Error("Client Actions need explicit names if server rendered");
1185
1240
  return url;
@@ -1187,7 +1242,8 @@ function action(fn, name) {
1187
1242
  if (!isServer) registerAction(url, mutate);
1188
1243
  return mutate;
1189
1244
  }
1190
- function handleResponse(response, navigate) {
1245
+ async function handleResponse(response, navigate) {
1246
+ let data;
1191
1247
  if (response instanceof Response && redirectStatusCodes.has(response.status)) {
1192
1248
  const locationUrl = response.headers.get("Location") || "/";
1193
1249
  if (locationUrl.startsWith("http")) {
@@ -1195,9 +1251,28 @@ function handleResponse(response, navigate) {
1195
1251
  } else {
1196
1252
  navigate(locationUrl);
1197
1253
  }
1254
+ } else data = response;
1255
+ // TODO: handle keys
1256
+ await revalidate();
1257
+ return data;
1258
+ }
1259
+
1260
+ function redirect(url, init = 302) {
1261
+ let responseInit = init;
1262
+ if (typeof responseInit === "number") {
1263
+ responseInit = {
1264
+ status: responseInit
1265
+ };
1266
+ } else if (typeof responseInit.status === "undefined") {
1267
+ responseInit.status = 302;
1198
1268
  }
1199
- // return keys
1200
- return;
1269
+ const headers = new Headers(responseInit.headers);
1270
+ headers.set("Location", url);
1271
+ const response = new Response(null, {
1272
+ ...responseInit,
1273
+ headers: headers
1274
+ });
1275
+ return response;
1201
1276
  }
1202
1277
 
1203
- export { A, A as Link, A as NavLink, Navigate, Route, Router, mergeSearchString as _mergeSearchString, action, cache, createAsync, createBeforeLeave, createIntegration, createMemoryHistory, hashIntegration, memoryIntegration, normalizeIntegration, pathIntegration, revalidate, staticIntegration, useBeforeLeave, useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams, useSubmission, useSubmissions };
1278
+ export { A, A as Link, A as NavLink, Navigate, Route, Router, mergeSearchString as _mergeSearchString, action, cache, createAsync, createBeforeLeave, createIntegration, createMemoryHistory, hashIntegration, memoryIntegration, normalizeIntegration, pathIntegration, redirect, revalidate, staticIntegration, useBeforeLeave, useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams, useSubmission, useSubmissions };
package/dist/routing.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { JSX, Accessor } from "solid-js";
2
- import type { BeforeLeaveEventArgs, Branch, Location, LocationChangeSignal, MatchFilters, NavigateOptions, Navigator, Params, Route, RouteContext, RouteDefinition, RouteMatch, RouterContext, RouterIntegration, SetParams } from "./types";
1
+ import { JSX, Accessor } from "solid-js";
2
+ import type { BeforeLeaveEventArgs, Branch, Intent, Location, LocationChangeSignal, MatchFilters, NavigateOptions, Navigator, Params, Route, RouteContext, RouteDefinition, RouteMatch, RouterContext, RouterIntegration, SetParams } from "./types";
3
3
  export declare const RouterContextObj: import("solid-js").Context<RouterContext | undefined>;
4
4
  export declare const RouteContextObj: import("solid-js").Context<RouteContext | undefined>;
5
5
  export declare const useRouter: () => RouterContext;
@@ -19,6 +19,6 @@ export declare function createBranches(routeDef: RouteDefinition | RouteDefiniti
19
19
  export declare function getRouteMatches(branches: Branch[], location: string): RouteMatch[];
20
20
  export declare function createLocation(path: Accessor<string>, state: Accessor<any>): Location;
21
21
  export declare function registerAction(url: string, fn: Function): void;
22
- export declare function getIntent(): "native" | "navigate" | "preload" | undefined;
22
+ export declare function getIntent(): Intent | undefined;
23
23
  export declare function createRouterContext(integration?: RouterIntegration | LocationChangeSignal, getBranches?: () => Branch[], base?: string): RouterContext;
24
24
  export declare function createRouteContext(router: RouterContext, parent: RouteContext, outlet: () => JSX.Element, match: () => RouteMatch, params: Params): RouteContext;
package/dist/routing.js CHANGED
@@ -153,7 +153,7 @@ export function createLocation(path, state) {
153
153
  const pathname = createMemo(() => url().pathname);
154
154
  const search = createMemo(() => url().search, true);
155
155
  const hash = createMemo(() => url().hash);
156
- const key = createMemo(() => "");
156
+ const key = () => "";
157
157
  return {
158
158
  get pathname() {
159
159
  return pathname();
@@ -217,6 +217,16 @@ export function createRouterContext(integration, getBranches, base = "") {
217
217
  return resolvePath(basePath, to);
218
218
  }
219
219
  };
220
+ const router = {
221
+ base: baseRoute,
222
+ location,
223
+ isRouting,
224
+ renderPath,
225
+ parsePath,
226
+ navigatorFactory,
227
+ beforeLeave,
228
+ submissions: createSignal(submissions)
229
+ };
220
230
  function navigateFromRoute(route, to, options) {
221
231
  // Untrack in case someone navigates in an effect - don't want to track `reference` or route paths
222
232
  untrack(() => {
@@ -249,7 +259,8 @@ export function createRouterContext(integration, getBranches, base = "") {
249
259
  if (resolvedTo !== current || nextState !== state()) {
250
260
  if (isServer) {
251
261
  const e = getRequestEvent();
252
- e && (e.response = Response.redirect(resolvedTo, 302));
262
+ e &&
263
+ (e.response = new Response(null, { status: 302, headers: { Location: resolvedTo } }));
253
264
  setSource({ value: resolvedTo, replace, scroll, state: nextState });
254
265
  }
255
266
  else if (beforeLeave.confirm(resolvedTo, options)) {
@@ -352,9 +363,9 @@ export function createRouterContext(integration, getBranches, base = "") {
352
363
  state: state && JSON.parse(state)
353
364
  });
354
365
  }
355
- function doPreload(a, path) {
366
+ function doPreload(a, url) {
356
367
  const preload = a.getAttribute("preload") !== "false";
357
- const matches = getRouteMatches(getBranches(), path);
368
+ const matches = getRouteMatches(getBranches(), url.pathname);
358
369
  const prevIntent = intent;
359
370
  intent = "preload";
360
371
  for (let match in matches) {
@@ -362,7 +373,20 @@ export function createRouterContext(integration, getBranches, base = "") {
362
373
  route.component &&
363
374
  route.component.preload &&
364
375
  route.component.preload();
365
- preload && route.load && route.load({ params, location });
376
+ preload &&
377
+ route.load &&
378
+ route.load({
379
+ params,
380
+ location: {
381
+ pathname: url.pathname,
382
+ search: url.search,
383
+ hash: url.hash,
384
+ query: extractSearchParams(url),
385
+ state: null,
386
+ key: ""
387
+ },
388
+ intent
389
+ });
366
390
  }
367
391
  intent = prevIntent;
368
392
  }
@@ -372,7 +396,7 @@ export function createRouterContext(integration, getBranches, base = "") {
372
396
  return;
373
397
  const [a, url] = res;
374
398
  if (!preloadTimeout[url.pathname])
375
- doPreload(a, url.pathname);
399
+ doPreload(a, url);
376
400
  }
377
401
  function handleAnchorIn(evt) {
378
402
  const res = handleAnchor(evt);
@@ -382,9 +406,9 @@ export function createRouterContext(integration, getBranches, base = "") {
382
406
  if (preloadTimeout[url.pathname])
383
407
  return;
384
408
  preloadTimeout[url.pathname] = setTimeout(() => {
385
- doPreload(a, url.pathname);
409
+ doPreload(a, url);
386
410
  delete preloadTimeout[url.pathname];
387
- }, 50);
411
+ }, 200);
388
412
  }
389
413
  function handleAnchorOut(evt) {
390
414
  const res = handleAnchor(evt);
@@ -397,14 +421,13 @@ export function createRouterContext(integration, getBranches, base = "") {
397
421
  }
398
422
  }
399
423
  function handleFormSubmit(evt) {
400
- let actionRef = evt.submitter && evt.submitter.getAttribute("formaction") || evt.target.action;
424
+ let actionRef = (evt.submitter && evt.submitter.getAttribute("formaction")) || evt.target.action;
401
425
  if (actionRef && actionRef.startsWith("action:")) {
402
426
  const data = new FormData(evt.target);
403
- actions.get(actionRef.slice(7))(data);
427
+ actions.get(actionRef).call(router, data);
404
428
  evt.preventDefault();
405
429
  }
406
430
  }
407
- ;
408
431
  // ensure delegated event run first
409
432
  delegateEvents(["click", "submit"]);
410
433
  document.addEventListener("click", handleAnchorClick);
@@ -429,24 +452,17 @@ export function createRouterContext(integration, getBranches, base = "") {
429
452
  return [];
430
453
  }
431
454
  const input = new Map(param.entries);
432
- return [{
455
+ return [
456
+ {
433
457
  url: param.url,
434
458
  result: param.error ? new Error(param.result.message) : param.result,
435
459
  input: input
436
- }];
460
+ }
461
+ ];
437
462
  }
438
463
  submissions = initFromFlash(location.query);
439
464
  }
440
- return {
441
- base: baseRoute,
442
- location,
443
- isRouting,
444
- renderPath,
445
- parsePath,
446
- navigatorFactory,
447
- beforeLeave,
448
- submissions: createSignal(submissions)
449
- };
465
+ return router;
450
466
  }
451
467
  export function createRouteContext(router, parent, outlet, match, params) {
452
468
  const { base, location } = router;
@@ -473,6 +489,6 @@ export function createRouteContext(router, parent, outlet, match, params) {
473
489
  component &&
474
490
  component.preload &&
475
491
  component.preload();
476
- load && load({ params, location });
492
+ load && load({ params, location, intent: intent || "navigate" });
477
493
  return route;
478
494
  }
package/dist/types.d.ts CHANGED
@@ -39,16 +39,20 @@ export interface RouterIntegration {
39
39
  signal: LocationChangeSignal;
40
40
  utils?: Partial<RouterUtils>;
41
41
  }
42
+ export type Intent = "native" | "navigate" | "preload";
42
43
  export interface RouteLoadFuncArgs {
43
44
  params: Params;
44
45
  location: Location;
46
+ intent: Intent;
45
47
  }
46
48
  export type RouteLoadFunc = (args: RouteLoadFuncArgs) => void;
47
- export interface RouteSectionProps extends RouteLoadFuncArgs {
49
+ export interface RouteSectionProps {
50
+ params: Params;
51
+ location: Location;
48
52
  children?: JSX.Element;
49
53
  }
50
54
  export type RouteDefinition<S extends string | string[] = any> = {
51
- path: S;
55
+ path?: S;
52
56
  matchFilters?: MatchFilters<S>;
53
57
  load?: RouteLoadFunc;
54
58
  children?: RouteDefinition | RouteDefinition[];
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "Ryan Turnquist"
7
7
  ],
8
8
  "license": "MIT",
9
- "version": "0.10.0-beta.0",
9
+ "version": "0.10.0-beta.2",
10
10
  "homepage": "https://github.com/solidjs/solid-router#readme",
11
11
  "repository": {
12
12
  "type": "git",