upstash.ts 3.1 KB

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