Avatar

@pomdtr

153 likes249 public vals
Joined June 14, 2023
I mainly enjoy tinkering with the val.town api: - VS Code integration: https://github.com/pomdtr/valtown-vscode - CLI: https://github.com/pomdtr/vt

Markdown to html (with github styling)

Readme
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import { micromark } from "npm:micromark";
import { frontmatter, frontmatterHtml } from "npm:micromark-extension-frontmatter";
import { gfm as gfmMarkdown, gfmHtml } from "npm:micromark-extension-gfm";
export function html(markdown: string) {
return micromark(markdown, {
extensions: [gfmMarkdown(), frontmatter()],
htmlExtensions: [gfmHtml(), frontmatterHtml()],
});
}
export async function gfm(markdown: string, options?: { title?: string; favicon?: string | URL }) {
const body = await html(markdown);
const faviconURL = new URL(options?.favicon ?? "📝", "https://fav.farm");
return `
<!DOCTYPE html>
<html>
<head>
<title>${options?.title || "Article"}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="${faviconURL.href}" rel="icon">
<link href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.4.0/github-markdown.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/prismjs@v1.29.0/themes/prism.css" rel="stylesheet">
<style>
.markdown-body {
box-sizing: border-box;
min-width: 200px;
max-width: 980px;
margin: 0 auto;
padding: 45px;
}
@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}
</style>
</head>
<body class="markdown-body">
${body}
<script src="https://cdn.jsdelivr.net/npm/prismjs@v1.29.0/components/prism-core.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@v1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
</body>
</html>`;
}

SQL Template Tag

Port of blakeembrey/sql-template-tag for usage in val.town.

Usage

import { sqlite } from "https://esm.town/v/std/sqlite"
import { sql, zip } from "https://esm.town/v/pomdtr/sql"

const query = sql`SELECT * FROM books WHERE author = ${author}`;
console.log(query.sql) // => "SELECT * FROM books WHERE author = ?"
console.log(query.args) // => [author]
const res = await sqlite.execute(query)
console.table(zip(res))

For advanced usage (ex: nesting queries), refer to the project readme.

