A type-safe URLSearchParams
SafeSearchParams is a library that provides a type-safe interface for working with URL search parameters. It allows you to define and enforce types for your search parameters, ensuring that your code is more robust and less error-prone.
SafeSearchParams is built on top of the native URLSearchParams
API, providing
a familiar interface while adding type safety and validation. It supports
parsing and serializing values of various types, including strings, integers,
regular expressions, enums, and more (you can also define your own custom
types).
When you work with search params you usually have something like this:
- Parse (and validate) the search params into a structured object
- Transform teh structured object back into search params when you navigate
This strategy has 2 issues:
- Any params that are not in the structured object are lost
- The order of the params is not preserved
This library aims to solve these issues by keeping the URLSearchParams object and providing methods to update it.
To install SafeSearchParams, you can use npm or yarn:
deno add jsr:@dldc/safe-search-params
import { rInteger, rString, safeSearchParams } from "@dldc/safe-search-params";
const params = safeSearchParams("a=1&b=2&c=hey");
console.log(params.get("a", rInteger())); // 1
console.log(params.get("b", rInteger())); // 2
console.log(params.get("c", rString())); // "hey"
console.log(params.get("d", rString())); // null
You can get multiple values as an object:
const params = safeSearchParams("a=1&b=2&c=hey");
const obj = params.getObj({ a: rInteger(), b: rInteger(), c: rString() });
console.log(obj); // { a: 1, b: 2, c: "hey" }
Note that any property that is missing or invalid will be null
in the object.
The getObjStrict
method is similar to getObj
, but it will return null
if
any value is invalid:
const params = safeSearchParams("a=hey");
const obj = params.getObjStrict({ a: rString() });
console.log(obj); // { a: "hey" }
const missing = params.getObjStrict({ b: rString() });
console.log(missing); // { b: null }
const invalid = params.getObjStrict({ a: rInteger() });
// a is not an integer
console.log(invalid); // null
You can handle multiple values for a single parameter:
const params = safeSearchParams("tag=first&tag=second&tag=third");
// by default it will return the first value
console.log(params.get("tag", rString())); // "first"
console.log(params.get("tag", rMultiple(rString()))); // ["first", "second", "third"]
You can update parameters using the set
and setObj
methods:
const params = safeSearchParams("a=1&b=2&c=hey");
const updated = params.set("a", rInteger(), 3);
console.log(updated.toString()); // "a=3&b=2&c=hey"
const updatedObj = params.setObj({ a: rInteger(), b: rInteger() }, {
a: 3,
b: 4,
});
console.log(updatedObj.toString()); // "a=3&b=4&c=hey"
You can delete parameters using the delete
method:
const params = safeSearchParams("a=1&b=2&c=hey");
const updated = params.delete("b");
console.log(updated.toString()); // "a=1&c=hey"
You can sort parameters by name using the sort
method:
const params = safeSearchParams("b=2&a=1&c=hey");
const sorted = params.sort();
console.log(sorted.toString()); // "a=1&b=2&c=hey"
The library provides detailed error messages when validation fails:
const params = safeSearchParams("a=1&b=2&c=hey");
try {
params.getOrThrow("c", rInteger());
} catch (error) {
console.error(error.message); // "Failed to validate Integer rule for property "c" with values: hey. Invalid integer"
}
You can define your own custom datatype. Here is an example of a UUID datatype:
import { TDatatype } from "@dldc/safe-search-params";
const uuidRegex =
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
export function rUUID(): TDatatype<string | null> {
return {
name: "UUID",
parse: (input) => {
const last = input.length > 0 ? input[0] : null;
if (last === null || !uuidRegex.test(last)) {
return { valid: false, error: "Invalid UUID" };
}
return { valid: true, value: last };
},
serialize: (output) => output === null ? [] : [output],
};
}
Creates a new TSafeSearchParams
instance.
function safeSearchParams(
init?: Iterable<string[]> | Record<string, string> | string | URLSearchParams,
): TSafeSearchParams;
urlSearchParams
: The underlyingURLSearchParams
object.
toString()
: Returns the search params as a string.get<Out>(name: string, type: TDatatype<Out>): Out | null
: Gets a specific value as the given type.getOrThrow<Out>(name: string, type: TDatatype<Out>): Out
: Gets a specific value as the given type, throwing an error if the value does not match the type.has(name: string, type: TDatatype<any>): boolean
: Checks if a specific parameter exists and matches the given type.getObj<Obj extends TDtObjBase>(obj: Obj): TDtObjOutput<Obj>
: Gets multiple values using the given mapping of name and types.getObjStrict<Obj extends TDtObjBase>(obj: Obj): TDtObjOutputStrict<Obj> | null
: Gets multiple values using the given mapping of name and types, returningnull
if any value is invalid.getObjOrThrow<Obj extends TDtObjBase>(obj: Obj): TDtObjOutputStrict<Obj>
: Gets multiple values as the given type, throwing an error if any values are invalid.append<T>(name: string, type: TDatatype<T>, value: T): TSafeSearchParams
: Adds a new value to the search params.delete(name: string): TSafeSearchParams
: Deletes a value from the search params.set<T>(name: string, type: TDatatype<T>, value: T): TSafeSearchParams
: Sets a single value.sort(): TSafeSearchParams
: Sorts the search params by name.setObj<Obj extends TDtObjBase>(obj: Obj, values: TDtObjOutput<Obj>): TSafeSearchParams
: Sets multiple values using the given mapping of name and types.
Defines a data type for parsing and serializing values.
interface TDatatype<Out> {
readonly name: string;
parse: (input: string[]) => TDtParseOutput<Out>;
serialize: (output: Out) => string[];
}
rString()
: Parses and serializes strings.rInteger()
: Parses and serializes integers.rPresent()
: Checks if a parameter is present.rRegex(regex: RegExp)
: Parses and serializes values matching a regular expression.rEnum(values: string[])
: Parses and serializes values from a predefined set.rMultiple(subType: TDatatype<TOut>)
: Parses and serializes multiple values of a given type.rRequired(parser: TDatatype<TOut>)
: Ensures that a value is not missing,null
, orundefined
.rUUID()
: Parses and serializes UUIDs.