TypeScriptとStorageManager

TypeScriptとStorageManager

この記事ではTypeScriptとStorageManagerについて説明します。

TypeScriptとStorageManagerについて実際的なサンプルを含めて解説します。

YouTube Video

TypeScriptとStorageManager

StorageManager とは

StorageManager は、Web アプリがどのくらいストレージを使用しているか、どの程度の容量を利用できるかを推定できる API です。また、ユーザやブラウザによる自動削除を防ぐための「永続化要求(persist())」も可能です。これにより、たとえばオフラインアプリで IndexedDB に大容量データを保存する際に、 「空き容量が十分あるか」や「データが自動削除されないか」を制御できます。

機能検出(Feature Detection)

まずは、ブラウザが navigator.storage をサポートしているかを確認します。

 1// TypeScript: feature detection for StorageManager
 2function supportsStorageManager(): boolean {
 3  return typeof navigator !== "undefined" &&
 4         typeof navigator.storage !== "undefined" &&
 5         typeof navigator.storage.estimate === "function";
 6}
 7
 8// Example usage
 9if (supportsStorageManager()) {
10  console.log("StorageManager is supported.");
11} else {
12  console.warn("StorageManager not supported. Falling back to localStorage or IndexedDB.");
13}
  • この関数は、StorageManager API の有無を段階的に確認します。
  • Safari など一部のブラウザでは navigator.storage が存在しないため、typeof による安全なチェックが重要です。

ストレージ使用量を推定する(estimate()

supportsStorageManager() を使って機能を確認した上で、navigator.storage.estimate() から使用量(usage)と上限(quota)を取得します。

 1// TypeScript: safely get storage estimate
 2async function getStorageEstimate(): Promise<{ usage: number; quota: number } | null> {
 3  if (!supportsStorageManager()) {
 4    console.warn("StorageManager not supported.");
 5    return null;
 6  }
 7
 8  const estimate = await navigator.storage.estimate();
 9  return { usage: estimate.usage ?? 0, quota: estimate.quota ?? 0 };
10}
11
12// Example usage
13getStorageEstimate().then(result => {
14  if (result) {
15    console.log(`Usage: ${result.usage} bytes / Quota: ${result.quota} bytes`);
16  }
17});
  • この関数は常に安全に動作し、非対応ブラウザでは null を返します。
  • usagequota は推定値であり、ブラウザごとに多少異なります。

データの永続化を要求する(persist()

重要なデータ(たとえばオフラインキャッシュなど)がブラウザの自動削除対象にならないよう、persist() を使って永続化をリクエストします。ただし、すべての環境で成功するわけではありません。

 1// TypeScript: safely request persistent storage
 2async function requestPersistence(): Promise<boolean> {
 3  if (!supportsStorageManager()) {
 4    console.warn("StorageManager not supported.");
 5    return false;
 6  }
 7
 8  if (typeof navigator.storage.persist !== "function") {
 9    console.warn("persist() not available in this browser.");
10    return false;
11  }
12
13  try {
14    const granted = await navigator.storage.persist();
15    return Boolean(granted);
16  } catch (err) {
17    console.error("persist() error:", err);
18    return false;
19  }
20}
21
22// Example usage
23requestPersistence().then(granted => {
24  console.log("Persistence granted?", granted);
25});
  • persist() が成功すれば、ユーザの手動削除を除き、ブラウザによる自動削除の対象外となります。ただし、ユーザの操作やブラウザ設定により拒否される可能性もあります。

容量を確認してからデータを保存する

大きなデータを保存する前に空き容量を確認して、書き込み失敗(QuotaExceededError)を防ぎます。

 1// TypeScript: ensure enough space before writing
 2async function ensureSpaceAndWrite(neededBytes: number, writeFn: () => Promise<void>): Promise<boolean> {
 3  const estimate = await getStorageEstimate();
 4  if (!estimate) {
 5    console.warn("Cannot check storage space. Proceeding without validation.");
 6    await writeFn();
 7    return true;
 8  }
 9
10  const free = estimate.quota - estimate.usage;
11  if (free < neededBytes) {
12    console.warn(`Not enough space. Free: ${free} bytes, needed: ${neededBytes} bytes.`);
13    return false;
14  }
15
16  await writeFn();
17  return true;
18}
19
20// Example usage
21ensureSpaceAndWrite(10 * 1024 * 1024, async () => {
22  console.log("Saving large data...");
23});
  • このように、保存前に空き容量を確認しておくと、ストレージ容量不足で書き込みが中断するリスクを減らせます。

TypeScript で localStorage を型安全に扱う

軽量な設定データなどは、localStorage を使うのが便利です。

以下のクラスはジェネリクスを使い、型安全なラッパーを実現します。

 1// TypeScript: typed localStorage wrapper
 2type Serializer<T> = {
 3  serialize: (v: T) => string;
 4  deserialize: (s: string) => T;
 5};
 6
 7class TypedLocalStorage<K extends string, V> {
 8  constructor(private storage: Storage, private serializer: Serializer<V>) {}
 9
10  set(key: K, value: V): void {
11    this.storage.setItem(key, this.serializer.serialize(value));
12  }
13
14  get(key: K): V | null {
15    const raw = this.storage.getItem(key);
16    if (raw === null) return null;
17    try {
18      return this.serializer.deserialize(raw);
19    } catch {
20      return null;
21    }
22  }
23
24  remove(key: K): void {
25    this.storage.removeItem(key);
26  }
27
28  clear(): void {
29    this.storage.clear();
30  }
31}
32
33// Example usage
34const jsonSerializer: Serializer<any> = {
35  serialize: v => JSON.stringify(v),
36  deserialize: s => JSON.parse(s),
37};
38
39const appStorage = new TypedLocalStorage<'theme' | 'token', any>(localStorage, jsonSerializer);
40appStorage.set('theme', { dark: true });
41console.log(appStorage.get('theme'));
  • このクラスにより、誤った型のデータを保存・取得するリスクを減らせます。

実運用での注意点とベストプラクティス

  • 常にブラウザがStorageManagerをサポートしているかチェックする StorageManager は比較的新しい API なので、必ず存在を確認してから使うこと。

  • 容量と互換性に注意する estimate() はブラウザによって値が異なり、推定値に過ぎません。

  • 非同期 API 設計を前提に estimate()persist() は Promise ベースの非同期 API。UI をブロックしないように設計しましょう。

  • セキュリティ上の配慮 localStorage にアクセストークンなどの機密情報を保存しない。可能なら HttpOnly クッキーや暗号化を検討します。

  • フォールバック設計 supportsStorageManager() が false の場合は、localStorageIndexedDB のみに切り替えるロジックを用意します。

まとめ

supportsStorageManager() による機能検出であらゆる環境に安全に対応し、navigator.storage.estimate() で使用量と上限を確認、persist() で永続化を要求できます。TypeScript ではラッパー関数と型定義を活用して読みやすく安全なコードを実現し、「存在確認、推定、永続化、書き込み前容量チェック」という流れでストレージを扱うのが基本です。

YouTubeチャンネルでは、Visual Studio Codeを用いて上記の記事を見ながら確認できます。 ぜひYouTubeチャンネルもご覧ください。

YouTube Video