@solidjs/router 0.14.10 → 0.15.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
@@ -427,30 +427,30 @@ The return value of the `preload` function is passed to the page component when
427
427
 
428
428
  Keep in mind these are completely optional. To use but showcase the power of our preload mechanism.
429
429
 
430
- ### `cache`
430
+ ### `query`
431
431
 
432
- To prevent duplicate fetching and to trigger handle refetching we provide a cache api. That takes a function and returns the same function.
432
+ To prevent duplicate fetching and to trigger handle refetching we provide a query api. That takes a function and returns the same function.
433
433
 
434
434
  ```jsx
435
- const getUser = cache(async (id) => {
435
+ const getUser = query(async (id) => {
436
436
  return (await fetch(`/api/users${id}`)).json()
437
- }, "users") // used as cache key + serialized arguments
437
+ }, "users") // used as the query key + serialized arguments
438
438
  ```
439
- It is expected that the arguments to the cache function are serializable.
439
+ It is expected that the arguments to the query function are serializable.
440
440
 
441
- This cache accomplishes the following:
441
+ This query accomplishes the following:
442
442
 
443
- 1. It does just deduping on the server for the lifetime of the request.
444
- 2. It does preload cache in the browser which lasts 5 seconds. When a route is preloaded on hover or when preload is called when entering a route it will make sure to dedupe calls.
443
+ 1. It does deduping on the server for the lifetime of the request.
444
+ 2. It fills a preload cache in the browser which lasts 5 seconds. When a route is preloaded on hover or when preload is called when entering a route it will make sure to dedupe calls.
445
445
  3. We have a reactive refetch mechanism based on key. So we can tell routes that aren't new to retrigger on action revalidation.
446
- 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.
446
+ 4. It will serve as a back/forward cache for browser navigation up to 5 mins. Any user based navigation or link click bypasses this cache. Revalidation or new fetch updates the cache.
447
447
 
448
448
  Using it with preload function might look like:
449
449
 
450
450
  ```js
451
451
  import { lazy } from "solid-js";
452
452
  import { Route } from "@solidjs/router";
453
- import { getUser } from ... // the cache function
453
+ import { getUser } from ... // the query function
454
454
 
455
455
  const User = lazy(() => import("./pages/users/[id].js"));
456
456
 
@@ -467,7 +467,7 @@ Inside your page component you:
467
467
 
468
468
  ```jsx
469
469
  // pages/users/[id].js
470
- import { getUser } from ... // the cache function
470
+ import { getUser } from ... // the query function
471
471
 
