From ee5caa2f942395b13badcfaed67cab1ba84cbeda Mon Sep 17 00:00:00 2001 From: "Alexis H. Munsayac" Date: Tue, 3 Mar 2026 09:17:03 +0800 Subject: [PATCH 1/9] Add `batchedQuery` --- pnpm-workspace.yaml | 2 ++ src/data/query.ts | 73 +++++++++++++++++++++++++++++++++++++-------- 2 files changed, 62 insertions(+), 13 deletions(-) create mode 100644 pnpm-workspace.yaml diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 00000000..efc037aa --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +onlyBuiltDependencies: + - esbuild diff --git a/src/data/query.ts b/src/data/query.ts index 67ff9efd..31f16fb0 100644 --- a/src/data/query.ts +++ b/src/data/query.ts @@ -4,11 +4,11 @@ import { getOwner, onCleanup, sharedConfig, - type Signal, - startTransition + startTransition, + type Signal } from "solid-js"; import { getRequestEvent, isServer } from "solid-js/web"; -import { useNavigate, getIntent, getInPreloadFn } from "../routing.js"; +import { getInPreloadFn, getIntent, useNavigate } from "../routing.js"; import type { CacheEntry, NarrowResponse } from "../types.js"; const LocationHeader = "Location"; @@ -130,7 +130,7 @@ export function query any>(fn: T, name: string): Cac } let res; if (!isServer && sharedConfig.has && sharedConfig.has(key)) { - res = sharedConfig.load!(key) // hydrating + 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 as any)); @@ -178,16 +178,14 @@ export function query any>(fn: T, name: string): Cac return async (v: any | Response) => { 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); + 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) { @@ -218,7 +216,7 @@ export function query any>(fn: T, name: string): Cac query.get = (key: string) => { const cached = getCache().get(key) as CacheEntry; return cached[2]; -} +}; query.set = (key: string, value: T extends Promise ? never : T) => { const cache = getCache(); @@ -232,7 +230,13 @@ query.set = (key: string, value: T extends Promise ? never : T) => { } else { cache.set( key, - (cached = [now, Promise.resolve(value), value, "preload", createSignal(now) as Signal & { count: number }]) + (cached = [ + now, + Promise.resolve(value), + value, + "preload", + createSignal(now) as Signal & { count: number } + ]) ); cached[4].count = 0; } @@ -275,3 +279,46 @@ function isPlainObject(obj: object) { (!(proto = Object.getPrototypeOf(obj)) || proto === Object.prototype) ); } + +function debounce(callback: (value: T) => Promise): (value: T) => Promise { + let timeout: ReturnType | undefined; + let current: Promise | undefined; + let resolve: (value: R) => void; + let reject: (value: unknown) => void; + + return value => { + if (timeout) { + clearTimeout(timeout); + } + timeout = setTimeout(() => { + callback(value).then(resolve, reject); + current = undefined; + }); + if (!current) { + current = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + } + return current; + }; +} + +export function batchedQuery( + callback: (queries: Query[]) => Promise, + lookup: (data: Data, query: Query) => Return +): (query: Query) => Promise { + const pendingQueries: Query[] = []; + + const debounced = debounce(async () => { + const result = await callback(pendingQueries); + pendingQueries.length = 0; + return result; + }); + + return async (query: Query) => { + pendingQueries.push(query); + const data = await debounced(pendingQueries); + return lookup(data, query); + }; +} From 7343298c8699a9cb88d64fa01a1fbecb0e3e50f1 Mon Sep 17 00:00:00 2001 From: "Alexis H. Munsayac" Date: Tue, 3 Mar 2026 15:34:39 +0800 Subject: [PATCH 2/9] fix expected timeout --- src/data/query.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/data/query.ts b/src/data/query.ts index 31f16fb0..bd7893dc 100644 --- a/src/data/query.ts +++ b/src/data/query.ts @@ -290,10 +290,20 @@ function debounce(callback: (value: T) => Promise): (value: T) => Promi if (timeout) { clearTimeout(timeout); } - timeout = setTimeout(() => { - callback(value).then(resolve, reject); - current = undefined; + const expected = setTimeout(() => { + callback(value).then((value) => { + if (expected === timeout) { + resolve(value); + current = undefined; + } + }, (value) => { + if (expected === timeout) { + reject(value); + current = undefined; + } + }); }); + timeout = expected; if (!current) { current = new Promise((res, rej) => { resolve = res; From 9d53e4d5ad3ab9a1e95fcb1e55bc5eff6e94d958 Mon Sep 17 00:00:00 2001 From: "Alexis H. Munsayac" Date: Tue, 3 Mar 2026 15:37:03 +0800 Subject: [PATCH 3/9] Revert "fix expected timeout" This reverts commit 7343298c8699a9cb88d64fa01a1fbecb0e3e50f1. --- src/data/query.ts | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/data/query.ts b/src/data/query.ts index bd7893dc..31f16fb0 100644 --- a/src/data/query.ts +++ b/src/data/query.ts @@ -290,20 +290,10 @@ function debounce(callback: (value: T) => Promise): (value: T) => Promi if (timeout) { clearTimeout(timeout); } - const expected = setTimeout(() => { - callback(value).then((value) => { - if (expected === timeout) { - resolve(value); - current = undefined; - } - }, (value) => { - if (expected === timeout) { - reject(value); - current = undefined; - } - }); + timeout = setTimeout(() => { + callback(value).then(resolve, reject); + current = undefined; }); - timeout = expected; if (!current) { current = new Promise((res, rej) => { resolve = res; From af574c28c15c5cabb184d86a8dfcf6f7e8918d8f Mon Sep 17 00:00:00 2001 From: "Alexis H. Munsayac" Date: Tue, 3 Mar 2026 15:38:43 +0800 Subject: [PATCH 4/9] fix pending queries to dispose immediately --- src/data/query.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/data/query.ts b/src/data/query.ts index 31f16fb0..7f168117 100644 --- a/src/data/query.ts +++ b/src/data/query.ts @@ -311,8 +311,9 @@ export function batchedQuery( const pendingQueries: Query[] = []; const debounced = debounce(async () => { - const result = await callback(pendingQueries); + const currentQueries = [...pendingQueries]; pendingQueries.length = 0; + const result = await callback(currentQueries); return result; }); From b5dc7c704b12d51092be4679de9abf76f19d297a Mon Sep 17 00:00:00 2001 From: "Alexis H. Munsayac" Date: Tue, 3 Mar 2026 15:42:50 +0800 Subject: [PATCH 5/9] Add `index` to lookup --- src/data/query.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/data/query.ts b/src/data/query.ts index 7f168117..80a2bee3 100644 --- a/src/data/query.ts +++ b/src/data/query.ts @@ -306,7 +306,7 @@ function debounce(callback: (value: T) => Promise): (value: T) => Promi export function batchedQuery( callback: (queries: Query[]) => Promise, - lookup: (data: Data, query: Query) => Return + lookup: (data: Data, query: Query, index: number) => Return ): (query: Query) => Promise { const pendingQueries: Query[] = []; @@ -318,8 +318,8 @@ export function batchedQuery( }); return async (query: Query) => { - pendingQueries.push(query); + const targetIndex = pendingQueries.push(query) - 1; const data = await debounced(pendingQueries); - return lookup(data, query); + return lookup(data, query, targetIndex); }; } From 2ae75e9901f16299fab295d577e9c999082d342e Mon Sep 17 00:00:00 2001 From: "Alexis H. Munsayac" Date: Wed, 4 Mar 2026 11:03:19 +0800 Subject: [PATCH 6/9] Update pnpm-workspace.yaml --- pnpm-workspace.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index efc037aa..e82e6dbd 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,2 +1,4 @@ +packages: + - ./ onlyBuiltDependencies: - esbuild From c68a4f69b47b7005c13217a53ad23f7b0e6c9240 Mon Sep 17 00:00:00 2001 From: "Alexis H. Munsayac" Date: Wed, 4 Mar 2026 11:04:51 +0800 Subject: [PATCH 7/9] Update query.ts --- src/data/query.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/data/query.ts b/src/data/query.ts index 80a2bee3..17bf1341 100644 --- a/src/data/query.ts +++ b/src/data/query.ts @@ -308,15 +308,15 @@ export function batchedQuery( callback: (queries: Query[]) => Promise, lookup: (data: Data, query: Query, index: number) => Return ): (query: Query) => Promise { - const pendingQueries: Query[] = []; - - const debounced = debounce(async () => { - const currentQueries = [...pendingQueries]; - pendingQueries.length = 0; + const debounced = debounce(async (queries: Query[]) => { + const currentQueries = [...queries]; + queries.length = 0; const result = await callback(currentQueries); return result; }); + const pendingQueries: Query[] = []; + return async (query: Query) => { const targetIndex = pendingQueries.push(query) - 1; const data = await debounced(pendingQueries); From 4d7c892e8d9593fa5a5842a5346fd5e5c3263dd6 Mon Sep 17 00:00:00 2001 From: "Alexis H. Munsayac" Date: Wed, 4 Mar 2026 15:06:19 +0800 Subject: [PATCH 8/9] add missing export --- src/data/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/index.ts b/src/data/index.ts index c6209164..ad665aca 100644 --- a/src/data/index.ts +++ b/src/data/index.ts @@ -1,5 +1,5 @@ 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 { query, revalidate, cache, type CachedFunction, batchedQuery } from "./query.js"; export { redirect, reload, json } from "./response.js"; From 28f350d7d43e234422951c363cf4201b5e8b42fe Mon Sep 17 00:00:00 2001 From: "Alexis H. Munsayac" Date: Wed, 4 Mar 2026 15:08:12 +0800 Subject: [PATCH 9/9] shift to throttle --- src/data/query.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/data/query.ts b/src/data/query.ts index 17bf1341..d35a50c4 100644 --- a/src/data/query.ts +++ b/src/data/query.ts @@ -280,20 +280,20 @@ function isPlainObject(obj: object) { ); } -function debounce(callback: (value: T) => Promise): (value: T) => Promise { +function throttle(callback: (value: T) => Promise): (value: T) => Promise { let timeout: ReturnType | undefined; let current: Promise | undefined; let resolve: (value: R) => void; let reject: (value: unknown) => void; return value => { - if (timeout) { - clearTimeout(timeout); + if (!timeout) { + timeout = setTimeout(() => { + callback(value).then(resolve, reject); + current = undefined; + timeout = undefined; + }); } - timeout = setTimeout(() => { - callback(value).then(resolve, reject); - current = undefined; - }); if (!current) { current = new Promise((res, rej) => { resolve = res; @@ -308,7 +308,7 @@ export function batchedQuery( callback: (queries: Query[]) => Promise, lookup: (data: Data, query: Query, index: number) => Return ): (query: Query) => Promise { - const debounced = debounce(async (queries: Query[]) => { + const debounced = throttle(async (queries: Query[]) => { const currentQueries = [...queries]; queries.length = 0; const result = await callback(currentQueries);