migrating-nango-deletion-detection
About migrating-nango-deletion-detection
A migration skill for developers updating Nango syncs to the newer deletion-detection flow. It specifies the order for deletion tracking and adds checkpoint handling for full refreshes that span execution windows.
Key features
- 01Replace legacy deletion detection calls
- 02Order deletion tracking boundaries
- 03Resume paginated refreshes from checkpoints
- 04Save and clear checkpoints by page
- 05Provide test and mock-recording commands
Use cases
- 01Migrate deletion detection in Nango syncs
- 02Resume long full refreshes across executions
- 03Prevent false deletions when data is incomplete
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ nangohq/ skills --skill migrating-nango-deletion-detectionAfter installing, type/migrating-nango-deletion-detectionin Claude Code to confirm it loaded.
Details
From the project READMEMigrating Nango Deletion Detection
Do this
- Find legacy usage:
deleteRecordsFromPreviousExecutions(trackDeletes:/track_deletes
- For each sync + model that needs automatic deletion detection:
- Add
await nango.trackDeletesStart('ModelName')at the start ofexec(before fetching/saving). - Replace
await nango.deleteRecordsFromPreviousExecutions('ModelName')withawait nango.trackDeletesEnd('ModelName'). - Keep
trackDeletesEndafter allbatchSave/batchUpdate/batchDeletecalls.
- Add
- Safety:
- Only call
trackDeletesEndif the full dataset was fetched + saved betweentrackDeletesStartandtrackDeletesEnd(otherwise you can cause false deletions). - Prefer letting exceptions bubble. If you
catch, re-throw when data is incomplete.
- Only call
Checkpointed full refresh (multi-execution)
Full refresh syncs need a pagination checkpoint (page/cursor/offset) in addition to delete tracking. Nango syncs run inside a time-limited execution window; without a checkpoint, a run that does not finish in that window restarts from page 1 next time, wasting compute re-fetching the same early pages and never reaching the rest of the dataset.
- Read the checkpoint first (
await nango.getCheckpoint()), and resume pagination from it when present. - Call
trackDeletesStart('ModelName')at the beginning of every execution in the refresh window. It is safe to call repeatedly — it will not overwrite the start of a delete-tracking window that a prior execution of the same logical refresh already opened. - After each successful
batchSave(), callsaveCheckpoint()with the next page/cursor — on every page, including the last one. Do not guard the call with "more pages remain" (if (nextPage) { ... }); a run whose whole dataset fits on the first page then never saves a checkpoint at all. - Call
clearCheckpoint()only after the last page is saved. Because every processed page must callsaveCheckpoint(), the normal page-processing path has a checkpoint row to clear. If a distinct path creates no checkpoint at all (for example, it processes no pages), do not callclearCheckpoint()on that path; it throwscheckpoint_conflictat runtime. This is not a substitute for saving the last page. - Call
trackDeletesEnd('ModelName')only after thatclearCheckpoint()— i.e. only in the execution that finishes saving the full dataset.
Tests
- Re-record mocks after code changes:
nango dryrun <sync-name> <connection-id> --validate -e dev --no-interactive --auto-confirmnango dryrun <sync-name> <connection-id> --save -e dev --no-interactive --auto-confirmnango generate:tests && npm test
- Never hand-edit
*.test.json.
Before/after
// Before
for await (const page of nango.paginate(cfg)) {
await nango.batchSave(page, 'Ticket');
}
await nango.deleteRecordsFromPreviousExecutions('Ticket');
// After
await nango.trackDeletesStart('Ticket');
for await (const page of nango.paginate(cfg)) {
await nango.batchSave(page, 'Ticket');
}
await nango.trackDeletesEnd('Ticket');
If the sync can exceed the execution window (large dataset, slow provider), add a pagination checkpoint so trackDeletesStart/trackDeletesEnd only wrap one logical refresh across however many executions it takes:
// After (checkpointed, multi-execution safe)
const checkpoint = await nango.getCheckpoint<{ page?: number }>();
let page = checkpoint?.page ?? 1;
// Safe on every execution: does not overwrite an already-open window.
await nango.trackDeletesStart('Ticket');
for await (const results of nango.paginate({
...cfg,
paginate: {
...cfg.paginate,
offset_start_value: page,
on_page: async ({ nextPageParam }) => {
page = typeof nextPageParam === 'number' ? nextPageParam : undefined;
}
}
})) {
await nango.batchSave(results, 'Ticket');
// Save on every page, including the last — do not guard this with
// `if (page !== undefined)`. Skipping it whenever there is no next page
// means a run whose entire dataset fits on the first page never saves a
// checkpoint, and the clearCheckpoint() below would then fail with
// checkpoint_conflict (deleting a row that was never written).
await nango.saveCheckpoint({ page });
}
// Every page, including the last, was checkpointed.
await nango.clearCheckpoint();
await nango.trackDeletesEnd('Ticket');