1import { crypto } from "jsr:@std/crypto"
2import { encodeHex } from "jsr:@std/encoding/hex"
3
4export const kv = await Deno.openKv()
5
6/**
7 * Wraps a promise in a try/catch block and returns a Result object representing
8 * either a successful value or an error.
9 *
10 * This utility function provides a more structured way to handle asynchronous operations
11 * without using try/catch blocks throughout your codebase. It follows a pattern similar
12 * to Rust's Result type, allowing for more predictable error handling.
13 *
14 * @template T - The type of the value returned by the promise on success
15 * @template E - The type of the error object (defaults to Error)
16 *
17 * @param promise - The promise to wrap and execute
18 *
19 * @returns A Promise resolving to a discriminated union object with:
20 * - On success: `{ success: true, value: T, error: null }`
21 * - On failure: `{ success: false, value: null, error: E }`
22 *
23 * @example
24 * // Success case
25 * const successResult = await tryCatch(Promise.resolve('data'));
26 * if (successResult.success) {
27 * console.log(successResult.value); // 'data'
28 * }
29 *
30 * @example
31 * // Error case
32 * const errorResult = await tryCatch(Promise.reject(new Error('Failed')));
33 * if (!errorResult.success) {
34 * console.error(errorResult.error.message); // 'Failed'
35 * }
36 *
37 * @example
38 * // Using with custom error type
39 * interface ApiError { code: number; message: string }
40 * const apiCall = tryCatch<UserData, ApiError>(fetchUserData());
41 */
42export async function tryCatch<T, E = Error>(
43 promise: Promise<T>,
44): Promise<
45 | {
46 success: true
47 value: T
48 error: null
49 }
50 | {
51 success: false
52 value: null
53 error: E
54 }
55> {
56 try {
57 const value = await promise
58 return { success: true, value, error: null }
59 } catch (error) {
60 return { success: false, value: null, error: error as E }
61 }
62}
63
64/**
65 * Fetches data from a URL with caching.
66 * Cache is stored in the KV
67 * @param url - The URL to fetch data from.
68 * @returns A promise that resolves to an object with success status, value, and error.
69 */
70export async function fetchWithCache<T>(url: string): Promise<
71 | {
72 success: true
73 value: T
74 error: null
75 }
76 | {
77 success: false
78 value: null
79 error: string
80 }
81> {
82 // Calculate the hash of the request URL
83 const keybuffer = new TextEncoder().encode(url)
84 const hashBuffer = await crypto.subtle.digest("SHA-256", keybuffer)
85 const hash = encodeHex(hashBuffer)
86
87 // Check if the data is already in cache
88 const cached = await kv.get<T>(["fetch-cache", hash])
89 if (cached.value) {
90 console.info("Serving cached request data")
91 return {
92 success: true,
93 value: cached.value,
94 error: null,
95 }
96 }
97
98 // Fetch the data from the URL
99 const data = await tryCatch(
100 fetch(url).then((res) => res.json() as T),
101 )
102
103 if (!data.success) {
104 return {
105 success: false,
106 value: null,
107 error: data.error.message,
108 }
109 }
110
111 // Store the fetched data in cache, with a 1-day expiration
112 await kv.set(["fetch-cache", hash], data.value, { expireIn: 60 * 60 * 24 })
113
114 return {
115 success: true,
116 value: data.value,
117 error: null,
118 }
119}