svelte-drama/swr

Option to use superjson to serialize/deserialize data

Closed this issue · 3 comments

https://www.npmjs.com/package/superjson

I want to use a Decimal from https://www.npmjs.com/package/decimal.js in an swr query, but an error is thrown if I return a Decimal from a fetcher. As I understand, it is related to the structuredClone as it is used to store data in IndexedDB. Would you consider adding an option to customize serialization/deserialization of data to be able to use objects with functions (classes) with swr?
image

BroadcastChannel also requires objects to fit the structuredClone algorithm.

Currently, you could write a wrapper around the model. Fetcher would return the serialized version of the data and then your wrapper could return fetch and live functions that deserialized it. What do you envision as advantages of having that baked directly into the library instead of letting very domain specific handling be taken care of by the consumer?

What do you envision as advantages of having that baked directly into the library instead of letting very domain specific handling be taken care of by the consumer?

Reasonable. I agree that this feature should be added only after this issue becomes not domain specific(i.e., when more people will ask for this). I'll write a wrapper meanwhile. Let this issue be an indicator that I encountered this problem just as I wrote my first query.

I managed to write this code and it works great:

import type { Fetcher, SuspenseFn } from "@svelte-drama/swr/dist/types";
import { swr as dramaSwr } from "@svelte-drama/swr";
import { derived, type Readable } from "svelte/store";
import superjson from "superjson";

export function swr<ID, T>(options: {
  key(params: ID): string;
  fetcher: Fetcher<ID, T>;
}): MySwrModel<ID, T> {
  const model = dramaSwr({
    key: options.key,
    fetcher: async (key, params) => {
      return superjson.serialize(await options.fetcher(key, params));
    },
  });
  return {
    ...model,
    live(params, suspend) {
      const store = model.live(params, suspend);
      return derived([store], ([data]) => {
        if (!data) {
          return data;
        }
        return superjson.deserialize(data);
      });
    },
    async refresh(params) {
      return superjson.deserialize(await model.refresh(params));
    },
  };
}

// here you can register any custom type you want. I needed `BigNumber` from `ethers`.
superjson.registerCustom<ethers.BigNumber, string>(
  {
    isApplicable: (v): v is ethers.BigNumber => ethers.BigNumber.isBigNumber(v),
    serialize: (v) => v.toString(),
    deserialize: (v) => ethers.BigNumber.from(v),
  },
  "ethers.BigNumber",
);

type MySwrModel<ID, T> = {
  clear(): Promise<void>;
  delete(params: ID): Promise<void>;
  live(params: ID, suspend?: SuspenseFn): Readable<T | undefined>;
  refresh(params: ID): Promise<T>;

  // TODO: support this too
  //   fetch(params: ID): Promise<T>;
  //   update: {
  //     (params: ID, data: T): T;
  //     (params: ID, fn: (data: T) => MaybePromise<T>): Promise<T>;
  //   };
};