about summary refs log tree commit diff
path: root/src/storage
diff options
context:
space:
mode:
authorSamuel Newman <mozzius@protonmail.com>2025-02-07 11:18:51 +0000
committerGitHub <noreply@github.com>2025-02-07 11:18:51 +0000
commit4b706c9519c548d53529876bdf815836ec71bdd4 (patch)
tree96485dcb1de8c7344287bebb890cd5832f527dfa /src/storage
parentb12d39b4cff0fe1b91fb16496c3fea1ed22de5cf (diff)
downloadvoidsky-4b706c9519c548d53529876bdf815836ec71bdd4.tar.zst
Per-user search history (#7588)
* per-user search history

* move to mmkv with cool new hook

* revert accidental changes

This reverts commit 27c89fa645eff0acb7a8fd852203ff1ea3725c69.

* restore limits
Diffstat (limited to 'src/storage')
-rw-r--r--src/storage/index.ts72
-rw-r--r--src/storage/schema.ts1
2 files changed, 70 insertions, 3 deletions
diff --git a/src/storage/index.ts b/src/storage/index.ts
index 8ec09eefa..9b39e1c1a 100644
--- a/src/storage/index.ts
+++ b/src/storage/index.ts
@@ -1,5 +1,5 @@
+import {useCallback, useEffect, useState} from 'react'
 import {MMKV} from 'react-native-mmkv'
-import {Did} from '@atproto/api'
 
 import {Account, Device} from '#/storage/schema'
 
@@ -65,6 +65,72 @@ export class Storage<Scopes extends unknown[], Schema> {
   removeMany<Key extends keyof Schema>(scopes: [...Scopes], keys: Key[]) {
     keys.forEach(key => this.remove([...scopes, key]))
   }
+
+  /**
+   * Fires a callback when the storage associated with a given key changes
+   *
+   * @returns Listener - call `remove()` to stop listening
+   */
+  addOnValueChangedListener<Key extends keyof Schema>(
+    scopes: [...Scopes, Key],
+    callback: () => void,
+  ) {
+    return this.store.addOnValueChangedListener(key => {
+      if (key === scopes.join(this.sep)) {
+        callback()
+      }
+    })
+  }
+}
+
+type StorageSchema<T extends Storage<any, any>> = T extends Storage<
+  any,
+  infer U
+>
+  ? U
+  : never
+type StorageScopes<T extends Storage<any, any>> = T extends Storage<
+  infer S,
+  any
+>
+  ? S
+  : never
+
+/**
+ * Hook to use a storage instance. Acts like a useState hook, but persists the
+ * value in storage.
+ */
+export function useStorage<
+  Store extends Storage<any, any>,
+  Key extends keyof StorageSchema<Store>,
+>(
+  storage: Store,
+  scopes: [...StorageScopes<Store>, Key],
+): [
+  StorageSchema<Store>[Key] | undefined,
+  (data: StorageSchema<Store>[Key]) => void,
+] {
+  type Schema = StorageSchema<Store>
+  const [value, setValue] = useState<Schema[Key] | undefined>(() =>
+    storage.get(scopes),
+  )
+
+  useEffect(() => {
+    const sub = storage.addOnValueChangedListener(scopes, () => {
+      setValue(storage.get(scopes))
+    })
+    return () => sub.remove()
+  }, [storage, scopes])
+
+  const setter = useCallback(
+    (data: Schema[Key]) => {
+      setValue(data)
+      storage.set(scopes, data)
+    },
+    [storage, scopes],
+  )
+
+  return [value, setter] as const
 }
 
 /**
@@ -77,10 +143,10 @@ export const device = new Storage<[], Device>({id: 'bsky_device'})
 /**
  * Account data that's specific to the account on this device
  */
-export const account = new Storage<[Did], Account>({id: 'bsky_account'})
+export const account = new Storage<[string], Account>({id: 'bsky_account'})
 
 if (__DEV__ && typeof window !== 'undefined') {
-  // @ts-ignore
+  // @ts-expect-error - dev global
   window.bsky_storage = {
     device,
     account,
diff --git a/src/storage/schema.ts b/src/storage/schema.ts
index d8b9874e4..667b43208 100644
--- a/src/storage/schema.ts
+++ b/src/storage/schema.ts
@@ -13,4 +13,5 @@ export type Device = {
 
 export type Account = {
   searchTermHistory?: string[]
+  searchAccountHistory?: string[]
 }