nuqs
Version:
Type-safe search params state manager for React - Like useState, but stored in the URL query string
132 lines (126 loc) • 4.13 kB
JavaScript
import { createContext, createElement, useContext } from 'react';
// src/errors.ts
var errors = {
303: "Multiple adapter contexts detected. This might happen in monorepos.",
404: "nuqs requires an adapter to work with your framework.",
409: "Multiple versions of the library are loaded. This may lead to unexpected behavior. Currently using `%s`, but `%s` (via the %s adapter) was about to load on top.",
414: "Max safe URL length exceeded. Some browsers may not be able to accept this URL. Consider limiting the amount of state stored in the URL.",
429: "URL update rate-limited by the browser. Consider increasing `throttleMs` for key(s) `%s`. %O",
500: "Empty search params cache. Search params can't be accessed in Layouts.",
501: "Search params cache already populated. Have you called `parse` twice?"
};
function error(code) {
return `[nuqs] ${errors[code]}
See https://err.47ng.com/NUQS-${code}`;
}
// src/url-encoding.ts
function renderQueryString(search) {
if (search.size === 0) {
return "";
}
const query = [];
for (const [key, value] of search.entries()) {
const safeKey = key.replace(/#/g, "%23").replace(/&/g, "%26").replace(/\+/g, "%2B").replace(/=/g, "%3D").replace(/\?/g, "%3F");
query.push(`${safeKey}=${encodeQueryValue(value)}`);
}
const queryString = "?" + query.join("&");
warnIfURLIsTooLong(queryString);
return queryString;
}
function encodeQueryValue(input) {
return input.replace(/%/g, "%25").replace(/\+/g, "%2B").replace(/ /g, "+").replace(/#/g, "%23").replace(/&/g, "%26").replace(/"/g, "%22").replace(/'/g, "%27").replace(/`/g, "%60").replace(/</g, "%3C").replace(/>/g, "%3E").replace(/[\x00-\x1F]/g, (char) => encodeURIComponent(char));
}
var URL_MAX_LENGTH = 2e3;
function warnIfURLIsTooLong(queryString) {
if (process.env.NODE_ENV === "production") {
return;
}
if (typeof location === "undefined") {
return;
}
const url = new URL(location.href);
url.search = queryString;
if (url.href.length > URL_MAX_LENGTH) {
console.warn(error(414));
}
}
// src/debug.ts
var debugEnabled = isDebugEnabled();
function debug(message, ...args) {
if (!debugEnabled) {
return;
}
const msg = sprintf(message, ...args);
performance.mark(msg);
try {
console.log(message, ...args);
} catch (error2) {
console.log(msg);
}
}
function warn(message, ...args) {
if (!debugEnabled) {
return;
}
console.warn(message, ...args);
}
function sprintf(base, ...args) {
return base.replace(/%[sfdO]/g, (match) => {
const arg = args.shift();
if (match === "%O" && arg) {
return JSON.stringify(arg).replace(/"([^"]+)":/g, "$1:");
} else {
return String(arg);
}
});
}
function isDebugEnabled() {
try {
if (typeof localStorage === "undefined") {
return false;
}
const test = "nuqs-localStorage-test";
localStorage.setItem(test, test);
const isStorageAvailable = localStorage.getItem(test) === test;
localStorage.removeItem(test);
if (!isStorageAvailable) {
return false;
}
} catch (error2) {
console.error(
"[nuqs]: debug mode is disabled (localStorage unavailable).",
error2
);
return false;
}
const debug2 = localStorage.getItem("debug") ?? "";
return debug2.includes("nuqs");
}
// src/adapters/lib/context.ts
var context = createContext({
useAdapter() {
throw new Error(error(404));
}
});
context.displayName = "NuqsAdapterContext";
if (debugEnabled && typeof window !== "undefined") {
if (window.__NuqsAdapterContext && window.__NuqsAdapterContext !== context) {
console.error(error(303));
}
window.__NuqsAdapterContext = context;
}
function createAdapterProvider(useAdapter2) {
return ({ children, ...props }) => createElement(
context.Provider,
{ ...props, value: { useAdapter: useAdapter2 } },
children
);
}
function useAdapter() {
const value = useContext(context);
if (!("useAdapter" in value)) {
throw new Error(error(404));
}
return value.useAdapter();
}
export { context, createAdapterProvider, debug, error, renderQueryString, useAdapter, warn };