Appearance
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:
ts
import { setContext } from "@alette/signal";
interface IApiContext {
hey: string;
}
declare module "@alette/signal" {
interface IGlobalContext extends IApiContext {}
}
export const setDefinedContext = setContext({ hey: 'hello!' });DANGER
- Overriding
IGlobalContextdoes not set the actual context value - thecontextproperty inside middleware staysundefinedat runtime. - To avoid this, use
setContext()instruction to set api context value.
Setting api context
To set api context, use setContext()api client instruction:
ts
import { client } from '@alette/signal';
import { setDefinedContext } from './context';
export const api = client(
setDefinedContext,
);
/*...*/Now your context type can be seen in middleware:
ts
export const getPosts = query(
queryParams(({ context: { hey } }) => ({
name: hey
}))
)To change api context use .tell():
ts
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():
ts
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:
ts
import { forContext } from '@alette/signal';
// ...
// returns { hey: 'Alette Signal' }
const apiContext = await api.ask(forContext());To access context in middleware use the context property:
ts
export const getPosts = query(
queryParams(({ context: { hey } }) => ({
name: hey
}))
)TIP
The context property is accessible in every middleware.
Full api client configuration with context
ts
import { coreApiPlugin, setContext } from "@alette/signal";
interface IApiContext {
hey: string;
}
declare module "@alette/signal" {
interface IGlobalContext extends IApiContext {}
}
export const setDefinedContext = setContext({ hey: 'hello!' });ts
import { coreApiPlugin, setContext } from "@alette/signal";
export const core = coreApiPlugin();
const {
query: baseQuery,
mutation: baseMutation,
custom: baseCustom,
} = core.use();TIP
To learn more about plugins, refer to the Alette Signal api plugin documentation.
ts
import { client, activatePlugins } from "@alette/signal";
import { setDefinedContext } from "./context.ts";
import {
core,
baseQuery,
baseMutation,
baseCustom
} from "./base.ts";
export const api = client(
setDefinedContext,
activatePlugins(core.plugin)
);
export const query = baseQuery.toFactory();
export const mutation = baseMutation.toFactory();
export const custom = baseCustom.toFactory();TIP
To learn more about .toFactory(), refer to the Alette Signal blueprint factory documentation.
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:
ts
export const myContext = {
name: 'Alette Signal'
}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:
ts
export const myContext = {
name: 'Alette Signal'
}ts
import { myContext } from '../context'
api.tell(setContext(myContext))ts
export const getPosts = query(
queryParams(({ context: { name } }) => ({
name: value
}))
)Now, context values can be mocked in tests via setContext():
ts
// ...
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.