Skip to content

Add a slice by copying a sibling ​

How-to. Grow a deployment by one slice — an HTTP controller, an AMQP consumer or a Temporal workflow's activities — without a generator. For what a slice is and why it is shaped this way, see Split a router into controllers and Split a worker into slices.

There is no generate slice command, and none is needed: a slice is the same shape on all three transports — a transport file minting one piece from (contract, key), and a module.ts providing and exporting it — so the sibling directory is the template. What no template can collapse is that the contract lives in a different package from its implementation, so adding a slice touches three places, in this order.

1. Declare the key in the contract package ​

The contract is the source of truth, and a client takes it without the server, so the new key goes there first:

TransportContract packageWhat you add
HTTPexamples/order-api-contracta fragment under a new key of contract
AMQPexamples/order-amqp-contracta consumer (and its queue binding)
Temporalexamples/order-temporal-contracta workflow and its activities' declarations

Nothing else compiles against a key the contract does not have, which is why this step comes first.

2. Copy the sibling directory ​

sh
cp -r examples/order-amqp-worker/src/slices/audit examples/order-amqp-worker/src/slices/invoicing

Then rename, in the two files you copied:

  • the key passed to the mint call — AmqpHandler(orderContract, "orderAudit") becomes the key you declared in step 1;
  • the export names — orderAudit → orderInvoicing, AuditSlice → InvoicingSlice, and the Module("…") name with it;
  • the body — the leaf itself, and the module's needs and imports, which say what this slice's providers take from outside and which vertical it owns.

The file to copy is named for its transport, and so is the call inside it:

TransportSibling to copyThe mint call
HTTPexamples/order-api/src/slices/customers/api.OrpcController(contract, path) in controller.ts
AMQPexamples/order-amqp-worker/src/slices/audit/AmqpHandler(contract, key) in handler.ts
Temporalexamples/order-temporal-worker/src/slices/billing/TemporalWorkflowActivities(contract, key) in activities.ts

A Temporal workflow's body is the one part that does not live in the slice: it runs in Temporal's workflow sandbox, bundled from the deployment's workflows.ts, so the workflow itself — its declareWorkflow export — is added there and only its activities are the slice's. It is also the one step the compiler does not check (step 4 says what does).

3. Add it to the root ​

In the deployment's src/module.ts, put the piece in the composing array and the slice module in imports:

TransportThe arrayAnd in imports
HTTPapi.OrpcRouter(contract)([…, newController])the slice module
AMQPAmqpHandlers(orderContract)([…, newHandler])the slice module
TemporalTemporalActivities(orderContract)([…, newActivities])the slice module

4. Let the compiler name what you missed ​

Run pnpm typecheck. Every step above but one has its own error, at the place that step lives, so a forgotten one is named rather than discovered at boot:

ForgotWhat pnpm typecheck reports
step 1, the contract keythe mint call refuses a key the contract does not declare
step 3, the piece in the arrayUNCOVERED HANDLERS (or CONTROLLERS, ACTIVITIES), naming the key
step 3, the slice in importsthe root has an UNDECLARED NEEDS naming the piece's port
the new slice's own needsthe slice module has an UNDECLARED NEEDS naming the port it reads

Read a wiring error says where in each message the name is.

The exception is a Temporal workflow's declareWorkflow export.TemporalActivities checks that every activity is covered, but the worker takes workflows.ts as a bundle path, which no type reaches. Forget the export and pnpm typecheck stays green, the worker boots with the workflows it already had, and the new one fails only when it is invoked: its workflow task fails for a type the bundle never registered, and Temporal retries the task, so the execution never completes. What catches it is a spec that runs the new workflow end to end through the real worker — client.executeWorkflow("…", …), as examples/order-temporal-worker/src/temporal-runtime.spec.ts does for fulfillOrder — which then times out instead of passing.

Then copy the sibling's spec the same way: a deployment's specs drive it through the real root on its test-fixtures.ts, so a new case is a new it on fixtures that already exist.

Where to go next ​

Released under the MIT License.