Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | 1x 1x 1x 75x 75x 99x 99x 99x 99x 99x 75x 1x 4x 4x 4x 4x 4x 4x 25x 25x 25x 25x 12x 12x 12x 12x 12x 12x 12x 13x 13x 13x 17x 17x 17x 13x 13x 13x 13x 13x 4x 4x | import type { ActualSchema, Empty, ExpectedSchema, Options } from "./types";
import { isOptions, replacePathParams, stringifyHash, stringifyQueries } from "./utilities";
/**
* Utility used to define parameter types in parameter schemas
* @example
* { id: type as string }
*/
export const type: unknown = undefined;
/**
* Utility used when no parameters are required for endpoints or methods
* @example
* routes({ "/path": { get: empty, post: empty } })
*
* routes({ "/path": empty })
*/
export const empty: Empty = undefined;
/**
* Higher-order function that returns a URL generator for an endpoint, given its parameters
*
* @param endpoint - The endpoint path, which can include path parameters (e.g., "/users/[id]")
* @param baseUrl - The base URL to prepend to the generated URL (optional)
* @param mocking - A flag indicating whether to generate a URL for mocking purposes, which allows missing parameters to be replaced with "*" (optional)
* @returns A function that takes an options object and returns a generated URL with path parameters replaced, query parameters appended, and hash fragment included as specified in the options.
*
* @example
* const getUrl = method("/users/[id]", "https://example.com/api");
* getUrl({ params: { id: "1" }, queries: { flag: true } });
* // => "https://example.com/api/users/1?flag=true"
*/
function method(endpoint: string, baseUrl = "", mocking = false) {
return (options?: Options) => {
const path = replacePathParams(endpoint, options?.params, mocking);
const queries = stringifyQueries(options?.queries, mocking);
const hash = stringifyHash(options?.hash, mocking);
return `${baseUrl}${path}${queries}${hash}`;
};
}
/**
* Takes a route schema and returns an object of type-safe URL builders.
* It provides autocomplete for path parameters during definition and ensures type safety at usage.
*
* @param schema - The routes schema, defining endpoints, their associated HTTP methods (which can be omitted for the shorthand feature), and various options (e.g., path, query, and hash parameters).
* @returns An object containing type-safe URL builders for each route and method.
*
* @example
* import { routes, type, empty } from "routopia";
*
* const myRoutes = routes({
* // Example for the /users route
* "/users": {
* get: {
* // Define query parameters
* queries: {
* required: type as string,
* optional: type as string | undefined,
* },
* },
* // No parameters needed
* post: empty,
* },
* "/users/[id]": {
* get: {
* // Define path parameters
* params: {
* id: type as string,
* },
* },
* },
*
* // The GET shorthand is available by omitting the method definition.
* "/short": empty, // Equivalent to: "/short": { get: empty },
* "/short/[param]": {
* // The same applies to defining parameters.
* params: {
* param: type as string,
* },
* queries: {
* q: type as string | undefined,
* },
* hash: type as string,
* },
* });
*/
export function routes<Schema extends ExpectedSchema<Schema>>(schema: Schema): ActualSchema<Schema, "">;
/**
* Takes a base URL and a routes schema, and returns an object of type-safe URL builders.
* It provides autocomplete for path parameters during definition and ensures type safety at usage.
*
* @param baseUrl - The base URL to prepend to the generated URLs.
* @param schema - The routes schema, defining endpoints, their associated HTTP methods (which can be omitted for the shorthand feature), and various options (e.g., path, query, and hash parameters).
* @returns An object containing type-safe URL builders for each route and method.
*
* @example
* import { routes, type, empty } from "routopia";
*
* const myRoutes = routes("https://example.com/api", {
* // Example for the /users route
* "/users": {
* get: {
* // Define query parameters
* queries: {
* required: type as string,
* optional: type as string | undefined,
* },
* },
* // No parameters needed
* post: empty,
* },
* "/users/[id]": {
* get: {
* // Define path parameters
* params: {
* id: type as string,
* },
* },
* },
*
* // The GET shorthand is available by omitting the method definition.
* "/short": empty, // Equivalent to: "/short": { get: empty },
* "/short/[param]": {
* // The same applies to defining parameters.
* params: {
* param: type as string,
* },
* queries: {
* q: type as string | undefined,
* },
* hash: type as string,
* },
* });
*/
export function routes<BaseUrl extends string, Schema extends ExpectedSchema<Schema>>(
baseUrl: BaseUrl,
schema: Schema,
): ActualSchema<Schema, BaseUrl>;
export function routes<Schema extends ExpectedSchema<Schema>, BaseUrl extends string>(
...args: [Schema] | [BaseUrl, Schema]
) {
const hasBaseUrl = args.length === 2;
const baseUrl = hasBaseUrl ? args[0] : "";
const schema = hasBaseUrl ? args[1] : args[0];
return Object.entries<object | Empty>(schema).reduce((acc, [endpoint, methodsOrOptions]) => {
const generate = (mocking = false) => method(endpoint, baseUrl, mocking);
const generatorWithMock = Object.assign(generate(), { mock: generate(true) });
const keys = methodsOrOptions && Object.keys(methodsOrOptions);
if (!keys || keys.every(isOptions)) {
return Object.assign(acc, {
[endpoint]: {
mock: generate(true),
get: generatorWithMock,
},
});
}
// The `keys` are `HttpMethod`, as enforced by `ExpectedSchema`.
return Object.assign(acc, {
[endpoint]: keys.reduce(
(acc, httpMethod) =>
Object.assign(acc, {
[httpMethod]: generatorWithMock,
}),
{
mock: generate(true),
},
),
});
}, {}) as ActualSchema<Schema, BaseUrl>;
}
|