Skip to content

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:

api/context.ts
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

  1. Overriding IGlobalContext does not set the actual context value - the context property inside middleware stays undefined at runtime.
  2. To avoid this, use setContext() instruction to set api context value.

Setting api context

To set api context, use setContext()api client instruction:

api/client.ts
ts
import { client } from '@alette/signal';
import { setDefinedContext } from './context';

export const api = client(
    setDefinedContext,
);

/*...*/

Now your context type can be seen in middleware:

api/posts.ts
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:

api/posts.ts
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

api/context.ts
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!' });
api/base.ts
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.

api/client.ts
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:

context/index.ts
ts
export const myContext = {
    name: 'Alette Signal'
}
api/posts.ts
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:

context/index.ts
ts
export const myContext = {
    name: 'Alette Signal'
}
api/base.ts
ts
import { myContext } from '../context'

api.tell(setContext(myContext))
api/posts.ts
ts
export const getPosts = query(
    queryParams(({ context: { name } }) => ({
        name: value
    }))
)

Now, context values can be mocked in tests via setContext():

apiContext.test.ts
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.

Released under the Apache 2.0 License.