Adapters
Adapters are a way of extending your exported API functionality with custom functions that execute on top of your defined router.
There are many options as to what you can do with an adapter, some of the use-cases include:
- Modifying calls to mutate result or arguments
- Extending functionality of existing calls (like query integration or swr integration)
- Adding middleware
- Adding constants
Declaration & Usage
Adapters are defined as an optional argument of the router call.
import { api } from '@hulla/api'
const a = api()
export const withAdapters = a.router({
name: 'example',
routes: [],
adapters: {
logRouterName: (adapter) => () => console.log(adapter.routerName)
}
})
withAdapters.logRouterName() // 'example'
Router Adapter API
One thing that warrants a special mention is the fact, that router contains not only all of the methods of your router API, but also the following internal methods, that can help you write more effective adapters:
find
- utility function that returns a{ route: string, call: Function }
object.invoke
- utility function, callfind
and invokes the function with args and any possible interceptorsmappedRouter
- nested object that encodes individual methods with functions - i.e.{ call: { foo: Function }}
Check out the Adapter API Reference for more info
Adapter declaration syntax
If you studied the initial example with a keen eye, you may have noticed, that the adapter logRouterName
is a curried callback - a function that returns a function. This is intentional, to prevent a common mistake a lot of people make when writing their adapters. Consider this:
adapters: { isAuthenticated: (_adp) => db.auth() } // 😱 DON'T DO THIS
// note we don't need adapter arg here, just making it explicit it's available if necessary
Looks relatively ok on first glance, and you’d expect it to work as usersAPI.isAuthenticated // boolean
but this is actually a mistake! Adapters are executed at the create
call with the passed router and this means, every time you tried using usersAPI
it would execute the db.auth()
, which could potentially be an issue, especially if you are using it inside client JSX that continually hydrates. For this reason, the line above actually a type error!
The proper way to do this would be:
const usersAPI = a.router({
name: 'users',
routes: [],
adapters: { isAuthenticated: (_adp) => () => db.auth() } // ✅ correct
// note the extra callback here ^
})
Now instead of we call usersAPI.isAuthenticated
usersAPI.isAuthenticated()
- this executed the db.auth()
only when we need it to, instead of every time the usersAPI
is accessed.
Declaring constants (override)
As mentioned, you will get a type error if you attempt to create a constant instead of curried function.
This is to prevent you some pesky hydration errors and racking up your cloud service provider bills. That being said, if you despite the warnings want to override this, the package does give you the power to do it.
// @ts-expect-error i know what i'm doing and am aware it will run on every call
const your = a.router({
name: 'smug-self-praise',
routes: [],
adapters: {
// @ts-expect-error i know what i'm doing and am aware it will run on every call
FAVORITE_PACKAGE: (_adp) => '@hulla/api'
}
})
your.FAVORITE_PACKAGE // '@hulla/api'
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 |