Readme

Blob Storage - Docs ↗

Val Town comes with blob storage built-in. It allows for storing any data, like text, JSON, or images. You can access it via std/blob.

Blob storage is scoped globally to your account. If you set a blob in one val, you can retrieve it by the same key in another val. It's backed by Cloudflare R2.

You may find this admin viewer helpful for viewing and editing your blobs.

Get JSON
import { blob } from "https://esm.town/v/std/blob"; let blobDemo = await blob.getJSON("myKey"); console.log(blobDemo); // returns `undefined` if not found
Set JSON
import { blob } from "https://esm.town/v/std/blob"; await blob.setJSON("myKey", { hello: "world" });
List keys
import { blob } from "https://esm.town/v/std/blob"; let allKeys = await blob.list(); console.log(allKeys); const appKeys = await blob.list("app_"); console.log(appKeys); // all keys that begin with `app_`
Delete by key
import { blob } from "https://esm.town/v/std/blob"; await blob.delete("myKey");

Examples

Error Handling

Utilities

Our Blob SDK also includes some utility functions to make working with blobs easier.

Copy
import { blob } from "https://esm.town/v/std/blob"; await blob.copy("myKey", "myKeyCopy");
Move
import { blob } from "https://esm.town/v/std/blob"; await blob.move("myKey", "myKeyNew");

Lower-level API

We provide access to the lower-level getter and setters, which are useful if you are storing non-JSON or binary data, need to stream in your response or request data, or do anything else lower-level.

  • async get(key: string): Retrieves a blob for a given key.
  • async set(key: string, value: string | BodyInit): Sets the blob value for a given key. See BodyInit.

Limitations

  • Blob-stored data counts towards your total Val Town storage – 10mb on the free plan and 1gb on pro. Check our pricing page to learn more.
  • Keys for blobs can be up to 512 characters long.

📝 Edit docs

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
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();
}
if (!res.ok) {
const body = await res.text();
throw new ValTownBlobError(body ? body : "Error getting blob data");
}
return res;
}
async function set(key: string, value: BodyInit) {
const res = await fetch(`${API_URL}/v1/blob/${encodeURIComponent(key)}`, {
method: "POST",
headers: {
Authorization: `Bearer ${Deno.env.get("valtown")}`,
},
body: value,
});
if (!res.ok) {
const body = await res.text();
throw new ValTownBlobError(body ? body : "Error setting blob data");
}
}
async function copy(previous: string, next: string) {
const res = await get(previous);
await set(next, res.body);
}
async function move(previous: string, next: string) {
await copy(previous, next);
await delete_(previous);
}
async function getJSON(key: string) {
try {
const res = await get(key);
return res.json();
} catch (e) {
if (e instanceof ValTownBlobNotFoundError) {
return undefined;
}
throw e;
}
}
async function setJSON(key: string, value: any) {
return set(key, JSON.stringify(value));
}
Val Town is a social website to write and deploy JavaScript.
Build APIs and schedule functions from your browser.
v11 was merged from the PR "add copy and move methods" by pomdtr
Comments
8
davidcrespo avatar

I think it would be helpful if this doc mentioned the scope of the blob storage. My assumption, because it is not mentioned, is that this is global (account-scoped), not val-scoped, i.e., if I get and set the same key from two different vals, they will be sharing and overwriting the same value in the global store.

std avatar

Great note! I added this to the readme above: "Blob storage is scoped globally to your account. If you set a blob in one val, you can retrieve it by the same key in another val." What do you think?

davidcrespo avatar

Sounds good!

davidcrespo avatar

Another note: I converted a val to use blob instead of separate val to store a string value, and I expected to use get and set because set has value: string | BodyInit for that. Took me a bit to figure out that

  1. get returns a Response (though I can see why it does) and it is not what you want for storing a string
  2. I can use getJSON with a plain string because a string is valid JSON

I think of these as things that could use noting in the docs rather than a problem with the API. However, I will say unqualified get/set presents to me as the default/simplest API unless I'm told otherwise, but it seems like getJSON/setJSON are actually the first thing most people will want to reach for. That is hinted at by the fact that most of the examples on this page and here use the JSON variant. I think get/set are good names based on the implementation (get is most general, getJSON wraps get) but it would be nice to see that more clearly in the docs.

std avatar

Great point! I reorganized the readme to emphasize how they are lower-level primitives. Lmk if you have any other suggestions

pomdtr avatar

It would be nice to have a rename/move function here.

stevekrouse avatar

I don't think rename/move is in the s3 API, so it'd be a get and then set operation, in which case building it would be a great pull request to this val (when we launch pull requests later today or tomorrow)

std avatar

Thanks for the PR @pomdtr!

v11
April 3, 2024