Author transforms
Define both sides
Section titled “Define both sides”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.
Add a context target
Section titled “Add a context target”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.
Register on the callee
Section titled “Register on the callee”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.
Apply on the caller
Section titled “Apply on the caller”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);WorkerEntrypoint services
Section titled “WorkerEntrypoint services”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');