way
- Type Safe Path Builder
Introduction
way
is a TypeScript library that makes it possible to define and create type safe URL paths.
It also supports handling query parameters in a type-safe way.
Both query serialization and query parsing is configurable, but way
supports
URLSearchParams and Zod out-of-the-box.
Installation
npm install @katis/way
Features
- Define URL path schemas with both named and parameter segments.
- Create paths with optional query parameters.
- Parse query parameters for a specific path in a type-safe manner.
- Create partial paths for router libraries etc.
Basic Usage
Defining Schema
A schema defines the shape of your paths and their associated queries. It can consist of named segments, parameter segments, and associated queries.
Here's an example of the features of the library:
import { way } from "@katis/way";
import zod from "zod";
const RootQuery = zod.object({
search: zod.string().optional(),
});
const ProductQuery = zod.object({
modal: zod.enum(["true", "false"]).transform((b) => b === "true"),
});
const schema = {
[way.path]: RootQuery,
products: {
[way.param.productId]: {
[way.path]: ProductQuery,
details: { [way.path]: way.NoQuery },
},
edit: { [way.path]: ProductEditQuery },
},
} satisfies way.Schema;
const root = way.root(schema);
const index = root({ search: "socks" });
const productA = root.products["prod-a"]({ modal: true });
const detailsB = root.products["prod-b"].details();
const query = root.products["productId"](way.query, "modal=true");
Relative paths
way.rel
can be used to create relative path builders:
const productsRel = root.products(way.rel);
const path = productsRel["prod-a"].details();
Routes
way.route
can be used to build a path from any segment without query parameters.
This is useful for configuring routing libraries.
const productsRel = root.products(way.rel);
function Routes() {
return (
<Routes>
{/* path = "/products" */}
<Route path={root.products(way.route)}>
{ /* path = ":productId/details" */ }
<Route path={productsRel[":productId"].details(way.route)}>
{ /* path = ":productId" */ }
<Route path={productsRel[":productId"](way.route)}>
</Route>
</Routes>
)
}
Custom query codec
A query codec is an object that encodes and decodes a query string into a JS object and back.
import queryString from "query-string";
const codec: way.QueryCodec = {
decode: (encoded) => queryString.parse(encoded, { parseBooleans: true }),
encode: (query) => queryString.stringify(query),
};
const RootQuery = zod.object({
modal: zod.boolean().default(false),
});
const root = way.root(schema, { codec });
const search = "?modal=true";
const query = root.products["1234"](way.query, search);
Custom query parser
Query parser is just an object with a parse
-method. The type of the query is
infered from the parser.
const DateQuery: way.QueryParser<{ date: Date }> = {
parse(obj) {
const date = new Date(obj.date as any);
if (isNaN(date.valueOf())) throw Error("Invalid date");
return { date };
},
};
const root = way.root({
[way.path]: DateQuery,
});
root({ date: new Date() });