Avatar

@std

15 likes12 public vals
Joined August 17, 2023
The Val Town Standard Library

Deprecated in favor of std/sqlite (also powered by Turso)

std/turso was the initial version of our integration with Turso. It was so popular, we rebuilt it to be faster and easier to use: std/sqlite.

turso (2).png

Turso is a serverless SQLite platform designed for the edge. It runs libSQL, their open contribution fork of SQLite.

Every Val Town user automatically gets their own Turso SQLite database! It's great for >100kb data (ie bigger than a val) or when you need SQL: relations, ACID transactions, etc.

Storage used in Turso will count against your Val Town total storage (10mb for free users; 1gb for Pro users). Contact us if you'd need more – it should be no problem!

Getting started

This val uses our public key auth scheme.

  1. Generate your keypair
  2. On your publicKey click the lock icon🔒 to change the permissions to Unlisted.
  3. Fork this helper function replacing stevekrouse with your own username
  4. Try out some queries!

Usage

This val returns a Turso SDK's Client, which supports execute, batch, and transaction.

await @me.turso().execute(`create table blobs(
  key text unique, 
  value text
)`)

More example usage

Architecture

This @std.turso function is the client or SDK to @std.tursoAPI, which acts as a "proxy" to Turso. It handles authentication, creates databases, and forwards on your SQL queries. You can get lower latency (~200ms vs ~800ms), more storage, databases, CLI & API access by having your own Turso account.

Readme
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { runValAPIAuth } from "https://esm.town/v/stevekrouse/runValAPIAuth";
// grab the types off turso's client without importing it
let tursoImport = () => import("https://esm.sh/@libsql/client/web");
type createClient = Awaited<ReturnType<typeof tursoImport>>["createClient"];
export function turso(keys, handle?) {
let val = "@std.tursoAPI";
let f = (method) => (...args) =>
runValAPIAuth({
val,
args: [method, args],
keys,
handle,
});
return {
execute: f("execute") as ReturnType<createClient>["execute"],
batch: f("batch") as ReturnType<createClient>["batch"],
transaction: f("transaction") as ReturnType<createClient>["transaction"],
};
}

