Avatar

postpostscript

he/him
57 public vals
Joined February 22, 2024
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export type ShaAlgorithm = "SHA-256" | "SHA-512";
export async function sha(
input: string | BufferSource | AsyncIterable<BufferSource> | Iterable<BufferSource>,
algo: ShaAlgorithm = "SHA-256",
) {
const [{ crypto }, { encodeHex }] = await Promise.all([
import("https://deno.land/std@0.207.0/crypto/mod.ts"),
import("https://deno.land/std@0.207.0/encoding/hex.ts"),
]);
const messageBuffer = typeof input === "string"
? new TextEncoder().encode(input)
: input;
const hashBuffer = await crypto.subtle.digest(algo, messageBuffer);
return encodeHex(hashBuffer);
}

sqliteBuilder: Opinionated safe(r) query builder using tagged templates

Create valimport { Statement } from "https://esm.town/v/postpostscript/sqliteBuilder"; const unsafeId = "1234 or TRUE" console.log(Statement` SELECT * FROM table WHERE id = ${unsafeId} ${Statement`AND otherCondition`} `) // StatementInstance { // sql: "\nSELECT *\nFROM table\nWHERE id = ?\nAND otherCondition\n", // args: [ "1234 or TRUE" ], // log: false // } const results = await Statement`SELECT ...`.execute() // [ { key: "value", anotherKey: "anotherValue" }, ... ]

Or you can pass it directly to @std/sqlite.execute:

Create valimport { sqlite } from "https://esm.town/v/std/sqlite" await sqlite.execute(Statement`Select ...`)

You can combine multiple statements using Statement.prototype.combineWith:

