Trending Vals
61
fil
earthquakes
HTTP
Earthquake map 🌏 This val loads earthquake data from USGS, a topojson file for the land shape, and supporting libraries. It then creates a map and save it as a SVG string. The result is cached for a day. Note that we must strive to keep it under val.town’s limit of 100kB, hence the heavy simplification of the land shape. (For a simpler example, see becker barley .) | | |
|-----|-----|
| Web page | https://fil-earthquakes.web.val.run/ |
| Observable Plot | https://observablehq.com/plot/ |
| linkedom | https://github.com/WebReflection/linkedom |
| topojson | https://github.com/topojson/topojson |
| earthquakes | https://earthquake.usgs.gov |
| world | https://observablehq.com/@visionscarto/world-atlas-topojson |
| css | https://milligram.io/ |
6
62
stevekrouse
easyAQI
Script
easyAQI Get the Air Quality Index (AQI) for a location via open data sources. It's "easy" because it strings together multiple lower-level APIs to give you a simple interface for AQI. Accepts a location in basically any string format (ie "downtown manhattan") Uses Nominatim to turn that into longitude and latitude Finds the closest sensor to you on OpenAQ Pulls the readings from OpenAQ Calculates the AQI via EPA's NowCAST algorithm Uses EPA's ranking to classify the severity of the score (ie "Unhealthy for Sensitive Groups") It uses blob storage to cache the openai location id for your location string to skip a couple steps for the next time. Example usage @stevekrouse.easyAQI({ location: "brooklyn navy yard" })
// Returns { "aqi": 23.6, "severity": "Good" } Forkable example: val.town/v/stevekrouse.easyAQIExample Also useful for getting alerts when the AQI is unhealthy near you: https://www.val.town/v/stevekrouse.aqi
3
65
maxm
eval
Script
Eval web demo Security Caveats This code runs in a Worker with { permissions: { write: false, read: false, net: false } } . This is likely very safe, but if you enable network access keep in mind that users might generate junk network traffic or attempt to make infinite loops. If sandboxed code knows the name of one of your private vals it will be able to import the code with import "https://esm.town/v/maxm/private" . If you enabled write: true in the Worker, the unix socket that Deno uses to communicate with the host can be deleted and intercepted. This might mean that evaluated code can steal the socket and read the next request. You should not use this to evaluate code that should not be read by a previous evaluation. All code is running on the same process and you are not protected from exotic attacks like speculative execution. Overview You can use this library to evaluate code: import { evalCode } from "https://esm.town/maxm/eval"
console.log(await evalCode("export const foo = 1")) // => 1 You can use this library with https://www.val.town/v/maxm/transformEvalCode to return the last value without needing to export it. This is how the /eval api endpoint used to work and makes the library preform similarly to a repl. import { evalCode } from "https://esm.town/maxm/eval"
import { transform } from "https://esm.town/maxm/transformEvalCode"
console.log(await evalCode(transform("1+1"))) // => 2 Here's an example UI application that demonstrates how you can string this all together: https://maxm-evalui.web.val.run/ (source: https://www.val.town/v/maxm/evalUI) Security Model Code is evaluated using a dynamic import within a Worker. await import(`data:text/tsx,${encodeURIComponent(e.data)}`); Running the code withing a Worker prevents access to GlobalThis and window from leaking between evals. Similarly, access to Deno.env is prevented and evaluations will see errors when trying to access any environment variables. TODO: what else?
4
67
70
stevekrouse
fileToDataURL
Script
File to Data URL Helpers to convert files to base64 & base64-encoded data URLs,
which are particularly helpful for sending images to LLMs
like ChatGPT, Anthropic, and Google. ChatGPT Live example import { fileToDataURL } from "https://esm.town/v/stevekrouse/fileToDataURL";
const dataURL = await fileToDataURL(file);
const response = await chat([
{
role: "system",
content: `You are an nutritionist.
Estimate the calories.
We only need a VERY ROUGH estimate.
Respond ONLY in a JSON array with values conforming to: {ingredient: string, calories: number}
`,
},
{
role: "user",
content: [{
type: "image_url",
image_url: {
url: dataURL,
},
}],
},
], {
model: "gpt-4o",
max_tokens: 200,
}); Anthropic Live example import { fileToBase64 } from "https://esm.town/v/stevekrouse/fileToDataURL";
const base64File = await fileToBase64(file);
let res = await anthropic.messages.create({
model: "claude-3-5-sonnet-20240620",
max_tokens: 1024,
messages: [
{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "base64",
"media_type": file.type,
"data": base64File,
},
},
{
"type": "text",
"text": `Write an HTML email
that evokes this photo in the funniest
way possible, with code fences.`,
},
],
},
],
}); Google Live example import { fileToBase64 } from "https://esm.town/v/stevekrouse/fileToDataURL";
const base64Image = await fileToBase64(image);
const result = await model.generateContent([
"Write all the names and authors of these books in JSON format. The response should be a valid JSON array of objects, each with 'title' and 'author' properties.",
{
inlineData: {
data: base64Image,
mimeType: image.type,
},
},
]);
1
71
gwoods22
sendNotification
Script
Push Notification Sender This val can be used in other vals to send notifications to a segment using OneSignal's REST API This is really handy if you want to send push notifications to your phone without building a native app! I built a barebones React PWA that asks for a password then loads the OneSignal Web SDK that I deployed to Netlify for free. OneSignal has easy to follow docs so you can build this functionality into a React, Angular, Vue app or even Wordpress! Then install the PWA on your platform of choice and you're off to the races! Setup Save your ONESIGNAL_TOKEN and SEGMENT_APPID from OneSignal to your Val Town environment variables Import into another val! import sendNotification from "https://esm.town/v/gwoods22/sendNotification";
1
74
yawnxyz
blobby
Script
Blobby Blobby is a simple wrapper around blob w/ more helpers for scoping, uploading/downloading, writing and reading strings, and so on. Todo Support lunr / semantic search, and embeddings Collections that support pointing to multiple blobs, like {description, embeddings, fileblob, ...} with a shared index / lookup
0
78
pomdtr
basicAuth
Script
Val Town Basic Auth Add basic auth on top of any http val Usage Wrap your HTTP handler in the basicAuth middleware. import { basicAuth } from "https://esm.town/v/pomdtr/basicAuth";
function handler(req: Request) {
return new Response("You are authenticated!");
}
export default basicAuth(handler, { verifyUser: (username, password) => username == "user" && password == "password" }); If you want to use an apiToken as a password: import { basicAuth } from "https://esm.town/v/pomdtr/basicAuth";
import { verifyToken } from "https://www.val.town/v/pomdtr/verifyToken"
function handler(req: Request) {
return new Response("You are authenticated!");
}
export default basicAuth(handler, { verifyUser: (_, password) => verifyToken(password) });
7
79
80
81
83
tmcw
devstats
HTTP
Developer Statistics This val lets you post statistics from your GitHub Actions runs to build charts of change over time. We do this by having a step at the end of our actions run like this: - name: devstats
run: |
curl -X "POST" "https://tmcw-devstats.web.val.run/" \
-H 'Authorization: Bearer ${{ secrets.DEVSTATS_TOKEN }}' \
-H 'Content-Type: application/json; charset=utf-8' \
-d $"{ \"name\": \"node_modules_kb\", \"value\": $(du -sk node_modules | awk '{print $1}') }" And setting a DEVSTATS_TOKEN value, which could be any short random value, both in Val Town environment variables
and as a secret in your GitHub Actions configuration. Currently the name you attach to a statistic can be anything, and the value is expected to be a number.
4
84
pomdtr
lastlogin
Script
Lastlogin Authentication for val.town Looking for an hono integration ? See @pomdtr/lastloginHono Support login in trough: Email Link QR Code Google Oauth Github Oauth Gitlab Oauth Facebook Oauth Demo You can try a demo at https://pomdtr-lastloginhonoexample.web.val.run (see @pomdtr/lastLoginHonoExample for code) Usage Wrap your http handlers in a lastlogin middleware (sessions will be persisted in the lastlogin_session table on your sqlite account). If you want to be the only one able to access your val, you can use @pomdtr/verifyUserEmail. import { lastlogin } from "https://esm.town/v/pomdtr/lastlogin";
import { verifyUserEmail } from "https://esm.town/v/pomdtr/verifyUserEmail";
export default lastlogin((req) => {
return new Response(`You are logged in as ${req.headers.get("X-LastLogin-Email")}`);
}, {
// check that the user email match your val town email
verifyEmail: verifyUserEmail
}); If you want to customize how is allowed to signup, you can set the verifyEmail option: import { lastlogin } from "https://esm.town/v/pomdtr/lastlogin";
export default lastlogin((req) => {
return new Response(`You are logged in as ${req.headers.get("X-LastLogin-Email")}`);
}, {
verifyEmail: (email) => { email == "steve@valtown" }
}); You can allow anyone to signup by returning a boolean from the verifyEmail function: import { lastlogin } from "https://esm.town/v/pomdtr/lastlogin";
export default lastlogin((req) => {
return new Response(`You are logged in as ${req.headers.get("X-LastLogin-Email")}`);
}, {
verifyEmail: (_email) => true
}); Public Routes import { lastlogin } from "https://esm.town/v/pomdtr/lastlogin";
import { verifyUserEmail } from "https://esm.town/v/pomdtr/verifyUserEmail";
export default lastlogin(() => {
return new Response("Hi!");
}, {
verifyEmail: verifyUserEmail,
public_routes: ["/", "/public/*"],
}); See the URLPattern API for reference. Logout Just redirect the user to /auth/logout
10
86
87
sqlite
db
HTTP
Query All Public Vals Example: val town leaderboard import { zip } from "https://esm.town/v/pomdtr/sql";
import { db } from "https://esm.town/v/sqlite/db";
const res = await db.execute("SELECT author_username, COUNT(*) AS val_count FROM vals GROUP BY author_username ORDER BY 2 DESC LIMIT 10");
console.table(zip(res));
0
88
xkonti
cache
Script
Implementation of Redis-like cache - a key-value store with expiring keys. Data is stored in the Val Town SQLite database and shared between all your vals. Setup 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 . 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. The setup() function should be ran before using the cache for the first time. You can do that by creating a small temporary Val: import { setup } from "https://esm.town/v/xkonti/cache";
await setup(); Optionally create a scheduled val that will delete expired keys on some interval - 15 minutes can be a good start. import { deleteExpired } from "https://esm.town/v/xkonti/cache";
export default async function cacheCleaner(interval: Interval) {
await deleteExpired();
} Usage After setting your cache up you can use it simply by importing functions from https://esm.town/v/xkonti/cache . set(key, value, ttl): Promise Set a value in the cache. key - the key to set the value for value - the value to set - it can be any value that can be serialized to JSON ttl - the time to live in seconds. In other words, after how many seconds the key will expire. If not set, the default TTL is used. returns the number of keys set: 1 if the key was inserted/updated, 0 if the ttl was 0 or invalid // Set a value in the cache with a default TTL
await set("luckyNumber", 13);
// Set a value that will expire in 5 minutes
await set("product:344798", { name: "Audio Interface", price: 209.99}, 5 * 60); setUntil(key, value, expiresAt): Promise Set a value in the cache until a specific date and time. key - the key to set the value for value - the value to set - it can be any value that can be serialized to JSON expiresAt - the expiration time as a UTC date string returns the number of keys set: 1 if the key was inserted/updated, 0 if the `expiresAt`` was in the past // Set a value in the cache until 2024-01-01 16:23:05 UTC
await setUntil(
"product:155392",
{ name: "Audio Interface", price: 209.99 },
new Date('2024-01-01T16:23:05Z').toISOString()
); setExpiration(key, ttl): Promise Update the expiration date of a cache entry based on TTL. If the key does not exist or is expired, nothing happens. key - the key of the cache entry to update ttl - the time to live in seconds from now. In other words, after how many seconds the key will expire. If not set, the default TTL is used. returns the number of keys updated: 1 if updated, 0 if not found or ttl was 0 // Set the expiration date in the cache with a default TTL
await setExpiration("luckyNumber");
// Set the expiration date in the cache for 5 minutes from now.
await setExpiration("luckyNumber", 5 * 60); setExpirationUntil(key, expiresAt): Promise Update the expiration date of a cache entry to a specific UTC date and time. If the key does not exist or is expired, nothing happens. key - the key of the cache entry to update expiresAt - the expiration time as a UTC date string returns the number of keys updated: 1 if updated, 0 if not found or expiresAt was in the past // Set the expiration date in the cache until 2024-01-01 16:23:05 UTC
await setExpirationUntil(
"product:155392",
new Date('2024-01-01T16:23:05Z').toISOString()
); exists(key): Promise Checks if the provided key exists (has value) in the cache. If the key is expired, it's considered non-existent. key - the key to check for existence // Check if the key is present in the cache
const hasLuckyNumber: Boolean = await exists("luckyNumber"); get (key): Promise<T | null> Get a value from the cache by key. You can provide a type of the return value or it will default to unknown . If there is no value for the key or the value has expired, null is returned. key - the key to get the value for // Get a value from the cache
const luckyNumber: number = await get<number>("luckyNumber");
const luckyNumber: number = await get("luckyNumber") as number; // same as above listKeys(prefix): Promise<string[]> Gets a list of all non-expired keys in the cache that match the prefix. If no prefix is provided, all keys are returned. prefix - the optional prefix to match keys against // Get all keys from the cache
const keys: string[] = await listKeys();
// Get all keys from the cache that start with "product:"
const keys: string[] = await listKeys("product:"); getMany (prefix, limit): Promise<Array<{ key: string, value: T }>> Get many key-value pairs from the cache that match the given prefix. prefix - the optional prefix to match keys against. If not provided, all keys are considered. limit - the optional maximum number of key-value pairs to return. If 0 , no limit is applied. Defaults to 0 . returns An array of key-value pairs. Each pair is an object with key and value properties. // Get all non-expired keys and their values
const everything = await getMany();
// Get all keys and values with a matching prefix
const allProducts = await getMany("product:");
// Get 5 keys and values with a matching prefix
const discountedProducts = await getMany("discounts:", 5); deleteKey(key): Promise Delete a key from the cache. key - the key to delete returns the number of keys deleted: 1 if the key was deleted, 0 if the key did not exist. // Delete a key from the cache
await deleteKey("luckyNumber"); deleteKeys(prefix): Promise Delete all keys from the cache that match the prefix. If no prefix is provided, all keys in the cache are deleted. prefix - the optional prefix to match keys against returns the number of keys deleted // Delete all keys from the cache
await deleteKeys();
// Delete all keys from the cache that start with "product:"
await deleteKeys("product:"); deleteExpired(): Promise Delete all expired keys from the cache. Perfect for running on a schedule to keep the cache small and fast. returns the number of keys deleted // Delete all expired keys from the cache
await deleteExpired();
2
90
postpostscript
expiringBlob
Script
expiringBlob: create-and-forget blobs using UUIDv7 How it Works This tool uses UUIDv7s in blob keys with UNIX timestamps included to keep track of when they expire.
Since UUIDv7s are sortable and the blob list endpoint returns results alphabetically, cleanup (checking the blob list and deleting any expired keys) is efficient since any expired keys will be at the front of the results! Each ID is prefixed with a prefix (default: expireBlob: ) and can be suffixed with a label (parameter order depends on the method).
Example blob keys: expiringBlob:01944432-4837-7a99-94e6-e0e162554e21
^Prefix ^UUIDv7
expiringBlob:01944432-4837-7a99-94e6-e0e162554e21:aaaa
^Prefix ^UUIDv7 ^Label Methods createKey : generate a key you can use with @std/blob methods set or setJSON : generate a blob key and save content to it in one step cleanup : delete expired blobs. You can run this manually or periodically with a cron val . If using a custom prefix, make sure to pass it to this method so that the correct blob list is searched. By default, createKey , set , and setJSON will create keys that expire in an hour, but you can override this by passing the expireAt: Date | number parameter. For convenience, expireIn is provided with methods to create future dates for each of the following: expireIn.hours(2) expireIn.minutes(5) expireIn.seconds(45) expireIn.ms(500) To request data once you've saved it, use @std/blob's get and getJSON as usual with the key provided by set or setJSON ! Full Example import { cleanup, createKey, expireIn, set, setJSON } from "https://esm.town/v/postpostscript/expiringBlob";
import { blob } from "https://esm.town/v/std/blob";
const aaaa = createKey("aaaa"); // equivalent of createKey("aaaa", expireIn.hours(1))
await blob.setJSON(aaaa, {
x: 1,
});
console.log(aaaa, await blob.getJSON(aaaa));
// expiringBlob:01944432-4837-7a99-94e6-e0e162554e21:aaaa { x: 1 }
console.log(await set("bbbb", "test", expireIn.seconds(1)));
// expiringBlob:019443fb-6106-77f2-ab0c-5142b099c81a:bbbb
console.log(await setJSON("cccc", { x: 1 }, expireIn.seconds(1)));
// expiringBlob:019443fb-6327-733c-9a0b-c8b9ee9faef0:cccc
await cleanup();
// [no logs]
await new Promise((resolve) => setTimeout(resolve, 1000));
await cleanup();
// deleting expired blob: expiringBlob:019443fb-6106-77f2-ab0c-5142b099c81a:bbbb
// deleting expired blob: expiringBlob:019443fb-6327-733c-9a0b-c8b9ee9faef0:cccc Forkable Cron Example Create a cron val with the following code to periodically delete expired blobs: import { cleanup } from "https://esm.town/v/postpostscript/expiringBlob";
export default () => cleanup();
0