xerpath v3.1.0
xerpath
Extensible path specification for router providers using tagged template literals.
Rationale
Almost every JavaScript router (HTTP or otherwise) out there has implemented their own syntax for route paths, which results in additional syntax parsing. The syntax for every router depends wildly on which router you're using.
This package / set of TypeScript typings / conventions aims to eliminate parsing of custom syntax altogether using nothing but template literals.
What it looks like
An example of how a consumer might use xerpath-inspired routing:
const path = r => r`foo/${r.param('a')}/bar`
myRouter.route(path, () => 'hello world')
Where path
is the path to be provided by consumers to the provider's router.
Yes, consumers would only need to pass a function. This way they would not need to know the implementation details (e.g. r
; r.param
) that takes place in the provider's end. This will eliminate the need for the context to be provided to the consumer.
Of course path
can also be a normal string, it's up to the myRouter.route
function to process.
What if I don't want to accept functions and just provide the context to the consumer?
No problem, again it's up to the myRouter.route
function to process.
Usage
As this is just a specification, there is almost no code that comes from this module. (But we do have TypeScript typings!)
Ultimately, this specification is defined by the TypeScript typings.
import {
BuiltPath,
BuiltPathComponent,
BuiltPathComponentMaker,
concatBuiltPath, // code
concatExtensiblePath, // code
createExtensiblePathRunner, // code
Path,
PathRunner
} from 'xerpath'
Providing a context
The provider may provide a context that has string properties and BuiltPathComponentMaker
values, which are assigned directly to the runner. This enables the consumer to use them and generate path components.
When using TypeScript, the context may then be declared as an interface and used with the extensible path. If the type argument is passed to the path as the type parameter TContext
, this will give the runner variable the intersection type PathRunner & TContext
. (Note that TContext
defaults to any
.)
interface MyContext {
word(): BuiltPathComponent
}
function word() {
/* ... */
}
let r: PathRunner & MyContext
r = createPathRunner<MyContext>({ word })
function route(path: Path<MyContext>) {
/* ... */
}
// r.word would be strongly typed here.
// This enables consumers who are using TypeScript to validate types
// or check the documentation of this function.
route(r => r`orange ${r.word()} apple`)
Consuming the path
To build a path, simply call the path with the runner/context.
Concatenate extensible paths and built paths
As extensible paths are just glorified strings, we can concatenate them together. Helper functions to concatenate extensible paths as well as built paths are provided.
Assigning path component makers to the context
E.g.
let r = createPathRunner({
// A path component that matches up to the next space.
word() {
return s => {
if (s.length === 0) {
return { match: false }
}
const res = /([^\s]*)(.*)/
return { match: true, value: res[1], remainingPath: res[2] }
}
},
// A path component that matches a regular expression.
regex(regex) {
return s => {
const res = regex.exec(s)
if (res == null) {
return { match: false }
}
return {
match: true,
value: res[0],
remainingPath: s.substr(res[0].length)
}
}
}
})
API
path
PathSingle
export interface PathSingle<TContext> {
(r: PathRunner & TContext): BuiltPath
}
A part of a path that takes the route context as an argument and outputs a BuiltPath
.
Path
export type Path<TContext> = ReadonlyArray<PathSingle<TContext> | string>
This is a path passed by the consumer to the provider.
E.g.
r => r`foo/${r.param('a')}/bar`
// Output by concat
['aaa/', r => r`${r.param('b')}`]
For providing a context, see § Providing a context
concat
export function concat(...paths: string[]): string
export function concat<
TContext extends Record<keyof TContext, ExtensiblePathComponentMaker> = any
>(...paths: (string | ExtensiblePath<TContext>)[]): ExtensiblePath<TContext>
Helper function to concatenate BuiltPath
s.
built-path-component-maker
BuiltPathComponentMaker
export interface BuiltPathComponentMaker {
(...args: any[]): BuiltPathComponent
}
E.g.
param
This is a function provided by the provider to the consumer by way of the context. In the example above, param
is the name (just the name) of the component maker.
For example, an HTTP router might provide a param
function for a single parameter within a slash, and a star
function for multiple parameters spanning multiple slashes.
The signature of the component maker is a function which takes arguments from the consumer and returns an extensible path component. For example, a param
component maker might take a HTTP route parameter name.
built-path-component
BuiltPathComponent
export interface BuiltPathComponent {
(s: string): null | undefined | { value?: any; remainingPath: string }
}
This is an individual custom path component returned from an BuiltPathComponentMaker
function.
E.g. The result of:
r.param('a')
The path component is the result of the component maker being run and is defined by the provider. The path component itself is just a function that takes a string, and attempts to match it.
An object with the property match
is returned that indicates whether a match is found.
If a match is found, the object will additionally contain the properties:
value
- Provider-defined value of the match. (E.g. a component makerparam
for an HTTP router might return a string up to a slash)remainingPath
- Remaining part of the string path after matching. (E.g. as in the example above, an HTTP router might return the rest of the string after the slash.) If there are no more characters remaning in the string, return the empty string.
path-runner
PathRunner
export interface PathRunner {
(
strings: TemplateStringsArray,
...values: ExtensiblePathComponent[]
): BuiltPath
}
E.g.
r
The runner is defined by the provider as a function that acts as the tag for the path. The runner is passed as an argument to the consumer-provided route. The runner doubles as the context for consumer routes. (§ Providing a context)
The runner must return an array of strings and BuiltPathComponent
s, in the order in which they appeared in the string. A createExtensiblePathRunner
function is provided that does this.
Note: The array may contain empty strings and should be handled by the provider's route function.
createExtensiblePathRunner
export function createExtensiblePathRunner(): ExtensiblePathRunner
export function createExtensiblePathRunner<
TContext extends Record<keyof TContext, ExtensiblePathComponentMaker> = any
>(context?: TContext): ExtensiblePathRunner & TContext
built-path
BuiltPath
export type BuiltPath = ReadonlyArray<string | BuiltPathComponent>
A route. The result of running the path runner against a Path
.
E.g. the result of:
r`foo/${r.param('a')}/bar`
concat
export function concat(...paths: string[]): string
export function concat(...paths: (string | BuiltPath)[]): BuiltPath