Readme
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import type { InValue, sqlite } from "https://esm.town/v/std/sqlite";
import { zip as zip2 } from "npm:lodash-es@4.17.21";
/**
* Supported value or SQL instance.
*/
type RawValue = InValue | Sql;
/**
* A SQL instance can be nested within each other to build SQL strings.
*/
class Sql {
readonly values: InValue[];
readonly strings: string[];
constructor(rawStrings: readonly string[], rawValues: readonly RawValue[]) {
if (rawStrings.length - 1 !== rawValues.length) {
if (rawStrings.length === 0) {
throw new TypeError("Expected at least 1 string");
}
throw new TypeError(
`Expected ${rawStrings.length} strings to have ${rawStrings.length - 1} values`,
);
}
const valuesLength = rawValues.reduce<number>(
(len, value) => len + (value instanceof Sql ? value.values.length : 1),
0,
);
this.values = new Array(valuesLength);
this.strings = new Array(valuesLength + 1);
this.strings[0] = rawStrings[0];
// Iterate over raw values, strings, and children. The value is always
// positioned between two strings, e.g. `index + 1`.
let i = 0,
pos = 0;
while (i < rawValues.length) {
const child = rawValues[i++];
const rawString = rawStrings[i];
// Check for nested `sql` queries.
if (child instanceof Sql) {
// Append child prefix text to current string.
this.strings[pos] += child.strings[0];
let childIndex = 0;
while (childIndex < child.values.length) {
this.values[pos++] = child.values[childIndex++];
this.strings[pos] = child.strings[childIndex];
}
// Append raw string to current string.
this.strings[pos] += rawString;
} else {
this.values[pos++] = child;
this.strings[pos] = rawString;
}
}
}
get sql() {
const len = this.strings.length;
let i = 1;
let value = this.strings[0];
while (i < len) value += `?${this.strings[i++]}`;
return value;
}
get args() {
return this.values;
}
toJSON() {
return {
sql: this.sql,
args: this.values,
};
}
}
/**
* Create a SQL query for a list of values.
*/
export function join(
values: readonly RawValue[],
separator = ",",
prefix = "",
suffix = "",
) {
if (values.length === 0) {
throw new TypeError(
"Expected `join([])` to be called with an array of multiple elements, but got an empty array",
);
}
return new Sql(

Val Town Adapter for lowdb

@std/blob is used as a lowdb sync.

See @pomdtr/lowdb_example for example usage.

Readme
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { blob } from "https://esm.town/v/std/blob?v=10";
import { Low } from "npm:lowdb@7.0.1";
class Blob<Data> {
readonly key: string;
constructor(key: string) {
this.key = key;
}
async read(): Promise<Data> {
const data = await blob.getJSON(this.key);
return data;
}
async write(data: Data) {
await blob.setJSON(this.key, data);
}
}
async function BlobPreset<Data>(key: string, initialData: Data) {
const db = new Low(new Blob<Data>(key), initialData);
await db.read();
return db;
}
export { Blob, BlobPreset };

Extract vals infos (author, name, version) from a val url (either from esm.town or val.town).

Example usage:

const {owner, name} = extractValInfo(import.meta.url)

Also returns a unique key for the val: @<author>/<name>

Readme
1
2
3
4
5
6
7
8
9
10
11
12
export function extractValInfo(url: string | URL) {
const { pathname, search } = new URL(url);
const [author, name] = pathname.split("/").slice(2);
const version = new URLSearchParams(search).get("v");
const slug = `${author}/${name}`;
const htmlUrl = `https://val.town/v/${author}/${name}`;
const rawUrl = `https://esm.town/v/${author}/${name}`;
const embedUrl = `https://val.town/embed/${author}/${name}`;
const httpEndpoint = `https://${author}-${name}.web.val.run`;
return { author, name, version, slug, htmlUrl, rawUrl, httpEndpoint, embedUrl };
}

Generate a sunbeam form from a val

sunbeam fetch 'https://pomdtr-valform.express.val.run/pomdtr.add' | sunbeam

Imgur

Readme
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import { extractArgs } from "https://esm.town/v/pomdtr/extractArgs";
import { fetchJSON } from "https://esm.town/v/stevekrouse/fetchJSON?v=41";
export async function valForm(req: express.Request, resp: express.Response) {
try {
const [author, name] = req.path.slice(1).split(".");
const { code } = await fetchJSON(
`https://api.val.town/v1/alias/${author}/${name}`,
);
const args = await extractArgs(code);
if (req.method == "POST") {
const payload = {
args: args.map((arg) =>
arg.type == "number"
? parseInt(req.body[arg.name])
: req.body[arg.name]
),
};
const res = await fetchJSON(
`https://api.val.town/v1/run/${author}.${name}`,
{
method: "POST",
body: JSON.stringify(payload),
},
);
resp.json(res);
return;
}
const inputs = [];
for (const arg of args) {
switch (arg.type) {
case "boolean":
inputs.push({ name: arg.name, title: arg.name, type: "checkbox" });
break;
case "number":
inputs.push({ name: arg.name, title: arg.name, type: "textfield" });
break;
case "string":
inputs.push({ name: arg.name, title: arg.name, type: "textfield" });
break;
default:
resp.status(500);
resp.send(`Unsupported arg type: ${arg.type}`);
return;
}
}
return resp.json({
type: "form",
title: `${author}.${name}`,
submitAction: {
type: "fetch",
request: {
url: `https://pomdtr-valForm.express.val.run/${author}.${name}`,
headers: {
"Content-Type": "application/json",
},
method: "POST",
body: "{{inputs:json}}",
},
inputs,
},
});
}
catch (e) {
resp.status(500);
resp.send(`An error occured: ${e}`);
}
}

CodeMirror Web Component

Available Attributes

  • language
  • readonly
  • code
  • theme

API

You can access the code using the code property:

Create valdocument.getElementById("editor").code
Readme
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const body = `<html>
<head>
<script
type="module"
src="https://raw.esm.sh/code-mirror-web-component@0.0.20/dist/code-mirror.js"
></script>
<script type="module">
document.getElementById("editor").addEventListener("code-change", (e) => {
console.log(e.detail.code);
});
</script>
</head>
<body style="margin: 0px; height: 100vh;">
<code-mirror id="editor" theme="dracula" language="sql" code="SELECT * FROM sqlite;"></code-mirror>
</body>
</html>
`;
export default function() {
return new Response(body, {
headers: {
"Content-Type": "text/html",
},
});
}

Val Town CDN

If you only want to get the source of a val, you should use https://esm.town/ instead.

Usage

curl https://pomdtr-cdn.web.val.run/<author>/<name>.<extension>[?v=<version>]

To see the code of this val, use https://pomdtr-cdn.web.val.run/pomdtr/cdn.ts

Examples

Fetching the val code

$  curl https://pomdtr-cdn.web.val.run/pomdtr/add.tsx

You can also use js, jsx and ts extension (only the content-type change, there is no transpilation).

Fetching private val

Pass an api token as an username

$ curl "https://<token>@pomdtr-cdn.web.val.run/pomdtr/privateVal.ts"

Fetching the val README

$ curl https://pomdtr-cdn.web.val.run/pomdtr/add.md

Getting an image

$ curl https://pomdtr-cdn.web.val.run/pomdtr/add.png

Fetching a specific version of a val

$ curl https://pomdtr-cdn.web.val.run/pomdtr/cdn.ts?v=66

You need to be authenticated to use this method.

Fetching the val metadata

$ curl https://pomdtr-cdn.web.val.run/pomdtr/add.json
Readme
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { extractValInfo } from "https://esm.town/v/pomdtr/extractValInfo";
import { parseAuthorizationHeader } from "https://esm.town/v/pomdtr/parseAuthorizationHeader";
import { readmeToHtmlResponse } from "https://esm.town/v/pomdtr/readme";
async function createScreenshot(code: string) {
const apiUrl = "https://sourcecodeshots.com/api/image";
const resp = await fetch(apiUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ code }),
});
if (!resp.ok) {
throw new Error(await resp.text());
}
return resp.blob();
}
export default async function(req: Request) {
const url = new URL(req.url);
const [author, file] = url.pathname.slice(1).split("/");
if (!author || !file) {
const { author, name } = extractValInfo(import.meta.url);
return readmeToHtmlResponse(author, name);
}
const [name, extension] = file.split(".");
const headers = {};
if (req.headers.get("Authorization")) {
const auth = parseAuthorizationHeader(
req.headers.get("Authorization"),
);
if (auth.type == "basic") {
headers["Authorization"] = `Bearer ${auth.username}`;
}
else {
headers["Authorization"] = req.headers.get("Authorization");
}
}
const resp = await fetch(`https://api.val.town/v1/alias/${author}/${name}`, {
headers,
});
if (!resp.ok) {
return resp;
}
let val = await resp.json();
if (url.searchParams.has("v")) {
const version = url.searchParams.get("v");
const resp = await fetch(
`https://api.val.town/v1/vals/${val.id}/versions/${version}`,
{ headers },
);
if (!resp.ok) {
return resp;
}
val = await resp.json();
}
if (extension == "json") {
return new Response(JSON.stringify(val, null, 2), {
headers: { "Content-Type": "text/json" },
});
}
if (extension == "md") {
return new Response(val.readme, {
headers: { "content-type": "text/markdown" },
});
}
if (extension == "js") {
return new Response(val.code, {
headers: {
"Content-Type": "text/javascript",
},
});
}
if (extension == "ts") {
return new Response(val.code, {
headers: {
"Content-Type": "text/javascript",
},
});
}
if (extension == "jsx") {
return new Response(val.code, {
headers: {
"Content-Type": "text/jsx",
},
});
}
if (extension == "tsx") {
return new Response(val.code, {
headers: {
"Content-Type": "text/tsx",

Test Explorer

3c584b1ccdd2115d031f49d69f2ac68ea33c450bbde9fdd462036dc2cbf8e907.png

Click on the play button next to list items to run them.

Usage

  1. Fork this val

  2. Create new tests in any of vals (and export them) (see @pomdtr/example_test)

Create valimport { assertEquals } from "https://deno.land/std@0.216.0/assert/mod.ts"; import { Test } from "https://esm.town/v/<account>/test_explorer"; export const exampleTestSuccess = new Test(() => { assertEquals(1 + 1, 2); }); export const exampleTestFailure = new Test(() => { assertEquals(1 + 1, 3); });
  1. Go to https://<account>-test_explorer.web.val.run to run your test
    • click on the val name to go to the val the tests are originating from
    • click on the test name to run it

ℹ️ You probably want to protect your test explorer behind an authentication middleware like @pomdtr/basicAuth

Discovery mechanism

In order to define a test, the user need to import the Test class from https://val.town/v/<account>/Test. So we can use the api to search for vals containing the https://val.town/v/<account>/Test string to locate the vals containing tests.

Next, we need to extract the tests from the val exports. We use exported instanceof Test to filter them (at some point we will probably use static analysis for this).

TODO

  • persist test results in sqlite
  • Improve styling (help welcome ❤️)
  • View logs in a modal
  • Batch http requests
Readme
1
2
3
4
5
import { router } from "https://esm.town/v/pomdtr/test_explorer_router";
export { Test } from "https://esm.town/v/pomdtr/test";
export default router.fetch;

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);

To authenticate, paste an api token in the password prompt.

Readme
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
function extractToken(authorization) {
const parts = authorization.split(" ");
if (parts[0] == "Bearer") {
return parts[1];
}
if (parts[0] != "Basic") {
return "";
}
const plainAuth = atob(parts[1]);
const credentials = plainAuth.split(":");
// allow `curl <token>@xxx.web.val.run`
return credentials[1] || credentials[0];
}
type User = {
id: string;
};
async function fetchUser(token: string): Promise<User> {
const resp = await fetch("https://api.val.town/v1/me", {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (resp.status !== 200) {
throw new Error("Could not fetch user");
}
return resp.json();
}
async function isTokenValid(token) {
try {
const [visitor, owner] = await Promise.all([fetchUser(token), fetchUser(Deno.env.get("valtown"))]);
return visitor.id == owner.id;
} catch (err) {
return false;
}
}
async function isRequestAuthenticated(req) {
if (!req.headers.has("authorization")) {
return false;
}
const token = extractToken(req.headers.get("authorization"));
return isTokenValid(token);
}
export function basicAuth(next: (Request) => Response | Promise<Response>) {
return async (req: Request) => {
if (req.headers.get("referer") == "https://www.val.town/") {
return new Response(
`Basic Auth is disabled in Val Town iframes.
<a href="/" target="blank_">Open in a new tab.</a>`,
{
status: 400,
headers: {
"Content-type": "text/html",
},
},
);
}
const isAuth = await isRequestAuthenticated(req);
if (!isAuth) {
return new Response("Unauthorized", {
status: 401,
headers: {
"WWW-Authenticate": "Basic",
},
});
}
return next(req);
};
}

Lowdb Example

This val demonstrates the integration between valtown and lowdb.

Read the Lodash section if you want to give superpowers to your DB.

Readme
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { extractValInfo } from "https://esm.town/v/pomdtr/extractValInfo";
import { BlobPreset } from "https://esm.town/v/pomdtr/lowdb";
type Data = {
posts: {
id: number;
title: string;
}[];
};
// use the val name as a key ("@pomdtr/testLowDB")
const { slug } = extractValInfo(import.meta.url);
// Read or initialize DB from blob
const db = await BlobPreset<Data>(slug, { posts: [] });
// Edit db content using plain JavaScript (updates are automatically persisted)
await db.update(({ posts }) => posts.push({ id: posts.length + 1, title: "lowdb is awesome" }));
console.log(db.data);