generated from garrettmills/template-npm-typescript
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
81 lines
3.1 KiB
81 lines
3.1 KiB
|
|
// ============ An example using dependent/constructed types to build route handlers ============
|
|
// This is a more advanced example that prevents a hypothetical web route definition library
|
|
// from accepting handler functions whose parameters do not match the types of the provided parameters.
|
|
// This is a simplification of real-world routing implementation I built in @extollo/lib:
|
|
// https://code.garrettmills.dev/extollo/lib/src/branch/master/src/http/routing/Route.ts
|
|
// https://code.garrettmills.dev/extollo/lib/src/branch/master/src/util/support/types.ts
|
|
|
|
// First, we'll define some helper types for constructing/destructing variadic tuple types:
|
|
|
|
/** Takes a tuple and a type and produces a new tuple suffixed w/ the type. */
|
|
type AppendTypeArray<Arr extends unknown[], Item> = [...Arr, Item]
|
|
|
|
/** Converts a tuple of types and formats it as a function signature. */
|
|
type TypeArraySignature<Arr extends unknown[], Return> = (...params: Arr) => Return
|
|
|
|
type ExampleTypeArray = AppendTypeArray<['test', 3.14], true> // => ['test', 3.14, true]
|
|
type ExampleTypeSignature = TypeArraySignature<ExampleTypeArray, void> // => ('test', 3.14, true) => void
|
|
|
|
|
|
// Now, some helper types used by the Route class:
|
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
interface Request {} // Stub type for the HTTP request
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
interface HandledRoute {} // Prevents adding parameters to handled routes
|
|
|
|
/** Type of functions that provide parameters to the request handler. */
|
|
type ParameterProvider<T> = (request: Request) => T
|
|
|
|
/**
|
|
* Stub route definition implementation. The type of the handler function
|
|
* accepted by `handleWith(...)` depends on the parameter middleware added
|
|
* via `withParameter(...)`.
|
|
*/
|
|
export class Route<HandlerParams extends unknown[] = []> {
|
|
|
|
/** Start a net GET route. */
|
|
public static get(endpoint: string): Route<[]> {
|
|
return new Route(endpoint)
|
|
}
|
|
|
|
constructor(
|
|
public readonly endpoint: string,
|
|
) {}
|
|
|
|
/** The collected parameter middleware. */
|
|
protected parameters: ParameterProvider<unknown>[] = []
|
|
|
|
/** Register a new middleware parameter. */
|
|
public withParameter<T>(provider: ParameterProvider<T>): Route<AppendTypeArray<HandlerParams, T>> {
|
|
this.parameters.push(provider)
|
|
return this as unknown as Route<AppendTypeArray<HandlerParams, T>>
|
|
}
|
|
|
|
/** Register an appropriately-typed handler for this route. */
|
|
public handleWith(handler: TypeArraySignature<HandlerParams, unknown>): HandledRoute {
|
|
return this
|
|
}
|
|
}
|
|
|
|
Route.get('/no-param')
|
|
.handleWith(() => 'hello!')
|
|
|
|
Route.get('/one-param')
|
|
.withParameter(() => 3.14)
|
|
.handleWith((pi: number) => `Pi is ${pi}!`)
|
|
|
|
Route.get('/two-params')
|
|
.withParameter(() => 'Hello, ')
|
|
.withParameter(() => 'John Doe')
|
|
.handleWith((greeting: string, name: string) => `${greeting}${name}`)
|
|
|
|
/* Route.get('/invalid-params')
|
|
.withParameter(() => 3.14)
|
|
.handleWith((pi: number, greeting: string) => `${pi}${greeting}`)*/
|
|
|
|
/* Route.get('/invalid-method-call')
|
|
.handleWith(() => 'Hello!')
|
|
.withParameter(() => 3.14)*/
|