Sync Errors
client.onError は、回復可能・回復不可能なすべての sync イベントで発火します。
kind フィールドで種別を判別します。このページは、union が定義する 6 つの kind
(push-http、pull-http、network、rebase-replay、schema-mismatch、
blob-upload-failed) に対する完全なトリアージ表です。
network
Section titled “network”原因: fetch() が throw しました。オフライン、DNS 障害、TLS 障害など。
回復: plasma は指数バックオフで自動的にリトライを続けます。ユーザーに オフラインであることを知らせる以外に、あなたがすべきことはありません。
UI:
onError: (err) => { if (err.kind === "network") setBanner("Reconnecting…")}次の push/pull が成功したらバナーを消します (明示的なイベントはありません。 outbox の深さをヒューリスティックとして使いましょう — 減ったならオンラインに 戻っています)。
push-http / pull-http
Section titled “push-http / pull-http”原因: push (または pull) の HTTP 呼び出しが、リトライ後に plasma が 回復不可能と判断した非 2xx ステータスを返しました。典型的にはリトライ枯渇後の 5xx、あるいは呼び出しレイヤーが表面化させる 4xx です。
回復: plasma はポーリングを続けます。次の試行が成功すれば暗黙に解消されます。 ログを取る以外にすべきことはありません。
UI:
onError: (err) => { if (err.kind === "push-http" || err.kind === "pull-http") { console.warn(`${err.kind} ${err.status} at ${err.url}`) }}サーバー側の mutator が throw する — client kind なし、静かに revert
Section titled “サーバー側の mutator が throw する — client kind なし、静かに revert”mutator がサーバー上で throw した場合 (PlasmaAuthorizationError、
MutatorValidationError、あるいは素の throw)、push の HTTP 呼び出しは
依然として { ok: true } を返し、SyncClientError は発火しません。
サーバーは次のことを行います。
- mutator のトランザクションをロールバックする。
_plasma_client_mutations.last_mutation_idを poison エントリの先へ進める (永遠にリトライされないように)。- 失敗した mutation の change log 行を出力しない。
クライアントは次の pull でこのドロップを知ります。lastMutationIDs が進んだ
ウォーターマークを報告するのに、対応する change が現れないためです。
dropConfirmed が outbox エントリを削除し、rebuildOptimistic が、その mutation
の書き込みを持たない base ストアの上に、生き残った outbox を再実行します。
optimistic ビューは revert します。
revert に向けた UX を設計する。 mutator が、UI で予測できる理由
(クォータ超過、拒否、アイテム削除) で失敗しうる場合は、mutate() を呼ぶ前に
コンポーネントで事前チェックしてください。特定の mutation の結果をクライアント側で
監視するということは、次の rebase で行そのものが消えるのを観察することを意味します。
schema-mismatch
Section titled “schema-mismatch”原因: クライアントとサーバーが SCHEMA_VERSION について食い違っています。
Schema mismatch で扱っています。
rebase-replay
Section titled “rebase-replay”原因: rebuildOptimistic 中にリプレイされているキュー済み mutation が
throw しました。通常は、参照していた行が別のクライアントによってすでに削除された
ためです (pull が削除を持ち込み、ローカルのリプレイが db.update(...).where(id=...)
を試みて失敗する)。
回復: plasma は rebase 中の throw を飲み込みます。次の push サイクルは、
その mutation をサーバーにも送ります — サーバー側の実行が同じ理由で throw し、
それが last_mutation_id を poison エントリの先へ進めます。outbox は次の pull で
空になります。
UI: rebase-replay の境界ですべきことはありません。ログで 1 つの mutation に
対する rebase-replay のストリームが見られる場合、それは row-not-found の競合が
2 回以上の pull サイクルにわたって続いていることを意味します。これは通常、
スケジュールされた削除が mutation の push と競合しているヒントであり、調査に
値します。
blob-upload-failed
Section titled “blob-upload-failed”原因: R2 の PUT /sync/blob/:hash が blobUploadRetry.maxAttempts
(デフォルト 5) 後に失敗しました。ネットワーク隣接ですが、独自のリトライ予算で
ゲートされています。
回復: upload レコードが state: "failed" に移ります。その blob 依存を持つ
mutation は BLOCKED になります — upload が解決するまで、その outbox エントリは
push されません。
イベントの形: { kind: "blob-upload-failed", hash, attempts, error }。
このイベントは mutation ID ではなく blob のハッシュ を運ぶことに注意してください —
1 つのハッシュが複数の outbox エントリを裏付けることがあるため、回復はハッシュ
単位です。
UI アクション:
onError: (err) => { if (err.kind === "blob-upload-failed") { setPendingUploads((prev) => [...prev, err]) }}
// Later, in the UI:<button onClick={() => client.retryBlobUpload(err.hash)}>Retry</button>retryBlobUpload(hash) は新しい試行予算で再キューします。ハッシュ単位の破棄は
ありません — mutation を完全に放棄するには、失敗したハッシュを参照する各 outbox
エントリに対して client.discardMutation(mutationID) を呼びます (client の
state をたどるか、Devtools パネルで ID を探します)。
blob-read-auth-cap-hit
Section titled “blob-read-auth-cap-hit”原因: GET /sync/blob/:hash リクエストが SyncHandlerOptions.readAuthMaxRefs
(デフォルト 128) を超える参照を持っていたため、サーバーは提供するかどうかを
決める前にすべての ref をチェックしませんでした。
回復: blob リクエストは正常に完了します — この上限は正しさではなく
パフォーマンスのガードです。チェックされた ref のいずれかが auth.read を通過すれば
blob は提供されます。どれも通らなければ 404 です。
UI: これはサーバー側のイベントで、client.onError 経由では公開されません。
サーバー側のテレメトリ hook から出力してください。これが見られる場合、影響を受けた
ユーザーは共有 blob の認可ギャップに当たっている可能性があります (通常の対処は
_plasma_blob_refs の最も古い参照を刈り込んで fan-out を下げることです)。
phase でフィルタリングする
Section titled “phase でフィルタリングする”phase: "push" | "pull" フィールドを運ぶのは network と schema-mismatch の
kind だけです — この 2 つについては、push 側 (ユーザーが保存を試みたばかり) と
pull 側 (バックグラウンドポーリング) の失敗を区別できます。
onError: (err) => { if ("phase" in err) { console.error(`[${err.phase}] ${err.kind}`, err) } else { console.error(`[?] ${err.kind}`, err) }}他の kind は暗黙に phase が定まっています。push-http は push、pull-http は
pull、rebase-replay は rebase、blob-upload-failed は upload ワーカーです。
次に読むもの
Section titled “次に読むもの”- コンセプト / Push, Pull, Rebase — plasma が自動でリトライするもの
- ファイルと blob — blob upload の完全な リトライ / 破棄フロー
- Schema mismatch — 独自のページを持つ唯一のエラー