Avatar

pomdtr

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

Markdown to html (with github styling)

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>`;
}

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 slug for the val: <author>/<name>

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

HTTP Client

Attach a postman-like http client to your vals, with bookmarks and history support

2fa974c630d393c3b4f419de8a142a2f35a11ceebda42d195b07623889e5604e.png

Usage

Wrap your http handler in an the httpClient middleware.

Create valimport {httpClient} from "https://esm.town/v/pomdtr/http_client" export default httpClient((req) => { return new Response("Hello World!") })

The http client will be shown on the root.

Adding bookmarks

You might want to bookmark some requests you need often. You can do it by passing a bookmark list as a middleware option:

Create valimport {httpClient} from "https://esm.town/v/pomdtr/http_client" export default httpClient((req) => { return new Response("Hello World!") }, { bookmarks: [ { "label": "Dummy Request", "request": new Request("https://dummyjson.com/products") } ]})

ad7b95611871eb090ddadfa48f73d14692280e5389db1592403053d311aaa623.png

Customizing the client path

Create valimport {httpClient} from "https://esm.town/v/pomdtr/http_client" export default httpClient((req) => { return new Response("Hello World!") }, { path: "/http-client" })

TODO

  • fix syntax highlighting on successive request
  • allow to prefill the initial request
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
/** @jsxImportSource npm:preact **/
import { html } from "https://esm.town/v/stevekrouse/html?v=5";
import { Base64 } from "npm:js-base64";
import { render } from "npm:preact-render-to-string";
type SerializedRequest = {
url: string;
method: string;
headers: [string, string][];
body: string | undefined;
};
type Bookmark = {
label: string;
request: Request;
};
async function serializeRequest(request: Request): Promise<SerializedRequest> {
return {
url: request.url,
method: request.method,
headers: Array.from(request.headers.entries()),
body: request.body ? Base64.encode(await request.text()) : undefined,
};
}
const getBody = async (bookmarks: Bookmark[]) => {
const serializedBookmarks = JSON.stringify(
await Promise.all(bookmarks.map(async (bookmark) => ({
label: bookmark.label,
request: await serializeRequest(bookmark.request),
}))),
);
return render(
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>REST Client</title>
<link rel="icon" href="https://fav.farm/🌐" />
<script src="https://cdn.tailwindcss.com"></script>
<script
type="module"
src="https://cdn.jsdelivr.net/npm/code-mirror-web-component@0.0.20/dist/code-mirror.js"
>
</script>
</head>
<body class="bg-orange-100">
<div id="app" data-bookmarks={serializedBookmarks}></div>
<script type="module" src="https://esm.town/v/pomdtr/http_client_component"></script>
</body>
</html>,
);
};
export function httpClient(next: (req: Request) => Response | Promise<Response>, options: {
path: string;
bookmarks: Bookmark[];
} = { path: "/", bookmarks: [] }) {
return async (req: Request) => {
const url = new URL(req.url);
if (url.pathname == options.path) {
return html(await getBody(options.bookmarks));
}
return next(req);
};
}
export default async function(req: Request) {
const bookmarks = [{ label: "Dummy Request", request: new Request("https://dummyjson.com/products") }];
return html(await getBody(bookmarks));
}

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.

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.

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

Generate a sunbeam form from a val

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

Imgur

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
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
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
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.

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