差分同期
フル同期を実行し、nextSyncToken を使ってイベントの変更差分や削除を取得します。
Calendarpit は、Google Calendar の events.list におけるフル同期と差分同期のフローを実装しています。流れは次の 2 段階です。
- 最初にフル同期を実行して
nextSyncTokenを取得します。 - 以降のリクエストで、そのトークンを
syncTokenとして渡します。その時点以降に作成・更新・削除されたイベントだけを取得できます。
プロトコルの詳細は Google の同期ガイドおよび events.list リファレンスを参照してください。
フル同期
events.list によるフル同期(events.instances は対象外)では、showDeleted=true の有無にかかわらず同期トークンが発行されます。
ページ送り
- 最終ページまで
nextPageTokenをたどってください。 - 各
pageTokenのリクエストには、初回と同じクエリパラメータを渡します。パラメータを変更すると HTTP410(invalidPageToken)が返ります。 - Calendarpit は最終ページでのみ
nextSyncTokenを返し、nextPageTokenと同時に返すことはありません。全ページを処理した後にnextSyncTokenを保存してください。 - 各ページのレコードと同期用のスナップショットは、最初のページを取得した時点でまとめて固定されます。ページ送りの最中に発生したイベントの書き込みは、次回の差分同期で返されます。
終了条件のない繰り返しの系列
singleEvents=true を指定する場合は、フル同期で必ず timeMax を設定してください。
終了条件のない繰り返しの系列とは、COUNT も UNTIL もない RRULE のことです。このような系列がカレンダーにあると、次のリクエストは失敗します。
singleEvents=true:timeMaxなしで発行されたトークンを使うと、HTTP400(unsupportedFeature)が返ります。timeMaxを指定してフル同期をやり直してください。singleEvents=false:timeMinのみを指定しtimeMaxを省略したフル同期のevents.listも拒否されます(イベント、繰り返し、時刻の扱いを参照)。singleEvents=falseでの差分同期はこの影響を受けません。
終了条件のない繰り返しの系列は、削除後も対象になります。リセットや Fixture の読み込みを行うまで、削除済みレコード(status: cancelled)としてカレンダーに残るためです。
// api is the Google SDK client configured with Calendarpit's rootUrl.
const query = {
calendarId: 'primary',
singleEvents: true,
showDeleted: true,
timeMin: '2026-10-01T00:00:00Z',
timeMax: '2026-11-01T00:00:00Z',
};
let pageToken: string | undefined;
let syncToken: string | undefined;
do {
const { data } = await api.events.list({ ...query, pageToken });
for (const event of data.items ?? []) {
if (event.status === 'cancelled') await removeLocalEvent(event.id!);
else await upsertLocalEvent(event);
}
pageToken = data.nextPageToken ?? undefined;
if (!pageToken) syncToken = data.nextSyncToken ?? undefined;
} while (pageToken);差分同期
watch 通知を受け取った後や定期的なポーリング時に、syncToken を指定して events.list を呼び出します。差分同期の結果も maxResults と pageToken でページ送りできます。
const previousToken = syncToken;
do {
const { data } = await api.events.list({
calendarId: 'primary',
singleEvents: true,
showDeleted: true,
syncToken: previousToken,
pageToken,
});
for (const event of data.items ?? []) {
if (event.status === 'cancelled') await removeLocalEvent(event.id!);
else await upsertLocalEvent(event);
}
pageToken = data.nextPageToken ?? undefined;
if (!pageToken) syncToken = data.nextSyncToken ?? undefined;
} while (pageToken);クエリのルール
以下のルールに違反するリクエストは HTTP 400(invalidParameter)で失敗します。
- 併用できないパラメータ:
syncTokenはtimeMin、timeMax、orderBy、updatedMin、privateExtendedProperty、sharedExtendedProperty、iCalUIDと同時に指定できません。また、events.instancesはsyncTokenに対応していません。 - 削除されたイベント:
showDeleted=falseは指定しないでください。差分同期には常に削除が含まれます。showDeleted=trueを指定するか、showDeletedを省略してください。 - 展開オプションの一致:
singleEventsと適用されるtimeZoneは、初回のフル同期と一致している必要があります。適用されるtimeZoneは、timeZoneパラメータの値です。省略時はカレンダーのタイムゾーンです。 - タイムゾーンの変更:
timeZoneパラメータを明示せずに発行されたトークンは、管理画面や管理 API でカレンダーのタイムゾーンを変更すると無効になります(HTTP400)。
差分の内容と挙動
-
リトライ: 同期トークンは一度使っても消費されません。リクエストが失敗した場合は、同じ
syncTokenでリトライできます。 -
毎回新しいトークン: 差分同期を実行するたびに、最終ページで新しい
nextSyncTokenが返されます。 -
変更がない場合:
itemsが[]になります。この場合も新しいnextSyncTokenが返されます。 -
削除済みレコード: 削除されたイベントは削除済みレコードとして保持され、
status: "cancelled"で返されます。 -
フィルタの非適用: 差分には、初回リクエストで指定した
updatedMin、拡張プロパティ、iCalUIDのフィルタは適用されません。カレンダー内で変更されたすべてのイベントが含まれます。 -
繰り返しの系列の対象期間: 差分の内容は
singleEventsによって変わります。singleEvents=falseの場合、差分には変更された繰り返しの系列の親イベント、保存された例外(個別に変更された回)、単発のイベントが含まれます。singleEvents=trueの場合、繰り返しの系列は初回のフル同期で指定したtimeMinとtimeMaxの期間内で再展開されます。親イベントを編集すると、その期間内のすべてのインスタンスが返されます。系列の期間短縮やキャンセルで除外されたインスタンスは、status: "cancelled"として返されます。- 繰り返しではない単発のイベントは、初回の
timeMinとtimeMaxの期間外でも、変更があれば返されます。
繰り返しの展開期間を移動する場合は、新しくフル同期を実行してください。
ライフサイクルと制限
同期トークンは、ユーザー、カレンダー、展開オプション、初回の対象期間に紐づく不透明な UUID です。
| トークン | 有効期間と上限 | 永続化 | 無効時のエラー |
|---|---|---|---|
syncToken | TTL 7 日間、ワークスペースあたり最大 100 件(古いものから削除) | SQLite に保存(:memory: モードでは再起動で消失) | HTTP 410(fullSyncRequired) |
pageToken | TTL 1 時間、ワークスペースあたり最大 1,000 ページ(古いものから削除) | インメモリのみ(再起動で消失) | HTTP 410(invalidPageToken) |
-
HTTP
410(fullSyncRequired):syncTokenが次のいずれかに当てはまる場合に返されます。- 不明、または期限切れ
- 上限の 100 件を超えて削除された
- リセットや Fixture の読み込みで消去された
- 別のユーザーやカレンダー向けに発行された
このエラーが返された場合は、そのカレンダーのローカル同期データを破棄してフル同期をやり直してください。
-
トークンの上限:
pageTokenを指定しないフル同期・差分同期のevents.listリクエストごとに、新しいトークンが発行されます。上限の 100 件は、すべてのユーザーとカレンダーで共有されます。管理 API でのイベント一覧取得も含みます。 -
リセットと Fixture:
POST /__calendarpit/resetおよびPOST /__calendarpit/fixturesを実行すると、すべての同期トークンとページトークンが消去されます(テスト制御 APIを参照)。同時にすべての OAuth 認可も取り消されるため、クライアントは先に再認可を行う必要があります。 -
ページカーソル: イベントの
pageTokenカーソルは、クエリパラメータと呼び出し元のユーザーに紐づいています。再起動、リセット、Fixture の読み込みを行うと失われます。HTTP410(invalidPageToken)が返された場合は、保持しているsyncTokenからページ送りをやり直すか、フル同期を最初からやり直してください。