(schema: z.ZodType<Output[]>)
| 63 | const MEMORY_CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour |
| 64 | |
| 65 | export function cachedPosthogQuery<Output>(schema: z.ZodType<Output[]>) { |
| 66 | const parse = (name: string, raw: unknown): Output[] => { |
| 67 | const result = schema.safeParse(raw); |
| 68 | if (!result.success) { |
| 69 | throw new Error(`${name} parse failed: ${z.prettifyError(result.error)}`); |
| 70 | } |
| 71 | return result.data; |
| 72 | }; |
| 73 | |
| 74 | const memoryCache = new Map<string, { value: Output[]; at: number }>(); |
| 75 | |
| 76 | return async (name: string, query: string): Promise<Output[]> => { |
| 77 | const memoryCached = memoryCache.get(name); |
| 78 | if (memoryCached && Date.now() - memoryCached.at < MEMORY_CACHE_TTL_MS) { |
| 79 | return memoryCached.value; |
| 80 | } |
| 81 | |
| 82 | const key = posthogQueryRedisKey(name); |
| 83 | |
| 84 | const cached = await redisClient.get<string>(key); |
| 85 | if (cached !== null) { |
| 86 | const data = parse(name, JSON.parse(cached)); |
| 87 | memoryCache.set(name, { value: data, at: Date.now() }); |
| 88 | return data; |
| 89 | } |
| 90 | |
| 91 | const startTime = performance.now(); |
| 92 | const response = await posthogQuery(name, query); |
| 93 | if (response.status !== 'ok') { |
| 94 | throw new Error(`${name} query failed: ${JSON.stringify(response.error, undefined, 2)}`); |
| 95 | } |
| 96 | const data = parse(name, response.body.results); |
| 97 | console.debug( |
| 98 | `[cachedPosthogQuery] ${name} returned ${data.length} rows in ${performance.now() - startTime}ms` |
| 99 | ); |
| 100 | |
| 101 | await redisClient.set(key, JSON.stringify(response.body.results), { ex: CACHE_TTL_SECONDS }); |
| 102 | memoryCache.set(name, { value: data, at: Date.now() }); |
| 103 | |
| 104 | return data; |
| 105 | }; |
| 106 | } |
nothing calls this directly
no test coverage detected