Avatar

std

The Val Town Standard Library
15 public vals
Joined August 17, 2023

Deprecated in favor of std/sqlite (also powered by Turso)

std/turso was the initial version of our integration with Turso. It was so popular, we rebuilt it to be faster and easier to use: std/sqlite.

turso (2).png

Turso is a serverless SQLite platform designed for the edge. It runs libSQL, their open contribution fork of SQLite.

Every Val Town user automatically gets their own Turso SQLite database! It's great for >100kb data (ie bigger than a val) or when you need SQL: relations, ACID transactions, etc.

Storage used in Turso will count against your Val Town total storage (10mb for free users; 1gb for Pro users). Contact us if you'd need more – it should be no problem!

Getting started

This val uses our public key auth scheme.

  1. Generate your keypair
  2. On your publicKey click the lock iconπŸ”’ to change the permissions to Unlisted.
  3. Fork this helper function replacing stevekrouse with your own username
  4. Try out some queries!

Usage

This val returns a Turso SDK's Client, which supports execute, batch, and transaction.

Create valawait @me.turso().execute(`create table blobs( key text unique, value text )`)

More example usage

Architecture

This @std.turso function is the client or SDK to @std.tursoAPI, which acts as a "proxy" to Turso. It handles authentication, creates databases, and forwards on your SQL queries. You can get lower latency (~200ms vs ~800ms), more storage, databases, CLI & API access by having your own Turso account.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { runValAPIAuth } from "https://esm.town/v/stevekrouse/runValAPIAuth";
// grab the types off turso's client without importing it
let tursoImport = () => import("https://esm.sh/@libsql/client/web");
type createClient = Awaited<ReturnType<typeof tursoImport>>["createClient"];
export function turso(keys, handle?) {
let val = "@std.tursoAPI";
let f = (method) => (...args) =>
runValAPIAuth({
val,
args: [method, args],
keys,
handle,
});
return {
execute: f("execute") as ReturnType<createClient>["execute"],
batch: f("batch") as ReturnType<createClient>["batch"],
transaction: f("transaction") as ReturnType<createClient>["transaction"],
};
}

Blob Storage - Docs β†—