Create valStatement`...`.combineWith(Statement`...`, " AND ") [ Statement`fieldA`, Statement`fieldB`, Statement`fieldC`, ].reduce((a, b) => a.combineWith(b, ", "))
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 SqliteInterface } from "https://esm.town/v/postpostscript/sqliteTypes";
import { MaybePromise } from "https://esm.town/v/postpostscript/typeUtils";
import { type InStatement, type InValue, sqlite } from "https://esm.town/v/std/sqlite";
import { LibsqlError, type Row } from "npm:@libsql/client";
export { LibsqlError } from "npm:@libsql/client";
export type ExecuteOptions<T extends Record<string, any>, I extends SqliteInterface> = {
fallback?: (
stmt: StatementInstance,
options: ExecuteOptions<T, I>,
error: Error,
) => MaybePromise<T[]>;
sqlite?: I;
};
export class StatementInstance {
public sql: string;
public args: InValue[];
public log: boolean;
constructor(sql: string, args: InValue[] = []) {
this.sql = sql;
this.args = args;
this.log = false;
}
combineWith(instance: StatementInstance, sep = "") {
return new StatementInstance(this.sql + sep + instance.sql, this.args.concat(instance.args));
}
execute<T extends Record<string, any>, I extends SqliteInterface>(
options: ExecuteOptions<T, I> = {},
): MaybePromise<T[]> {
if (this.log) {
console.log("executing", {
sql: this.sql,
args: this.args,
});
}
const callback = ({ columns, rows }) => {
return rows.map(row => {
return Object.fromEntries(columns.map((key, i) => {
return [key, row[i]];
}));
});
};
const errorHandle = (e: Error) => {
if (options.fallback) {
return options.fallback(this, {
...options,
fallback: undefined,
}, e);
}
throw e;
};
try {
const queryResult = (options.sqlite ?? sqlite).execute(this);
if (!(queryResult instanceof Promise)) {
return callback(queryResult);
}
return queryResult.then(callback).catch(errorHandle);
} catch (e) {
return errorHandle(e);
}
}
toJSON() {
return {
sql: this.sql,
args: this.args,
};
}
async debug() {
const result = await this.execute();
const { default: Table } = await import("npm:easy-table");
console.debug(Table.print(result));
return result;
}
}
export function Statement(
strings: TemplateStringsArray,
...replacements: (InValue | StatementInstance)[]
) {
return strings.reduce((statement, string, index) => {
const stringInstance = new StatementInstance(string);
if (replacements.length >= index + 1) {
const replacement = replacements[index];
return statement
.combineWith(stringInstance)
.combineWith(
replacement instanceof StatementInstance ? replacement : new StatementInstance("?", [replacement]),
);
}
return statement.combineWith(stringInstance);

html: create sanitized HTML using tagged templates

Examples

Create valimport { html } from "https://esm.town/v/postpostscript/html" const unsafeInput = "<script>alert(1)</script>" console.log(html`Value: ${unsafeInput}`) // Value: &lt;script&gt;alert(1)&lt;/script&gt;

These can be combined -- HTML marked as safe (instance is RawHTML) will be directly inserted:

Create valconst scripts = html`<script>alert(1)</script>` console.log(html`<head> ${scripts} </head>`.toString()) // <head> // <script>alert(1)</script> // </head>

To easily create HTTP Response outputs like @stevekrouse/html, use the htmlResponse utility:

Create valimport { html, htmlResponse } from "https://esm.town/v/postpostscript/html"; export default function(req: Request) { return htmlResponse` Request URL: ${decodeURIComponent(req.url)} `; }

Tests: @postpostscript/htmlTest

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
import { hybridTaggedTemplateMethod } from "https://esm.town/v/postpostscript/hybridTaggedTemplateMethod";
import { html as createHTMLResponse } from "https://esm.town/v/stevekrouse/html";
export class RawHTML extends String {
constructor(value: unknown) {
if (value instanceof RawHTML) {
super(value);
} else {
super(coerceString(value));
}
}
trim() {
return new RawHTML(this.toString().trim());
}
}
export function htmlEscape(text: string | RawHTML | unknown) {
if (text instanceof RawHTML) {
return text;
}
return new RawHTML(
coerceString(text)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;"),
);
}
export function coerceString(value: unknown) {
if (typeof value === "string") {
return value;
} else if (typeof value === "number" || typeof value === "boolean") {
return value.toString();
} else if (value === undefined) {
return "";
}
return JSON.stringify(value) || "";
}
export const html = hybridTaggedTemplateMethod({
transformReplacement(replacement) {
return replacement instanceof Array
? replacement.map(part => htmlEscape(part)).join("")
: htmlEscape(replacement);
},
transformResult(result) {
return new RawHTML(result);
},
});
export const rawHtml = hybridTaggedTemplateMethod({
transformReplacement(replacement) {
return replacement instanceof Array
? replacement.join("")
: replacement;
},
transformResult(result) {
return new RawHTML(result);
},
});
export const htmlResponse = hybridTaggedTemplateMethod({
transformReplacement(replacement) {
return replacement instanceof Array
? replacement.map(part => htmlEscape(part)).join("")
: htmlEscape(replacement);
},
transformResult(result) {
return createHTMLResponse(result);
},
});

fetchWorker: communicate with a worker over a fetch-like interface!

Example:

Create valimport { fetchWorker } from "https://esm.town/v/postpostscript/fetchWorker"; using worker = await fetchWorker({ url: "https://esm.town/v/postpostscript/fetchWorkerExample", handler: "handler", }); const res = await worker.fetch("/", { method: "POST", body: JSON.stringify({ test: 1, }), headers: { "Content-Type": "application/json", }, }); console.log(await res.json()); // { // method: "POST", // url: "https://7ae81ab0-04cf-485a-ae09-054c4d3be6b3.val.town/", // text: { test: 1 }, // headers: { "content-type": "application/json" } // }

Full Example

Options:

  • url (string, required): URL that the worker will fetch the handler from
  • handler (string, defaults to default): name of export that will execute the 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
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 { readerFromStreamReader } from "https://deno.land/std@0.223.0/io/mod.ts";
import { httpClient } from "https://esm.town/v/pomdtr/http_client";
import { disposable } from "https://esm.town/v/postpostscript/disposable";
export const workerURL = new URL(import.meta.url);
workerURL.searchParams.set("worker", "1");
export async function fetchWorker(opts: {
url: string;
handler?: string;
}) {
const worker = disposable(
new Worker(workerURL.toString(), {
type: "module",
}),
(worker) => worker.terminate(),
);
// wait for worker setup to finish
const { resolve, promise } = Promise.withResolvers();
worker.onmessage = resolve;
worker.postMessage({
code: "setup",
...opts,
});
await promise;
const resolvers: {
[id: string]: (value: Response) => void;
} = {};
worker.onmessage = (e) => {
const {
id,
response: {
status,
headers,
body,
},
} = e.data;
const response = new Response(body, {
status,
headers,
});
resolvers[id](response);
};
return {
fetch: async (input: URL | RequestInfo, init?: RequestInit) => {
const req = new Request(input, init);
const body: ArrayBuffer | null = req.body
? await req.blob().then(blob => blob.arrayBuffer())
: null;
const id = crypto.randomUUID();
const { resolve, promise } = Promise.withResolvers<Response>();
resolvers[id] = resolve;
worker.postMessage({
code: "request",
id,
request: {
url: req.url,
body,
init: {
method: req.method,
headers: Object.fromEntries(req.headers.entries()),
},
},
});
return promise;
},
instance: worker,
terminate: () => worker.terminate(),
[Symbol.dispose]: () => worker.terminate(),
};
}
export function setupWorker() {
let handler: (req: Request) => Promise<Response>;
self.onmessage = async (e) => {
if (e.data.code === "setup") {
const module = await import(e.data.url);
handler = module[e.data.handler ?? "default"];
self.postMessage({
code: "ready",
});
}
if (e.data.code === "request") {
const {
id,

lock: distributed lock

Forked from @stevekrouse/dlock! 🙏

Example:

Create valimport { acquireLock } from "https://esm.town/v/postpostscript/lock"; using exampleLock = await acquireLock(); // lock will be released when it goes out of context

Full Example

Options:

Create valusing exampleLock = await acquireLock({ id: "lockExample", retries: 0, ttl: 5, // seconds retryTimeout: 1000, // ms });
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
import { sleep } from "https://deno.land/x/sleep/mod.ts";
import { fetchJSON } from "https://esm.town/v/stevekrouse/fetchJSON";
import { parentReference } from "https://esm.town/v/stevekrouse/parentReference";
import { searchParams } from "https://esm.town/v/stevekrouse/searchParams";
export async function acquireLock(opts?: LockOptions): Promise<{
release: () => Promise<LockResponse | undefined>;
}> {
let id = opts?.id ?? parentReference().userHandle + "-" + parentReference().valName;
id = id.replace(/[^\w]+/g, "_");
const ttl = opts?.ttl ?? 3;
async function handleError(error: string) {
if (typeof opts.retries === "number" && (opts.retry ?? 0) >= opts.retries) {
throw new LockError(`LockError: failed with message "${error || "unknown error"}"`);
}
await sleep((opts?.retryTimeout ?? 1000) / 1000);
return acquireLock({
...opts,
id,
ttl,
retry: (opts.retry ?? 0) + 1,
});
}
const res = await makeRequest(id, "acquire", {
ttl,
});
const { lease } = res;
if (!lease) {
return handleError(res.error);
}
const acquired = await makeRequest(id, "acquire", {
ttl,
lease,
});
if (!acquired.lease) {
return handleError(res.error);
}
let released = false;
async function release(): Promise<LockResponse | undefined> {
if (released) return;
released = true;
return makeRequest(id, "release", {
lease,
});
}
return {
release,
[Symbol.dispose]: release,
};
}
export class LockError extends Error {}
export type LockOptions = {
id?: string;
ttl?: number;
retry?: number;
retries?: number;
retryTimeout?: number;
};
export type LockResponse = {
lease?: number;
deadline: number;
error?: "string";
};
export async function makeRequest(id: string, method: "release" | "acquire", params): Promise<LockResponse> {
return fetchJSON(
`https://dlock.univalent.net/lock/${id}/${method}?${searchParams(params)}`,
);
}

disposable: wrapper to make an object disposable with the using keyword

using feature explanation in TypeScript 5.2 docs

Example:

Create valimport { disposable } from "https://esm.town/v/postpostscript/disposable"; using state = disposable( { x: 1, y: 2, }, (value) => console.log("state disposed", value), ); // state disposed { x: 1, y: 2, [Symbol(Symbol.dispose)]: [Function (anonymous)] }

or, to use a proxy instead of modifying the original object:

Create valusing state = disposable( { x: 1, y: 2, }, (value) => console.log("proxyState disposed", value), true, ); // proxyState disposed { x: 1, y: 2 }

Full Example

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
export function disposable<T extends object>(
value: T,
onDispose: (value: T) => void,
proxy = false,
): T {
const dispose = async () => {
if (proxy && value[Symbol.dispose]) {
await value[Symbol.dispose]();
}
return onDispose(value);
};
if (!proxy) {
value[Symbol.dispose] = dispose;
return value;
}
return new Proxy(value, {
get(target, key, receiver) {
if (key === Symbol.dispose) {
return dispose;
}
return Reflect.get(target, key, receiver);
},
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
export function promiseHandles<T>() {
let resolve: (value: T | PromiseLike<T>) => void,
reject: (reason?: any) => void;
const promise = new Promise<T>((_resolve, _reject) => {
resolve = _resolve;
reject = _reject;
});
return {
resolve,
reject,
promise,
};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { workerify } from "https://esm.town/v/postpostscript/workerify";
export function createWorker() {
return workerify(() => import("https://esm.town/v/postpostscript/workerifyExampleUnsafeLibrary"));
}
using mod = await createWorker();
console.log(await mod.x());
console.log(await mod.x.nested.z());
console.log(await mod.add(4, 2));
try {
await mod.stealAPIKey();
} catch (e) {
console.error(e);
}

workerify: proxy a module through a worker (with types!)

Example:

import { workerify } from "https://esm.town/v/postpostscript/workerify";

using mod = await workerify(() => import("https://esm.town/v/postpostscript/workerifyExampleUnsafeLibrary"));
console.log(await mod.x.y());

Full Example:

It's limited by what can be sent over [worker].postMessage(), i.e. inputs and outputs will need to be JSONifiable

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
// SPDX-License-Identifier: Apache-2.0
export async function workerify<T extends () => unknown>(getModule: T, opts?: WorkerOptions) {
let workerReady: (value: unknown) => void;
const promises: {
[id: string]: {
resolve: (value: unknown) => void;
reject: (value: unknown) => void;
};
} = {};
const workerURL = new URL("workerifyWorker", import.meta.url);
workerURL.searchParams.set("v", "5");
workerURL.searchParams.set(
"module",
new URL(
getModule
.toString()
.match(/import\(([^\)]*)\)/)![1]
.trim()
.slice(1, -1),
import.meta.url,
).toString(),
);
const worker = new Worker(workerURL, {
type: "module",
...opts,
});
worker.onmessage = (e) => {
if (e.data.code === "response") {
promises[e.data.id][e.data.success ? "resolve" : "reject"](
e.data.response,
);
}
workerReady(void 0);
};
await new Promise((resolve) => (workerReady = resolve));
function message(message: { code: string; keys: string[]; args: unknown[] }) {
return new Promise((resolve, reject) => {
const id = Math.random().toString().slice(2);
promises[id] = { resolve, reject };
worker.postMessage({
id,
...message,
});
});
}
function wrap(keys: string[]): unknown {
return new Proxy(keys.length ? () => {} : {}, {
get(target, prop, receiver) {
if (typeof prop === "symbol") {
if (prop === Symbol.dispose) {
return () => worker.terminate();
}
return Reflect.get(target, prop, receiver);
}
if (prop === "then") {
return undefined;
}
if (prop === "toString") {
return () =>
JSON.stringify({
__workerifyProxyTo: keys,
});
}
return wrap(keys.concat(prop as string));
},
apply(_target, _thisArg, args) {
return message({
code: "call",
keys,
args,
});
},
});
}
return wrap([]) as ReturnType<T> extends Promise<infer R> ? Workerify<R> : never;
}
export type Workerify<T> =
& {
(): Promise<T>;
}
& {
[K in keyof T]: T[K] extends (...args: any[]) => any ? Promisify<T[K]>
: Workerify<T[K]>;
};
type Promisify<T> = T extends (...args: infer A) => infer R ? (...args: A) => Promise<R>
: never;

reactiveStateBlob: wrap blob state in a proxy to autosave it on changes

Examples (full example at @postpostscript/reactiveStateBlobExample)

import { reactiveStateBlob } from "https://esm.town/v/postpostscript/reactiveStateBlob"

using state = await reactiveStateBlob({
  viewCount: 0,
  rows: [] as {
    x: number;
    y: number;
  }[],
});

state.viewCount += 1;
state.rows.push({
  x: Math.random(),
  y: Math.random(),
});

This infers the key from the name of the val that uses it. To specify it, pass the key option:

Create valusing state = await reactiveStateBlob({ viewCount: 0, rows: [] as { x: number; y: number; }[], }, { key: 'reactiveStateBlobExample.state', });

Updating Schema

If you want to update the schema, or always verify the state that is pulled from the job, pass a function as the first argument:

using state = await reactiveStateBlob((existingState) => {
  return {
    viewCount: (existingState.viewCount ?? 0) as number,
    rows: (existingState.rows ?? []) as {
      x: number;
      y: number;
    }[],
	someNewField: (existingState.someNewField ?? "") as string,
  }
})

Options

using state = await reactiveStateBlob<{
  value: number;
}>({
  value: 0,
}, {
  log: true,      // log when saving
  key: "blobKey", // blob key to fetch/save to
  timeout: 100,   // ms, defaults to 10
  lock: true, // or LockOptions (see https://www.val.town/v/postpostscript/lock#options)
})

See Also

@postpostscript/counter (example at @postpostscript/counterExample)

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 { rootValRef } from "https://esm.town/v/andreterron/rootValRef?v=3";
import { disposable } from "https://esm.town/v/postpostscript/disposable";
import { acquireLock, type LockOptions } from "https://esm.town/v/postpostscript/lock";
import {
type PersistentState,
persistentStateLazy,
persistentStateReactive,
} from "https://esm.town/v/postpostscript/persistentState";
import { blob } from "https://esm.town/v/std/blob";
export type PersistentStateGetDefault<T> = Promise<T> | T | ((value: any) => Promise<T> | T);
export type PersistentStateBlobOptions<T> = {
key: string;
getDefault: PersistentStateGetDefault<T>;
timeout?: number;
log?: boolean;
};
export async function reactiveStateBlob<T extends Record<string, unknown>>(
getDefault: PersistentStateGetDefault<T>,
opts?: {
key?: string;
timeout?: number;
log?: boolean;
lock?: boolean | LockOptions;
},
): Promise<T> {
const _key = opts?.key ?? (() => {
const { handle, name } = rootValRef();
return `reactiveStateBlob:${handle}/${name}`;
})();
const stateLock = opts?.lock
? await acquireLock({
id: `lock:${_key}`,
...((opts?.lock && typeof opts.lock === "object") ? opts.lock : {}),
})
: undefined;
const cache = persistentStateBlobReactive({
...opts,
key: _key,
getDefault,
});
return disposable(await cache.get(), (value) => {
return stateLock?.release();
}, true);
}
export function persistentStateBlob<T extends Record<string, unknown>>(
opts: PersistentStateBlobOptions<T>,
): PersistentState<T> {
return {
async get(): Promise<T> {
return normalizeValue(opts.getDefault, await blob.getJSON(opts.key));
},
set(value) {
if (opts.log) {
console.log("persistentStateBlob saving to blob:", opts.key, "=", value);
}
return blob.setJSON(opts.key, value);
},
};
}
export function persistentStateBlobReactive<T extends Record<string, unknown>>(
opts: PersistentStateBlobOptions<T>,
): PersistentState<T> {
return persistentStateReactive(persistentStateBlob(opts));
}
export function persistentStateBlobLazy<T extends Record<string, unknown>>(
opts: PersistentStateBlobOptions<T>,
): PersistentState<T> {
return persistentStateLazy(persistentStateBlob(opts));
}
async function normalizeValue<T>(value: PersistentStateGetDefault<T>, existing: unknown): Promise<T> {
if (value instanceof Function) {
return value(existing);
}
return existing as T ?? value;
}