nuqs
Version:
Type-safe search params state manager for React - Like useState, but stored in the URL query string
177 lines (173 loc) • 4.95 kB
JavaScript
import { warn, debug, error } from './chunk-5WWTJYGR.js';
// src/utils.ts
function safeParse(parser, value, key) {
try {
return parser(value);
} catch (error2) {
warn(
"[nuqs] Error while parsing value `%s`: %O" + (key ? " (for key `%s`)" : ""),
value,
error2,
key
);
return null;
}
}
function getDefaultThrottle() {
if (typeof window === "undefined") return 50;
const isSafari = Boolean(window.GestureEvent);
if (!isSafari) {
return 50;
}
try {
const match = navigator.userAgent?.match(/version\/([\d\.]+) safari/i);
return parseFloat(match[1]) >= 17 ? 120 : 320;
} catch {
return 320;
}
}
// src/update-queue.ts
var FLUSH_RATE_LIMIT_MS = getDefaultThrottle();
var updateQueue = /* @__PURE__ */ new Map();
var queueOptions = {
history: "replace",
scroll: false,
shallow: true,
throttleMs: FLUSH_RATE_LIMIT_MS
};
var transitionsQueue = /* @__PURE__ */ new Set();
var lastFlushTimestamp = 0;
var flushPromiseCache = null;
function getQueuedValue(key) {
return updateQueue.get(key);
}
function resetQueue() {
updateQueue.clear();
transitionsQueue.clear();
queueOptions.history = "replace";
queueOptions.scroll = false;
queueOptions.shallow = true;
queueOptions.throttleMs = FLUSH_RATE_LIMIT_MS;
}
function enqueueQueryStringUpdate(key, value, serialize, options) {
const serializedOrNull = value === null ? null : serialize(value);
debug("[nuqs queue] Enqueueing %s=%s %O", key, serializedOrNull, options);
updateQueue.set(key, serializedOrNull);
if (options.history === "push") {
queueOptions.history = "push";
}
if (options.scroll) {
queueOptions.scroll = true;
}
if (options.shallow === false) {
queueOptions.shallow = false;
}
if (options.startTransition) {
transitionsQueue.add(options.startTransition);
}
queueOptions.throttleMs = Math.max(
options.throttleMs ?? FLUSH_RATE_LIMIT_MS,
Number.isFinite(queueOptions.throttleMs) ? queueOptions.throttleMs : 0
);
return serializedOrNull;
}
function getSearchParamsSnapshotFromLocation() {
return new URLSearchParams(location.search);
}
function scheduleFlushToURL({
getSearchParamsSnapshot = getSearchParamsSnapshotFromLocation,
updateUrl,
rateLimitFactor = 1
}) {
if (flushPromiseCache === null) {
flushPromiseCache = new Promise((resolve, reject) => {
if (!Number.isFinite(queueOptions.throttleMs)) {
debug("[nuqs queue] Skipping flush due to throttleMs=Infinity");
resolve(getSearchParamsSnapshot());
setTimeout(() => {
flushPromiseCache = null;
}, 0);
return;
}
function flushNow() {
lastFlushTimestamp = performance.now();
const [search, error2] = flushUpdateQueue({
updateUrl,
getSearchParamsSnapshot
});
if (error2 === null) {
resolve(search);
} else {
reject(search);
}
flushPromiseCache = null;
}
function runOnNextTick() {
const now = performance.now();
const timeSinceLastFlush = now - lastFlushTimestamp;
const throttleMs = queueOptions.throttleMs;
const flushInMs = rateLimitFactor * Math.max(0, Math.min(throttleMs, throttleMs - timeSinceLastFlush));
debug(
"[nuqs queue] Scheduling flush in %f ms. Throttled at %f ms",
flushInMs,
throttleMs
);
if (flushInMs === 0) {
flushNow();
} else {
setTimeout(flushNow, flushInMs);
}
}
setTimeout(runOnNextTick, 0);
});
}
return flushPromiseCache;
}
function flushUpdateQueue({
updateUrl,
getSearchParamsSnapshot
}) {
const search = getSearchParamsSnapshot();
if (updateQueue.size === 0) {
return [search, null];
}
const items = Array.from(updateQueue.entries());
const options = { ...queueOptions };
const transitions = Array.from(transitionsQueue);
resetQueue();
debug("[nuqs queue] Flushing queue %O with options %O", items, options);
for (const [key, value] of items) {
if (value === null) {
search.delete(key);
} else {
search.set(key, value);
}
}
try {
compose(transitions, () => {
updateUrl(search, {
history: options.history,
scroll: options.scroll,
shallow: options.shallow
});
});
return [search, null];
} catch (err) {
console.error(error(429), items.map(([key]) => key).join(), err);
return [search, err];
}
}
function compose(fns, final) {
const recursiveCompose = (index) => {
if (index === fns.length) {
return final();
}
const fn = fns[index];
if (!fn) {
throw new Error("Invalid transition function");
}
fn(() => recursiveCompose(index + 1));
};
recursiveCompose(0);
}
export { FLUSH_RATE_LIMIT_MS, enqueueQueryStringUpdate, getQueuedValue, resetQueue, safeParse, scheduleFlushToURL };