Skip to content

Feature Slices

As your application grows, defining all job types and processors in a single file becomes unwieldy. Feature slices let you split them by domain — each slice owns its type definitions and processor handlers, composed together at the application level.

A slice consists of two files: definitions and processors.

  • Directorysrc/
    • slice-orders-definitions.ts
    • slice-orders-processors.ts
    • slice-notifications-definitions.ts
    • slice-notifications-processors.ts
    • client.ts
    • index.ts

Definitions declare the job types for a feature:

slice-orders-definitions.ts
import { defineJobTypes } from "queuert";
export const orderJobTypes = defineJobTypes<{
"orders.create": { entry: true; input: { userId: string }; output: { orderId: string } };
"orders.fulfill": { input: { orderId: string }; output: { fulfilled: boolean } };
}>();

Processors implement the handlers, typed against the slice’s definitions:

slice-orders-processors.ts
import { createProcessors } from "queuert";
import { client } from "./client.js";
import { orderJobTypes } from "./slice-orders-definitions.js";
export const orderProcessors = createProcessors({
client,
jobTypes: orderJobTypes,
processors: {
"orders.create": {
attemptHandler: async ({ job, complete }) =>
complete(async ({ continueWith }) =>
continueWith({ typeName: "orders.fulfill", input: { orderId: "123" } }),
),
},
"orders.fulfill": {
attemptHandler: async ({ job, complete }) => complete(async () => ({ fulfilled: true })),
},
},
});

createProcessors type-checks each handler against the slice’s own definitions (plus any external defs it declares), then returns a Processors that’s plugged into createInProcessWorker.

At the application level, pass arrays of slices directly to createClient and createInProcessWorker:

import { createClient, createInProcessWorker } from "queuert";
import { orderJobTypes } from "./slice-orders-definitions.js";
import { orderProcessors } from "./slice-orders-processors.js";
import { notificationJobTypes } from "./slice-notifications-definitions.js";
import { notificationProcessors } from "./slice-notifications-processors.js";
const client = await createClient({
stateAdapter,
notifyAdapter,
jobTypes: [orderJobTypes, notificationJobTypes],
});
const worker = await createInProcessWorker({
client,
processors: [orderProcessors, notificationProcessors],
});

Both fields accept a single slice or an array of slices. When an array is passed, duplicate type/processor keys are detected:

  • createClient / jobTypes — overlapping type names produce a TypeScript error at compile time; validated registries with overlapping getTypeNames() throw DuplicateJobTypeError at runtime.
  • createInProcessWorker / processors — overlapping processor keys produce a TypeScript error at compile time and throw DuplicateJobTypeError at runtime.

When a slice needs to reference job types from another slice — for example, declaring a blocker from the notifications domain — use the optional TExternal type parameter on defineJobTypes:

slice-orders-definitions.ts
import { type JobTypeDefinitions, defineJobTypes } from "queuert";
import { type notificationJobTypes } from "./slice-notifications-definitions.js";
export const orderJobTypes = defineJobTypes<
{
"orders.place": {
entry: true;
input: { userId: string };
continueWith: { typeName: "orders.confirm" };
};
"orders.confirm": {
input: { orderId: string };
output: { confirmed: boolean };
blockers: [{ typeName: "notifications.send" }];
};
},
// External types — available for blocker reference validation, not owned by this slice
JobTypeDefinitions<typeof notificationJobTypes>
>();
  • T (first parameter) = owned definitions — these become the registry’s phantom type
  • TExternal (second parameter) = read-only reference context, defaults to Record<never, never>
  • blockers validates against entry types in T & TExternal
  • The registry’s phantom type remains T only — TExternal types are not included

This eliminates the need for “workflow slices” that duplicate type definitions just to make blocker references type-check. When slices are passed as an array to createClient, all references resolve against the full set of definitions.

When writing processors that reference types from another slice, nothing special is needed — the client already exposes every slice’s types, so continueWith and blockers resolve against the full set.

orders.create-order
orders.fulfill-order
notifications.send-notification