generated from garrettmills/template-npm-typescript
parent
0b710eed40
commit
3c3132a7b3
@ -0,0 +1,44 @@
|
||||
|
||||
// ============ An implementation of first() ============
|
||||
|
||||
/**
|
||||
* Get the type of the ith element of a tuple.
|
||||
* Kind: Vector -> number -> *
|
||||
*/
|
||||
type ElementOf<Arr extends unknown[], Index extends keyof Arr> = Arr[Index]
|
||||
|
||||
// An example - this returns the type of the second element of the tuple
|
||||
// In this case, literal values are types (kinda), so this resolves to the literal type 'hello'
|
||||
// That is, it is satisfiable by the subset of strings {hello}
|
||||
// In an editor like VSCode, hover over the name of the type to see what it resolved to
|
||||
type ExampleElementOf = ElementOf<[true, 'hello', 3.14], 1>
|
||||
|
||||
// This succeeds because the value 'hello' is assignable to the type 'hello'
|
||||
const exampleElementGood: ExampleElementOf = 'hello'
|
||||
|
||||
// This fails because the value 'fubar' is not assignable to the type 'hello'
|
||||
// const exampleElementBad: ExampleElementOf = 'fubar'
|
||||
|
||||
/**
|
||||
* Accepts non-empty tuples.
|
||||
* Does this by coercing the inferred tuple Arr to the intersection type Arr with an interface
|
||||
* that MUST have an index 0 which has the type of the first element of Arr.
|
||||
* Kind: Vector -> *
|
||||
*/
|
||||
type NonEmpty<Arr extends unknown[]> = Arr & { 0: ElementOf<Arr, 0> }
|
||||
|
||||
/**
|
||||
* Gets the first element of a non-empty tuple.
|
||||
*
|
||||
* Accepts a parameter `arr` of type `NonEmpty<Arr>` where `Arr` is the inferred
|
||||
* type of the tuple.
|
||||
*
|
||||
* @param arr
|
||||
*/
|
||||
function first<Arr extends unknown[]>(arr: NonEmpty<Arr>): ElementOf<Arr, 0> {
|
||||
return arr[0]
|
||||
}
|
||||
|
||||
const firstNumber = first([3.14]) // => type: number
|
||||
const firstString = first(['hello', 3.14]) // => type: string
|
||||
// const firstEmpty = first([]) // => type: never
|
@ -1,3 +0,0 @@
|
||||
|
||||
export const HELLO_WORLD = 'Hello, World!'
|
||||
|
@ -0,0 +1,63 @@
|
||||
|
||||
// ============ An example using dependent types to overload function signatures ============
|
||||
// In this example, we define a type CertCheckResponse which defines the structure of a response
|
||||
// object for a hypothetical certificate validation API.
|
||||
// We then implement a function sendCheckResponse whose return type and parameter signature
|
||||
// depends on the value of the success parameter.
|
||||
|
||||
/** A helper function that gets a Date one hour in the future. */
|
||||
function inOneHour(): Date {
|
||||
const d = new Date()
|
||||
d.setMinutes(d.getMinutes() + 60)
|
||||
return d
|
||||
}
|
||||
|
||||
|
||||
/** A type conditional on a boolean parameter. */
|
||||
type CertCheckResponse<Success extends boolean> = Success extends true
|
||||
? {issuer: string, expires: Date}
|
||||
: {error: string}
|
||||
|
||||
|
||||
// Hover over the identifier to view the computed types.
|
||||
type ExampleSuccess = CertCheckResponse<true>
|
||||
type ExampleFail = CertCheckResponse<false>
|
||||
|
||||
|
||||
/**
|
||||
* This is where the magic happens. We combine TypeScripts function/method overloading with
|
||||
* the dependent types to make not only the return type but also the function signature dependent
|
||||
* on the value of the `success` parameter.
|
||||
*
|
||||
* (The third and final signature is the signature of the underlying implementation and must be
|
||||
* compatible with all the other signatures.)
|
||||
*
|
||||
* @param success
|
||||
* @param issuer
|
||||
* @param expires
|
||||
*/
|
||||
function sendCheckResponse(success: true, issuer: string, expires: Date): CertCheckResponse<typeof success>
|
||||
function sendCheckResponse(success: false, error: string): CertCheckResponse<typeof success>
|
||||
function sendCheckResponse(success: boolean, issuerOrError: string, expires?: Date): CertCheckResponse<typeof success>
|
||||
function sendCheckResponse(success: boolean, issuerOrError: string, expires: Date = new Date()): CertCheckResponse<typeof success> {
|
||||
if ( success ) {
|
||||
return {
|
||||
issuer: issuerOrError,
|
||||
expires: expires,
|
||||
}
|
||||
}
|
||||
|
||||
return {error: issuerOrError}
|
||||
}
|
||||
|
||||
|
||||
const successResponse = sendCheckResponse(true, 'contoso.com', inOneHour()) // => {issuer: string, expires: Date}
|
||||
const failureResponse = sendCheckResponse(false, 'Invalid issuer.') // => {error: string}
|
||||
|
||||
// Interestingly, this gracefully degrades to more general types when the value
|
||||
// of success cannot be inferred statically. This is why we include the 3rd overload
|
||||
// signature with `success: boolean`.
|
||||
let someBool!: boolean // example of a non-specific boolean value
|
||||
|
||||
// This has the union type {issuer: string, expires: Date} | {error: string}
|
||||
const ambiguousResponse = sendCheckResponse(someBool, 'String?')
|
@ -0,0 +1,80 @@
|
||||
|
||||
// ============ 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)*/
|
Loading…
Reference in new issue