Api context
Api context in Alette Signal is a value sharing mechanism that shares values across the whole api system.
Setting api context type
To set api context type, you need to override Alette Signal IGlobalContext
interface:
// ./src/api/base.ts
import { client } from '@alette/signal'
declare module "@alette/signal" {
interface IGlobalContext {
hey: string;
}
}
// ...
Now your context type can be seen in middleware:
// ./src/api/posts.ts
export const getPosts = query(
queryParams(({ context: { hey } }) => ({
name: hey
}))
)
DANGER
- Overriding
IGlobalContext
does not set the actual context value - thecontext
property inside middleware staysundefined
at runtime. - To avoid this, use
setContext()
instruction to set api context value.
Setting api context
To set api context, use setContext()
api client instruction:
import { client } from '@alette/signal'
const api = client(
setContext({ hey: 'Alette Signal' })
)
To change api context use .tell()
:
api.tell(setContext({ hey: 'Not Alette Signal?' }))
DANGER
Api context set via .tell()
will be wiped after api client reset.
The api context can also be set by passing a function to setContext()
:
setContext(() => ({ hey: 'Alette Signal' }))
// or
setContext(async () => ({ hey: 'Alette Signal' }))
INFO
Alette Signal waits for async setContext()
to finish before running requests.
Accessing api context
To access api context, use forContext()
api client question:
import { setContext } from '@alette/signal'
// ...
// returns { hey: 'Alette Signal' }
const apiContext = await api.ask(forContext())
To access context in middleware use the context
property:
// ./src/api/posts.ts
export const getPosts = query(
queryParams(({ context: { hey } }) => ({
name: hey
}))
)
Direct value access
Direct value access is an antipattern where global values are accessed directly inside api code without passing them through api context.
Direct value access makes code untestable:
// ./src/context/index.ts
export const myContext = {
name: 'Alette Signal'
}
// ./src/api/posts.ts
import { myContext } from '../../context'
export const getPosts = query(
queryParams(() => {
// The "name" property cannot be changed later in tests
const value = myContext.name
return {
name: value
}
})
)
To avoid this, always pass values the api depends on through the api context:
// ./src/context/index.ts
export const myContext = {
name: 'Alette Signal'
}
// ./src/api/base.ts
import { myContext } from '../../context'
api.tell(setContext(myContext))
// ./src/api/posts.ts
export const getPosts = query(
queryParams(({ context: { name } }) => ({
name: value
}))
)
Now, context values can be mocked in tests via setContext()
:
// ...
test('it uses context name as a query parameter', async () => {
const expectedName = 'Not Alette Signal'
api.tell(setContext({
...myContext,
name: expectedName
}))
// Later...
expect(returnedName).toEqual(expectedName)
})
Did you know?
Alette Signal api context is a Dependency Injection mechanism.