Forked from stevekrouse/dlock
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;
}