Avatar

nbbaier

Valin' up a storm
128 public vals
Joined January 12, 2023

SQLite Explorer

View and interact with your Val Town SQLite data. It's based off Steve's excellent SQLite Admin val, adding the ability to run SQLite queries directly in the interface. This new version has a revised UI and that's heavily inspired by LibSQL Studio by invisal. This is now more an SPA, with tables, queries and results showing up on the same page.

image.webp

Install

Install the latest stable version (v81) by forking this val:

Install Stable Release (v81)

Authentication

Login to your SQLite Explorer with password authentication with your Val Town API Token as the password.

Todos / Plans

  • improve error handling
  • improve table formatting
  • sticky table headers
  • add codemirror
  • add loading indication to the run button (initial version shipped)
  • add ability to favorite queries
  • add saving of last query run for a table (started)
  • add visible output for non-query statements
  • add schema viewing
  • add refresh to table list sidebar after CREATE/DROP/ALTER statements
  • add automatic execution of initial select query on double click
  • add views to the sidebar
  • add triggers to sidebar
  • add upload from SQL, CSV and JSON
  • add ability to connect to a non-val town Turso database
  • fix wonky sidebar separator height problem (thanks to @stevekrouse)
  • make result tables scrollable
  • add export to CSV, and JSON (CSV and JSON helper functions written in this val. Thanks to @pomdtr for merging the initial version!)
  • add listener for cmd+enter to submit query
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
/** @jsxImportSource https://esm.sh/hono@latest/jsx **/
import { modifyFetchHandler } from "https://esm.town/v/andreterron/codeOnValTown?v=50";
import { iframeHandler } from "https://esm.town/v/nbbaier/iframeHandler";
import { resetStyle } from "https://esm.town/v/nbbaier/resetStyle";
import { sqlToCSV, sqlToJSON } from "https://esm.town/v/nbbaier/sqliteExportHelpers";
import {
EditorSection,
MockTable,
Separator,
Table,
TablesList,
} from "https://esm.town/v/nbbaier/sqliteStudioComponents";
import { sqliteStyle } from "https://esm.town/v/nbbaier/sqliteStyle";
import { passwordAuth } from "https://esm.town/v/pomdtr/password_auth?v=70";
import { ResultSet, sqlite } from "https://esm.town/v/std/sqlite";
import { reloadOnSaveFetchMiddleware } from "https://esm.town/v/stevekrouse/reloadOnSave";
import { Hono } from "npm:hono";
import type { FC } from "npm:hono/jsx";
import { jsxRenderer } from "npm:hono/jsx-renderer";
import papa from "npm:papaparse";
const HTML: FC = ({ children }) => {
return (
<html>
<head>
<title>SQLite Explorer</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@300..700&family=Source+Sans+3:ital,wght@0,200..900;1,200..900&display=swap"
rel="stylesheet"
/>
<style
dangerouslySetInnerHTML={{ __html: resetStyle }}
/>
<style
dangerouslySetInnerHTML={{ __html: sqliteStyle }}
/>
<script src="https://unpkg.com/htmx.org@1.9.9/dist/htmx.min.js"></script>
<script src="https://unpkg.com/hyperscript.org@0.9.12"></script>
<script type="module" src="https://esm.town/v/nbbaier/resizeScript" />
<script type="module" src="https://esm.town/v/nbbaier/tableSelectScript" />
<script
type="module"
src="https://raw.esm.sh/code-mirror-web-component@0.0.20/dist/code-mirror.js"
>
</script>
</head>
<body _="
on keydown[event.metaKey and key is 'Enter'] log 'command + enter' then send submitForm to #sql-editor
">
<div class="root-container">
<header>
<h1>SQLite Explorer</h1>
</header>
{children}
</div>
<script type="module" src="https://esm.town/v/nbbaier/downloadScript" />
<script type="module" src="https://esm.town/v/nbbaier/enableButtonsScript" />
<script type="module" src="https://esm.town/v/nbbaier/getCodeScript" />
</body>
</html>
);
};
const app = new Hono();
app.use(
"*",
jsxRenderer(
({ children }) => {
return <HTML children={children} />;
},
{ docType: false },
),
);
app.get("/", async (c) => {
let data = await sqlite.batch(
[
`SELECT name FROM sqlite_schema WHERE type ='table' AND name NOT LIKE 'sqlite_%';`,
`SELECT name FROM sqlite_schema WHERE type ='view' AND name NOT LIKE 'sqlite_%';`,
],
);
let views = data[1].rows.map(view => {
return { type: "view", name: view[0] };
});
let tables = data[0].rows.map(view => {
return { type: "table", name: view[0] };
});
return c.render(
<main class="sidebar-layout">
<div class="sidebar">
<TablesList tables={[...tables, ...views]}></TablesList>

valToGH

A utility function for programmatically updating a GitHub repository with code retrieved from a Val.

NOTE: This function currently does not change the contents of a file if it is already present. I will however be adding that functionality.

Usage

Create valimport { valToGH } from 'https://esm.town/v/nbbaier/valToGH'; const repo = "yourGitHubUser/yourRepo"; const val = "valUser/valName"; // or vals = ["valUser/valName1","valUser/valName2", ...] const ghToken = Deno.env.get("yourGitHubToken"); const result = await valToGH(repo, val, ghToken); console.log(result);

Parameters

  • repo: The GitHub repository in the format {user}/{repo}.
  • val: A single repository in the format {user}/{val}.
  • ghToken: Your GitHub token for authentication (must have write permission to the target repo)

Options

  • branch: Optional target branch. Default is main.
  • prefix: Optional directory path prefix for each file.
  • message: Optional commit message. Default is the current date and time in the format yyyy-MM-dd T HH:mm:ss UTC.
  • ts: Optional flag to use a .ts extension for the committed file instead of the default .tsx.
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 { API_URL } from "https://esm.town/v/std/API_URL?v=5";
import { fetchJSON } from "https://esm.town/v/stevekrouse/fetchJSON?v=42";
import { Octokit } from "npm:@octokit/rest";
import { DateTime } from "npm:luxon";
async function getLatestCommit(ghUser: string, ghRepo: string, branch: string, client: Octokit)
{
const { data: refData } = await client.rest.git.getRef({
owner: ghUser,
repo: ghRepo,
ref: `heads/${branch}`,
});
return refData.object.sha;
}
async function createNewTree(
ghUser: string,
ghRepo: string,
tree: {
path?: string;
mode?: "100644" | "100755" | "040000" | "160000" | "120000";
type?: "tree" | "commit" | "blob";
sha?: string;
content?: string;
}[],
commitSHA: string,
client: Octokit,
) {
const {
data: { sha: currentTreeSHA },
} = await client.rest.git.createTree({
owner: ghUser,
repo: ghRepo,
tree,
base_tree: commitSHA,
parents: [commitSHA],
});
return currentTreeSHA;
}
async function createNewCommit(
ghUser: string,
ghRepo: string,
commitSHA: string,
currentTreeSHA: string,
message: string,
client: Octokit,
) {
const {
data: { sha: newCommitSHA },
} = await client.rest.git.createCommit({
owner: ghUser,
repo: ghRepo,
tree: currentTreeSHA,
message: message,
parents: [commitSHA],
});
return newCommitSHA;
}
async function updateBranchRef(
owner: string,
repo: string,
newCommitSHA: string,
branch: string = "main",
client: Octokit,
) {
const result = await client.rest.git.updateRef({
owner,
repo,
ref: `heads/${branch}`,
sha: newCommitSHA,
});
return result;
}
type Options = { branch?: string; prefix?: string; message?: string; ts?: boolean };
/**
* Commits a value or an array of values to a GitHub repository.
*
* @param {string} repo - The GitHub repository in the format of {owner}/{repo}.
* @param {string | string[]} val - A single value or array of values in the format of {user}/{val}.
* @param {string} ghToken - The GitHub token for authentication.
* @param {Options} [options] - Options to configure the commit.
* @param {string} [options.branch="main"] - The branch to commit to.
* @param {string} [options.prefix=""] - Prefix to be added to the file path.
* @param {string} [options.message="<current date and time> UTC"] - The commit message.
* @param {boolean} [options.ts=true] - Whether to use TypeScript or JavaScript.
* @returns {Promise<any>} - The result of the commit.
*/
export async function valToGH(
repo: string, // owner/repo
val: string | string[], // user/val
ghToken: string,
options?: Options,
) {
const client = new Octokit({ auth: ghToken });

Val Town AI Readme Writer

This val provides a class ReadmeWriter for generating readmes for vals with OpenAI. It can both draft readmes and update them directly

PRs welcome! See Todos below for some ideas I have.

Usage

To draft a readme for a given code, use the draftReadme method:

import { ReadmeWriter } from "https://esm.town/v/nbbaier/readmeGPT";

const readmeWriter = new ReadmeWriter({});
const val = "https://www.val.town/v/:username/:valname";

const generatedReadme = await readmeWriter.draftReadme(val);

To write and update a readme for a given code, use the writeReadme method:

import { ReadmeWriter } from "https://esm.town/v/nbbaier/readmeGPT";

const readmeWriter = new ReadmeWriter({});
const val = "https://www.val.town/v/:username/:valname";

const successMessage = await readmeWriter.writeReadme(val);

API Reference

Class: ReadmeWriter

The ReadmeWriter class represents a utility for generating and updating README files.

Constructor

Creates an instance of the ReadmeWriter class.

Parameters:
  • model (optional): The model to be used for generating the readme. Defaults to "gpt-3.5-turbo".
  • apiKey (optional): An OpenAI API key. Defaults to Deno.env.get("OPENAI_API_KEY").

Methods

  • draftReadme(val: string): Promise<string>: Generates a readme for the given val.

    • Parameters:

      • val: URL of the code repository.
    • Returns:

      • A promise that resolves to the generated readme.
  • writeReadme(val: string): Promise<string>: Generates and updates a readme for the given val.

    • Parameters:

      • val: URL of the code repository.
    • Returns:

      • A promise that resolves to a success message if the update is successful.

Todos

  • Additional options to pass to the OpenAI model
  • Ability to pass more instructions to the prompt to modify how the readme is constructed
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 WriterOptions } from "https://esm.town/v/nbbaier/WriterOptions";
import { fetch } from "https://esm.town/v/std/fetch?v=4";
import OpenAI, { type ClientOptions } from "npm:openai";
export class ReadmeWriter {
model: string;
openai: OpenAI;
apiKey: string;
valtownKey: string;
constructor(options: WriterOptions) {
const { model, ...openaiOptions } = options;
this.model = model ? model : "gpt-3.5-turbo";
this.openai = new OpenAI(openaiOptions);
this.valtownKey = Deno.env.get("valtown");
}
private createPrompt(code: string, userPrompt?: string) {
return `
You are an AI assistant that writes documentation for code. You output readmes
in GitHub flavored markdown. Usage sections should include a single code snippet
that a user can copy and paste. Never return anything other than documentation for
the code you are provided.
${userPrompt}
Take the below code and return a markdown readme:
${code}
`;
}
private async getVal(username: string, valName: string) {
try {
const res = await fetch(`https://api.val.town/v1/alias/${username}/${valName}`, {
method: "GET",
headers: {
"accept": "*/*",
"Content-Type": "application/json",
"Authorization": `Bearer ${this.valtownKey}`,
},
});
const { id, code } = await res.json();
return { id, code };
} catch (error) {
throw new Error("Error getting val code: " + error.message);
}
}
private async performOpenAICall(prompt: string) {
try {
const response = await this.openai.chat.completions.create({
messages: [{ role: "system", content: prompt }],
model: this.model,
});
if (!response.choices || response.choices.length === 0) {
throw new Error("No response from OpenAI");
}
const readme = response.choices[0].message?.content;
if (!readme) {
throw new Error("No readme returned by OpenAI. Try again.");
}
return readme;
} catch (error) {
throw new Error("Error generating readme: " + error.message);
}
}
private async updateReadme(id: string, readme: string) {
try {
const res = await fetch(`https://api.val.town/v1/vals/${id}`, {
method: "PUT",
headers: {
"accept": "*/*",
"Content-Type": "application/json",
"Authorization": `Bearer ${this.valtownKey}`,
},
body: JSON.stringify({ "readme": readme }),
});
return res.status;
} catch (error) {
throw new Error("Error updating readme: " + error.message);
}
}
private async processRequest(val: string, userPrompt?: string) {
const url = new URL(val);
const [, _, username, valName] = url.pathname.split("/");
const { id, code } = await this.getVal(username, valName);
const prompt = this.createPrompt(code, userPrompt);
const readme = await this.performOpenAICall(prompt);
return { id, readme };
}
async draftReadme(val: string, userPrompt?: string) {
const { readme } = await this.processRequest(val, userPrompt);
return 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
import { swaggerUI } from "npm:@hono/swagger-ui";
import { z } from "npm:@hono/zod-openapi";
import { createRoute, OpenAPIHono } from "npm:@hono/zod-openapi";
import { html } from "npm:hono/html";
const ParamsSchema = z.object({
id: z
.string()
.min(3)
.openapi({
param: {
name: "id",
in: "path",
},
example: "1212121",
}),
});
const UserSchema = z
.object({
id: z.string().openapi({
example: "123",
}),
name: z.string().openapi({
example: "John Doe",
}),
age: z.number().openapi({
example: 42,
}),
})
.openapi("User");
const route = createRoute({
method: "get",
path: "/users/{id}",
request: {
params: ParamsSchema,
},
responses: {
200: {
content: {
"application/json": {
schema: UserSchema,
},
},
description: "Retrieve the user",
},
},
});
const app = new OpenAPIHono();
app.get("/", c => c.html(html`Try going to <a href="/ui">/ui</a>`));
app.openapi(route, (c) => {
const { id } = c.req.valid("param");
return c.json({
id,
age: 20,
name: "Ultra-man",
});
});
// The OpenAPI documentation will be available at /doc
app.doc("/doc", {
openapi: "3.0.0",
info: {
version: "1.0.0",
title: "My API",
},
});
app.get("/ui", swaggerUI({ url: "/doc" }));
export default app.fetch;

SQLite Dump Util

A utility function that generates SQL statements to dump the data and schema of tables in a SQLite database.

Usage

This example specifically dumps the users table and logs the output:

Create valimport { sqliteDump } from "https://esm.town/v/nbbaier/sqliteDump"; const dump = await sqliteDump(["users"]); console.log(dump)

You can optionally specify a callback to handle the result. The example below dumps the users table and emails it using std/email.

Create valimport { email } from "https://esm.town/v/std/email"; import { sqliteDump } from "https://esm.town/v/nbbaier/sqliteDump"; await sqliteDump(["users"], async (res) => { await email({ text: res }); });

Function Signature

function sqliteDump(tables?: string[], callback?: ((result: string) => string | void | Promise<void>) | undefined): Promise<string | void>

Parameters

  • tables: (Optional) Array of table names to include in the dump. If not provided, all tables will be included.
  • callback: (Optional) An (potentially async) callback function to process the dump result. The callback receives the dump string as its argument.
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
import { email } from "https://esm.town/v/std/email";
import { sqlite } from "https://esm.town/v/std/sqlite";
function generateInsertStatements(
tableName: string,
columns: string[],
rows: string[][],
): string[] {
const columnNames = columns.join(", ");
let insertStatements = [];
for (const row of rows) {
const values = row.map(v => `'${typeof v === "string" ? v.replace(/'/g, "%27") : v}'`).join(", ");
const insertStatement = `INSERT INTO ${tableName} (${columnNames}) VALUES (${values});`;
insertStatements.push(insertStatement);
}
return insertStatements;
}
export async function sqliteDump(
tables?: string[],
callback?: (result: string) => string | void | Promise<void>,
) {
const dumpTables = tables !== undefined
? tables
: ((
await sqlite.execute(
`select name, type from sqlite_schema where type = 'table'`,
)
).rows.map((row) => row[0]) as string[]);
let statements: string[] = [];
for (const table of dumpTables) {
const schemaQuery = await sqlite.execute(
`SELECT name, sql FROM sqlite_schema WHERE name = '${table}'`,
);
const tableSchema = schemaQuery.rows[0][1] as string;
const createStatement = tableSchema.replace("CREATE TABLE", "CREATE TABLE IF NOT EXISTS");
const { columns, rows } = await sqlite.execute(`select * from ${table}`);
const insertStatements = generateInsertStatements(table, columns, rows);
statements.push(createStatement, ...insertStatements);
}
if (callback === undefined) {
callback = (result) => {
return result;
};
}
const result = `BEGIN TRANSACTION;
${statements.join(";\n").replace(";;", ";")}
COMMIT;`;
return callback(result);
}

Val Town Idea List Aggregator

This val serves as an aggregator for all vals with the tag // @vtIdeas in their code. To create your own idea list, simple fork this template val and follow the instructions there (instructions can also be found on the the aggregator homepage)

Feature Wish List

Here's some stuff that could be added to this val:

  • Pull in the actual lists of ideas from the various aggregated readmes and collect them in a (sortable?) table
  • A community list of val ideas, submittable to by form
  • Instead of a link list of links, make each Idea List a card, with revealable content
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
import findIdeaLists from "https://esm.town/v/nbbaier/findIdeaLists";
import { ideaList } from "https://esm.town/v/nbbaier/ideaList";
import { api } from "https://esm.town/v/pomdtr/api";
import { extractMetadata } from "https://esm.town/v/pomdtr/extractMetadata";
import { extractValInfo } from "https://esm.town/v/pomdtr/extractValInfo";
import { gfm } from "https://esm.town/v/pomdtr/gfm";
import { html } from "https://esm.town/v/stevekrouse/html?v=5";
import { Hono } from "npm:hono";
const app = new Hono();
const homepage = extractValInfo(import.meta.url);
const ideaLists = (await findIdeaLists()).map(val => {
const valName = val.name;
const author = val.author.username.replace("@", "");
const title = extractMetadata("title", val.code);
return { valName, author, title };
});
app.get("/", async c => {
const items = ideaLists.map(val => {
const { valName, author, title } = val;
return `- [${title}](/ideas/${author}.${valName}) by [${author}](https://www.val.town/u/${author})`;
});
const reservedVals = ideaLists.map(val => {
const { valName, author, title } = val;
return `[\`${valName}\`](https://www.val.town/v/${author}/${valName})`;
});
const markdown = `# Val Town Idea Aggregator
This val serves as an aggegrator for personal lists of ideas people have for what they
want to build on val town (or anything else having to do with Val Town, I suppose).
## Ideas
${items.join("\n")}
## Starting your own list
To create your own collection and add to this list, do the following:
1. Fork [this template val](https://www.val.town/v/nbbaier/vtIdeasTemplate)
2. Add the following metadata to the top of your freshly forked val's code
\`\`\`
// @vtIdeas
// @title <insert a nice title>
\`\`\`
3. Change your new val's name (see the list below for names already in use)
4. Add your ideas to the readme!
**Note:** Make your val public when you're ready for it to be listed here.
### Reserved Val Names
The following val names are already in use, and therefore should **not** be used: ${reservedVals.join(", ")}
## Feature Wish List
Here's some stuff that could be added to this site (sure there's a lot more!). Feel free to help if you can!
- Pull in the actual lists of ideas from the various aggregated readmes and collect them in a (sortable?) table
- A community list of val ideas, submittable to by form
`;
return c.html(await gfm(markdown, { title: "Val Town Idea Aggregator" }));
});
app.get("/ideas/:name", async (c) => {
const name = c.req.param("name");
return c.html(await ideaList(name.split(".")[0], name.split(".")[1]));
});
app.get("/ideas/:name/edit", async (c) => {
const name = c.req.param("name");
const editURL = `https://val.town/v/${name.split(".")[0]}/${name.split(".")[1]}`;
return c.redirect(editURL);
});
export const blog = app.fetch;

Create an API from a lowdb blob

This val exports a function that takes a lowdb instance and returns a Hono router that can be used to interact with the data. This is the beginning of an implementation of something like json-server for Val Town.

The resulting server also comes with a frontend at /. The code for the frontend can be found here.

See this val for an example.

Things I'd like to implement

  • All HTTP methods
  • Custom route definitions (like in json-server)
  • Custom frontends
  • Filtering, sorting, pagination, etc
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
/** @jsxImportSource https://esm.sh/hono@3.9.2/jsx **/
import { Homepage } from "https://esm.town/v/nbbaier/dbToAPIFrontend";
import { checkResource, getResources, type Options, validate } from "https://esm.town/v/nbbaier/dbToApiHelpers";
import { Hono } from "npm:hono";
// TODO: implement options
export async function createServer(db, options: Options & { auth?: { username: string; password: string } } = {}) {
const app = new Hono();
if (options.auth) {
app.use(
"*",
);
}
// Make sure our db contains "data"
if (!validate(db)) {
app.get("/", c => c.text("Wrong data shape"));
return app;
}
// Extract information about the resources in the db
const resources = getResources(db);
// Get / => returns home
app.get("/", c => c.html(<Homepage resources={resources} />));
// GET /db => returns all db data
app.get("/db", c => c.json(db.data));
// GET /:resource => if :resource is valid, returns db.data[resource]
app.get("/:resource", c => {
const { resource } = c.req.param();
const queries = c.req.queries();
if (!checkResource(resource, resources)) {
return c.text(`The requested resource (${resource}) is not available`);
}
let q;
let chain = db.data[resource];
if (queries.q) {
q = queries.q[0];
chain = chain.filter(obj => {
obj.name === q;
});
}
return c.json({ data: chain });
});
// GET /:resource/:id => if :resource is valid, returns db.data[resource]
app.get("/:resource/:id", c => {
const { resource, id } = c.req.param();
if (!checkResource(resource, resources)) {
return c.text(`The requested resource (${resource}) is not available`);
}
const items = db.data[resource] as { id: number }[];
return c.json(items.find(item => item.id === parseInt(id)));
});
// TODO POST, PUT, PATCH, DELETE /:resource
app.on(["POST", "PATCH", "PUT", "DELETE"], "/:resource", (c) => {
const { method } = c.req;
const { resource } = c.req.param();
if (!checkResource(resource, resources)) {
return c.text(`The requested resource (${resource}) is not available`);
}
return c.text(`${method} /${resource}`);
});
return app;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { createVal, ValResponse } from "https://esm.town/v/nbbaier/createVal";
import { fetchText } from "https://esm.town/v/stevekrouse/fetchText?v=6";
const code = await fetchText("https://raw.githubusercontent.com/nbbaier/vt-template-test/main/index.tsx");
let val: ValResponse;
try {
val = await createVal({
token: Deno.env.get("valtown"),
code,
privacy: "public",
});
console.log(`https://val.town/v/${val.author.username}/${val.name}`);
} catch (e) {
console.log(e);
}

The beginnings of a val town implementation of the approach to declarative sqlite migrations laid out in this post: Simple declarative schema migration for SQLite

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
import { createSqlite } from "https://esm.town/v/postpostscript/sqliteWasm?v=148";
import { sqlite } from "https://esm.town/v/std/sqlite?v=6";
import _ from "npm:lodash";
// The schema that we want to end up with
const schema = `
create table sales (product TEXT, year INTEGER, income INTEGER, type TEXT);
create table inventory (product TEXT, count INTEGER);
`;
// Create a new db and create the "pristine" tables from the schema above
const pristine = createSqlite();
await pristine.batch(schema.trim().split("\n"));
// Create an object that contains the table names and sql of our target state
const pristineTables = Object.fromEntries(
(await pristine.execute(`SELECT name, sql FROM sqlite_schema
WHERE type = 'table' AND name != 'sqlite_sequence'`)).rows as unknown as string[][],
);
// Create an object that gets the names and sql for the tables in our current db state
const currentTables = Object.fromEntries(
(await sqlite.execute(`SELECT name, sql FROM sqlite_schema
WHERE type = 'table' AND name != 'sqlite_sequence'`)).rows as unknown as string[][],
);
// Figure out which tables are changed and which tables are new
const newTables = _.difference(_.keys(pristineTables), _.keys(currentTables));
const changedTables = _.intersection(_.keys(pristineTables), _.keys(currentTables));
// Log the create statement for the new tables
for (const table of newTables) {
const createTableSql = pristineTables[table];
console.log(createTableSql);
}
// Log the create statement for the changed table with the suffix _new
for (const table of changedTables) {
const regex = new RegExp(` ${table} `);
const createTableSql = pristineTables[table].replace(regex, ` ${table}_new `);
console.log(createTableSql);
}
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
import type { ChatCompletion, ChatCompletionCreateParamsNonStreaming, Message } from "npm:@types/openai";
// a test comment for vt-backup
// another test comment for vt-backup
async function getOpenAI() {
// if you don't have a key, use our std library version
if (Deno.env.get("OPENAI_API_KEY") === undefined) {
const { OpenAI } = await import("https://esm.town/v/std/openai");
return new OpenAI();
} else {
const { OpenAI } = await import("npm:openai");
return new OpenAI();
}
}
export function startChat(chatOptions: Omit<ChatCompletionCreateParamsNonStreaming, "messages"> & { system: string } = {
max_tokens: 30,
model: "gpt-3.5-turbo",
system: "You are a helpful assistant.",
}) {
const { system, ...options } = chatOptions;
return async function gpt(strings, ...values) {
const openai = await getOpenAI();
const input = String.raw({ raw: strings }, ...values);
const messages = [{ role: "system", content: system }, { role: "user", content: input }];
const createParams: ChatCompletionCreateParamsNonStreaming = {
...options,
messages,
};
const completion = await openai.chat.completions.create(createParams);
return { ...completion, content: completion.choices[0].message.content };
};
}