Skip to content

Middleware reuse

Middleware reuse in Alette Signal refers to the ability of storing and sharing preconfigured middleware across request blueprints while keeping types intact.

Preconfiguring middleware

To preconfigure middleware, extract it into a variable and pass the required arguments:

ts
import { headers } from '@alette/signal';

const withCommonHeaders = headers({ 'X-Header': 'hi' });

To preconfigure multiple middleware at once, use the slot() utility:

ts
import { headers, slot, tap, tapError } from '@alette/signal';

const withCommon = slot(
    headers({ 'X-Header': 'hi' }),
    tap((response, requestData) => {
        console.log("Received response:", { response, requestData })
    }),
    tapError((error, requestData) => {
        console.log("Failed with error:", { error, requestData })
    })
);

The slot() utility can accept preconfigured middleware:

ts
import { headers, slot, /*...*/ } from '@alette/signal';

const withCommonHeaders = headers({ 'X-Header': 'hi' });

const withCommon = slot(
    // Types are preserved
    withCommonHeaders,
    /*...*/
);

The slot() utility allows middleware to access typed request data:

ts
import { output, slot, as, map } from '@alette/signal';

const Post = as<{/*...*/}>();

const withPostTransformation = slot(
    output(Post),
    map(post => ({ value: post.id, label: post.title }))
);

The slot() utility can accept implicit middleware:

ts
import { slot, retry, throttle } from '@alette/signal';

const withCommon = slot(
    retry, // retry from "query()" by default
	throttle // 500ms by default 
);

Applying preconfigured middleware

To apply preconfigured middleware, pass them to a request blueprint:

api/base.ts
ts
import { coreApiPlugin, headers, slot } from "@alette/signal";

export const core = coreApiPlugin();
const withCommonHeaders = headers({ 'X-Header': 'hi' });

const {
    query,
    mutation,
    custom,
} = core.use();

export const baseQuery = query(withCommonHeaders);
export const baseMutation = mutation(withCommonHeaders);
export const baseCustom = custom(withCommonHeaders);

To apply multiple preconfigured middleware, invoke the function returned from the slot() utility and spread the result into a request blueprint:

api/base.ts
ts
import {
    coreApiPlugin,
    headers,
    slot,
    tap,
    tapError
} from "@alette/signal";

export const core = coreApiPlugin();

const {
    query,
    mutation,
    custom,
} = core.use();

const withCommon = slot(
    headers({ 'X-Header': 'hi' }),
    tap((response, requestData) => {
        console.log("Received response:", { response, requestData })
    }),
    tapError((error, requestData) => {
        console.log("Failed with error:", { error, requestData })
    })
);

export const baseQuery = query(...withCommon());
export const baseMutation = mutation(...withCommon());
export const baseCustom = custom(...withCommon());

Full example

api/base.ts
ts
import { 
    coreApiPlugin,
	headers,
	slot,
	tap,
	tapError
} from "@alette/signal";

export const core = coreApiPlugin();

const {
    query,
    mutation,
    custom,
} = core.use();

const withCommon = slot(
    headers({ 'X-Header': 'hi' }),
    tap((response, requestData) => {
        console.log("Received response:", { response, requestData })
    }),
    tapError((error, requestData) => {
        console.log("Failed with error:", { error, requestData })
    })
);

export const baseQuery = query(...withCommon());
export const baseMutation = mutation(...withCommon());
export const baseCustom = custom(...withCommon());
api/client.ts
ts
import { client, activatePlugins, setOrigin } from "@alette/signal";
import { 
    core,
	baseQuery,
	baseMutation,
	baseCustom
} from "./base.ts";

export const api = client(
    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.

Now requests will be executed with common headers and preconfigured tap() and tapError() loggers:

api/post.ts
ts
import { output, path, tap, as } from '@alette/signal';
import { query } from './client.ts';

const Post = as<{/*...*/}>();

const getPost = query(
    output(Post),
    path('/post/1'),
    tap((_, { headers }) => {
    	// Will log "{ 'X-Header': 'hi' }"
        console.log(headers);
	})
);

// The preconfigured "tap()" middleware
// will log the response
getPost.spawn();

Released under the Apache 2.0 License.