472
472
  export default function User(props) {
473
473
  const user = createAsync(() => getUser(props.params.id));
@@ -483,9 +483,9 @@ getUser.key // returns "users"
483
483
  getUser.keyFor(id) // returns "users[5]"
484
484
  ```
485
485
 
486
- 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`.
486
+ You can revalidate the query 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 query (ie "users" in the example above). You can also invalidate a single entry by using `keyFor`.
487
487
 
488
- `cache` can be defined anywhere and then used inside your components with:
488
+ `query` can be defined anywhere and then used inside your components with:
489
489
 
490
490
  ### `createAsync`
491
491
 
@@ -502,7 +502,7 @@ const user = createAsync((currentValue) => getUser(params.id))
502
502
  return <h1>{user.latest.name}</h1>;
503
503
  ```
504
504
 
505
- Using `cache` in `createResource` directly won't work properly as the fetcher is not reactive and it won't invalidate properly.
505
+ Using `query` in `createResource` directly won't work properly as the fetcher is not reactive and it won't invalidate properly.
506
506
 
507
507
  ### `createAsyncStore`
508
508
 
@@ -558,7 +558,9 @@ const deleteUser = action(api.deleteTodo)
558
558
  </form>
559
559
  ```
560
560
 
561
- #### Notes of `<form>` implementation and SSR
561
+ Actions also a second argument which can be the name or an option object with `name` and `onComplete`. `name` is used to identify SSR actions that aren't server functions (see note below). `onComplete` allows you to configure behavior when `action`s complete. Keep in mind `onComplete` does not work when JavaScript is disabled.
562
+
563
+ #### Notes on `<form>` implementation and SSR
562
564
  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.
563
565
 
564
566
  ```jsx
@@ -597,13 +599,13 @@ const submission = useSubmission(action, (input) => filter(input));
597
599
 
598
600
  ### Response Helpers
599
601
 
600
- 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.
602
+ These are used to communicate router navigations from query/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.
601
603
 
602
604
  #### `redirect(path, options)`
603
605
 
604
606
  Redirects to the next route
605
607
  ```js
606
- const getUser = cache(() => {
608
+ const getUser = query(() => {
607
609
  const user = await api.getCurrentUser()
608
610
  if (!user) throw redirect("/login");
609
611
  return user;
@@ -614,7 +616,7 @@ const getUser = cache(() => {
614
616
 
615
617
  Reloads the data on the current page
616
618
  ```js
617
- const getTodo = cache(async (id: number) => {
619
+ const getTodo = query(async (id: number) => {
618
620
  const todo = await fetchTodo(id);
619
621
  return todo;
620
622
  }, "todo")
@@ -937,7 +939,7 @@ Related without Outlet component it has to be passed in manually. At which point
937
939
 
938
940
  ### `data` functions & `useRouteData`
939
941
 
940
- These have been replaced by a preload mechanism. This allows link hover preloads (as the preload 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.
942
+ These have been replaced by a preload mechanism. This allows link hover preloads (as the preload function can be run as much as wanted without worry about reactivity). It support deduping/query 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.
941
943
 
942
944
  That being said you can reproduce the old pattern largely by turning off preloads at the router level and then injecting your own Context:
943
945
 
@@ -10,4 +10,8 @@ export declare function useSubmissions<T extends Array<any>, U, V>(fn: Action<T,
10
10
  };
11
11
  export declare function useSubmission<T extends Array<any>, U, V>(fn: Action<T, U, V>, filter?: (input: V) => boolean): Submission<T, NarrowResponse<U>> | SubmissionStub;
12
12
  export declare function useAction<T extends Array<any>, U, V>(action: Action<T, U, V>): (...args: Parameters<Action<T, U, V>>) => Promise<NarrowResponse<U>>;
13
- export declare function action<T extends Array<any>, U = void>(fn: (...args: T) => Promise<U>, name?: string): Action<T, U, T>;
13
+ export declare function action<T extends Array<any>, U = void>(fn: (...args: T) => Promise<U>, name?: string): Action<T, U>;
14
+ export declare function action<T extends Array<any>, U = void>(fn: (...args: T) => Promise<U>, options?: {
15
+ name?: string;
16
+ onComplete?: (s: Submission<T, U>) => void;
17
+ }): Action<T, U>;
@@ -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.js";
4
4
  import { mockBase } from "../utils.js";
5
- import { cacheKeyOp, hashKey, revalidate, cache } from "./cache.js";
5
+ import { cacheKeyOp, hashKey, revalidate, query } from "./query.js";
6
6
  export const actions = /* #__PURE__ */ new Map();
7
7
  export function useSubmissions(fn, filter) {
8
8
  const router = useRouter();
@@ -24,8 +24,8 @@ export function useSubmission(fn, filter) {
24
24
  const submissions = useSubmissions(fn, filter);
25
25
  return new Proxy({}, {
26
26
  get(_, property) {
27
- if (submissions.length === 0 && property === "clear" || property === "retry")
28
- return (() => { });
27
+ if ((submissions.length === 0 && property === "clear") || property === "retry")
28
+ return () => { };
29
29
  return submissions[submissions.length - 1]?.[property];
30
30
  }
31
31
  });
@@ -34,7 +34,7 @@ export function useAction(action) {
34
34
  const r = useRouter();
35
35
  return (...args) => action.apply({ r }, args);
36
36
  }
37
- export function action(fn, name) {
37
+ export function action(fn, options = {}) {
38
38
  function mutate(...variables) {
39
39
  const router = this.r;
40
40
  const form = this.f;
@@ -46,6 +46,18 @@ export function action(fn, name) {
46
46
  function handler(error) {
47
47
  return async (res) => {
48
48
  const result = await handleResponse(res, error, router.navigatorFactory());
49
+ let retry = null;
50
+ o.onComplete?.({
51
+ ...submission,
52
+ result: result?.data,
53
+ error: result?.error,
54
+ pending: false,
55
+ retry() {
56
+ return retry = submission.retry();
57
+ }
58
+ });
59
+ if (retry)
60
+ return retry;
49
61
  if (!result)
50
62
  return submission.clear();
51
63
  setResult(result);
@@ -69,7 +81,7 @@ export function action(fn, name) {
69
81
  return !result();
70
82
  },
71
83
  clear() {
72
- router.submissions[1](v => v.filter(i => i.input !== variables));
84
+ router.submissions[1](v => v.filter(i => i !== submission));
73
85
  },
74
86
  retry() {
75
87
  setResult(undefined);
@@ -80,8 +92,9 @@ export function action(fn, name) {
80
92
  ]);
81
93
  return p.then(handler(), handler(true));
82
94
  }
95
+ const o = typeof options === "string" ? { name: options } : options;
83
96
  const url = fn.url ||
84
- (name && `https://action/${name}`) ||
97
+ (o.name && `https://action/${o.name}`) ||
85
98
  (!isServer ? `https://action/${hashString(fn.toString())}` : "");
86
99
  mutate.base = url;
87
100
  return toAction(mutate, url);
@@ -142,7 +155,7 @@ async function handleResponse(response, error, navigate) {
142
155
  // invalidate
143
156
  cacheKeyOp(keys, entry => (entry[0] = 0));
144
157
  // set cache
145
- flightKeys && flightKeys.forEach(k => cache.set(k, custom[k]));
158
+ flightKeys && flightKeys.forEach(k => query.set(k, custom[k]));
146
159
  // trigger revalidation
147
160
  await revalidate(keys, false);
148
161
  return data != null ? { data } : undefined;
@@ -1,4 +1,4 @@
1
1
  export { createAsync, createAsyncStore, type AccessorWithLatest } from "./createAsync.js";
2
2
  export { action, useSubmission, useSubmissions, useAction, type Action } from "./action.js";
3
- export { cache, revalidate, type CachedFunction } from "./cache.js";
3
+ export { query, revalidate, cache, type CachedFunction } from "./query.js";
4
4
  export { redirect, reload, json } from "./response.js";
@@ -1,4 +1,4 @@
1
1
  export { createAsync, createAsyncStore } from "./createAsync.js";
2
2
  export { action, useSubmission, useSubmissions, useAction } from "./action.js";
3
- export { cache, revalidate } from "./cache.js";
3
+ export { query, revalidate, cache } from "./query.js";
4
4
  export { redirect, reload, json } from "./response.js";
@@ -0,0 +1,20 @@
1
+ import type { CacheEntry, NarrowResponse } from "../types.js";
2
+ export declare function revalidate(key?: string | string[] | void, force?: boolean): Promise<void>;
3
+ export declare function cacheKeyOp(key: string | string[] | void, fn: (cacheEntry: CacheEntry) => void): void;
4
+ export type CachedFunction<T extends (...args: any) => any> = T extends (...args: infer A) => infer R ? ([] extends {
5
+ [K in keyof A]-?: A[K];
6
+ } ? (...args: never[]) => R extends Promise<infer P> ? Promise<NarrowResponse<P>> : NarrowResponse<R> : (...args: A) => R extends Promise<infer P> ? Promise<NarrowResponse<P>> : NarrowResponse<R>) & {
7
+ keyFor: (...args: A) => string;
8
+ key: string;
9
+ } : never;
10
+ export declare function query<T extends (...args: any) => any>(fn: T, name: string): CachedFunction<T>;
11
+ export declare namespace query {
12
+ export var get: (key: string) => any;
13
+ export var set: <T>(key: string, value: T extends Promise<any> ? never : T) => void;
14
+ var _a: (key: string) => boolean;
15
+ export var clear: () => void;
16
+ export { _a as delete };
17
+ }
18
+ /** @deprecated use query instead */
19
+ export declare const cache: typeof query;
20
+ export declare function hashKey<T extends Array<any>>(args: T): string;
@@ -0,0 +1,218 @@
1
+ import { createSignal, getListener, getOwner, onCleanup, sharedConfig, startTransition } from "solid-js";
2
+ import { getRequestEvent, isServer } from "solid-js/web";
3
+ import { useNavigate, getIntent, getInPreloadFn } from "../routing.js";
4
+ const LocationHeader = "Location";
5
+ const PRELOAD_TIMEOUT = 5000;
6
+ const CACHE_TIMEOUT = 180000;
7
+ let cacheMap = new Map();
8
+ // cleanup forward/back cache
9
+ if (!isServer) {
10
+ setInterval(() => {
11
+ const now = Date.now();
12
+ for (let [k, v] of cacheMap.entries()) {
13
+ if (!v[4].count && now - v[0] > CACHE_TIMEOUT) {
14
+ cacheMap.delete(k);
15
+ }
16
+ }
17
+ }, 300000);
18
+ }
19
+ function getCache() {
20
+ if (!isServer)
21
+ return cacheMap;
22
+ const req = getRequestEvent();
23
+ if (!req)
24
+ throw new Error("Cannot find cache context");
25
+ return (req.router || (req.router = {})).cache || (req.router.cache = new Map());
26
+ }
27
+ export function revalidate(key, force = true) {
28
+ return startTransition(() => {
29
+ const now = Date.now();
30
+ cacheKeyOp(key, entry => {
31
+ force && (entry[0] = 0); //force cache miss
32
+ entry[4][1](now); // retrigger live signals
33
+ });
34
+ });
35
+ }
36
+ export function cacheKeyOp(key, fn) {
37
+ key && !Array.isArray(key) && (key = [key]);
38
+ for (let k of cacheMap.keys()) {
39
+ if (key === undefined || matchKey(k, key))
40
+ fn(cacheMap.get(k));
41
+ }
42
+ }
43
+ export function query(fn, name) {
44
+ // prioritize GET for server functions
45
+ if (fn.GET)
46
+ fn = fn.GET;
47
+ const cachedFn = ((...args) => {
48
+ const cache = getCache();
49
+ const intent = getIntent();
50
+ const inPreloadFn = getInPreloadFn();
51
+ const owner = getOwner();
52
+ const navigate = owner ? useNavigate() : undefined;
53
+ const now = Date.now();
54
+ const key = name + hashKey(args);
55
+ let cached = cache.get(key);
56
+ let tracking;
57
+ if (isServer) {
58
+ const e = getRequestEvent();
59
+ if (e) {
60
+ const dataOnly = (e.router || (e.router = {})).dataOnly;
61
+ if (dataOnly) {
62
+ const data = e && (e.router.data || (e.router.data = {}));
63
+ if (data && key in data)
64
+ return data[key];
65
+ if (Array.isArray(dataOnly) && !matchKey(key, dataOnly)) {
66
+ data[key] = undefined;
67
+ return Promise.resolve();
68
+ }
69
+ }
70
+ }
71
+ }
72
+ if (getListener() && !isServer) {
73
+ tracking = true;
74
+ onCleanup(() => cached[4].count--);
75
+ }
76
+ if (cached &&
77
+ cached[0] &&
78
+ (isServer ||
79
+ intent === "native" ||
80
+ cached[4].count ||
81
+ Date.now() - cached[0] < PRELOAD_TIMEOUT)) {
82
+ if (tracking) {
83
+ cached[4].count++;
84
+ cached[4][0](); // track
85
+ }
86
+ if (cached[3] === "preload" && intent !== "preload") {
87
+ cached[0] = now;
88
+ }
89
+ let res = cached[1];
90
+ if (intent !== "preload") {
91
+ res =
92
+ "then" in cached[1]
93
+ ? cached[1].then(handleResponse(false), handleResponse(true))
94
+ : handleResponse(false)(cached[1]);
95
+ !isServer && intent === "navigate" && startTransition(() => cached[4][1](cached[0])); // update version
96
+ }
97
+ inPreloadFn && "then" in res && res.catch(() => { });
98
+ return res;
99
+ }
100
+ let res = !isServer && sharedConfig.context && sharedConfig.has(key)
101
+ ? sharedConfig.load(key) // hydrating
102
+ : fn(...args);
103
+ if (cached) {
104
+ cached[0] = now;
105
+ cached[1] = res;
106
+ cached[3] = intent;
107
+ !isServer && intent === "navigate" && startTransition(() => cached[4][1](cached[0])); // update version
108
+ }
109
+ else {
110
+ cache.set(key, (cached = [now, res, , intent, createSignal(now)]));
111
+ cached[4].count = 0;
112
+ }
113
+ if (tracking) {
114
+ cached[4].count++;
115
+ cached[4][0](); // track
116
+ }
117
+ if (isServer) {
118
+ const e = getRequestEvent();
119
+ if (e && e.router.dataOnly)
120
+ return (e.router.data[key] = res);
121
+ }
122
+ if (intent !== "preload") {
123
+ res =
124
+ "then" in res
125
+ ? res.then(handleResponse(false), handleResponse(true))
126
+ : handleResponse(false)(res);
127
+ }
128
+ inPreloadFn && "then" in res && res.catch(() => { });
129
+ // serialize on server
130
+ if (isServer &&
131
+ sharedConfig.context &&
132
+ sharedConfig.context.async &&
133
+ !sharedConfig.context.noHydrate) {
134
+ const e = getRequestEvent();
135
+ (!e || !e.serverOnly) && sharedConfig.context.serialize(key, res);
136
+ }
137
+ return res;
138
+ function handleResponse(error) {
139
+ return async (v) => {
140
+ if (v instanceof Response) {
141
+ const url = v.headers.get(LocationHeader);
142
+ if (url !== null) {
143
+ // client + server relative redirect
144
+ if (navigate && url.startsWith("/"))
145
+ startTransition(() => {
146
+ navigate(url, { replace: true });
147
+ });
148
+ else if (!isServer)
149
+ window.location.href = url;
150
+ else if (isServer) {
151
+ const e = getRequestEvent();
152
+ if (e)
153
+ e.response = { status: 302, headers: new Headers({ Location: url }) };
154
+ }
155
+ return;
156
+ }
157
+ if (v.customBody)
158
+ v = await v.customBody();
159
+ }
160
+ if (error)
161
+ throw v;
162
+ cached[2] = v;
163
+ return v;
164
+ };
165
+ }
166
+ });
167
+ cachedFn.keyFor = (...args) => name + hashKey(args);
168
+ cachedFn.key = name;
169
+ return cachedFn;
170
+ }
171
+ query.get = (key) => {
172
+ const cached = getCache().get(key);
173
+ return cached[2];
174
+ };
175
+ query.set = (key, value) => {
176
+ const cache = getCache();
177
+ const now = Date.now();
178
+ let cached = cache.get(key);
179
+ if (cached) {
180
+ cached[0] = now;
181
+ cached[1] = Promise.resolve(value);
182
+ cached[2] = value;
183
+ cached[3] = "preload";
184
+ }
185
+ else {
186
+ cache.set(key, (cached = [now, Promise.resolve(value), value, "preload", createSignal(now)]));
187
+ cached[4].count = 0;
188
+ }
189
+ };
190
+ query.delete = (key) => getCache().delete(key);
191
+ query.clear = () => getCache().clear();
192
+ /** @deprecated use query instead */
193
+ export const cache = query;
194
+ function matchKey(key, keys) {
195
+ for (let k of keys) {
196
+ if (k && key.startsWith(k))
197
+ return true;
198
+ }
199
+ return false;
200
+ }
201
+ // Modified from the amazing Tanstack Query library (MIT)
202
+ // https://github.com/TanStack/query/blob/main/packages/query-core/src/utils.ts#L168
203
+ export function hashKey(args) {
204
+ return JSON.stringify(args, (_, val) => isPlainObject(val)
205
+ ? Object.keys(val)
206
+ .sort()
207
+ .reduce((result, key) => {
208
+ result[key] = val[key];
209
+ return result;
210
+ }, {})
211
+ : val);
212
+ }
213
+ function isPlainObject(obj) {
214
+ let proto;
215
+ return (obj != null &&
216
+ typeof obj === "object" &&
217
+ (!(proto = Object.getPrototypeOf(obj)) || proto === Object.prototype));
218
+ }
package/dist/index.d.ts CHANGED
@@ -4,4 +4,4 @@ export * from "./lifecycle.js";
4
4
  export { useHref, useIsRouting, useLocation, useMatch, useCurrentMatches, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, usePreloadRoute } from "./routing.js";
5
5
  export { mergeSearchString as _mergeSearchString } from "./utils.js";
6
6
  export * from "./data/index.js";
7
- export type { Location, LocationChange, MatchFilter, MatchFilters, NavigateOptions, Navigator, OutputMatch, Params, PathMatch, RouteSectionProps, RoutePreloadFunc, RoutePreloadFuncArgs, RouteDefinition, RouteDescription, RouteMatch, RouterIntegration, RouterUtils, SetParams, BeforeLeaveEventArgs, RouteLoadFunc, RouteLoadFuncArgs, RouterResponseInit, CustomResponse } from "./types.js";
7
+ export type { Location, LocationChange, MatchFilter, MatchFilters, NavigateOptions, Navigator, OutputMatch, Params, PathMatch, RouteSectionProps, RoutePreloadFunc, RoutePreloadFuncArgs, RouteDefinition, RouteDescription, RouteMatch, RouterIntegration, RouterUtils, SetParams, Submission, BeforeLeaveEventArgs, RouteLoadFunc, RouteLoadFuncArgs, RouterResponseInit, CustomResponse } from "./types.js";
package/dist/index.js CHANGED
@@ -894,7 +894,7 @@ if (!isServer) {
894
894
  setInterval(() => {
895
895
  const now = Date.now();
896
896
  for (let [k, v] of cacheMap.entries()) {
897
- if (!v[3].count && now - v[0] > CACHE_TIMEOUT) {
897
+ if (!v[4].count && now - v[0] > CACHE_TIMEOUT) {
898
898
  cacheMap.delete(k);
899
899
  }
900
900
  }
@@ -911,7 +911,7 @@ function revalidate(key, force = true) {
911
911
  const now = Date.now();
912
912
  cacheKeyOp(key, entry => {
913
913
  force && (entry[0] = 0); //force cache miss
914
- entry[3][1](now); // retrigger live signals
914
+ entry[4][1](now); // retrigger live signals
915
915
  });
916
916
  });
917
917
  }
@@ -921,7 +921,7 @@ function cacheKeyOp(key, fn) {
921
921
  if (key === undefined || matchKey(k, key)) fn(cacheMap.get(k));
922
922
  }
923
923
  }
924
- function cache(fn, name) {
924
+ function query(fn, name) {
925
925
  // prioritize GET for server functions
926
926
  if (fn.GET) fn = fn.GET;
927
927
  const cachedFn = (...args) => {
@@ -950,20 +950,20 @@ function cache(fn, name) {
950
950
  }
951
951
  if (getListener() && !isServer) {
952
952
  tracking = true;
953
- onCleanup(() => cached[3].count--);
953
+ onCleanup(() => cached[4].count--);
954
954
  }
955
- if (cached && cached[0] && (isServer || intent === "native" || cached[3].count || Date.now() - cached[0] < PRELOAD_TIMEOUT)) {
955
+ if (cached && cached[0] && (isServer || intent === "native" || cached[4].count || Date.now() - cached[0] < PRELOAD_TIMEOUT)) {
956
956
  if (tracking) {
957
- cached[3].count++;
958
- cached[3][0](); // track
957
+ cached[4].count++;
958
+ cached[4][0](); // track
959
959
  }
960
- if (cached[2] === "preload" && intent !== "preload") {
960
+ if (cached[3] === "preload" && intent !== "preload") {
961
961
  cached[0] = now;
962
962
  }
963
963
  let res = cached[1];
964
964
  if (intent !== "preload") {
965
965
  res = "then" in cached[1] ? cached[1].then(handleResponse(false), handleResponse(true)) : handleResponse(false)(cached[1]);
966
- !isServer && intent === "navigate" && startTransition(() => cached[3][1](cached[0])); // update version
966
+ !isServer && intent === "navigate" && startTransition(() => cached[4][1](cached[0])); // update version
967
967
  }
968
968
  inPreloadFn && "then" in res && res.catch(() => {});
969
969
  return res;
@@ -973,15 +973,15 @@ function cache(fn, name) {
973
973
  if (cached) {
974
974
  cached[0] = now;
975
975
  cached[1] = res;
976
- cached[2] = intent;
977
- !isServer && intent === "navigate" && startTransition(() => cached[3][1](cached[0])); // update version
976
+ cached[3] = intent;
977
+ !isServer && intent === "navigate" && startTransition(() => cached[4][1](cached[0])); // update version
978
978
  } else {
979
- cache.set(key, cached = [now, res, intent, createSignal(now)]);
980
- cached[3].count = 0;
979
+ cache.set(key, cached = [now, res,, intent, createSignal(now)]);
980
+ cached[4].count = 0;
981
981
  }
982
982
  if (tracking) {
983
- cached[3].count++;
984
- cached[3][0](); // track
983
+ cached[4].count++;
984
+ cached[4][0](); // track
985
985
  }
986
986
  if (isServer) {
987
987
  const e = getRequestEvent();
@@ -1021,6 +1021,7 @@ function cache(fn, name) {
1021
1021
  if (v.customBody) v = await v.customBody();
1022
1022
  }
1023
1023
  if (error) throw v;
1024
+ cached[2] = v;
1024
1025
  return v;
1025
1026
  };
1026
1027
  }
@@ -1029,20 +1030,29 @@ function cache(fn, name) {
1029
1030
  cachedFn.key = name;
1030
1031
  return cachedFn;
1031
1032
  }
1032
- cache.set = (key, value) => {
1033
+ query.get = key => {
1034
+ const cached = getCache().get(key);
1035
+ return cached[2];
1036
+ };
1037
+ query.set = (key, value) => {
1033
1038
  const cache = getCache();
1034
1039
  const now = Date.now();
1035
1040
  let cached = cache.get(key);
1036
1041
  if (cached) {
1037
1042
  cached[0] = now;
1038
- cached[1] = value;
1039
- cached[2] = "preload";
1043
+ cached[1] = Promise.resolve(value);
1044
+ cached[2] = value;
1045
+ cached[3] = "preload";
1040
1046
  } else {
1041
- cache.set(key, cached = [now, value,, createSignal(now)]);
1042
- cached[3].count = 0;
1047
+ cache.set(key, cached = [now, Promise.resolve(value), value, "preload", createSignal(now)]);
1048
+ cached[4].count = 0;
1043
1049
  }
1044
1050
  };
1045
- cache.clear = () => getCache().clear();
1051
+ query.delete = key => getCache().delete(key);
1052
+ query.clear = () => getCache().clear();
1053
+
1054
+ /** @deprecated use query instead */
1055
+ const cache = query;
1046
1056
  function matchKey(key, keys) {
1047
1057
  for (let k of keys) {
1048
1058
  if (k && key.startsWith(k)) return true;
@@ -1093,7 +1103,7 @@ function useAction(action) {
1093
1103
  r
1094
1104
  }, args);
1095
1105
  }
1096
- function action(fn, name) {
1106
+ function action(fn, options = {}) {
1097
1107
  function mutate(...variables) {
1098
1108
  const router = this.r;
1099
1109
  const form = this.f;
@@ -1107,6 +1117,17 @@ function action(fn, name) {
1107
1117
  function handler(error) {
1108
1118
  return async res => {
1109
1119
  const result = await handleResponse(res, error, router.navigatorFactory());
1120
+ let retry = null;
1121
+ o.onComplete?.({
1122
+ ...submission,
1123
+ result: result?.data,
1124
+ error: result?.error,
1125
+ pending: false,
1126
+ retry() {
1127
+ return retry = submission.retry();
1128
+ }
1129
+ });
1130
+ if (retry) return retry;
1110
1131
  if (!result) return submission.clear();
1111
1132
  setResult(result);
1112
1133
  if (result.error && !form) throw result.error;
@@ -1126,7 +1147,7 @@ function action(fn, name) {
1126
1147
  return !result();
1127
1148
  },
1128
1149
  clear() {
1129
- router.submissions[1](v => v.filter(i => i.input !== variables));
1150
+ router.submissions[1](v => v.filter(i => i !== submission));
1130
1151
  },
1131
1152
  retry() {
1132
1153
  setResult(undefined);
@@ -1136,7 +1157,10 @@ function action(fn, name) {
1136
1157
  }]);
1137
1158
  return p.then(handler(), handler(true));
1138
1159
  }
1139
- const url = fn.url || name && `https://action/${name}` || (!isServer ? `https://action/${hashString(fn.toString())}` : "");
1160
+ const o = typeof options === "string" ? {
1161
+ name: options
1162
+ } : options;
1163
+ const url = fn.url || o.name && `https://action/${o.name}` || (!isServer ? `https://action/${hashString(fn.toString())}` : "");
1140
1164
  mutate.base = url;
1141
1165
  return toAction(mutate, url);
1142
1166
  }
@@ -1191,7 +1215,7 @@ async function handleResponse(response, error, navigate) {
1191
1215
  // invalidate
1192
1216
  cacheKeyOp(keys, entry => entry[0] = 0);
1193
1217
  // set cache
1194
- flightKeys && flightKeys.forEach(k => cache.set(k, custom[k]));
1218
+ flightKeys && flightKeys.forEach(k => query.set(k, custom[k]));
1195
1219
  // trigger revalidation
1196
1220
  await revalidate(keys, false);
1197
1221
  return data != null ? {
@@ -1658,4 +1682,4 @@ function json(data, init = {}) {
1658
1682
  return response;
1659
1683
  }
1660
1684
 
1661
- export { A, HashRouter, MemoryRouter, Navigate, Route, Router, StaticRouter, mergeSearchString as _mergeSearchString, action, cache, createAsync, createAsyncStore, createBeforeLeave, createMemoryHistory, createRouter, json, keepDepth, notifyIfNotBlocked, redirect, reload, revalidate, saveCurrentDepth, useAction, useBeforeLeave, useCurrentMatches, useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, usePreloadRoute, useResolvedPath, useSearchParams, useSubmission, useSubmissions };
1685
+ export { A, HashRouter, MemoryRouter, Navigate, Route, Router, StaticRouter, mergeSearchString as _mergeSearchString, action, cache, createAsync, createAsyncStore, createBeforeLeave, createMemoryHistory, createRouter, json, keepDepth, notifyIfNotBlocked, query, redirect, reload, revalidate, saveCurrentDepth, useAction, useBeforeLeave, useCurrentMatches, useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, usePreloadRoute, useResolvedPath, useSearchParams, useSubmission, useSubmissions };
package/dist/types.d.ts CHANGED
@@ -183,7 +183,7 @@ export type SubmissionStub = {
183
183
  export interface MaybePreloadableComponent extends Component {
184
184
  preload?: () => void;
185
185
  }
186
- export type CacheEntry = [number, any, Intent | undefined, Signal<number> & {
186
+ export type CacheEntry = [number, Promise<any>, any, Intent | undefined, Signal<number> & {
187
187
  count: number;
188
188
  }];
189
189
  export type NarrowResponse<T> = T extends CustomResponse<infer U> ? U : Exclude<T, Response>;
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "Ryan Turnquist"
7
7
  ],
8
8
  "license": "MIT",
9
- "version": "0.14.10",
9
+ "version": "0.15.1",
10
10
  "homepage": "https://github.com/solidjs/solid-router#readme",
11
11
  "repository": {
12
12
  "type": "git",