Radish alpha
r
rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5
Radicle web interface
Radicle
Git
radicle-explorer src lib localStore.ts
import type { Writable } from "svelte/store";

import { writable, get } from "svelte/store";
import { z } from "zod";

type Equals<X, Y> =
  (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2
    ? true
    : false;

/**
 * An extension of Svelte's `writable` that also saves its state to localStorage and
 * automatically restores it.
 * @param key The localStorage key to use for saving the writable's contents.
 * @param schema A Zod schema describing the contents of the writable.
 * @param initialValue The initial value to use if no prior state has been saved in
 * localstorage.
 * @param disableLocalStorage Skip interaction with localStorage, for example during SSR.
 * @returns A stored writable.
 */
export default function storedWritable<
  S extends z.infer<T>,
  T extends z.ZodType = z.ZodType<S>,
>(
  key: string,
  schema: T,
  initialValue: z.infer<typeof schema>,
  disableLocalStorage = false,
): Writable<
  Equals<T, typeof schema> extends true ? S : z.infer<typeof schema>
> & { clear: () => void } {
  const stored = !disableLocalStorage ? localStorage.getItem(key) : null;

  const parseFromJson = (content: string) => {
    return z
      .string()
      .transform((_, ctx) => {
        try {
          return JSON.parse(content);
        } catch {
          ctx.addIssue({
            code: z.ZodIssueCode.custom,
            message: "invalid json",
          });
          return z.never;
        }
      })
      .pipe(schema)
      .safeParse(content);
  };

  // Subscribe to window storage event to keep changes from another tab in sync.
  if (!disableLocalStorage) {
    window?.addEventListener("storage", event => {
      if (event.key === key) {
        if (event.newValue === null) {
          w.set(initialValue as S);
          return;
        }

        const { success, data } = parseFromJson(event.newValue);
        w.set(success ? (data as S) : (initialValue as S));
      }
    });
  }
  const parsed = stored ? parseFromJson(stored) : undefined;
  const w = writable<S>(
    parsed?.success ? (parsed.data as S) : (initialValue as S),
  );

  /**
   * Set writable value and inform subscribers. Updates the writeable's stored data in
   * localstorage.
   * */
  function set(...args: Parameters<typeof w.set>) {
    w.set(...args);
    if (!disableLocalStorage) localStorage.setItem(key, JSON.stringify(get(w)));
  }

  /**
   * Update writable value using a callback and inform subscribers. Updates the writeable's
   * stored data in localstorage.
   * */
  function update(...args: Parameters<typeof w.update>) {
    w.update(...args);
    if (!disableLocalStorage) localStorage.setItem(key, JSON.stringify(get(w)));
  }

  /**
   * Delete any data saved for this StoredWritable in localstorage.
   */
  function clear() {
    w.set(initialValue as S);
    if (!disableLocalStorage) localStorage.removeItem(key);
  }

  return {
    subscribe: w.subscribe,
    set,
    update,
    clear,
  };
}