restate / v1.0.0
信頼性の設計
ストレージが遅い、失敗する、存在しないとき restate が何を保証するか、そして報告されるエラーの読み方。
保証
これらの動作は 1.0 の契約の一部で、パッケージのテストでカバーされています。
| 状況 | restate の動作 |
|---|---|
保存を立て続けに呼ぶ | ストアごとに順番に実行し、キューで待っている保存は最新の値を書き込みます。 |
レコードの読み込みに失敗した | autoSave と一括保存は LOAD_FAILED を返してスキップし、置き換えられるのは明示的な save() だけです。 |
保存中にコンポーネントがアンマウントされる | 要求済みの保存は完了し、破棄されたストアへのその後の書き込みは無視されます。 |
ストレージが使えない | ストアはメモリ上で動き、status().available は false、保存は reject されます。 |
アダプターが応答しない | 操作は operationTimeoutMs でタイムアウトし、後続の処理はアダプターが落ち着くまで待ちます。 |
保留中の処理が多すぎる | 際限なく増え続けるのではなく、新しい操作を QUEUE_FULL で reject します。 |
順番どおりの保存
1 つのストアの操作が重なることはありません。別の保存が待っている間に出した保存は新しい値を引き継ぐので、変更のたびに保存してもキューを埋めずに最新の状態を書き込めます。
useDraft.set('A');
const first = useDraft.save(); // writes "A"
useDraft.set('B');
const second = useDraft.save(); // waits in the queue
useDraft.set('C');
const third = useDraft.save(); // joins the waiting save
await Promise.all([first, second, third]); // ['A', 'C', 'C'] — two writes試してみる
ストレージを遅くしたり、書き込みを失敗させたり、ストレージをなくしたりしてみてください。
サンプルを準備中…
読み込みに失敗したとき
restateManager.ready は BULK_OPERATION_FAILED で reject され、失敗したストアを一覧にします。そのレコードが読み込めるまで、autoSave と saveAll はそれを置き換えず LOAD_FAILED を報告します。
編集を残して保存するには initialize() で再試行し、編集を捨てるには restore() を呼び、意図的にレコードを上書きするには save() を呼びます。
try {
await restateManager.ready;
} catch (error) {
// BULK_OPERATION_FAILED: one entry per store that could not load.
for (const { store, error: cause } of error.failures) {
console.error(store.key, cause.code);
}
}保存して閉じる
アンマウント前に要求した保存は完了するので、「保存して閉じる」のハンドラーで待つ必要はありません。
function Document({ onClose }) {
const useDraft = useRestateScope((scope) => persistedRestate('', { scope, key: 'draft' }));
const saveAndClose = () => {
useDraft.save(); // finishes even after this component unmounts
onClose();
};
// …
}保存データの検証とマイグレーション
version、migrate、validate が古いレコードや壊れたレコードから守ります。validate は保存の前と、復元またはマイグレーションの後に実行されます。
import { persistedRestate } from '@kkwaenggwari/restate';
const useSettings = persistedRestate(
{ theme: 'system' },
{
key: 'settings',
version: 2,
migrate(value, from, to) {
if (from === 1 && to === 2 && typeof value?.darkMode === 'boolean') {
return { theme: value.darkMode ? 'dark' : 'light' };
}
throw new Error('Unsupported stored version');
},
validate: (value) => ['system', 'light', 'dark'].includes(value?.theme),
},
);独自のストレージを使う
getItem、setItem、removeItem を持つオブジェクトなら何でも使えます。メソッドは値を返しても Promise を返してもかまいません。isAvailable は省略できます。
import { persistedRestate } from '@kkwaenggwari/restate';
const sessionStorageAdapter = {
isAvailable: () => typeof sessionStorage !== 'undefined',
getItem: (key) => sessionStorage.getItem(key),
setItem: (key, value) => sessionStorage.setItem(key, value),
removeItem: (key) => sessionStorage.removeItem(key),
};
export const useFilters = persistedRestate(
{ query: '' },
{
key: 'filters',
storage: sessionStorageAdapter,
},
);制限とタイムアウト
1 つの操作を実行できる時間、待機できる操作の数、値の最大サイズを調整できます。
persistedRestate(value, {
key: 'draft',
operationTimeoutMs: 10_000, // default 30000
maxPendingOperations: 64, // default 256
maxStorageCharacters: 1_000_000,
});エラーコード
restate が作るエラーの name は RestateError で、code を持ち、保存ストアなら key も持ちます。ストレージアダプターやあなたのコールバックは、独自のエラーで reject することがあります。
| コード | 意味 |
|---|---|
BULK_OPERATION_FAILED | restateManager.ready や saveAll などの一括呼び出しで失敗がありました。error.failures を確認してください。 |
INDEXEDDB_ABORTED | IndexedDB のトランザクションが中断されました。 |
INDEXEDDB_BLOCKED | 別のタブの接続によって、データベースのオープンがブロックされました。 |
INDEXEDDB_CONNECTION_CLOSING | リクエストの開始時に、データベース接続が閉じかけていました。 |
INDEXEDDB_OPEN_FAILED | データベースを開けませんでした。 |
INDEXEDDB_REQUEST_FAILED | IndexedDB のリクエストが失敗しました。 |
INDEXEDDB_STORE_MISSING | 作成済みのデータベースにオブジェクトストアがありません。 |
INVALID_STORAGE_RECORD | 保存されたレコードが有効な restate のレコードではありません。 |
INVALID_STORED_VALUE | 値が validate を通りませんでした。 |
LIFECYCLE_LIMIT | updater が開始と停止を繰り返し続けました。 |
LOAD_FAILED | レコードを読み込めなかったため、自動保存や一括保存で置き換えませんでした。 |
MMKV_UNAVAILABLE | react-native-mmkv を読み込めませんでした。 |
MULTIPLE_ERRORS | 1 つの操作で複数のコールバックが失敗しました。error.errors を確認してください。 |
NOTIFICATION_LIMIT | オブザーバーがループの中で変更を予約し続けました。 |
OBSERVER_ERROR | リスナーまたはライフサイクルのコールバックが例外を投げました。 |
OPERATION_ABORTED | ストレージ操作がキャンセルされました。 |
QUEUE_FULL | maxPendingOperations で許される数より多くの操作が待機していました。 |
REENTRANT_SETTER | state の updater が、実行中に同じストアを変更しようとしました。 |
SCOPE_DESTROYED | 破棄されたスコープでストアを作ろうとしました。 |
SEQUENCE_EXHAUSTED | 内部のシーケンスで使える ID がなくなりました。 |
STORAGE_BLOCKED | タイムアウトまたは中断の後、以前のアダプター呼び出しがまだ完了していません。 |
STORAGE_TIMEOUT | 操作に operationTimeoutMs より長くかかりました。 |
STORAGE_UNAVAILABLE | この環境ではストレージを使えません。ストアはメモリ上で動き続けます。 |
STORAGE_VALUE_TOO_LARGE | エンコードした値が maxStorageCharacters より長くなっています。 |
STORAGE_VERSION_MISMATCH | レコードのバージョンが異なり、それを扱う migrate 関数がありません。 |
STORE_DESTROYED | 破棄されたストアを購読しようとしたか、ストレージの使用を要求しました。 |