• nbbaier avatar
    vtIdeaList
    @nbbaier
    Vals I Want to Build A running list of things I want to build on Val Town, hosted in a Val Town readme . [ ] A bare bones CMS to edit markdown files hosted on a github (or val town readmes eventually) [ ] A set of Vals for interacting with github repos via Octokit (useful for the above CMS idea) [ ] A full json-server like implementation for quickly generating stub APIs based on json/valtown blob data ( begun ) [ ] An implementation of Convert bookmarklet to Chrome extension [ ] A tool for generating an NPM package from a Val or set of Vals (something like [dnt]- [ ] (https://github.com/denoland/dnt/)) [ ] An email handler for forwarding emails to Tana [ ] A component library (this would be a wild swing for me) [ ] A val to get a dependency graph of a val(s) ( started here by rlesser ) [ ] A single val that wraps a FeTS client to the API for saving a couple of lines of boilerplate [ ] A val for generating OpenAPI specs from jsdoc comments within vals (sort of like this npm package ) [ ] Conways game of life pst, if you want to see stuff I would love to see built right into Val Town, here you go
    HTTP (deprecated)
  • maxm avatar
    asciiNycCameras
    @maxm
    ASCII NYC Traffic Cameras All of NYC's traffic cameras available as streaming ASCII images: https://maxm-asciinyccameras.web.val.run/ NYC has a bunch of traffic cameras and makes them available through static images like this one . If you refresh the page you'll see the image update every 2 seconds or so. I thought it might be fun to make these cameras viewable as an ASCII art video feed. I made a small library that takes most of its logic from this repo . You can see a basic example of how to convert any image to ASCII here . I pull in NYC GeoJSON from here and then hook up a Server-Sent Events endpoint to stream the ASCII updates to the browser. (Polling would work just as well, I've just been on a bit of a SSE kick lately.) Hilariously (and expectedly) The ASCII representation is about 4x the size of the the source jpeg and harder to see, but it has a retro-nostalgia look to it that is cool to me :)
    HTTP (deprecated)
  • postpostscript avatar
    blogSqliteUniverse
    @postpostscript
    sqliteUniverse: Make SQLite Queries Against Multiple Endpoints in Deno (Val Town) (Part 1) Prerequisite Knowledge Val Town-Hosted SQLite Val Town hosts SQLite as part of its standard library ( @std/sqlite ). This makes a fetch request against their closed-source API (using your API token) which returns results in a consistent format. This is great because you can host your own endpoints that work similarly, and reuse code that was only designed in mind for that original hosted interface The standard format (abridged to important fields): POST /execute { "statement": { "sql": "SELECT * FROM some_table", "args": [] } } Output: { "rows": [[1, "first", 1709942400], [2, "second", 1709942401]], "columns": ["id", "name", "lastModified"] } POST /batch { "statements": [ { "sql": "INSERT INTO some_table VALUES (?, ?, ?)", "args": [3, "third", 1709942402] }, { "sql": "SELECT * FROM some_table", "args": [] } ] } Output: [ { "rows": [], "columns": ["id", "name", "lastModified"] }, { "rows": [[1, "first", 1709942400], [2, "second", 1709942401], [3, "third", 1709942402]], "columns": ["id", "name", "lastModified"] }, ] SQLite in Wasm There is a deno package (sqlite) which lets you (among other things) create SQLite databases in-memory using WebAssembly. I've created a Val which wraps this to enable it to be a drop-in replacement for @std/sqlite: @postpostscript/sqliteWasm Example import { createSqlite } from "https://esm.town/v/postpostscript/sqliteWasm"; import { Statement } from "https://esm.town/v/postpostscript/sqliteBuilder"; const sqlite = createSqlite(); console.log(sqlite.batch([ Statement` CREATE TABLE test ( id TEXT PRIMARY KEY, value TEXT ) `, Statement` INSERT INTO test VALUES ( ${"some-id"}, ${"some-value"} ) `, Statement` SELECT * FROM test `, ])) Result: [ { rows: [], rowsAffected: 0, columns: [] }, { rows: [], rowsAffected: 1, columns: [] }, { rows: [ [ "some-id", "some-value" ] ], rowsAffected: 0, columns: [ "id", "value" ] } ] Dump Tool I have modified @nbbaier's great work at @postpostscript/sqliteDump to support dumping from any sqlite interface, whether the standard library's version, over HTTP, or through the above Wasm implementation Putting it All Together All of the above enables: Serving a subset of your private data publicly for others to query (Example: @postpostscript/sqlitePublic ) Backing up your database and querying against that backup (via @postpostscript/sqliteBackup's sqliteFromBlob and sqliteToBlob ) But we can do more..! What if we could query from multiple of these data sources.. at the same time! 😱 sqliteUniverse sqliteUniverse is an @std/sqlite compatible interface that determines where a table should route to based on different patterns Table Name Patterns The actual table name will always come after a "/", with the exception of tables without any endpoint, for example users . Everything before the last "/" is the endpoint name. Endpoint interfaces will be chosen in the following order: Exact match in options.interfaces.exact e.g. @std/sqlite/someTable would match options.interfaces.exact["@std/sqlite"] Each pattern in options.interfaces.patterns options.interfaces.fallback will be called An error is thrown if none of the above matches AND returns an sqlite interface. If there is a match but the handler returns nothing, it will continue down the list Default options.interfaces.patterns : patterns.https - /^https:\/\// ( https://example.com/somePath/tableName ): fetch from https://example.com/somePath/batch patterns.val - /^@/ ( @author/name/somePath/tableName ): fetch from the val's endpoint, https://author-name.web.val.run/somePath/batch Other Available Patterns: The following patterns are accessible through import { patterns } from "https://esm.town/v/postpostscript/sqliteUniverse" : patterns.blob - /^blob:\/\// ( blob://backup:sqlite:1709960402936 ) - import the database from private blob backup:sqlite:1709960402936 Overriding Default Options The sqliteUniverse export contains defaults insuring no private data will be leaked. If you want to reduce or extend these options, use the sqliteUniverseWithOptions export and pass a modified interfaces option in the first argument: Examples of how to set options.interfaces.exact , options.interfaces.patterns , and options.interfaces.fallback : import { sqliteUniverseWithOptions, patterns, defaultPatterns } from "https://esm.town/v/postpostscript/sqliteUniverse"; import { createSqlite } from "https://esm.town/v/postpostscript/sqliteWasm"; import { sqliteFromAPI } from "https://esm.town/v/postpostscript/sqliteFromAPI"; import { Statement } from "https://esm.town/v/postpostscript/sqliteBuilder"; const sqlite = sqliteUniverseWithOptions({ interfaces: { exact: { // `SELECT * FROM "some-endpoint/someTable"` will match // `SELECT * FROM "some-endpoint/somePath/someTable"` will NOT match "some-endpoint": ({ endpoint, tables }) => { const sqlite = createSqlite() sqlite.batch([ Statement`CREATE TABLE someTable (someField TEXT PRIMARY KEY)`, Statement`INSERT INTO someTable VALUES (${"some-field"})` ]) return sqlite }, }, patterns: [ ...defaultPatterns, [ // shorthand e.g. ~/sqlitePublic -> @postpostscript/sqlitePublic /^~\/(\w+)/, ({ endpoint, tables, match }) => { return sqliteFromAPI(`@postpostscript/${match[1]}`) }, ] ], fallback({ endpoint, tables }) { // if an endpoint is not found, this will be called return sqliteFromAPI(`@postpostscript/sqlitePublic`) }, }, }) console.log(await Statement` SELECT * FROM "some-endpoint/someTable" JOIN "~/sqliteVals/vals" JOIN authIdExampleComments_comment LIMIT 1 `.execute({ sqlite })) Output: [ { someField: "some-field", id: "aeb70bbb-05fc-403b-8d6a-130c423ecb53", name: "discordWelcomedMembers", code: "// set at Sun Mar 10 2024 00:32:48 GMT+0000 (Coordinated Universal Time)\n" + "export let discordWelcomedM"... 8289 more characters, version: 234771, privacy: "public", public: 1, run_start_at: "2024-03-10T00:32:48.978Z", run_end_at: "2024-03-10T00:32:48.978Z", created_at: "2024-03-10T00:32:48.978Z", author_id: "a0bf3b31-15a5-4d5c-880e-4b1e22c9bc18", author_username: "stevekrouse", username: "postpostscript", comment: "test", date_added: 1709776325.857 } ] Part 2 Since Val Town currently has a character limit for val readmes, this will have to continue in Part 2 !
    HTTP (deprecated)
  • geltoob avatar
    VALLE
    @geltoob
    Forked from janpaul123/VALLE
    HTTP (deprecated)
  • oijoijcoiejoijce avatar
    VALLE
    @oijoijcoiejoijce
    Forked from janpaul123/VALLE
    HTTP (deprecated)
  • mrblanchard avatar
    VALLE
    @mrblanchard
    Forked from janpaul123/VALLE
    HTTP (deprecated)
  • stevekrouse avatar
    hono_react_ssr
    @stevekrouse
    Forked from stevekrouse/ssr_react_mini
    Script
  • janpaul123 avatar
    valleBlogV0
    @janpaul123
    Fork this val to your own profile. Create a Val Town API token , open the browser preview of this val, and use the API token as the password to log in.
    HTTP (deprecated)
  • eugenechantk avatar
    VALLE
    @eugenechantk
    Forked from janpaul123/VALLE
    HTTP (deprecated)
  • maxm avatar
    wasmBlobHost
    @maxm
    WASM Binary Host https://maxm-wasmblobhost.web.val.run/ Where should you host your WASM blobs to use in vals? Host them here! Upload with curl: curl -X POST -H "Content-Type: application/wasm" \ --data-binary @main.wasm https://maxm-wasmBlobHost.web.val.run
    HTTP (deprecated)
  • kylem avatar
    gitReleaseNotes
    @kylem
    Github Release Notes from package.json Enter a raw github URL to a package.json, https://raw.githubusercontent.com/username/repo/branch/package.json and get a response of all release notes for all packages from current to latest. Roadmap [ ] GenAI summary [ ] Weekly cron email reports [ ] Send update PRs [ ] Other package managers like PyPi [ ] Formatting/styling [ ] Loading spinner
    HTTP (deprecated)
  • pomdtr avatar
    love_letter
    @pomdtr
    <3 Val Town Val Town is my new favourite thing. Never heard of it ? Well, according to it's homepage, Val Town is a social website to write and deploy TypeScript. It's often introduced as zappier for developers , or twitter for code . The idea is simple: you write down a javascript snippet (named vals) in your browser, and it's instantly executed on a server. You can use it to: execute a function on a cron schedule host a small websites (this article hosted on Val Town ) send yourself emails ... But there is more to Val Town than this. If you take a look at the trending vals , you will quickly notice a pattern: most of the vals are about Val Town itself. People are using Val Town to extend Val Town, and it's fascinating to see what they come up with. I've built a few of these extensions myself, and this article is about one of them. Fixing the Val Town Search Val.town is built around the http import feature of Deno. Each val is a standalone module, that you can import in other vals. It works both for your own vals, and for the vals of other users. All of this is great, but there is one big issue: the search feature is terrible . It only works for exact text matches, and there is no way to set any filters based on username , creation_date , or anything else. This makes it really hard to find a val you are looking for, even if you are the one who wrote it. In any other platform, I would have just given up and moved on. But Val Town is different. I was confident that I could address this issue in userspace, without having to wait for the platform to implement it. Val Town allows you to run a val on a cron schedule, so I wrote a val that would fetch all the vals from the API, and store them as a sqlite table (did I mention that every user get it's own sqlite database ?). const createQuery = `CREATE TABLE IF NOT EXISTS vals ( ... );`; // run every hour export default function(interval: Interval) { // create the val table await options.sqlite.execute(createQuery); let url = "https://api.val.town/v1/search/vals?query=%20&limit=100"; // fetch all vals, and store them in the sqlite table while (true) { const resp = await fetch(url); if (!resp.ok) { throw new Error(await resp.text()); } const res = await resp.json(); const rows = res.data.map(valToRow); await insertRows(rows, options); if (!res.links.next) { break; } url = res.links.next; } } Once the val had finished running, I had a table with all the vals from the platform. I could now run queries on this table to find the vals I was looking for. import { sqlite } from "https://esm.town/v/std/sqlite" const res = await sqlite.execute(`SELECT * FROM vals WHERE author = 'pomdtr' && code LIKE '%search%'`); Of course I could have stopped there, but I wanted to go further. I wanted to share this table with other users, so they could run their own queries on it. Isolating the Vals Table There was still a challenge to overcome: the table was part of my account database, and I didn't want to give everyone access to it (there are some sensitive tables in there). One way to solve this issue would be to publish a stripped-down api that only allows a few predefined queries. But that would be boring, and I wanted to give users the full power of SQL. So I decided to isolate the val table in a separate account. There is a neat trick to achieve this on val.town: each val get's it own email address, and email sent to vals can be forwarded to your own email address. import { email as sendEmail } from "https://esm.town/v/std/email?v=11"; // triggered each time an email is sent to pomdtr.sqlite_email@valtown.email export default async function(email: Email) { // forward the email to my own email address await sendEmail({ subject: email.subject, html: email.html, text: email.text, }); } Since val.town account can be created with a val.email address, you can create an infinite number of accounts (and thus sqlite databases) using this trick. So say hello to the sqlite account , which is a separate account that only contains the vals table. After creating the account, I just needed to fork the cron val from my main account to get a copy of the vals table in the sqlite account. Publishing the Table The val.town stdlib provides a neat rpc function that provides a simple way to expose a function as an API. So I decided to write a simple val that would run a query on the table, and return the result. import { rpc } from "https://esm.town/v/std/rpc?v=5"; import { InStatement, sqlite } from "https://esm.town/v/std/sqlite?v=4"; // rpc create an server, exposed on the val http endpoint export default rpc(async (statement: InStatement) => { try { // run the query, then return the result as json return await sqlite.execute(statement); } catch (e) { throw new Response(e.message, { status: 500, }); } }); Everyone can now run queries on the table thanks a publically accessible endpoint (you even have write access to it, but I trust you to not mess with it). You can test it locally using curl and jq : echo "SELECT * FROM vals WHERE lower(name) LIKE '%feed%' and lower(name) like '%email%' LIMIT 100" | jq -R '{args: [.]} ' | xargs -0 -I {} curl -X POST "https://sqlite-execute.web.val.run" -H "Content-Type: application/json" -d {} | jq Of course I don't expect the average val.town user to use shell commands to run queries, so I also built an helper val to interact with the API, allowing users to run queries from their own vals. // only the import changed from the previous example import { db } from "https://esm.town/v/sqlite/db"; // this query will run on the `sqlite` account const res = await db.execute(`SELECT * FROM vals WHERE author = 'pomdtr' && code LIKE '%search%'`); I've seen some really cool vals built on top of this API. Someone even wrote down a guide to help users interact with it from the command-line! I hope that someone will build an search UI to interact with it at some point, but in the meantime, you can use a community-contributed sqlite web interface to run queries on top of the vals table. Val.town as a code-taking app As I've tried to show, having both a runtime, an editor and an API on the same platform is quite a magic formula. It's probably why val.town resonates so much with me. Using CodeSandbox, Stackblitz, Repl.it, Gitpod, Github Codespaces or Gitpod feels pretty much the same, everything still revolves around the same concept of a project/repository. They feel uninspired somehow, trying to replicate the desktop IDE experience in the browser, instead of embracing the new possibilities that the web platform offers. Val.town breaks this mold. I see it as a code-taking app, a place where I can just dump my ideas without worrying about the usual frictions of writing and deploying code.
    HTTP (deprecated)
  • pomdtr avatar
    lowdb_example
    @pomdtr
    Lowdb Example This val demonstrates the integration between valtown and lowdb . Read the Lodash section if you want to give superpowers to your DB.
    Script
  • maxm avatar
    forwarder
    @maxm
    Forked from stevekrouse/forwarder
    Email
  • lho avatar
    VALLE
    @lho
    Forked from janpaul123/VALLE
    HTTP (deprecated)
  • postpostscript avatar
    ReloadScript
    @postpostscript
    Forked from stevekrouse/ReloadScript
    Script
May 30, 2024