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.
Defining a Slice
Section titled “Defining a Slice”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:
import { defineJobTypeRegistry } from "queuert";
export const orderJobTypeRegistry = defineJobTypeRegistry<{ "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:
import { createJobTypeProcessorRegistry } from "queuert";import { client } from "./client.js";import { orderJobTypeRegistry } from "./slice-orders-definitions.js";
export const orderJobTypeProcessorRegistry = createJobTypeProcessorRegistry({ client, jobTypeRegistry: orderJobTypeRegistry, 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 })), }, },});createJobTypeProcessorRegistry type-checks each handler against the slice’s own definitions, then returns a JobTypeProcessorRegistry that carries the slice’s type definitions via phantom symbol properties. This enables lightweight compatibility checks when passed to createInProcessWorker or mergeJobTypeProcessorRegistries.
Composing Slices
Section titled “Composing Slices”At the application level, merge registries and processors from all slices:
import { createClient, createInProcessWorker, mergeJobTypeRegistries, mergeJobTypeProcessorRegistries,} from "queuert";import { orderJobTypeRegistry } from "./slice-orders-definitions.js";import { orderJobTypeProcessorRegistry } from "./slice-orders-processors.js";import { notificationJobTypeRegistry } from "./slice-notifications-definitions.js";import { notificationJobTypeProcessorRegistry } from "./slice-notifications-processors.js";
const mergedJobTypeRegistry = mergeJobTypeRegistries({ slices: [orderJobTypeRegistry, notificationJobTypeRegistry] });
const client = await createClient({ stateAdapter, notifyAdapter, jobTypeRegistry: mergedJobTypeRegistry });
const worker = await createInProcessWorker({ client, jobTypeProcessorRegistry: mergeJobTypeProcessorRegistries({ slices: [orderJobTypeProcessorRegistry, notificationJobTypeProcessorRegistry], }),});mergeJobTypeRegistries detects overlapping keys at compile time and at runtime:
- Compile-time — overlapping type names produce a TypeScript error
- Runtime — validated registries with overlapping
getTypeNames()throwDuplicateJobTypeError
mergeJobTypeProcessorRegistries detects overlapping processor keys at runtime, throwing DuplicateJobTypeError.
Cross-Slice References
Section titled “Cross-Slice References”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 defineJobTypeRegistry:
import { type JobTypeRegistryDefinitions, defineJobTypeRegistry } from "queuert";import { type notificationJobTypeRegistry } from "./slice-notifications-definitions.js";
export const orderJobTypeRegistry = defineJobTypeRegistry< { "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 JobTypeRegistryDefinitions<typeof notificationJobTypeRegistry>>();T(first parameter) = owned definitions — these become the registry’s phantom typeTExternal(second parameter) = read-only reference context, defaults toRecord<never, never>blockersvalidates against entry types inT & TExternal- The registry’s phantom type remains
Tonly —TExternaltypes are not included
This eliminates the need for “workflow slices” that duplicate type definitions just to make blocker references type-check. After merging with mergeJobTypeRegistries, all references resolve against the full set of definitions.
When writing processors for a slice with external references, createJobTypeProcessorRegistry automatically extracts both owned and external definitions from the registry:
import { createJobTypeProcessorRegistry } from "queuert";import { client } from "./client.js";import { orderJobTypeRegistry } from "./slice-orders-definitions.js";
const orderJobTypeProcessorRegistry = createJobTypeProcessorRegistry({ client, jobTypeRegistry: orderJobTypeRegistry, processors: { // handlers have full type inference for continueWith, blockers, etc. },});Naming Convention
Section titled “Naming Convention”orders.create-orderorders.fulfill-ordernotifications.send-notificationSee Also
Section titled “See Also”- Utilities — mergeJobTypeRegistries — API reference
- Utilities — mergeJobTypeProcessorRegistries — API reference
- Chain Patterns — continuation references and patterns
- showcase-slices example — full runnable example