Val Town Blob Storage - https://docs.val.town/std/blob

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
import { API_URL } from "https://esm.town/v/std/API_URL";
import { ValTownBlobError } from "https://esm.town/v/std/ValTownBlobError";
import { ValTownBlobNotFoundError } from "https://esm.town/v/std/ValTownBlobNotFoundError";
export const blob = {
get: get,
set: set,
copy: copy,
move: move,
list: list,
delete: delete_,
getJSON: getJSON,
setJSON: setJSON,
};
async function list(prefix?: string): Promise<{ key: string; size: number; lastModified: string }[]> {
let querystring = prefix ? `?prefix=${encodeURIComponent(prefix)}` : "";
const res = await fetch(`${API_URL}/v1/blob${querystring}`, {
headers: {
Authorization: `Bearer ${Deno.env.get("valtown")}`,
},
});
if (!res.ok) {
const body = await res.text();
throw new ValTownBlobError(body ? body : "Error listing blobs");
}
return res.json();
}
async function delete_(key: string) {
const res = await fetch(`${API_URL}/v1/blob/${encodeURIComponent(key)}`, {
method: "DELETE",
headers: {
Authorization: `Bearer ${Deno.env.get("valtown")}`,
},
});
if (!res.ok) {
const body = await res.text();
throw new ValTownBlobError(body ? body : "Error deleting blob");
}
}
async function get(key: string) {
const res = await fetch(`${API_URL}/v1/blob/${encodeURIComponent(key)}`, {
headers: {
Authorization: `Bearer ${Deno.env.get("valtown")}`,
},
});
if (res.status === 404) {
throw new ValTownBlobNotFoundError();
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
import { API_URL } from "https://esm.town/v/std/API_URL";
import { parseSendGridEmail } from "https://esm.town/v/stevekrouse/parseSendGridEmail?v=8";
import { parseSendGridEmails } from "https://esm.town/v/stevekrouse/parseSendGridEmails?v=10";
import process from "node:process";
export const email = async (data: {
to?: (IAddress | string)[] | IAddress | string;
from?: IAddress | string;
cc?: (IAddress | string)[] | IAddress | string;
bcc?: (IAddress | string)[] | IAddress | string;
subject?: string;
replyTo?: (IAddress | string)[] | IAddress | string;
html?: string;
text?: string;
attachments?: AttachmentData[];
}) => {
let result = await fetch(
`${API_URL}/v1/email`,
{
method: "POST",
body: JSON.stringify({
to: parseSendGridEmails(data.to),
from: data.from && parseSendGridEmail(data.from),
cc: parseSendGridEmails(data.cc),
bcc: parseSendGridEmails(data.bcc),
replyToList: parseSendGridEmails(data.replyTo),
subject: data.subject,
html: data.html,
text: data.text,
attachments: data.attachments,
}),
headers: {
authorization: "Bearer " + process.env.valtown,
},
},
).then(r => r.json());
if (result?.message !== "Email accepted to be sent") {
let message = result?.message ?? result?.error ?? JSON.stringify(result);
throw new Error("Val Town Email Error: " + message);
}
return result;
};
interface IAddress {
email: string;
name?: string;
}
interface AttachmentData {
content: string;
filename: string;
type?: string;
Readme
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import process from "node:process";
import { API_URL } from "https://esm.town/v/std/API_URL";
import { rawFetch } from "https://esm.town/v/std/rawFetch";
export async function fetch(input: string | URL, init?: RequestInit) {
let query = new URLSearchParams({
url: input.toString(),
});
return rawFetch(`${API_URL}/v1/fetch?${query}`, {
...init,
headers: {
"X-Valtown-Authorization": `Bearer ${process.env.valtown}`,
...init?.headers ?? {},
},
});
}
Fork
1
export default () => Response.json(Deno.version);
1
2
3
4
5
6
7
8
9
10
11
12
13
import { blob } from "https://esm.town/v/std/blob";
const KEY = "blob_counter";
const oldState = await blob.getJSON(KEY) ?? 0;
console.log("Old value", oldState);
await blob.setJSON(KEY, oldState + 1);
const newState = await blob.getJSON(KEY);
console.log("New value", newState);
export let docsBlobCounterDemo = undefined;
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
import { API_URL } from "https://esm.town/v/std/API_URL";
import { LibsqlError, type ResultSet, type TransactionMode } from "npm:@libsql/client";
import { z } from "npm:zod";
export const sqlite = {
execute,
batch,
};
// ------------
// Functions
// ------------
async function execute(statement: InStatement): Promise<ResultSet> {
const res = await fetch(`${API_URL}/v1/sqlite/execute`, {
method: "POST",
headers: {
Authorization: `Bearer ${Deno.env.get("valtown")}`,
},
body: JSON.stringify({ statement }),
});
if (!res.ok) {
throw createResError(await res.text());
}
return res.json();
}
async function batch(statements: InStatement[], mode?: TransactionMode): Promise<ResultSet[]> {
const res = await fetch(`${API_URL}/v1/sqlite/batch`, {
method: "POST",
headers: {
Authorization: `Bearer ${Deno.env.get("valtown")}`,
},
body: JSON.stringify({ statements, mode }),
});
if (!res.ok) {
throw createResError(await res.text());
}
return res.json();
}
function createResError(body: string) {
try {
const e = zLibsqlError.parse(JSON.parse(body));
// e.message already contains the code, and LibsqlError adds the
// code to the beginning, so we remove it here
const msg = e.message.replace(e.code, "").replace(/^:\s+/, "");
return new LibsqlError(msg, e.code, e.rawCode);
} catch (_) {
// Failed to parse libsql error

Returns what version of Val Town you're currently running.

  • v2 – currently on val.town
  • v3-staging – v3.val.town
  • v3 – when we deploy v3 to val.town
Readme
1
export let runtime = (): "v2" | "v3" | "v3-staging" => "v3";

Val Town API URL

When Val Town code is run on Val Town servers we use a local URL so we can save time by skipping a roundtrip to the public internet. However, if you want to run your vals that use our API, ie std library vals, locally, you'll want to use our public API's URL, https://api.val.town. We recommend importing and using std/API_URL whenever you use our API so that you are always using the most efficient route.

Example Usage

import { API_URL } from "https://esm.town/v/std/API_URL";

const response = await fetch(`${API_URL}/v1/me`, {
    headers: {
      Authorization: `Bearer ${Deno.env.get("valtown")}`,
      Accept: "application/json",
    },
});
const data = await response.json();
console.log(data)
Readme
1
2
3
4
5
6
7
8
function envOrUndefined(key: string): string | undefined {
// try/catch prevents crashes if the script doesn't have env access
try {
return Deno.env.get("VALTOWN_API_URL");
} catch {}
}
export const API_URL = envOrUndefined("VALTOWN_API_URL") ?? "https://api.val.town";
1
2
3
4
5
6
7
8
9
10
11
import { fetch } from "https://esm.town/v/std/fetch";
import { API_URL } from "https://esm.town/v/std/API_URL";
export function runVal(name, ...args) {
return fetch(`${API_URL}/v1/run/${name.replace("@", "")}`, {
method: "POST",
body: JSON.stringify({
args,
}),
}).then((r) => r.json());
}