upstash.ts 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. import { STORAGE_KEY } from "@/app/constant";
  2. import { SyncStore } from "@/app/store/sync";
  3. import { corsFetch } from "../cors";
  4. import { chunks } from "../format";
  5. export type UpstashConfig = SyncStore["upstash"];
  6. export type UpStashClient = ReturnType<typeof createUpstashClient>;
  7. export function createUpstashClient(store: SyncStore) {
  8. const config = store.upstash;
  9. const storeKey = config.username.length === 0 ? STORAGE_KEY : config.username;
  10. const chunkCountKey = `${storeKey}-chunk-count`;
  11. const chunkIndexKey = (i: number) => `${storeKey}-chunk-${i}`;
  12. const proxyUrl =
  13. store.useProxy && store.proxyUrl.length > 0 ? store.proxyUrl : undefined;
  14. return {
  15. async check() {
  16. try {
  17. const res = await corsFetch(this.path(`get/${storeKey}`), {
  18. method: "GET",
  19. headers: this.headers(),
  20. proxyUrl,
  21. });
  22. console.log("[Upstash] check", res.status, res.statusText);
  23. return [200].includes(res.status);
  24. } catch (e) {
  25. console.error("[Upstash] failed to check", e);
  26. }
  27. return false;
  28. },
  29. async redisGet(key: string) {
  30. const res = await corsFetch(this.path(`get/${key}`), {
  31. method: "GET",
  32. headers: this.headers(),
  33. proxyUrl,
  34. });
  35. console.log("[Upstash] get key = ", key, res.status, res.statusText);
  36. const resJson = (await res.json()) as { result: string };
  37. return resJson.result;
  38. },
  39. async redisSet(key: string, value: string) {
  40. const res = await corsFetch(this.path(`set/${key}`), {
  41. method: "POST",
  42. headers: this.headers(),
  43. body: value,
  44. proxyUrl,
  45. });
  46. console.log("[Upstash] set key = ", key, res.status, res.statusText);
  47. },
  48. async get() {
  49. const chunkCount = Number(await this.redisGet(chunkCountKey));
  50. if (!Number.isInteger(chunkCount)) return;
  51. const chunks = await Promise.all(
  52. new Array(chunkCount)
  53. .fill(0)
  54. .map((_, i) => this.redisGet(chunkIndexKey(i))),
  55. );
  56. console.log("[Upstash] get full chunks", chunks);
  57. return chunks.join("");
  58. },
  59. async set(_: string, value: string) {
  60. // upstash limit the max request size which is 1Mb for “Free” and “Pay as you go”
  61. // so we need to split the data to chunks
  62. let index = 0;
  63. for await (const chunk of chunks(value)) {
  64. await this.redisSet(chunkIndexKey(index), chunk);
  65. index += 1;
  66. }
  67. await this.redisSet(chunkCountKey, index.toString());
  68. },
  69. headers() {
  70. return {
  71. Authorization: `Bearer ${config.apiKey}`,
  72. };
  73. },
  74. path(path: string) {
  75. let url = config.endpoint;
  76. if (!url.endsWith("/")) {
  77. url += "/";
  78. }
  79. if (path.startsWith("/")) {
  80. path = path.slice(1);
  81. }
  82. return url + path;
  83. },
  84. };
  85. }