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 { sqlite } from "https://esm.town/v/std/sqlite";
import { jsonCodec } from "npm:lexicodec";
import { KeyValuePair, ScanStorageArgs, Tuple, WriteOps } from "npm:tuple-database";
export class ValTupleStorage {
ready: Promise<void>;
private async init() {
await sqlite.execute(`
create table if not exists ${this.tableName} (
key text primary key,
value text
)`);
}
constructor(public tableName: string) {
this.ready = this.init();
}
async scan(args: ScanStorageArgs = {}) {
await this.ready;
// Bounds.
let start = args.gte ? jsonCodec.encode(args.gte) : undefined;
let startAfter: string | undefined = args.gt
? jsonCodec.encode(args.gt)
: undefined;
let end: string | undefined = args.lte ? jsonCodec.encode(args.lte) : undefined;
let endBefore: string | undefined = args.lt
? jsonCodec.encode(args.lt)
: undefined;
const sqlArgs = {
start,
startAfter,
end,
endBefore,
limit: args.limit,
};
const where = [
start ? "key >= :start" : undefined,
startAfter ? "key > :startAfter" : undefined,
end ? "key <= :end" : undefined,
endBefore ? "key < :endBefore" : undefined,
]
.filter(Boolean)
.join(" and ");
let sqlQuery = `select * from ${this.tableName}`;
if (where) {
sqlQuery += " where ";
sqlQuery += where;
}
sqlQuery += " order by key";
if (args.reverse) {
sqlQuery += " desc";
}
if (args.limit) {
sqlQuery += ` limit :limit`;
}
const results = await sqlite.execute({
sql: sqlQuery,
args: sqlArgs,
});
const rows = results.rows.map((row: any) => {
const obj: any = {};
row.forEach((value, index) => {
obj[results.columns[index]] = value;
});
return obj;
});
return rows.map(
({ key, value }) => ({
key: jsonCodec.decode(key) as Tuple,
value: JSON.parse(value),
} as KeyValuePair),
);
}
async write(writes: WriteOps) {
await this.ready;
const batch: any[] = [];
for (const { key, value } of writes.set || []) {
batch.push({
sql: `insert or replace into ${this.tableName} values (:key, :value)`,
args: {
key: jsonCodec.encode(key),
value: JSON.stringify(value),
},
});
}
for (const tuple of writes.remove || []) {
batch.push({
sql: `delete from ${this.tableName} where key = :key`,
args: { key: jsonCodec.encode(tuple) },
👆 This is a val. Vals are TypeScript snippets of code, written in the browser and run on our servers. Create scheduled functions, email yourself, and persist small pieces of data — all from the browser.