51. First you should decide on a name of a SQL table that will be used for storing cache data. It could something like `cacheData` or `kv`. Set that value to a new Environment Variable `CACHE_TABLE_NAME`.
62. Optionally you might add a new `CACHE_DEFAULT_TTL` Environment Variable. It's value should be set to a number of seconds that will be used when saving new values to the cache without providing the expiration time. By default it's 24h.
73. The `setup()` function should be ran before using the cache for the first time. You can do that by creating a small temporary Val:
8```ts
9import { setup } from "https://esm.town/v/xkonti/cache";
13```ts
14import { deleteExpired } from "https://esm.town/v/xkonti/cache";
15export default async function cacheCleaner(interval: Interval) {
16await deleteExpired();
17}
20# Usage
2122After setting your cache up you can use it simply by importing functions from `https://esm.town/v/xkonti/cache`.
2324## set(key, value, ttl): Promise<number>
testRunnermain.tsx1 match
2import { sleep } from "https://esm.town/v/stevekrouse/sleep?v=1";
34export async function testRunner<
5Input extends {
6val?: Ref;
singleformulaEndpointmain.tsx24 matches
1const functionMap = {
2// Basic Arithmetic Operations, with checks for type as numbers
3"add": (a, b) => typeof a === "number" && typeof b === "number" ? a + b : null,
84"datebetween": (startDate, endDate, unit) => {
85if (!startDate || !endDate || typeof unit !== "string") {
86return new Response("Invalid arguments for datebetween function");
87}
88let difference = Math.abs(endDate - startDate);
160// Conditional logic with checks
161"if": (a, b, c) => {
162console.log(`if function called with a: ${a}, b: ${b}, c: ${c}`);
163const result = a ? b : c;
164console.log(`if function result: ${result}`);
165return result;
166},
175};
176177function tokenize(formula) {
178const tokens = [];
179const functionsRegex = new RegExp(`\\b(${Object.keys(functionMap).join("|")})\\b`, "g");
180const tokenRegex = new RegExp(
181`(\\[\\[date:\\d{4}-\\d{2}-\\d{2}\\]\\])|`
182+ `${functionsRegex.source}|`
183+ `("(?:[^"\\\\]|\\\\.)*")|` // Match double-quoted strings
184+ `(-?\\d+(?:\\.\\d+)?)|` // Match numbers (including negative and decimal numbers)
203}
204205function parse(tokens) {
206let position = 0;
207208function peek() {
209return tokens[position];
210}
211212function consume() {
213position++;
214}
215216function isNumber(token) {
217return !isNaN(parseFloat(token)) && isFinite(token);
218}
219220function isString(token) {
221return token.startsWith("\"") && token.endsWith("\"");
222}
223224function parsePrimaryExpr() {
225const token = peek();
226if (token === "Yes") {
239consume();
240return { type: "date", value: token.slice(7, -2) };
241} else if (token && functionMap[token]) {
242consume();
243return { type: "function", name: token };
244} else if (token.match(/^[a-zA-Z_]+$/)) {
245consume();
256}
257258function parseExpr() {
259let expr = parsePrimaryExpr();
260while (peek() === "(") {
283}
284285function evaluate(ast) {
286console.log(`Evaluating node: ${JSON.stringify(ast)}`);
287288if (typeof ast !== "object" || ast === null || typeof ast.type === "undefined") {
289throw new Error("Invalid input to evaluate function: Input must be an AST object");
290}
291298return new Date(ast.value);
299case "call":
300if (typeof functionMap[ast.name] !== "function") {
301throw new Error(`Function '${ast.name}' not found in functionMap`);
302}
303const argsEvaluated = ast.args.map(arg => evaluate(arg));
304const result = functionMap[ast.name](...argsEvaluated);
305if (result === null || result === undefined) {
306throw new Error(`Function '${ast.name}' returned null or undefined`);
307}
308return result;
321}
322} catch (error) {
323console.error(`Error in evaluate function: ${error.message} for AST node ${JSON.stringify(ast)}`, error);
324throw error;
325}
326}
327328export async function FormulaEndpoint(req) {
329const url = new URL(req.url);
330console.log(`Received request with URL: ${req.url}`);
twEndpointmain.tsx1 match
2import process from "node:process";
34export async function extractTwitterContent(req) {
5try {
6console.log("Starting to extract Twitter content...");
findIdeaListsmain.tsx1 match
3import { extractValInfo } from "https://esm.town/v/pomdtr/extractValInfo";
45export default async function() {
6let { data: articles } = await api(`/v1/search/vals?query=${encodeURIComponent("@vtIdeas")}`);
7
valtownIdeasmain.tsx1 match
6import { html } from "https://esm.town/v/stevekrouse/html?v=5";
78export async function examplePost(req: Request) {
9const { author, name } = extractValInfo(import.meta.url);
10
emailJasonAboutValTownmain.tsx2 matches
1import { email } from "https://esm.town/v/std/email?v=11";
23export default async function emailJasonAboutValTown(interval: Interval) {
4// no-op if it's not 2024: I don't want to email Jason every year!
5const is2024 = new Date().getFullYear() === 2024;
10<p>Hey Jason, following up on our conversation back in December about getting Steve Krouse on LWJ to explore Val Town with you!</p>
11<p>Hope all is well, Pete</p>
12<p><em>(This email was sent programmatically via a val.town scheduled function.)</em></p>
13</main>
14`;
vtWishListmain.tsx1 match
7import { html } from "https://esm.town/v/stevekrouse/html?v=5";
89export async function examplePost(req: Request) {
10const { author, name } = extractValInfo(import.meta.url);
11const { code, readme } = await api(`/v1/alias/${author}/${name}`);
vtIdeasTemplatemain.tsx1 match
3import { html } from "https://esm.town/v/stevekrouse/html?v=5";
45export async function examplePost(req: Request) {
6const { author, name } = extractValInfo(import.meta.url);
7