Val Town comes with blob storage built-in. It allows for storing any data: text, JSON, 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
Create valimport { blob } from "https://esm.town/v/std/blob"; let blobDemo = await blob.getJSON("myKey"); console.log(blobDemo); // returns `undefined` if not found
Set JSON
Create valimport { blob } from "https://esm.town/v/std/blob"; await blob.setJSON("myKey", { hello: "world" });
List keys
Create valimport { blob } from "https://esm.town/v/std/blob"; let allKeys = await blob.listKeys(); console.log(allKeys); const appKeys = await blob.listKeys("app_"); console.log(appKeys); // all keys that begin with `app_`
Delete by key
Create valimport { 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
Create valimport { blob } from "https://esm.town/v/std/blob"; await blob.copy("myKey", "myKeyCopy");
Move
Create valimport { blob } from "https://esm.town/v/std/blob"; await blob.move("myKey", "myKeyNew");

Lower-level API

We do 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
98
99
100
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";
/**
* Provides functions for interacting with your account's blob storage.
* Blobs can store any data type (text, JSON, images, etc.) and allow
* retrieval across different vals using the same key.
* ([Docs β†—](https://docs.val.town/std/blob))
*/
export const blob = {
/**
* Gets the data of a blob with the given key.
*
* @param {string} key - The key of the blob to get.
* @returns {Promise<Response>} A promise that resolves with the blob data as a Response object.
* @throws {ValTownBlobNotFoundError} If the blob is not found.
* @throws {ValTownBlobError} If an error occurs while getting the blob data.
*/
get: get,
/**
* Sets the data of a blob with the given key.
*
* @param {string} key - The key of the blob to set.
* @param {BodyInit} value - The data to set for the blob.
* @throws {ValTownBlobError} If an error occurs while setting the blob data.
*/
set: set,
/**
* Copies the data of a blob from one key to another.
*
* @param {string} previous - The key of the blob to copy from.
* @param {string} next - The key of the blob to copy to.
*/
copy: copy,
/**
* Moves the data of a blob from one key to another by copying and then deleting the original.
*
* @param {string} previous - The key of the blob to move from.
* @param {string} next - The key of the blob to move to.
*/
move: move,
/**
* Lists blobs with an optional prefix filter.
*
* @param {string} [prefix] - The prefix to filter the blobs by.
* @returns {Promise<{ key: string; size: number; lastModified: string }[]>} A promise that resolves with an array of blob metadata.
*/
list: list,
/**
* Deletes a blob with the given key.
*
* @param {string} key - The key of the blob to delete.
* @throws {ValTownBlobError} If an error occurs while deleting the blob.
*/
delete: delete_,
/**
* Gets the JSON data of a blob with the given key.
*
* @param {string} key - The key of the blob to get.
* @returns {Promise<any | undefined>} A promise that resolves with the JSON data of the blob, or undefined if the blob is not found.
* @throws {ValTownBlobError} If an error occurs while getting the blob data.
*/
getJSON: getJSON,
/**
* Sets the JSON data of a blob with the given key.
*
* @param {string} key - The key of the blob to set.
* @param {any} value - The JSON data to set for the blob.
* @throws {ValTownBlobError} If an error occurs while setting the blob data.
*/
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")}`,

Email - Docs β†—

Send emails with std/email. You can only send emails to yourself if you're on Val Town Free. If you're on Val Town Pro, you can email anyone.

Want to receive emails instead? Create an email handler val

Basic usage

Create valimport { email } from "https://esm.town/v/std/email"; await email({ subject: "New Ink & Switch Post!", text: "https://www.inkandswitch.com/embark/" });

subject

The email subject line. It defaults to Message from @your_username on Val Town.

to, cc, and bcc

By default, the to field is set to the owner of the Val Town account that calls it.

If you have Val Town Pro, you can send emails to anyone via the to, cc, and bcc fields.

If you don't have Val Town Pro, you can only send emails to yourself, so leave those fields blank.

from

The from is limited to a few options:

  1. It defaults to notifications@val.town if you don't specify it.

  2. If you do specify it, it must be of the form: your_username.valname@valtown.email.

replyTo

replyTo accepts a string email or an object with strings for email and name (optional).

This can be useful if you are sending emails to others with Val Town Pro.

Create valimport { email } from "https://esm.town/v/std/email"; await email({ to: "someone_else@example.com", from: "your_username.valname@valtown.email", replyTo: "your_email@example.com", text: "these pretzels are making me thirsty", });

Attachments

You can attach files to your emails by using the attachments field. Attachments need to be Base64 encoded, which is that the btoa method is doing in this example:

Create valimport { email } from "https://esm.town/v/std/email"; export const stdEmailAttachmentExample = email({ attachments: [ { content: btoa("hello attachments!"), filename: "test.txt", type: "text", disposition: "attachment", }, ], });

Here's an example sending a PDF.

πŸ“ 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
98
99
100
import { API_URL } from "https://esm.town/v/std/API_URL";
import { parseSendGridEmail } from "https://esm.town/v/stevekrouse/parseSendGridEmail?v=8";
import { parseSendGridEmails } from "https://esm.town/v/stevekrouse/parseSendGridEmails?v=10";
/**
* Send emails. You can only send emails to yourself if
* you’re on Val Town Free. If you’re on Val Town Pro,
* you can email anyone. ([Docs β†—](https://docs.val.town/std/email))
*/
export const email = async (data: {
/**
* The email address(es) to send the email to. Only available to Val Town Pro subscribers.
* Can be a single string, IAddress object, or an array of strings/IAddress objects.
* @default the email of the logged user calling this function.
*/
to?: (IAddress | string)[] | IAddress | string;
/**
* The email address to set as the "from" address. It can only be set to username.valname@valtown.email addresses.
* Can be a string or IAddress object.
* @default notifications@val.town
*/
from?: IAddress | string;
/**
* The email address(es) to CC on the email. Only available to Val Town Pro subscribers.
* Can be a single string, IAddress object, or an array of strings/IAddress objects.
*/
cc?: (IAddress | string)[] | IAddress | string;
/**
* The email address(es) to blind carbon copy (BCC) on the email. Only available to Val Town Pro subscribers.
* Can be a single string, IAddress object, or an array of strings/IAddress objects.
*/
bcc?: (IAddress | string)[] | IAddress | string;
/**
* The subject line for the email.
*/
subject?: string;
/**
* The email address(es) to set as the "reply-to" address.
* Can be a single string, IAddress object, or an array of strings/IAddress objects.
*/
replyTo?: (IAddress | string)[] | IAddress | string;
/**
* The HTML content for the email body.
*/
html?: string;
/**
* The plain text content for the email body.
*/
text?: string;
/**
* An array of attachment data to include with the email.
*/
attachments?: AttachmentData[];
}) => {
let result = await fetch(
`${API_URL}/v1/email`,
{
method: "POST",
body: JSON.stringify({
to: parseSendGridEmails(data.to),
from: data.from && parseSendGridEmail(data.from),
cc: parseSendGridEmails(data.cc),
bcc: parseSendGridEmails(data.bcc),
replyToList: parseSendGridEmails(data.replyTo),
subject: data.subject,
html: data.html,
text: data.text,
attachments: data.attachments,
}),
headers: {
authorization: "Bearer " + Deno.env.get("valtown"),
},
},
).then(r => r.json());
if (result?.message !== "Email accepted to be sent") {
let message = result?.message ?? result?.error ?? JSON.stringify(result);
throw new Error("Val Town Email Error: " + message);
}
return result;
};
export interface IAddress {
email: string;
name?: string;
}
export interface AttachmentData {
content: string;
filename: string;
type?: string;
disposition?: string;
contentId?: string;

Proxied fetch - Docs β†—

The Javascript Fetch API is directly available within a Val. However sometimes fetch calls are blocked by the receiving server for using particular IP addresses. Additionally, network blips or unreliable web services may lead to failures if not handled properly.

The Val Town standard library contains an alternative version, std/fetch, that wraps the Javascript Fetch API to provide additional functionality. The fetch function from std/fetch reroutes requests using a proxy vendor so that requests obtain different IP addresses. It also automatically retries failed requests several times. Note that using std/fetch will be significantly slower than directly calling the Javascript Fetch API due to extra network hops.

Usage

After importing std/fetch, the fetch method is used with the same signature as the Javascript Fetch API.

Create valimport { fetch } from "https://esm.town/v/std/fetch"; let result = await fetch("https://api64.ipify.org?format=json"); let json = await result.json(); console.log(json.ip);

If you run the above code multiple times, you'll see that it returns different IP addresses, because std/fetch uses proxies so that each request is made from a different IP address.

πŸ“ 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
import { API_URL } from "https://esm.town/v/std/API_URL";
import { rawFetch } from "https://esm.town/v/std/rawFetch";
/**
* Wraps the JavaScript Fetch function to anonymize where the request is
* coming from ([Docs β†—](https://docs.val.town/std/fetch))
*
* @param {string | URL} input - The URL to fetch
* @param {RequestInit} [requestInit] - Optional configuration data (HTTP
* method, headers, etc) ([Docs β†—](https://deno.land/api@v1.42.1?s=RequestInit))
*/
export async function fetch(input: string | URL, requestInit?: RequestInit) {
let query = new URLSearchParams({
url: input.toString(),
});
return rawFetch(`${API_URL}/v1/fetch?${query}`, {
...requestInit,
headers: {
"X-Valtown-Authorization": `Bearer ${Deno.env.get("valtown")}`,
...requestInit?.headers ?? {},
},
});
}

OpenAI Proxy

This OpenAI API proxy injects Val Town's API keys. For usage documentation, check out https://www.val.town/v/std/openai

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
import { parseBearerString } from "https://esm.town/v/andreterron/parseBearerString";
import { API_URL } from "https://esm.town/v/std/API_URL?v=5";
import { RateLimit } from "npm:@rlimit/http";
const allowedPathnames = [
"/",
"/v1/chat/completions",
];
const rlimit = new RateLimit({
namespace: Deno.env.get("rlimit_namespace"),
maximum: 10,
interval: "1m",
});
export default async function(req: Request): Promise<Response> {
// Ensure only allowed pathnames are used
const { pathname, search } = new URL(req.url);
if (!allowedPathnames.includes(pathname)) {
return new Response("Path not supported", { status: 404 });
}
// Checks the user identity
const authHeader = req.headers.get("Proxy-Authorization") || req.headers.get("Authorization");
const token = authHeader ? parseBearerString(authHeader) : undefined;
const meRes = await fetch(`${API_URL}/v1/me`, { headers: { Authorization: `Bearer ${token}` } });
if (!meRes.ok) {
return new Response("Unauthorized", { status: 401 });
}
const user = await meRes.json();
// Check rate limit
const { ok } = await rlimit.check(`user:${user.id}`);
if (!ok) {
// Rate limited
return new Response("Too Many Requests", { status: 429 });
}
// Proxy the request
const url = new URL("." + pathname, "https://api.openai.com");
url.search = search;
const headers = new Headers(req.headers);
headers.set("Host", url.hostname);
headers.set("Authorization", `Bearer ${Deno.env.get("OPENAI_API_KEY")}`);
headers.set("OpenAI-Organization", Deno.env.get("OPENAI_API_ORG"));
const openAIRes = await fetch(url, {
method: req.method,
headers,
body: await limitFreeModel(req, user),
redirect: "manual",
});
const res = new Response(openAIRes.body, openAIRes);
// Remove internal header
res.headers.delete("openai-organization");
return res;
}
async function limitFreeModel(req: Request, user: any) {
if (user.tier === "pro") return req.body;
const input = await req.json();
console.log(input);
return JSON.stringify({
...input,
model: "gpt-3.5-turbo",
});
}
// Adapted from https://blog.r0b.io/post/creating-a-proxy-with-deno/

OpenAI - Docs β†—

Use OpenAI's chat completion API with std/openai. This integration enables access to OpenAI's language models without needing to acquire API keys.

For free Val Town users, all calls are sent to gpt-3.5-turbo.

Streaming is not yet supported. Upvote the HTTP response streaming feature request if you need it!

Usage

Create valimport { OpenAI } from "https://esm.town/v/std/openai"; const openai = new OpenAI(); const completion = await openai.chat.completions.create({ messages: [ { role: "user", content: "Say hello in a creative way" }, ], model: "gpt-4", max_tokens: 30, }); console.log(completion.choices[0].message.content);

Limits

While our wrapper simplifies the integration of OpenAI, there are a few limitations to keep in mind:

  • Usage Quota: We limit each user to 10 requests per minute.
  • Features: Chat completions is the only endpoint available.

If these limits are too low, let us know! You can also get around the limitation by using your own keys:

  1. Create your own API key on OpenAI's website
  2. Create an environment variable named OPENAI_API_KEY
  3. Use the OpenAI client from npm:openai:
Create valimport { OpenAI } from "npm:openai"; const openai = new OpenAI();

πŸ“ 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
import { type ClientOptions, OpenAI as RawOpenAI } from "npm:openai";
/**
* API Client for interfacing with the OpenAI API. Uses Val Town credentials.
*/
export class OpenAI {
private rawOpenAIClient: RawOpenAI;
/**
* API Client for interfacing with the OpenAI API. Uses Val Town credentials.
*
* @param {number} [opts.timeout=10 minutes] - The maximum amount of time (in milliseconds) the client will wait for a response before timing out.
* @param {number} [opts.httpAgent] - An HTTP agent used to manage HTTP(s) connections.
* @param {Core.Fetch} [opts.fetch] - Specify a custom `fetch` function implementation.
* @param {number} [opts.maxRetries=2] - The maximum number of times the client will retry a request.
* @param {Core.Headers} opts.defaultHeaders - Default headers to include with every request to the API.
* @param {Core.DefaultQuery} opts.defaultQuery - Default query parameters to include with every request to the API.
* @param {boolean} [opts.dangerouslyAllowBrowser=false] - By default, client-side use of this library is not allowed, as it risks exposing your secret API credentials to attackers.
*/
constructor(options: Omit<ClientOptions, "baseURL" | "apiKey" | "organization"> = {}) {
this.rawOpenAIClient = new RawOpenAI({
...options,
baseURL: "https://std-openaiproxy.web.val.run/v1",
apiKey: Deno.env.get("valtown"),
organization: null,
});
}
get chat() {
return this.rawOpenAIClient.chat;
}
readonly beta = {
get chat(): RawOpenAI["beta"]["chat"] {
return this.rawOpenAIClient.beta.chat;
}
}
}

OpenAI Proxy

This OpenAI API proxy injects Val Town's API keys. For usage documentation, check out https://www.val.town/v/std/openai

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
import { parseBearerString } from "https://esm.town/v/andreterron/parseBearerString";
import { API_URL } from "https://esm.town/v/std/API_URL?v=5";
import { RateLimit } from "npm:@rlimit/http";
const allowedPathnames = [
"/",
"/v1/chat/completions",
];
const rlimit = new RateLimit({
namespace: Deno.env.get("rlimit_namespace"),
maximum: 10,
interval: "1m",
});
export default async function(req: Request): Promise<Response> {
// Ensure only allowed pathnames are used
const { pathname, search } = new URL(req.url);
if (!allowedPathnames.includes(pathname)) {
return new Response("Path not supported", { status: 404 });
}
// Checks the user identity
const authHeader = req.headers.get("Proxy-Authorization") || req.headers.get("Authorization");
const token = authHeader ? parseBearerString(authHeader) : undefined;
const meRes = await fetch(`${API_URL}/v1/me`, { headers: { Authorization: `Bearer ${token}` } });
if (!meRes.ok) {
return new Response("Unauthorized", { status: 401 });
}
const user = await meRes.json();
// Check rate limit
const { ok } = await rlimit.check(`user:${user.id}`);
if (!ok) {
// Rate limited
return new Response("Too Many Requests", { status: 429 });
}
// Proxy the request
const url = new URL("." + pathname, "https://api.openai.com");
url.search = search;
const headers = new Headers(req.headers);
headers.set("Host", url.hostname);
headers.set("Authorization", `Bearer ${Deno.env.get("OPENAI_API_KEY")}`);
headers.set("OpenAI-Organization", Deno.env.get("OPENAI_API_ORG"));
const openAIRes = await fetch(url, {
method: req.method,
headers,
body: await limitFreeModel(req, user),
redirect: "manual",
});
const res = new Response(openAIRes.body, openAIRes);
// Remove internal header
res.headers.delete("openai-organization");
return res;
}
async function limitFreeModel(req: Request, user: any) {
if (user.tier === "pro") return req.body;
const input = await req.json();
console.log(user.id, input);
return JSON.stringify({
...input,
model: "gpt-3.5-turbo",
});
}
// Adapted from https://blog.r0b.io/post/creating-a-proxy-with-deno/

Parse email

A small wrapper around email-addresses, an RFC 5322 email address parser.

Usage

Create valimport { parseAddressList } from "https://esm.town/v/std/parse_email"; console.log(parseAddressList('Steve <steve@val.town>, tom@val.town'));

Should output:

[
  {
    parts: {
      name: {
        name: "display-name",
        tokens: "'Steve ",
        semantic: "'Steve",
        children: [ [Object] ]
      },
      address: {
        name: "addr-spec",
        tokens: "steve@val.town",
        semantic: "steve@val.town",
        children: [ [Object] ]
      },
      local: {
        name: "local-part",
        tokens: "steve",
        semantic: "steve",
        children: [ [Object] ]
      },
      domain: {
        name: "domain",
        tokens: "val.town",
        semantic: "val.town",
        children: [ [Object] ]
      },
      comments: [
        { name: "cfws", tokens: " ", semantic: " ", children: [Array] }
      ]
    },
    type: "mailbox",
    name: "'Steve",
    address: "steve@val.town",
    local: "steve",
    domain: "val.town",
    comments: "",
    groupName: null
  },
  {
    parts: {
      name: null,
      address: {
        name: "addr-spec",
        tokens: " tom@val.town'",
        semantic: "tom@val.town'",
        children: [ [Object] ]
      },
      local: {
        name: "local-part",
        tokens: " tom",
        semantic: "tom",
        children: [ [Object] ]
      },
      domain: {
        name: "domain",
        tokens: "val.town'",
        semantic: "val.town'",
        children: [ [Object] ]
      },
      comments: [
        { name: "cfws", tokens: " ", semantic: "", children: [Array] }
      ]
    },
    type: "mailbox",
    name: null,
    address: "tom@val.town'",
    local: "tom",
    domain: "val.town'",
    comments: "",
    groupName: null
  }
]
1
2
3
4
5
import addrs from "npm:email-addresses";
const { parseAddressList } = addrs;
export { parseAddressList };
1
export default () => Response.json(Deno.version);
1
2
3
4
5
6
7
8
9
10
11
12
13
import { blob } from "https://esm.town/v/std/blob";
const KEY = "blob_counter";
const oldState = await blob.getJSON(KEY) ?? 0;
console.log("Old value", oldState);
await blob.setJSON(KEY, oldState + 1);
const newState = await blob.getJSON(KEY);
console.log("New value", newState);
export let docsBlobCounterDemo = undefined;