restructure project, add claudemd
This commit is contained in:
17
web/runtime/@solidjs/router/dist/data/action.d.ts
vendored
Normal file
17
web/runtime/@solidjs/router/dist/data/action.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
import { JSX } from "solid-js";
|
||||
import type { Submission, SubmissionStub, NarrowResponse } from "../types.js";
|
||||
export type Action<T extends Array<any>, U, V = T> = (T extends [FormData | URLSearchParams] | [] ? JSX.SerializableAttributeValue : unknown) & ((...vars: T) => Promise<NarrowResponse<U>>) & {
|
||||
url: string;
|
||||
with<A extends any[], B extends any[]>(this: (this: any, ...args: [...A, ...B]) => Promise<NarrowResponse<U>>, ...args: A): Action<B, U, V>;
|
||||
};
|
||||
export declare const actions: Map<string, Action<any, any, any>>;
|
||||
export declare function useSubmissions<T extends Array<any>, U, V>(fn: Action<T, U, V>, filter?: (input: V) => boolean): Submission<T, NarrowResponse<U>>[] & {
|
||||
pending: boolean;
|
||||
};
|
||||
export declare function useSubmission<T extends Array<any>, U, V>(fn: Action<T, U, V>, filter?: (input: V) => boolean): Submission<T, NarrowResponse<U>> | SubmissionStub;
|
||||
export declare function useAction<T extends Array<any>, U, V>(action: Action<T, U, V>): (...args: Parameters<Action<T, U, V>>) => Promise<NarrowResponse<U>>;
|
||||
export declare function action<T extends Array<any>, U = void>(fn: (...args: T) => Promise<U>, name?: string): Action<T, U>;
|
||||
export declare function action<T extends Array<any>, U = void>(fn: (...args: T) => Promise<U>, options?: {
|
||||
name?: string;
|
||||
onComplete?: (s: Submission<T, U>) => void;
|
||||
}): Action<T, U>;
|
||||
163
web/runtime/@solidjs/router/dist/data/action.js
vendored
Normal file
163
web/runtime/@solidjs/router/dist/data/action.js
vendored
Normal file
@@ -0,0 +1,163 @@
|
||||
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;
|
||||
}
|
||||
32
web/runtime/@solidjs/router/dist/data/createAsync.d.ts
vendored
Normal file
32
web/runtime/@solidjs/router/dist/data/createAsync.d.ts
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
import { type ReconcileOptions } from "solid-js/store";
|
||||
/**
|
||||
* As `createAsync` and `createAsyncStore` are wrappers for `createResource`,
|
||||
* this type allows to support `latest` field for these primitives.
|
||||
* It will be removed in the future.
|
||||
*/
|
||||
export type AccessorWithLatest<T> = {
|
||||
(): T;
|
||||
latest: T;
|
||||
};
|
||||
export declare function createAsync<T>(fn: (prev: T) => Promise<T>, options: {
|
||||
name?: string;
|
||||
initialValue: T;
|
||||
deferStream?: boolean;
|
||||
}): AccessorWithLatest<T>;
|
||||
export declare function createAsync<T>(fn: (prev: T | undefined) => Promise<T>, options?: {
|
||||
name?: string;
|
||||
initialValue?: T;
|
||||
deferStream?: boolean;
|
||||
}): AccessorWithLatest<T | undefined>;
|
||||
export declare function createAsyncStore<T>(fn: (prev: T) => Promise<T>, options: {
|
||||
name?: string;
|
||||
initialValue: T;
|
||||
deferStream?: boolean;
|
||||
reconcile?: ReconcileOptions;
|
||||
}): AccessorWithLatest<T>;
|
||||
export declare function createAsyncStore<T>(fn: (prev: T | undefined) => Promise<T>, options?: {
|
||||
name?: string;
|
||||
initialValue?: T;
|
||||
deferStream?: boolean;
|
||||
reconcile?: ReconcileOptions;
|
||||
}): AccessorWithLatest<T | undefined>;
|
||||
96
web/runtime/@solidjs/router/dist/data/createAsync.js
vendored
Normal file
96
web/runtime/@solidjs/router/dist/data/createAsync.js
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* This is mock of the eventual Solid 2.0 primitive. It is not fully featured.
|
||||
*/
|
||||
import { createResource, sharedConfig, untrack, catchError } from "solid-js";
|
||||
import { createStore, reconcile, unwrap } from "solid-js/store";
|
||||
import { isServer } from "solid-js/web";
|
||||
import { setFunctionName } from "../utils.js";
|
||||
export function createAsync(fn, options) {
|
||||
let resource;
|
||||
let prev = () => !resource || resource.state === "unresolved" ? undefined : resource.latest;
|
||||
[resource] = createResource(() => subFetch(fn, catchError(() => untrack(prev), () => undefined)), v => v, options);
|
||||
const resultAccessor = (() => resource());
|
||||
if (options?.name)
|
||||
setFunctionName(resultAccessor, options.name);
|
||||
Object.defineProperty(resultAccessor, "latest", {
|
||||
get() {
|
||||
return resource.latest;
|
||||
}
|
||||
});
|
||||
return resultAccessor;
|
||||
}
|
||||
export function createAsyncStore(fn, options = {}) {
|
||||
let resource;
|
||||
let prev = () => !resource || resource.state === "unresolved"
|
||||
? undefined
|
||||
: unwrap(resource.latest);
|
||||
[resource] = createResource(() => subFetch(fn, catchError(() => untrack(prev), () => undefined)), v => v, {
|
||||
...options,
|
||||
storage: (init) => createDeepSignal(init, options.reconcile)
|
||||
});
|
||||
const resultAccessor = (() => resource());
|
||||
Object.defineProperty(resultAccessor, "latest", {
|
||||
get() {
|
||||
return resource.latest;
|
||||
}
|
||||
});
|
||||
return resultAccessor;
|
||||
}
|
||||
function createDeepSignal(value, options) {
|
||||
const [store, setStore] = createStore({
|
||||
value: structuredClone(value)
|
||||
});
|
||||
return [
|
||||
() => store.value,
|
||||
(v) => {
|
||||
typeof v === "function" && (v = v());
|
||||
setStore("value", reconcile(structuredClone(v), options));
|
||||
return store.value;
|
||||
}
|
||||
];
|
||||
}
|
||||
// mock promise while hydrating to prevent fetching
|
||||
class MockPromise {
|
||||
static all() {
|
||||
return new MockPromise();
|
||||
}
|
||||
static allSettled() {
|
||||
return new MockPromise();
|
||||
}
|
||||
static any() {
|
||||
return new MockPromise();
|
||||
}
|
||||
static race() {
|
||||
return new MockPromise();
|
||||
}
|
||||
static reject() {
|
||||
return new MockPromise();
|
||||
}
|
||||
static resolve() {
|
||||
return new MockPromise();
|
||||
}
|
||||
catch() {
|
||||
return new MockPromise();
|
||||
}
|
||||
then() {
|
||||
return new MockPromise();
|
||||
}
|
||||
finally() {
|
||||
return new MockPromise();
|
||||
}
|
||||
}
|
||||
function subFetch(fn, prev) {
|
||||
if (isServer || !sharedConfig.context)
|
||||
return fn(prev);
|
||||
const ogFetch = fetch;
|
||||
const ogPromise = Promise;
|
||||
try {
|
||||
window.fetch = () => new MockPromise();
|
||||
Promise = MockPromise;
|
||||
return fn(prev);
|
||||
}
|
||||
finally {
|
||||
window.fetch = ogFetch;
|
||||
Promise = ogPromise;
|
||||
}
|
||||
}
|
||||
9
web/runtime/@solidjs/router/dist/data/events.d.ts
vendored
Normal file
9
web/runtime/@solidjs/router/dist/data/events.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { RouterContext } from "../types.js";
|
||||
type NativeEventConfig = {
|
||||
preload?: boolean;
|
||||
explicitLinks?: boolean;
|
||||
actionBase?: string;
|
||||
transformUrl?: (url: string) => string;
|
||||
};
|
||||
export declare function setupNativeEvents({ preload, explicitLinks, actionBase, transformUrl }?: NativeEventConfig): (router: RouterContext) => void;
|
||||
export {};
|
||||
123
web/runtime/@solidjs/router/dist/data/events.js
vendored
Normal file
123
web/runtime/@solidjs/router/dist/data/events.js
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
import { delegateEvents } from "solid-js/web";
|
||||
import { onCleanup } from "solid-js";
|
||||
import { actions } from "./action.js";
|
||||
import { mockBase } from "../utils.js";
|
||||
export function setupNativeEvents({ preload = true, explicitLinks = false, actionBase = "/_server", transformUrl } = {}) {
|
||||
return (router) => {
|
||||
const basePath = router.base.path();
|
||||
const navigateFromRoute = router.navigatorFactory(router.base);
|
||||
let preloadTimeout;
|
||||
let lastElement;
|
||||
function isSvg(el) {
|
||||
return el.namespaceURI === "http://www.w3.org/2000/svg";
|
||||
}
|
||||
function handleAnchor(evt) {
|
||||
if (evt.defaultPrevented ||
|
||||
evt.button !== 0 ||
|
||||
evt.metaKey ||
|
||||
evt.altKey ||
|
||||
evt.ctrlKey ||
|
||||
evt.shiftKey)
|
||||
return;
|
||||
const a = evt
|
||||
.composedPath()
|
||||
.find(el => el instanceof Node && el.nodeName.toUpperCase() === "A");
|
||||
if (!a || (explicitLinks && !a.hasAttribute("link")))
|
||||
return;
|
||||
const svg = isSvg(a);
|
||||
const href = svg ? a.href.baseVal : a.href;
|
||||
const target = svg ? a.target.baseVal : a.target;
|
||||
if (target || (!href && !a.hasAttribute("state")))
|
||||
return;
|
||||
const rel = (a.getAttribute("rel") || "").split(/\s+/);
|
||||
if (a.hasAttribute("download") || (rel && rel.includes("external")))
|
||||
return;
|
||||
const url = svg ? new URL(href, document.baseURI) : new URL(href);
|
||||
if (url.origin !== window.location.origin ||
|
||||
(basePath && url.pathname && !url.pathname.toLowerCase().startsWith(basePath.toLowerCase())))
|
||||
return;
|
||||
return [a, url];
|
||||
}
|
||||
function handleAnchorClick(evt) {
|
||||
const res = handleAnchor(evt);
|
||||
if (!res)
|
||||
return;
|
||||
const [a, url] = res;
|
||||
const to = router.parsePath(url.pathname + url.search + url.hash);
|
||||
const state = a.getAttribute("state");
|
||||
evt.preventDefault();
|
||||
navigateFromRoute(to, {
|
||||
resolve: false,
|
||||
replace: a.hasAttribute("replace"),
|
||||
scroll: !a.hasAttribute("noscroll"),
|
||||
state: state ? JSON.parse(state) : undefined
|
||||
});
|
||||
}
|
||||
function handleAnchorPreload(evt) {
|
||||
const res = handleAnchor(evt);
|
||||
if (!res)
|
||||
return;
|
||||
const [a, url] = res;
|
||||
transformUrl && (url.pathname = transformUrl(url.pathname));
|
||||
router.preloadRoute(url, a.getAttribute("preload") !== "false");
|
||||
}
|
||||
function handleAnchorMove(evt) {
|
||||
clearTimeout(preloadTimeout);
|
||||
const res = handleAnchor(evt);
|
||||
if (!res)
|
||||
return (lastElement = null);
|
||||
const [a, url] = res;
|
||||
if (lastElement === a)
|
||||
return;
|
||||
transformUrl && (url.pathname = transformUrl(url.pathname));
|
||||
preloadTimeout = setTimeout(() => {
|
||||
router.preloadRoute(url, a.getAttribute("preload") !== "false");
|
||||
lastElement = a;
|
||||
}, 20);
|
||||
}
|
||||
function handleFormSubmit(evt) {
|
||||
if (evt.defaultPrevented)
|
||||
return;
|
||||
let actionRef = evt.submitter && evt.submitter.hasAttribute("formaction")
|
||||
? evt.submitter.getAttribute("formaction")
|
||||
: evt.target.getAttribute("action");
|
||||
if (!actionRef)
|
||||
return;
|
||||
if (!actionRef.startsWith("https://action/")) {
|
||||
// normalize server actions
|
||||
const url = new URL(actionRef, mockBase);
|
||||
actionRef = router.parsePath(url.pathname + url.search);
|
||||
if (!actionRef.startsWith(actionBase))
|
||||
return;
|
||||
}
|
||||
if (evt.target.method.toUpperCase() !== "POST")
|
||||
throw new Error("Only POST forms are supported for Actions");
|
||||
const handler = actions.get(actionRef);
|
||||
if (handler) {
|
||||
evt.preventDefault();
|
||||
const data = new FormData(evt.target, evt.submitter);
|
||||
handler.call({ r: router, f: evt.target }, evt.target.enctype === "multipart/form-data"
|
||||
? data
|
||||
: new URLSearchParams(data));
|
||||
}
|
||||
}
|
||||
// ensure delegated event run first
|
||||
delegateEvents(["click", "submit"]);
|
||||
document.addEventListener("click", handleAnchorClick);
|
||||
if (preload) {
|
||||
document.addEventListener("mousemove", handleAnchorMove, { passive: true });
|
||||
document.addEventListener("focusin", handleAnchorPreload, { passive: true });
|
||||
document.addEventListener("touchstart", handleAnchorPreload, { passive: true });
|
||||
}
|
||||
document.addEventListener("submit", handleFormSubmit);
|
||||
onCleanup(() => {
|
||||
document.removeEventListener("click", handleAnchorClick);
|
||||
if (preload) {
|
||||
document.removeEventListener("mousemove", handleAnchorMove);
|
||||
document.removeEventListener("focusin", handleAnchorPreload);
|
||||
document.removeEventListener("touchstart", handleAnchorPreload);
|
||||
}
|
||||
document.removeEventListener("submit", handleFormSubmit);
|
||||
});
|
||||
};
|
||||
}
|
||||
4
web/runtime/@solidjs/router/dist/data/index.d.ts
vendored
Normal file
4
web/runtime/@solidjs/router/dist/data/index.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
export { createAsync, createAsyncStore, type AccessorWithLatest } from "./createAsync.js";
|
||||
export { action, useSubmission, useSubmissions, useAction, type Action } from "./action.js";
|
||||
export { query, revalidate, cache, type CachedFunction } from "./query.js";
|
||||
export { redirect, reload, json } from "./response.js";
|
||||
4
web/runtime/@solidjs/router/dist/data/index.js
vendored
Normal file
4
web/runtime/@solidjs/router/dist/data/index.js
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
export { createAsync, createAsyncStore } from "./createAsync.js";
|
||||
export { action, useSubmission, useSubmissions, useAction } from "./action.js";
|
||||
export { query, revalidate, cache } from "./query.js";
|
||||
export { redirect, reload, json } from "./response.js";
|
||||
23
web/runtime/@solidjs/router/dist/data/query.d.ts
vendored
Normal file
23
web/runtime/@solidjs/router/dist/data/query.d.ts
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { CacheEntry, NarrowResponse } from "../types.js";
|
||||
/**
|
||||
* Revalidates the given cache entry/entries.
|
||||
*/
|
||||
export declare function revalidate(key?: string | string[] | void, force?: boolean): Promise<void>;
|
||||
export declare function cacheKeyOp(key: string | string[] | void, fn: (cacheEntry: CacheEntry) => void): void;
|
||||
export type CachedFunction<T extends (...args: any) => any> = T extends (...args: infer A) => infer R ? ([] extends {
|
||||
[K in keyof A]-?: A[K];
|
||||
} ? (...args: never[]) => R extends Promise<infer P> ? Promise<NarrowResponse<P>> : NarrowResponse<R> : (...args: A) => R extends Promise<infer P> ? Promise<NarrowResponse<P>> : NarrowResponse<R>) & {
|
||||
keyFor: (...args: A) => string;
|
||||
key: string;
|
||||
} : never;
|
||||
export declare function query<T extends (...args: any) => any>(fn: T, name: string): CachedFunction<T>;
|
||||
export declare namespace query {
|
||||
export var get: (key: string) => any;
|
||||
export var set: <T>(key: string, value: T extends Promise<any> ? never : T) => void;
|
||||
var _a: (key: string) => boolean;
|
||||
export var clear: () => void;
|
||||
export { _a as delete };
|
||||
}
|
||||
/** @deprecated use query instead */
|
||||
export declare const cache: typeof query;
|
||||
export declare function hashKey<T extends Array<any>>(args: T): string;
|
||||
232
web/runtime/@solidjs/router/dist/data/query.js
vendored
Normal file
232
web/runtime/@solidjs/router/dist/data/query.js
vendored
Normal file
@@ -0,0 +1,232 @@
|
||||
import { createSignal, getListener, getOwner, onCleanup, sharedConfig, startTransition } from "solid-js";
|
||||
import { getRequestEvent, isServer } from "solid-js/web";
|
||||
import { useNavigate, getIntent, getInPreloadFn } from "../routing.js";
|
||||
const LocationHeader = "Location";
|
||||
const PRELOAD_TIMEOUT = 5000;
|
||||
const CACHE_TIMEOUT = 180000;
|
||||
let cacheMap = new Map();
|
||||
// cleanup forward/back cache
|
||||
if (!isServer) {
|
||||
setInterval(() => {
|
||||
const now = Date.now();
|
||||
for (let [k, v] of cacheMap.entries()) {
|
||||
if (!v[4].count && now - v[0] > CACHE_TIMEOUT) {
|
||||
cacheMap.delete(k);
|
||||
}
|
||||
}
|
||||
}, 300000);
|
||||
}
|
||||
function getCache() {
|
||||
if (!isServer)
|
||||
return cacheMap;
|
||||
const req = getRequestEvent();
|
||||
if (!req)
|
||||
throw new Error("Cannot find cache context");
|
||||
return (req.router || (req.router = {})).cache || (req.router.cache = new Map());
|
||||
}
|
||||
/**
|
||||
* Revalidates the given cache entry/entries.
|
||||
*/
|
||||
export function revalidate(key, force = true) {
|
||||
return startTransition(() => {
|
||||
const now = Date.now();
|
||||
cacheKeyOp(key, entry => {
|
||||
force && (entry[0] = 0); //force cache miss
|
||||
entry[4][1](now); // retrigger live signals
|
||||
});
|
||||
});
|
||||
}
|
||||
export function cacheKeyOp(key, fn) {
|
||||
key && !Array.isArray(key) && (key = [key]);
|
||||
for (let k of cacheMap.keys()) {
|
||||
if (key === undefined || matchKey(k, key))
|
||||
fn(cacheMap.get(k));
|
||||
}
|
||||
}
|
||||
export function query(fn, name) {
|
||||
// prioritize GET for server functions
|
||||
if (fn.GET)
|
||||
fn = fn.GET;
|
||||
const cachedFn = ((...args) => {
|
||||
const cache = getCache();
|
||||
const intent = getIntent();
|
||||
const inPreloadFn = getInPreloadFn();
|
||||
const owner = getOwner();
|
||||
const navigate = owner ? useNavigate() : undefined;
|
||||
const now = Date.now();
|
||||
const key = name + hashKey(args);
|
||||
let cached = cache.get(key);
|
||||
let tracking;
|
||||
if (isServer) {
|
||||
const e = getRequestEvent();
|
||||
if (e) {
|
||||
const dataOnly = (e.router || (e.router = {})).dataOnly;
|
||||
if (dataOnly) {
|
||||
const data = e && (e.router.data || (e.router.data = {}));
|
||||
if (data && key in data)
|
||||
return data[key];
|
||||
if (Array.isArray(dataOnly) && !matchKey(key, dataOnly)) {
|
||||
data[key] = undefined;
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (getListener() && !isServer) {
|
||||
tracking = true;
|
||||
onCleanup(() => cached[4].count--);
|
||||
}
|
||||
if (cached &&
|
||||
cached[0] &&
|
||||
(isServer ||
|
||||
intent === "native" ||
|
||||
cached[4].count ||
|
||||
Date.now() - cached[0] < PRELOAD_TIMEOUT)) {
|
||||
if (tracking) {
|
||||
cached[4].count++;
|
||||
cached[4][0](); // track
|
||||
}
|
||||
if (cached[3] === "preload" && intent !== "preload") {
|
||||
cached[0] = now;
|
||||
}
|
||||
let res = cached[1];
|
||||
if (intent !== "preload") {
|
||||
res =
|
||||
"then" in cached[1]
|
||||
? cached[1].then(handleResponse(false), handleResponse(true))
|
||||
: handleResponse(false)(cached[1]);
|
||||
!isServer && intent === "navigate" && startTransition(() => cached[4][1](cached[0])); // update version
|
||||
}
|
||||
inPreloadFn && "then" in res && res.catch(() => { });
|
||||
return res;
|
||||
}
|
||||
let res;
|
||||
if (!isServer && sharedConfig.has && sharedConfig.has(key)) {
|
||||
res = sharedConfig.load(key); // hydrating
|
||||
// @ts-ignore at least until we add a delete method to sharedConfig
|
||||
delete globalThis._$HY.r[key];
|
||||
}
|
||||
else
|
||||
res = fn(...args);
|
||||
if (cached) {
|
||||
cached[0] = now;
|
||||
cached[1] = res;
|
||||
cached[3] = intent;
|
||||
!isServer && intent === "navigate" && startTransition(() => cached[4][1](cached[0])); // update version
|
||||
}
|
||||
else {
|
||||
cache.set(key, (cached = [now, res, , intent, createSignal(now)]));
|
||||
cached[4].count = 0;
|
||||
}
|
||||
if (tracking) {
|
||||
cached[4].count++;
|
||||
cached[4][0](); // track
|
||||
}
|
||||
if (isServer) {
|
||||
const e = getRequestEvent();
|
||||
if (e && e.router.dataOnly)
|
||||
return (e.router.data[key] = res);
|
||||
}
|
||||
if (intent !== "preload") {
|
||||
res =
|
||||
"then" in res
|
||||
? res.then(handleResponse(false), handleResponse(true))
|
||||
: handleResponse(false)(res);
|
||||
}
|
||||
inPreloadFn && "then" in res && res.catch(() => { });
|
||||
// serialize on server
|
||||
if (isServer &&
|
||||
sharedConfig.context &&
|
||||
sharedConfig.context.async &&
|
||||
!sharedConfig.context.noHydrate) {
|
||||
const e = getRequestEvent();
|
||||
(!e || !e.serverOnly) && sharedConfig.context.serialize(key, res);
|
||||
}
|
||||
return res;
|
||||
function handleResponse(error) {
|
||||
return async (v) => {
|
||||
if (v instanceof Response) {
|
||||
const e = getRequestEvent();
|
||||
if (e) {
|
||||
for (const [key, value] of v.headers) {
|
||||
if (key == "set-cookie")
|
||||
e.response.headers.append("set-cookie", value);
|
||||
else
|
||||
e.response.headers.set(key, value);
|
||||
}
|
||||
}
|
||||
const url = v.headers.get(LocationHeader);
|
||||
if (url !== null) {
|
||||
// client + server relative redirect
|
||||
if (navigate && url.startsWith("/"))
|
||||
startTransition(() => {
|
||||
navigate(url, { replace: true });
|
||||
});
|
||||
else if (!isServer)
|
||||
window.location.href = url;
|
||||
else if (e)
|
||||
e.response.status = 302;
|
||||
return;
|
||||
}
|
||||
if (v.customBody)
|
||||
v = await v.customBody();
|
||||
}
|
||||
if (error)
|
||||
throw v;
|
||||
cached[2] = v;
|
||||
return v;
|
||||
};
|
||||
}
|
||||
});
|
||||
cachedFn.keyFor = (...args) => name + hashKey(args);
|
||||
cachedFn.key = name;
|
||||
return cachedFn;
|
||||
}
|
||||
query.get = (key) => {
|
||||
const cached = getCache().get(key);
|
||||
return cached[2];
|
||||
};
|
||||
query.set = (key, value) => {
|
||||
const cache = getCache();
|
||||
const now = Date.now();
|
||||
let cached = cache.get(key);
|
||||
if (cached) {
|
||||
cached[0] = now;
|
||||
cached[1] = Promise.resolve(value);
|
||||
cached[2] = value;
|
||||
cached[3] = "preload";
|
||||
}
|
||||
else {
|
||||
cache.set(key, (cached = [now, Promise.resolve(value), value, "preload", createSignal(now)]));
|
||||
cached[4].count = 0;
|
||||
}
|
||||
};
|
||||
query.delete = (key) => getCache().delete(key);
|
||||
query.clear = () => getCache().clear();
|
||||
/** @deprecated use query instead */
|
||||
export const cache = query;
|
||||
function matchKey(key, keys) {
|
||||
for (let k of keys) {
|
||||
if (k && key.startsWith(k))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Modified from the amazing Tanstack Query library (MIT)
|
||||
// https://github.com/TanStack/query/blob/main/packages/query-core/src/utils.ts#L168
|
||||
export function hashKey(args) {
|
||||
return JSON.stringify(args, (_, val) => isPlainObject(val)
|
||||
? Object.keys(val)
|
||||
.sort()
|
||||
.reduce((result, key) => {
|
||||
result[key] = val[key];
|
||||
return result;
|
||||
}, {})
|
||||
: val);
|
||||
}
|
||||
function isPlainObject(obj) {
|
||||
let proto;
|
||||
return (obj != null &&
|
||||
typeof obj === "object" &&
|
||||
(!(proto = Object.getPrototypeOf(obj)) || proto === Object.prototype));
|
||||
}
|
||||
4
web/runtime/@solidjs/router/dist/data/response.d.ts
vendored
Normal file
4
web/runtime/@solidjs/router/dist/data/response.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
import type { RouterResponseInit, CustomResponse } from "../types.js";
|
||||
export declare function redirect(url: string, init?: number | RouterResponseInit): CustomResponse<never>;
|
||||
export declare function reload(init?: RouterResponseInit): CustomResponse<never>;
|
||||
export declare function json<T>(data: T, init?: RouterResponseInit): CustomResponse<T>;
|
||||
42
web/runtime/@solidjs/router/dist/data/response.js
vendored
Normal file
42
web/runtime/@solidjs/router/dist/data/response.js
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
export function redirect(url, init = 302) {
|
||||
let responseInit;
|
||||
let revalidate;
|
||||
if (typeof init === "number") {
|
||||
responseInit = { status: init };
|
||||
}
|
||||
else {
|
||||
({ revalidate, ...responseInit } = init);
|
||||
if (typeof responseInit.status === "undefined") {
|
||||
responseInit.status = 302;
|
||||
}
|
||||
}
|
||||
const headers = new Headers(responseInit.headers);
|
||||
headers.set("Location", url);
|
||||
revalidate !== undefined && headers.set("X-Revalidate", revalidate.toString());
|
||||
const response = new Response(null, {
|
||||
...responseInit,
|
||||
headers: headers
|
||||
});
|
||||
return response;
|
||||
}
|
||||
export function reload(init = {}) {
|
||||
const { revalidate, ...responseInit } = init;
|
||||
const headers = new Headers(responseInit.headers);
|
||||
revalidate !== undefined && headers.set("X-Revalidate", revalidate.toString());
|
||||
return new Response(null, {
|
||||
...responseInit,
|
||||
headers
|
||||
});
|
||||
}
|
||||
export function json(data, init = {}) {
|
||||
const { revalidate, ...responseInit } = init;
|
||||
const headers = new Headers(responseInit.headers);
|
||||
revalidate !== undefined && headers.set("X-Revalidate", revalidate.toString());
|
||||
headers.set("Content-Type", "application/json");
|
||||
const response = new Response(JSON.stringify(data), {
|
||||
...responseInit,
|
||||
headers
|
||||
});
|
||||
response.customBody = () => data;
|
||||
return response;
|
||||
}
|
||||
Reference in New Issue
Block a user