Guide
Restore and publish an operation
Recover an earlier revenue calculation, test its output, and publish the restored draft. Restore preserves history (including the later revision) and does not start a run.
Workflow
- 1Install @permute/sdk. Set PERMUTE_API_KEY, PERMUTE_WORKSPACE_ID, PERMUTE_OPERATION_ID, and PERMUTE_REVISION_ID for an existing standalone operation and the revision you want to restore. The key needs operation write access and read access to its inputs.
- 2Use client.revisions.list(operationId) to choose a revision, then client.revisions.get(operationId, revisionId) to review its saved definition. This example expects one revenue row with a total field; set EXPECTED_TOTAL to the value you want to verify.
- 3Save the example as restore-operation.ts and run bun restore-operation.ts. It restores the chosen revision, runs the draft, checks the exact run output, and publishes only if the draft is still the one it tested.
- 4A 409 means the resource changed. Read the latest state and reconcile before retrying. For a Data Flow child, restore its revision with unchanged graph connections, then run and publish the parent flow. Dashboard source uses the resourceId returned by dashboards.getSource and takes effect when restored, with no separate publication step.
Complete example
import { PermuteClient } from '@permute/sdk';
const client = new PermuteClient({
apiKey: process.env.PERMUTE_API_KEY!,
}).withWorkspace(process.env.PERMUTE_WORKSPACE_ID!);
const operationId = process.env.PERMUTE_OPERATION_ID!;
const revisionId = process.env.PERMUTE_REVISION_ID!;
const expectedTotal = Number(process.env.EXPECTED_TOTAL);
if (!Number.isFinite(expectedTotal)) throw new Error('Set EXPECTED_TOTAL to the expected revenue');
// Restore the selected definition using the current version token.
const history = await client.revisions.list(operationId);
if (!history.capabilities.restore || !history.capabilities.publish) {
throw new Error('Choose a standalone operation that supports restore and publish');
}
const restored = await client.revisions.restore(operationId, revisionId, {
expectedVersion: history.version,
});
const started = await client.operations.run(operationId, { version: 'draft' });
async function waitForRun() {
const deadline = Date.now() + 5 * 60_000;
while (Date.now() < deadline) {
const workflow = await client.operations.listRuns({ workflowId: started.workflowId });
if (workflow.status === 'failed') {
throw new Error(workflow.items.find((run) => run.error)?.error ?? 'Workflow failed');
}
if (workflow.status === 'completed') return workflow.items.find((run) => run.operationId === operationId);
await new Promise((resolve) => setTimeout(resolve, 1500));
}
throw new Error('Timed out waiting for the test run');
}
const run = await waitForRun();
if (!run?.outputDocId) throw new Error('Run output is missing');
if (run.revisionId !== restored.draft?.revisionId) throw new Error('The run used a different draft');
const preview = await client.operations.getRunOutput(operationId, run.id);
const blockId = Object.entries(preview.names).find(([, name]) => name.toLowerCase() === 'revenue')?.[0];
const rows = blockId ? preview.tables[blockId] as Array<{ total: unknown }> : undefined;
console.table(rows);
if (rows?.length !== 1 || Number(rows[0].total) !== expectedTotal) {
throw new Error('Unexpected revenue; inspect the calculation before publishing');
}
// Re-read after testing and reject intervening draft changes.
const ready = await client.revisions.list(operationId);
if (ready.draft?.revisionId !== restored.draft?.revisionId) {
throw new Error('The draft changed during testing; review and test it again');
}
await client.revisions.publish(operationId, { expectedVersion: ready.version });
console.log('Published restored revision:', ready.draft?.revisionId);