nuqs
Version:
Type-safe search params state manager for Next.js - Like React.useState, but stored in the URL query string
353 lines (345 loc) • 9.49 kB
JavaScript
;
// src/debug.ts
var enabled = isDebugEnabled();
function debug(message, ...args) {
if (!enabled) {
return;
}
const msg = sprintf(message, ...args);
performance.mark(msg);
console.log(message, ...args);
}
function warn(message, ...args) {
if (!enabled) {
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") || debug2.includes("next-usequerystate");
}
// 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/parsers.ts
function createParser(parser) {
function parseServerSideNullable(value) {
if (typeof value === "undefined") {
return null;
}
let str = "";
if (Array.isArray(value)) {
if (value[0] === void 0) {
return null;
}
str = value[0];
}
if (typeof value === "string") {
str = value;
}
return safeParse(parser.parse, str);
}
return {
eq: (a, b) => a === b,
...parser,
parseServerSide: parseServerSideNullable,
withDefault(defaultValue) {
return {
...this,
defaultValue,
parseServerSide(value) {
return parseServerSideNullable(value) ?? defaultValue;
}
};
},
withOptions(options) {
return {
...this,
...options
};
}
};
}
var parseAsString = createParser({
parse: (v) => v,
serialize: (v) => `${v}`
});
var parseAsInteger = createParser({
parse: (v) => {
const int = parseInt(v);
if (Number.isNaN(int)) {
return null;
}
return int;
},
serialize: (v) => Math.round(v).toFixed()
});
var parseAsHex = createParser({
parse: (v) => {
const int = parseInt(v, 16);
if (Number.isNaN(int)) {
return null;
}
return int;
},
serialize: (v) => {
const hex = Math.round(v).toString(16);
return hex.padStart(hex.length + hex.length % 2, "0");
}
});
var parseAsFloat = createParser({
parse: (v) => {
const float = parseFloat(v);
if (Number.isNaN(float)) {
return null;
}
return float;
},
serialize: (v) => v.toString()
});
var parseAsBoolean = createParser({
parse: (v) => v === "true",
serialize: (v) => v ? "true" : "false"
});
var parseAsTimestamp = createParser({
parse: (v) => {
const ms = parseInt(v);
if (Number.isNaN(ms)) {
return null;
}
return new Date(ms);
},
serialize: (v) => v.valueOf().toString()
});
var parseAsIsoDateTime = createParser({
parse: (v) => {
const date = new Date(v);
if (Number.isNaN(date.valueOf())) {
return null;
}
return date;
},
serialize: (v) => v.toISOString()
});
function parseAsStringEnum(validValues) {
return createParser({
parse: (query) => {
const asEnum = query;
if (validValues.includes(asEnum)) {
return asEnum;
}
return null;
},
serialize: (value) => value.toString()
});
}
function parseAsStringLiteral(validValues) {
return createParser({
parse: (query) => {
const asConst = query;
if (validValues.includes(asConst)) {
return asConst;
}
return null;
},
serialize: (value) => value.toString()
});
}
function parseAsNumberLiteral(validValues) {
return createParser({
parse: (query) => {
const asConst = parseFloat(query);
if (validValues.includes(asConst)) {
return asConst;
}
return null;
},
serialize: (value) => value.toString()
});
}
function parseAsJson(parser) {
return createParser({
parse: (query) => {
try {
const obj = JSON.parse(query);
if (typeof parser === "function") {
return parser(obj);
}
return obj;
} catch {
return null;
}
},
serialize: (value) => JSON.stringify(value),
eq(a, b) {
return a === b || JSON.stringify(a) === JSON.stringify(b);
}
});
}
function parseAsArrayOf(itemParser, separator = ",") {
const itemEq = itemParser.eq ?? ((a, b) => a === b);
const encodedSeparator = encodeURIComponent(separator);
return createParser({
parse: (query) => {
if (query === "") {
return [];
}
return query.split(separator).map(
(item, index) => safeParse(
itemParser.parse,
item.replaceAll(encodedSeparator, separator),
`[${index}]`
)
).filter((value) => value !== null && value !== void 0);
},
serialize: (values) => values.map((value) => {
const str = itemParser.serialize ? itemParser.serialize(value) : String(value);
return str.replaceAll(separator, encodedSeparator);
}).join(separator),
eq(a, b) {
if (a === b) {
return true;
}
if (a.length !== b.length) {
return false;
}
return a.every((value, index) => itemEq(value, b[index]));
}
});
}
// 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)}`);
}
return "?" + query.join("&");
}
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");
}
// src/serializer.ts
function createSerializer(parsers) {
function serialize(baseOrValues, values = {}) {
const [base, search] = isBase(baseOrValues) ? splitBase(baseOrValues) : ["", new URLSearchParams()];
const vals = isBase(baseOrValues) ? values : baseOrValues;
for (const key in parsers) {
const parser = parsers[key];
const value = vals[key];
if (!parser || value === void 0) {
continue;
}
const isMatchingDefault = parser.defaultValue !== void 0 && (parser.eq ?? ((a, b) => a === b))(value, parser.defaultValue);
if (value === null || parser.clearOnDefault && isMatchingDefault) {
search.delete(key);
} else {
search.set(key, parser.serialize(value));
}
}
return base + renderQueryString(search);
}
return serialize;
}
function isBase(base) {
return typeof base === "string" || base instanceof URLSearchParams || base instanceof URL;
}
function splitBase(base) {
if (typeof base === "string") {
const [path = "", search] = base.split("?");
return [path, new URLSearchParams(search)];
} else if (base instanceof URLSearchParams) {
return ["", new URLSearchParams(base)];
} else {
return [
base.origin + base.pathname,
new URLSearchParams(base.searchParams)
];
}
}
// src/errors.ts
var errors = {
409: "Multiple versions of the library are loaded. This may lead to unexpected behavior. Currently using `%s`, but `%s` was about to load on top.",
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}`;
}
exports.createParser = createParser;
exports.createSerializer = createSerializer;
exports.debug = debug;
exports.error = error;
exports.getDefaultThrottle = getDefaultThrottle;
exports.parseAsArrayOf = parseAsArrayOf;
exports.parseAsBoolean = parseAsBoolean;
exports.parseAsFloat = parseAsFloat;
exports.parseAsHex = parseAsHex;
exports.parseAsInteger = parseAsInteger;
exports.parseAsIsoDateTime = parseAsIsoDateTime;
exports.parseAsJson = parseAsJson;
exports.parseAsNumberLiteral = parseAsNumberLiteral;
exports.parseAsString = parseAsString;
exports.parseAsStringEnum = parseAsStringEnum;
exports.parseAsStringLiteral = parseAsStringLiteral;
exports.parseAsTimestamp = parseAsTimestamp;
exports.renderQueryString = renderQueryString;
exports.safeParse = safeParse;