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.