Files
bsky-social-app/src/storage
Samuel Newman 4f1d4821c5 Speed up startup by persisting some queries (#9594)
* persist startup queries

* Use IDB for query storage (#9687)

* Add storage abstraction for persisted query data

Introduce a platform-specific storage abstraction layer for react-query
persistence:
- Native: Uses MMKV for high-performance synchronous storage
- Web: Uses IndexedDB via the `idb` library for efficient async storage

This replaces the previous AsyncStorage implementation with more performant
platform-native solutions. The abstraction maintains API compatibility with
@tanstack/query-async-storage-persister.

* Refactor storage abstraction to use factory pattern

Change createPersistedQueryStorage to a factory function that accepts a
storage ID, allowing multiple isolated storage instances:
- Native: Each instance gets its own MMKV store
- Web: Each instance gets its own IndexedDB database

Adopt the factory pattern in:
- react-query.tsx: Uses 'persisted_queries' storage
- ageAssurance/data.tsx: Uses 'age_assurance' storage

This provides better separation between different query client caches
and allows each to be managed independently.

---------

Co-authored-by: Claude <noreply@anthropic.com>

* Refactor to use archival storage

(cherry picked from commit a773b40e41c96f821cd32260919ce1437c0fc3ab)

* Improve archive db types

(cherry picked from commit 80e4959ba2aa00c984c26aed2f7dfae1095720b0)

* rm idb

* clear on logout, bust on app version

* create abstraction for persisting queries, make gcTime infinite

* Rm abstraction

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Eric Bailey <git@esb.lol>
2026-02-02 11:55:01 -06:00
..
2024-09-05 12:31:24 -07:00

#/storage

Usage

Import the correctly scoped store from #/storage. Each instance of Storage (the base class, not to be used directly), has the following interface:

  • set([...scope, key], value)
  • get([...scope, key])
  • remove([...scope, key])
  • removeMany([...scope], [...keys])

For example, using our device store looks like this, since it's scoped to the device (the most base level scope):

import { device } from '#/storage';

device.set(['foobar'], true);
device.get(['foobar']);
device.remove(['foobar']);
device.removeMany([], ['foobar']);

TypeScript

Stores are strongly typed, and when setting a given value, it will need to conform to the schemas defined in #/storage/schema. When getting a value, it will be returned to you as the type defined in its schema.

Scoped Stores

Some stores are (or might be) scoped to an account or other identifier. In this case, storage instances are created with type-guards, like this:

type AccountSchema = {
  language: `${string}-${string}`;
};

type DID = `did:${string}`;

const account = new Storage<
  [DID],
  AccountSchema
>({
  id: 'account',
});

account.set(
  ['did:plc:abc', 'language'],
  'en-US',
);

const language = account.get([
  'did:plc:abc',
  'language',
]);

Here, if ['did:plc:abc'] is not supplied along with the key of language, the get will return undefined (and TS will yell at you).