Skip to content

Author transforms

import {
applyTransforms,
createTransformContextTarget,
defineTransform,
registerTransform,
} from '@repo/do-transforms';
const observability = defineTransform<MyDO, { requestId?: string }>()
.caller(
(options: { requestId: string }) =>
async ({ next }) =>
next({ context: { requestId: options.requestId } })
)
.callee(
(options: { metricName: string }) =>
async ({ context, next }) => {
console.log(options.metricName, context.requestId);
return next();
}
);

The caller options and callee registration options are independently typed. The shared context shape ensures the caller cannot send fields the callee target does not accept.

A target only needs setContext() when a transform sends context:

export class MyDO extends DurableObject<Env> {
setContext(context: { requestId?: string }) {
return createTransformContextTarget(this, context);
}
async greet(name: string) {
return `Hello, ${name}`;
}
}

createTransformContextTarget creates the promise-pipelined target on which the callee transform runs. Domain methods remain ordinary async methods.

applyTransforms(MyDO, {
all: [
registerTransform(observability, {
metricName: 'do_rpc_calls',
}),
],
});

Use methods to narrow a transform to selected async methods:

applyTransforms(MyDO, {
all: [registerTransform(metrics, { prefix: 'my_do' })],
methods: {
deleteTodo: [registerTransform(authorization, { role: 'admin' })],
},
});

Global transforms run outside method-specific transforms.

const stub = env.MY_DO.get(id).with(observability, {
requestId: crypto.randomUUID(),
});
await stub.greet('Ada');

Parameterless transforms omit options on both APIs:

registerTransform(betterResultCodec);
stub.with(betterResultCodec);

The same registration model works for named entrypoints:

export class InternalService extends WorkerEntrypoint<Env> {
setContext(context: { requestId?: string }) {
return createTransformContextTarget(this, context);
}
async greet(name: string) {
return `Hello, ${name}`;
}
}
applyTransforms(InternalService, {
all: [registerTransform(observability, { metricName: 'service_calls' })],
});
await env.INTERNAL_SERVICE
.with(observability, { requestId: crypto.randomUUID() })
.greet('Ada');