Skip to main content
Version: 1.0.0

Test a compensation handler

This test checks the SDK contract with a business adapter that releases a reservation: identifier, original receipt, idempotency key and result. A local adapter holds a reservation in memory. See the compensation guide for runtime-governed compensation.

Install the SDK​

Terminal
touba sdk install javascript

Define the handler​

release.mjs
import {createToolHandler, createToolResult} from '@touba/sdk';

export function releaseHandler(releaseReservation) {
return createToolHandler({
toolRef:'tool:reservations/ReleaseReservation',
inputSignature:[{name:'reservation_id',type_ref:'builtin:Text'}],
outputType:'builtin:Text',
run:async ({request:reservation_id, context}) => {
const response = await releaseReservation({
reservationId:reservation_id,
originalReceipt:context.compensation.original_effect_receipt,
idempotencyKey:context.effect.effect_id,
});
return createToolResult(response.message, {
effectReceipt:context.createEffectReceipt({outcome:'applied',receipt_ref:response.receiptRef}),
compensationReceipt:context.createCompensationReceipt({result:'completed'}),
});
},
});
}

Check data and business idempotency​

The execution metadata below are test fixtures. In execution, the runtime establishes context and checks receipts, authority and budgets.

exercise.mjs
import assert from 'node:assert/strict';
import {releaseHandler} from './release.mjs';

const reservations = new Set(['RES-42']);
const completed = new Map();
const original = {outcome:'applied', receipt_ref:'reservation:RES-42'};
let writes = 0;
const handler = releaseHandler(async ({reservationId, originalReceipt, idempotencyKey}) => {
assert.equal(reservationId,'RES-42');
assert.deepEqual(originalReceipt,original);
assert.equal(idempotencyKey,'effect:release-42');
if (!completed.has(idempotencyKey)) {
assert.equal(reservations.delete(reservationId),true); writes++;
completed.set(idempotencyKey,{message:'Released',receiptRef:'release:RES-42'});
}
return completed.get(idempotencyKey);
});
const request = {arguments:{reservation_id:'RES-42'},env:{},
compensation:{original_effect_receipt:original}, effect:{effect_id:'effect:release-42'},
createEffectReceipt:value=>value, createCompensationReceipt:value=>value};
await handler(request);
await handler(request);
assert.equal(writes,1); assert.equal(reservations.size,0);
await assert.rejects(handler({...request,arguments:{reservation_id:42}}));
assert.equal(writes,1);
console.log('COMPENSATION_HANDLER_CONTRACT_OK');
Terminal
node exercise.mjs

Expected result: COMPENSATION_HANDLER_CONTRACT_OK. Replace the adapter with your provider's implementation and persist its idempotency keys. This callback test complements runtime compensation tests.