Custom Methods
Custom methods are an advanced concept for creating your own methods that define routes in the router.
What are methods?
They are a way intreracting with your router calls. Each defined route call has it’s specified method.
For example, each procedure has a hard-coded method under the hood with value call
This method is the user-facing way of invoking individual procedures
myAPI.call(...) // method === 'call' (accessing procedure)
If you’re familiar with the request integration, each exported method of request
is a specific HTTP method,
i.e.
routes: [
a.request.get(...) // method === 'get'
]
// later:
myAPI.get(...) // accessing method 'get'
Essentially custom methods are intended to be used as a helpers for defining your own routes (procedures). They do not do anything special, just think of them as extra functions attached your api sdk in initialization
Usage
Defining custom methods can be quite simple when you just want to change the method, but can get relatively complex once you need to create
your own method creation syntax like procedure
.
When you just want to change the method
The package exports a utility function called method
that helps you override your methods
It takes two arguments:
- Name of the
method
(string
) - The route to override method of (i.e.
procedure
)
import { api, method } from '@hulla/api'
const a = api()
export const exampleAPI = a.router({
name: 'custom method',
routes: [
method('custom', a.procedure('foo', () => 'bar'))
]
})
exampleAPI.custom('foo') // 'bar'
// ^ note the custom method,
When you need specific implementation logic for custom methods
But what if I want to access calls with my own methods? Well that’s where it gets more complicated, because you need to type out all the individual types of a route. Here’s a blueprint
We essentially are defining or own version of a procedure
Fun fact: This is similar to how the request calls in the request integration are implemented. So this pattern is powerful enough to create your own integrations. But you must understand the internal workings/types of the package to make use of it.
import type { Call, Args, Context, Obj, Resolver, Fn, Resolver, Context } from '@hulla/api'
import { api } from '@hulla/api'
// context needs to be the first parsed generic argument, otherwise we wouldn't be able
// to provide full type information about custom context to the Resolver
export function custom<const CTX extends Obj>(context: CTX) {
// and we'll curry the function for creating a Call (route)
// we'll hardcore a method of 'custom', but you can use a generic, function parameter, etc.
return <const N extend string, CTX extends Obj, const A extends Args, const R, const R2 = R>(
route: N,
fn: Fn<A, R>,
resolver?: Resolver<CTX & Context<N, 'custom' A>, R, R2>
): Call<N, 'custom', CTX & Context<N, 'custom', A>, A, R, R2> => {
doSomething()
return {
route,
fn,
resolver,
method: 'custom'
}
}
}
// Then we need to initialize our api with a custom method:
const a = api({
// you don't need to define custom context, but including it for type-safety demonstration
context: { foo: 'foo' },
methods: ctx => ({
custom: custom(ctx)
})
})
And then we’d define our router using our custom method
export const exampleAPI = a.router({
name: 'with custom call',
routes: [
a.custom('fancy', () => null), // calls doSomething()
a.procedure('not-so-fancy', () => null), // doesn't call doSomething()
]
})
Difference between adapters and custom methods
While both are primarily used by library authors for integrations, there are some key differences
Difference | Adapters | Custom Methods |
---|---|---|
Intended use | Adapting already defined routes | A way of defining new routes |
Defined in | Inside the .router() call | Inside the api() initalization |
Used in | in your API calls (exported .router() result) | routes parameter of .router() |
Argument | RouterAdapter | Context |
Return Type | Object with adapter methods | Object with custom methods |