mcpField
M
migrating-nango-deletion-detection

migrating-nango-deletion-detection

github.com/nangohq開発3124 回インストール10 週間前に更新
リポジトリ

migrating-nango-deletion-detection について

Nangoの同期処理を新しい削除検出方式へ移行する開発者向けスキルです。削除追跡の呼び出し順を示し、実行時間をまたぐ全件更新にはチェックポイント処理を加えます。

主な機能

  1. 01旧削除検出呼び出しの置き換え
  2. 02削除追跡の開始・終了順序
  3. 03ページ更新のチェックポイント再開
  4. 04ページごとの保存とチェックポイント消去
  5. 05テストとモック記録のコマンド

活用シーン

  1. 01Nango同期処理の削除検出移行
  2. 02長時間の全件更新を複数実行で再開
  3. 03データ未取得時の誤削除防止

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/nangohq/skills --skill migrating-nango-deletion-detection

インストール後、Claude Code で/migrating-nango-deletion-detectionを入力して読み込まれたか確認します。

詳細

Nango 削除検出の移行

実施手順

  1. レガシーな使用箇所を探します。
    • deleteRecordsFromPreviousExecutions(
    • trackDeletes: / track_deletes
  2. 自動削除検出が必要な同期 + モデルごとに、次の操作を行います。
    • exec の開始時(データの取得/保存前)に await nango.trackDeletesStart('ModelName') を追加します。
    • await nango.deleteRecordsFromPreviousExecutions('ModelName') を await nango.trackDeletesEnd('ModelName') に置き換えます。
    • すべての batchSave/batchUpdate/batchDelete 呼び出しの後に trackDeletesEnd を配置します。
  3. 安全性:
    • trackDeletesStart と trackDeletesEnd の間にデータセット全体を取得して保存した場合にのみ、trackDeletesEnd を呼び出します(そうしないと、誤った削除が発生する可能性があります)。
    • 例外はそのまま呼び出し元に伝播させることを推奨します。catch する場合は、データが不完全なときに例外を再スローしてください。

チェックポイントを使用した全件更新(複数回の実行)

全件更新の同期では、削除追跡に加えてページネーションの checkpoint(ページ/カーソル/オフセット)が必要です。Nango の同期は時間制限のある実行ウィンドウ内で動作します。チェックポイントがないと、ウィンドウ内に完了しなかった実行は次回、1 ページ目から再開するため、同じ初期ページの再取得に計算リソースを浪費し、残りのデータセットに到達できません。

  • まずチェックポイントを読み取り(await nango.getCheckpoint())、チェックポイントがある場合はそこからページネーションを再開します。
  • 更新ウィンドウ内の各実行の開始時に trackDeletesStart('ModelName') を呼び出します。繰り返し呼び出しても安全です。以前の実行で同じ論理的な更新用に開始された削除追跡ウィンドウの開始位置が上書きされることはありません。
  • batchSave() の成功後、次のページ/カーソルを指定して saveCheckpoint() を呼び出します。最終ページを含め、すべてのページで呼び出してください。「残りのページがある」場合だけ呼び出すようにガードしないでください(if (nextPage) { ... })。データセット全体が最初のページに収まる実行では、チェックポイントが一度も保存されなくなります。
  • 最終ページを保存した後にのみ clearCheckpoint() を呼び出します。処理した各ページで saveCheckpoint() を呼び出す必要があるため、通常のページ処理パスにはクリア対象のチェックポイント行があります。チェックポイントがまったく作成されない別のパス(たとえば、ページを処理しない場合)では、そのパスで clearCheckpoint() を呼び出さないでください。実行時に checkpoint_conflict が発生します。これは最終ページを保存する代わりにはなりません。
  • データセット全体の保存が完了する実行でのみ、かつその実行内で clearCheckpoint() を呼び出した後に、trackDeletesEnd('ModelName') を呼び出します。

テスト

  • コード変更後にモックを再記録します。
    • nango dryrun <sync-name> <connection-id> --validate -e dev --no-interactive --auto-confirm
    • nango dryrun <sync-name> <connection-id> --save -e dev --no-interactive --auto-confirm
    • nango generate:tests && npm test
  • *.test.json を手動で編集しないでください。

変更前/変更後

// 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');

同期が実行ウィンドウを超える可能性がある場合(データセットが大きい、プロバイダーの処理が遅いなど)は、ページネーションのチェックポイントを追加して、何回の実行にまたがっても、1 つの論理的な更新を trackDeletesStart/trackDeletesEnd で囲むようにします。

// 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');

コメント