UNPKG

@solidjs/router

Version:
164 lines (163 loc) 5.84 kB
import { $TRACK, createMemo, createSignal, onCleanup, getOwner } from "solid-js"; import { isServer } from "solid-js/web"; import { useRouter } from "../routing.js"; import { mockBase, setFunctionName } from "../utils.js"; import { cacheKeyOp, hashKey, revalidate, query } from "./query.js"; export const actions = /* #__PURE__ */ new Map(); export function useSubmissions(fn, filter) { const router = useRouter(); const subs = createMemo(() => router.submissions[0]().filter(s => s.url === fn.base && (!filter || filter(s.input)))); return new Proxy([], { get(_, property) { if (property === $TRACK) return subs(); if (property === "pending") return subs().some(sub => !sub.result); return subs()[property]; }, has(_, property) { return property in subs(); } }); } export function useSubmission(fn, filter) { const submissions = useSubmissions(fn, filter); return new Proxy({}, { get(_, property) { if ((submissions.length === 0 && property === "clear") || property === "retry") return () => { }; return submissions[submissions.length - 1]?.[property]; } }); } export function useAction(action) { const r = useRouter(); return (...args) => action.apply({ r }, args); } export function action(fn, options = {}) { function mutate(...variables) { const router = this.r; const form = this.f; const p = (router.singleFlight && fn.withOptions ? fn.withOptions({ headers: { "X-Single-Flight": "true" } }) : fn)(...variables); const [result, setResult] = createSignal(); let submission; function handler(error) { return async (res) => { const result = await handleResponse(res, error, router.navigatorFactory()); let retry = null; o.onComplete?.({ ...submission, result: result?.data, error: result?.error, pending: false, retry() { return (retry = submission.retry()); } }); if (retry) return retry; if (!result) return submission.clear(); setResult(result); if (result.error && !form) throw result.error; return result.data; }; } router.submissions[1](s => [ ...s, (submission = { input: variables, url, get result() { return result()?.data; }, get error() { return result()?.error; }, get pending() { return !result(); }, clear() { router.submissions[1](v => v.filter(i => i !== submission)); }, retry() { setResult(undefined); const p = fn(...variables); return p.then(handler(), handler(true)); } }) ]); return p.then(handler(), handler(true)); } const o = typeof options === "string" ? { name: options } : options; const name = o.name || (!isServer ? String(hashString(fn.toString())) : undefined); const url = fn.url || (name && `https://action/${name}`) || ""; mutate.base = url; if (name) setFunctionName(mutate, name); return toAction(mutate, url); } function toAction(fn, url) { fn.toString = () => { if (!url) throw new Error("Client Actions need explicit names if server rendered"); return url; }; fn.with = function (...args) { const newFn = function (...passedArgs) { return fn.call(this, ...args, ...passedArgs); }; newFn.base = fn.base; const uri = new URL(url, mockBase); uri.searchParams.set("args", hashKey(args)); return toAction(newFn, (uri.origin === "https://action" ? uri.origin : "") + uri.pathname + uri.search); }; fn.url = url; if (!isServer) { actions.set(url, fn); getOwner() && onCleanup(() => actions.delete(url)); } return fn; } const hashString = (s) => s.split("").reduce((a, b) => ((a << 5) - a + b.charCodeAt(0)) | 0, 0); async function handleResponse(response, error, navigate) { let data; let custom; let keys; let flightKeys; if (response instanceof Response) { if (response.headers.has("X-Revalidate")) keys = response.headers.get("X-Revalidate").split(","); if (response.customBody) { data = custom = await response.customBody(); if (response.headers.has("X-Single-Flight")) { data = data._$value; delete custom._$value; flightKeys = Object.keys(custom); } } if (response.headers.has("Location")) { const locationUrl = response.headers.get("Location") || "/"; if (locationUrl.startsWith("http")) { window.location.href = locationUrl; } else { navigate(locationUrl); } } } else if (error) return { error: response }; else data = response; // invalidate cacheKeyOp(keys, entry => (entry[0] = 0)); // set cache flightKeys && flightKeys.forEach(k => query.set(k, custom[k])); // trigger revalidation await revalidate(keys, false); return data != null ? { data } : undefined; }