@graffiti-garden/implementation-decentralized 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/1-services/2-dids.d.ts +1 -0
- package/dist/1-services/2-dids.d.ts.map +1 -1
- package/dist/1-services/3-storage-buckets.d.ts +5 -0
- package/dist/1-services/3-storage-buckets.d.ts.map +1 -1
- package/dist/1-services/4-inboxes.d.ts +2 -1
- package/dist/1-services/4-inboxes.d.ts.map +1 -1
- package/dist/browser/index.js +7 -7
- package/dist/browser/index.js.map +3 -3
- package/dist/cjs/1-services/2-dids-tests.js +53 -0
- package/dist/cjs/1-services/2-dids-tests.js.map +3 -3
- package/dist/cjs/1-services/2-dids.js +14 -4
- package/dist/cjs/1-services/2-dids.js.map +2 -2
- package/dist/cjs/1-services/3-storage-buckets-tests.js +179 -0
- package/dist/cjs/1-services/3-storage-buckets-tests.js.map +3 -3
- package/dist/cjs/1-services/3-storage-buckets.js +91 -0
- package/dist/cjs/1-services/3-storage-buckets.js.map +3 -3
- package/dist/cjs/1-services/4-inboxes-tests.js +20 -0
- package/dist/cjs/1-services/4-inboxes-tests.js.map +3 -3
- package/dist/cjs/1-services/4-inboxes.js +36 -17
- package/dist/cjs/1-services/4-inboxes.js.map +2 -2
- package/dist/cjs/3-protocol/4-graffiti.js +1 -1
- package/dist/cjs/3-protocol/4-graffiti.js.map +1 -1
- package/dist/esm/1-services/2-dids-tests.js +54 -1
- package/dist/esm/1-services/2-dids-tests.js.map +3 -3
- package/dist/esm/1-services/2-dids.js +14 -4
- package/dist/esm/1-services/2-dids.js.map +2 -2
- package/dist/esm/1-services/3-storage-buckets-tests.js +180 -1
- package/dist/esm/1-services/3-storage-buckets-tests.js.map +2 -2
- package/dist/esm/1-services/3-storage-buckets.js +92 -1
- package/dist/esm/1-services/3-storage-buckets.js.map +2 -2
- package/dist/esm/1-services/4-inboxes-tests.js +20 -0
- package/dist/esm/1-services/4-inboxes-tests.js.map +2 -2
- package/dist/esm/1-services/4-inboxes.js +36 -17
- package/dist/esm/1-services/4-inboxes.js.map +2 -2
- package/dist/esm/3-protocol/4-graffiti.js +1 -1
- package/dist/esm/3-protocol/4-graffiti.js.map +1 -1
- package/package.json +1 -1
- package/src/1-services/2-dids-tests.ts +62 -1
- package/src/1-services/2-dids.ts +17 -5
- package/src/1-services/3-storage-buckets-tests.ts +199 -1
- package/src/1-services/3-storage-buckets.ts +129 -1
- package/src/1-services/4-inboxes-tests.ts +25 -0
- package/src/1-services/4-inboxes.ts +47 -19
- package/src/3-protocol/4-graffiti.ts +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/1-services/3-storage-buckets-tests.ts"],
|
|
4
|
-
"sourcesContent": ["import { describe, expect, test } from \"vitest\";\nimport { StorageBuckets } from \"./3-storage-buckets\";\nimport { GraffitiErrorUnauthorized } from \"./utilities\";\nimport { GraffitiErrorNotFound } from \"@graffiti-garden/api\";\n\nexport function storageBucketTests(\n storageBucketEndpoint: string,\n storageBucketToken: string,\n) {\n describe(\"Storage buckets\", async () => {\n const storageBuckets = new StorageBuckets();\n\n test(\"put, get, delete\", async () => {\n const key = Math.random().toString(36).substring(2, 15);\n const input = \"Hello world\";\n const bytes = new TextEncoder().encode(input);\n\n await expect(\n storageBuckets.get(storageBucketEndpoint, key),\n ).rejects.toThrow(GraffitiErrorNotFound);\n\n await storageBuckets.put(\n storageBucketEndpoint,\n key,\n bytes,\n storageBucketToken,\n );\n\n const resultBytes = await storageBuckets.get(\n storageBucketEndpoint,\n key,\n bytes.length,\n );\n const result = new TextDecoder().decode(resultBytes);\n expect(result).toEqual(input);\n\n await storageBuckets.delete(\n storageBucketEndpoint,\n key,\n storageBucketToken,\n );\n\n await expect(\n storageBuckets.get(storageBucketEndpoint, key),\n ).rejects.toThrow(GraffitiErrorNotFound);\n });\n\n test(\"get with limit less than object\", async () => {\n const key = Math.random().toString(36).substring(2, 15);\n const input = \"Hello world\";\n const bytes = new TextEncoder().encode(input);\n\n await storageBuckets.put(\n storageBucketEndpoint,\n key,\n bytes,\n storageBucketToken,\n );\n\n await expect(\n storageBuckets.get(storageBucketEndpoint, key, bytes.length - 1),\n ).rejects.toThrow();\n });\n\n test(\"unauthorized\", async () => {\n const key = Math.random().toString(36).substring(2, 15);\n const input = \"Hello world\";\n const bytes = new TextEncoder().encode(input);\n\n await expect(\n storageBuckets.put(storageBucketEndpoint, key, bytes, \"invalid-token\"),\n ).rejects.toThrow(GraffitiErrorUnauthorized);\n await expect(\n storageBuckets.delete(storageBucketEndpoint, key, \"invalid-token\"),\n ).rejects.toThrow(GraffitiErrorUnauthorized);\n await expect(\n storageBuckets.export(storageBucketEndpoint, \"invalid-token\").next(),\n ).rejects.toThrow(GraffitiErrorUnauthorized);\n });\n\n test(\"export\", async () => {\n // Put a whole bunch of stuff so the export needs to page\n const keys = new Set<string>();\n for (let i = 0; i < 256; i++) {\n const key = Math.random().toString(36).substring(2, 15);\n keys.add(key);\n\n const input = \"Hello world \" + i;\n const bytes = new TextEncoder().encode(input);\n await storageBuckets.put(\n storageBucketEndpoint,\n key,\n bytes,\n storageBucketToken,\n );\n }\n\n // Export\n const retrievedKeys = new Set<string>();\n const iterator = storageBuckets.export(\n storageBucketEndpoint,\n storageBucketToken,\n );\n for await (const result of iterator) {\n if (keys.has(result.key)) {\n retrievedKeys.add(result.key);\n }\n }\n expect(retrievedKeys.size).toEqual(keys.size);\n\n // Delete all the keys\n for (const key of keys) {\n await storageBuckets.delete(\n storageBucketEndpoint,\n key,\n storageBucketToken,\n );\n }\n }, 1000000);\n });\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,UAAU,QAAQ,YAAY;AACvC,SAAS,sBAAsB;AAC/B,SAAS,iCAAiC;AAC1C,SAAS,6BAA6B;
|
|
4
|
+
"sourcesContent": ["import { describe, expect, test } from \"vitest\";\nimport { StorageBuckets } from \"./3-storage-buckets\";\nimport { GraffitiErrorUnauthorized } from \"./utilities\";\nimport { GraffitiErrorNotFound, GraffitiErrorTooLarge } from \"@graffiti-garden/api\";\nimport { encode as dagCborEncode, decode as dagCborDecode } from \"@ipld/dag-cbor\";\n\nexport function storageBucketTests(\n storageBucketEndpoint: string,\n storageBucketToken: string,\n) {\n describe(\"Storage buckets\", async () => {\n const storageBuckets = new StorageBuckets();\n\n test(\"put, get, delete\", async () => {\n const key = Math.random().toString(36).substring(2, 15);\n const input = \"Hello world\";\n const bytes = new TextEncoder().encode(input);\n\n await expect(\n storageBuckets.get(storageBucketEndpoint, key),\n ).rejects.toThrow(GraffitiErrorNotFound);\n\n await storageBuckets.put(\n storageBucketEndpoint,\n key,\n bytes,\n storageBucketToken,\n );\n\n const resultBytes = await storageBuckets.get(\n storageBucketEndpoint,\n key,\n bytes.length,\n );\n const result = new TextDecoder().decode(resultBytes);\n expect(result).toEqual(input);\n\n await storageBuckets.delete(\n storageBucketEndpoint,\n key,\n storageBucketToken,\n );\n\n await expect(\n storageBuckets.get(storageBucketEndpoint, key),\n ).rejects.toThrow(GraffitiErrorNotFound);\n });\n\n test(\"get with limit less than object\", async () => {\n const key = Math.random().toString(36).substring(2, 15);\n const input = \"Hello world\";\n const bytes = new TextEncoder().encode(input);\n\n await storageBuckets.put(\n storageBucketEndpoint,\n key,\n bytes,\n storageBucketToken,\n );\n\n await expect(\n storageBuckets.get(storageBucketEndpoint, key, bytes.length - 1),\n ).rejects.toThrow();\n });\n\n test(\"overwrites across the small and large storage cutoff\", async () => {\n const key = crypto.randomUUID();\n const small = new Uint8Array([1, 2, 3]);\n const large = new Uint8Array(32 * 1024 + 1).fill(42);\n const otherLarge = new Uint8Array(32 * 1024 + 1).fill(43);\n\n await storageBuckets.put(storageBucketEndpoint, key, large, storageBucketToken);\n expect(await storageBuckets.get(storageBucketEndpoint, key)).toEqual(large);\n\n await storageBuckets.put(storageBucketEndpoint, key, otherLarge, storageBucketToken);\n expect(await storageBuckets.get(storageBucketEndpoint, key)).toEqual(otherLarge);\n\n await storageBuckets.put(storageBucketEndpoint, key, small, storageBucketToken);\n expect(await storageBuckets.get(storageBucketEndpoint, key)).toEqual(small);\n\n await storageBuckets.put(storageBucketEndpoint, key, large, storageBucketToken);\n expect(await storageBuckets.get(storageBucketEndpoint, key)).toEqual(large);\n\n await storageBuckets.delete(storageBucketEndpoint, key, storageBucketToken);\n await expect(storageBuckets.get(storageBucketEndpoint, key)).rejects.toThrow(\n GraffitiErrorNotFound,\n );\n });\n\n test(\"batch reads arbitrary keys from one bucket\", async () => {\n const first = crypto.randomUUID();\n const second = crypto.randomUUID();\n const missing = crypto.randomUUID();\n const small = new Uint8Array([1, 2, 3]);\n const large = new Uint8Array(32 * 1024 + 1).fill(7);\n await storageBuckets.put(storageBucketEndpoint, first, small, storageBucketToken);\n await storageBuckets.put(storageBucketEndpoint, second, large, storageBucketToken);\n try {\n const response = await fetch(`${storageBucketEndpoint}/values`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/cbor\" },\n body: dagCborEncode({\n keys: [first, missing, second, first],\n maxValueBytes: small.length,\n }).slice(),\n });\n expect(response.status).toBe(200);\n const decoded = dagCborDecode(await response.arrayBuffer()) as {\n results: Array<{ status: number; value?: Uint8Array; etag?: string }>;\n };\n expect(decoded.results.map(({ status }) => status)).toEqual([200, 404, 413, 200]);\n expect(decoded.results[0].value).toEqual(small);\n expect(decoded.results[0].etag).toBeTypeOf(\"string\");\n expect(decoded.results[3].value).toEqual(small);\n const largeResponse = await fetch(`${storageBucketEndpoint}/values`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/cbor\" },\n body: dagCborEncode({\n keys: [second],\n maxValueBytes: large.length,\n }).slice(),\n });\n expect(largeResponse.status).toBe(200);\n const largeDecoded = dagCborDecode(await largeResponse.arrayBuffer()) as {\n results: Array<{ status: number; value?: Uint8Array }>;\n };\n expect(largeDecoded.results[0].value).toEqual(large);\n\n const oversizedResponse = await fetch(`${storageBucketEndpoint}/values`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/cbor\" },\n body: dagCborEncode({\n keys: [first, second],\n maxValueBytes: 1024 * 1024,\n }).slice(),\n });\n expect(oversizedResponse.status).toBe(400);\n\n const settled = await Promise.allSettled([\n storageBuckets.get(storageBucketEndpoint, first, small.length),\n storageBuckets.get(storageBucketEndpoint, missing, small.length),\n storageBuckets.get(storageBucketEndpoint, second, small.length),\n ]);\n expect(settled[0]).toEqual({ status: \"fulfilled\", value: small });\n expect(settled[1].status).toBe(\"rejected\");\n expect((settled[1] as PromiseRejectedResult).reason).toBeInstanceOf(GraffitiErrorNotFound);\n expect(settled[2].status).toBe(\"rejected\");\n expect((settled[2] as PromiseRejectedResult).reason).toBeInstanceOf(GraffitiErrorTooLarge);\n expect(await storageBuckets.get(storageBucketEndpoint, second)).toEqual(large);\n } finally {\n await storageBuckets.delete(storageBucketEndpoint, first, storageBucketToken);\n await storageBuckets.delete(storageBucketEndpoint, second, storageBucketToken);\n }\n });\n\n test(\"accepts a full batch of maximum-length Unicode keys\", async () => {\n const key = \"\u754C\".repeat(255);\n const body = dagCborEncode({\n keys: Array(32).fill(key),\n maxValueBytes: 0,\n }).slice();\n expect(body.byteLength).toBeGreaterThan(16 * 1024);\n const response = await fetch(`${storageBucketEndpoint}/values`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/cbor\" },\n body,\n });\n expect(response.status).toBe(200);\n const decoded = dagCborDecode(await response.arrayBuffer()) as {\n results: Array<{ status: number }>;\n };\n expect(decoded.results.map(({ status }) => status)).toEqual(Array(32).fill(404));\n });\n\n test(\"concurrent bounded gets share one batch\", async () => {\n const keys = Array.from({ length: 3 }, () => crypto.randomUUID());\n const values = keys.map((_, i) => new Uint8Array([i + 1]));\n for (let i = 0; i < keys.length; i++) {\n await storageBuckets.put(storageBucketEndpoint, keys[i], values[i], storageBucketToken);\n }\n const originalFetch = globalThis.fetch;\n const calls: string[] = [];\n globalThis.fetch = (input, init) => {\n const request = new Request(input, init);\n calls.push(`${request.method} ${request.url}`);\n return originalFetch(input, init);\n };\n try {\n expect(await Promise.all(keys.map((key) =>\n storageBuckets.get(storageBucketEndpoint, key, 1),\n ))).toEqual(values);\n expect(calls.filter((call) => call === `POST ${storageBucketEndpoint}/values`)).toHaveLength(1);\n expect(calls.filter((call) => call.includes(\"/value/\"))).toHaveLength(0);\n } finally {\n globalThis.fetch = originalFetch;\n for (const key of keys) {\n await storageBuckets.delete(storageBucketEndpoint, key, storageBucketToken);\n }\n }\n });\n\n test(\"falls back to GET when a bucket server does not support batches\", async () => {\n const keys = Array.from({ length: 2 }, () => crypto.randomUUID());\n const value = new Uint8Array([42]);\n for (const key of keys) {\n await storageBuckets.put(storageBucketEndpoint, key, value, storageBucketToken);\n }\n const originalFetch = globalThis.fetch;\n let batchAttempts = 0;\n globalThis.fetch = (input, init) => {\n const request = new Request(input, init);\n if (request.method === \"POST\" && request.url === `${storageBucketEndpoint}/values`) {\n batchAttempts++;\n return Promise.resolve(new Response(\"Not found\", { status: 404 }));\n }\n return originalFetch(input, init);\n };\n try {\n const legacyBuckets = new StorageBuckets();\n expect(await Promise.all(keys.map((key) =>\n legacyBuckets.get(storageBucketEndpoint, key, 1),\n ))).toEqual([value, value]);\n expect(await Promise.all(keys.map((key) =>\n legacyBuckets.get(storageBucketEndpoint, key, 1),\n ))).toEqual([value, value]);\n expect(batchAttempts).toBe(1);\n } finally {\n globalThis.fetch = originalFetch;\n for (const key of keys) {\n await storageBuckets.delete(storageBucketEndpoint, key, storageBucketToken);\n }\n }\n });\n\n test(\"a batch error does not disable batching or fall back to GET\", async () => {\n const originalFetch = globalThis.fetch;\n let batchAttempts = 0;\n let getAttempts = 0;\n globalThis.fetch = (input, init) => {\n const request = new Request(input, init);\n if (request.method === \"POST\") {\n batchAttempts++;\n return Promise.resolve(new Response(\"Batch limit exceeded\", { status: 413 }));\n }\n getAttempts++;\n return originalFetch(input, init);\n };\n try {\n const buckets = new StorageBuckets();\n const readPair = () => Promise.all([\n buckets.get(storageBucketEndpoint, \"a\", 1),\n buckets.get(storageBucketEndpoint, \"b\", 1),\n ]);\n await expect(readPair()).rejects.toThrow(\"Batch limit exceeded\");\n await expect(readPair()).rejects.toThrow(\"Batch limit exceeded\");\n expect(batchAttempts).toBe(2);\n expect(getAttempts).toBe(0);\n } finally {\n globalThis.fetch = originalFetch;\n }\n });\n\n test(\"unauthorized\", async () => {\n const key = Math.random().toString(36).substring(2, 15);\n const input = \"Hello world\";\n const bytes = new TextEncoder().encode(input);\n\n await expect(\n storageBuckets.put(storageBucketEndpoint, key, bytes, \"invalid-token\"),\n ).rejects.toThrow(GraffitiErrorUnauthorized);\n await expect(\n storageBuckets.delete(storageBucketEndpoint, key, \"invalid-token\"),\n ).rejects.toThrow(GraffitiErrorUnauthorized);\n await expect(\n storageBuckets.export(storageBucketEndpoint, \"invalid-token\").next(),\n ).rejects.toThrow(GraffitiErrorUnauthorized);\n });\n\n test(\"export\", async () => {\n // Put a whole bunch of stuff so the export needs to page\n const keys = new Set<string>();\n for (let i = 0; i < 256; i++) {\n const key = Math.random().toString(36).substring(2, 15);\n keys.add(key);\n\n const input = \"Hello world \" + i;\n const bytes = new TextEncoder().encode(input);\n await storageBuckets.put(\n storageBucketEndpoint,\n key,\n bytes,\n storageBucketToken,\n );\n }\n\n // Export\n const retrievedKeys = new Set<string>();\n const iterator = storageBuckets.export(\n storageBucketEndpoint,\n storageBucketToken,\n );\n for await (const result of iterator) {\n if (keys.has(result.key)) {\n retrievedKeys.add(result.key);\n }\n }\n expect(retrievedKeys.size).toEqual(keys.size);\n\n // Delete all the keys\n for (const key of keys) {\n await storageBuckets.delete(\n storageBucketEndpoint,\n key,\n storageBucketToken,\n );\n }\n }, 1000000);\n });\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -4,9 +4,18 @@ import {
|
|
|
4
4
|
verifyHTTPSEndpoint
|
|
5
5
|
} from "./utilities";
|
|
6
6
|
import { string, array, object, optional, nullable } from "zod/mini";
|
|
7
|
-
import { decode as dagCborDecode } from "@ipld/dag-cbor";
|
|
7
|
+
import { encode as dagCborEncode, decode as dagCborDecode } from "@ipld/dag-cbor";
|
|
8
|
+
import { GraffitiErrorNotFound, GraffitiErrorTooLarge } from "@graffiti-garden/api";
|
|
9
|
+
const BATCH_KEYS = 32;
|
|
10
|
+
const BATCH_TOTAL_VALUE_BYTES = 1024 * 1024;
|
|
11
|
+
const BATCH_WAIT_MS = 2;
|
|
12
|
+
function batchWidth(maxBytes) {
|
|
13
|
+
return Math.min(BATCH_KEYS, Math.floor(BATCH_TOTAL_VALUE_BYTES / maxBytes));
|
|
14
|
+
}
|
|
8
15
|
class StorageBuckets {
|
|
9
16
|
getAuthorizationEndpoint = getAuthorizationEndpoint;
|
|
17
|
+
queues = /* @__PURE__ */ new Map();
|
|
18
|
+
unsupportedBatchEndpoints = /* @__PURE__ */ new Set();
|
|
10
19
|
async put(storageBucketEndpoint, key, value, authorizationToken) {
|
|
11
20
|
verifyHTTPSEndpoint(storageBucketEndpoint);
|
|
12
21
|
const url = `${storageBucketEndpoint}/value/${encodeURIComponent(key)}`;
|
|
@@ -31,6 +40,88 @@ class StorageBuckets {
|
|
|
31
40
|
}
|
|
32
41
|
async get(storageBucketEndpoint, key, maxBytes) {
|
|
33
42
|
verifyHTTPSEndpoint(storageBucketEndpoint);
|
|
43
|
+
if (maxBytes !== void 0 && Number.isSafeInteger(maxBytes) && maxBytes > 0 && batchWidth(maxBytes) >= 2 && !this.unsupportedBatchEndpoints.has(storageBucketEndpoint)) {
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
let queuesByMaxBytes = this.queues.get(storageBucketEndpoint);
|
|
46
|
+
if (!queuesByMaxBytes) {
|
|
47
|
+
queuesByMaxBytes = /* @__PURE__ */ new Map();
|
|
48
|
+
this.queues.set(storageBucketEndpoint, queuesByMaxBytes);
|
|
49
|
+
}
|
|
50
|
+
let queue = queuesByMaxBytes.get(maxBytes);
|
|
51
|
+
if (!queue) {
|
|
52
|
+
queue = { requests: [] };
|
|
53
|
+
queuesByMaxBytes.set(maxBytes, queue);
|
|
54
|
+
}
|
|
55
|
+
queue.requests.push({ key, resolve, reject });
|
|
56
|
+
if (queue.requests.length === batchWidth(maxBytes)) {
|
|
57
|
+
this.flush(storageBucketEndpoint, maxBytes, queue);
|
|
58
|
+
} else if (queue.timer === void 0) {
|
|
59
|
+
queue.timer = setTimeout(
|
|
60
|
+
() => this.flush(storageBucketEndpoint, maxBytes, queue),
|
|
61
|
+
BATCH_WAIT_MS
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return this.getOne(storageBucketEndpoint, key, maxBytes);
|
|
67
|
+
}
|
|
68
|
+
flush(endpoint, maxBytes, queue) {
|
|
69
|
+
if (queue.timer !== void 0) clearTimeout(queue.timer);
|
|
70
|
+
const requests = queue.requests;
|
|
71
|
+
const queuesByMaxBytes = this.queues.get(endpoint);
|
|
72
|
+
queuesByMaxBytes.delete(maxBytes);
|
|
73
|
+
if (queuesByMaxBytes.size === 0) this.queues.delete(endpoint);
|
|
74
|
+
if (requests.length === 1 || this.unsupportedBatchEndpoints.has(endpoint)) {
|
|
75
|
+
requests.forEach(({ key, resolve, reject }) => {
|
|
76
|
+
void this.getOne(endpoint, key, maxBytes).then(resolve, reject);
|
|
77
|
+
});
|
|
78
|
+
} else {
|
|
79
|
+
void this.getBatch(endpoint, maxBytes, requests);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async getBatch(endpoint, maxBytes, requests) {
|
|
83
|
+
try {
|
|
84
|
+
const response = await fetch(`${endpoint}/values`, {
|
|
85
|
+
method: "POST",
|
|
86
|
+
headers: { "Content-Type": "application/cbor", Accept: "application/cbor" },
|
|
87
|
+
body: dagCborEncode({
|
|
88
|
+
keys: requests.map(({ key }) => key),
|
|
89
|
+
maxValueBytes: maxBytes
|
|
90
|
+
}).slice()
|
|
91
|
+
});
|
|
92
|
+
if (response.status === 404) {
|
|
93
|
+
this.unsupportedBatchEndpoints.add(endpoint);
|
|
94
|
+
await Promise.all(requests.map(async ({ key, resolve, reject }) => {
|
|
95
|
+
try {
|
|
96
|
+
resolve(await this.getOne(endpoint, key, maxBytes));
|
|
97
|
+
} catch (error) {
|
|
98
|
+
reject(error);
|
|
99
|
+
}
|
|
100
|
+
}));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (!response.ok) throw new Error(await response.text());
|
|
104
|
+
const decoded = dagCborDecode(await response.arrayBuffer());
|
|
105
|
+
if (!decoded || typeof decoded !== "object" || !("results" in decoded) || !Array.isArray(decoded.results) || decoded.results.length !== requests.length) throw new Error("Invalid storage batch response");
|
|
106
|
+
decoded.results.forEach((result, index) => {
|
|
107
|
+
const { resolve, reject } = requests[index];
|
|
108
|
+
if (!result || typeof result !== "object" || !("status" in result)) {
|
|
109
|
+
reject(new Error("Invalid storage batch result"));
|
|
110
|
+
} else if (result.status === 200 && "value" in result && result.value instanceof Uint8Array && result.value.byteLength <= maxBytes) {
|
|
111
|
+
resolve(result.value);
|
|
112
|
+
} else if (result.status === 404) {
|
|
113
|
+
reject(new GraffitiErrorNotFound("Value not found"));
|
|
114
|
+
} else if (result.status === 413) {
|
|
115
|
+
reject(new GraffitiErrorTooLarge("Value exceeds maximum byte limit"));
|
|
116
|
+
} else {
|
|
117
|
+
reject(new Error("Invalid storage batch result"));
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
} catch (error) {
|
|
121
|
+
requests.forEach(({ reject }) => reject(error));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async getOne(storageBucketEndpoint, key, maxBytes) {
|
|
34
125
|
const url = `${storageBucketEndpoint}/value/${encodeURIComponent(key)}`;
|
|
35
126
|
const response = await fetchWithErrorHandling(url);
|
|
36
127
|
const reader = response.body?.getReader();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/1-services/3-storage-buckets.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n fetchWithErrorHandling,\n getAuthorizationEndpoint,\n verifyHTTPSEndpoint,\n} from \"./utilities\";\nimport { string, array, object, optional, nullable } from \"zod/mini\";\nimport { decode as dagCborDecode } from \"@ipld/dag-cbor\";\n\nexport class StorageBuckets {\n getAuthorizationEndpoint = getAuthorizationEndpoint;\n\n async put(\n storageBucketEndpoint: string,\n key: string,\n value: Uint8Array,\n authorizationToken: string,\n ): Promise<void> {\n verifyHTTPSEndpoint(storageBucketEndpoint);\n const url = `${storageBucketEndpoint}/value/${encodeURIComponent(key)}`;\n\n await fetchWithErrorHandling(url, {\n method: \"PUT\",\n headers: {\n \"Content-Type\": \"application/octet-stream\",\n Authorization: `Bearer ${authorizationToken}`,\n },\n body: value.slice(),\n });\n }\n\n async delete(\n storageBucketEndpoint: string,\n key: string,\n authorizationToken: string,\n ): Promise<void> {\n verifyHTTPSEndpoint(storageBucketEndpoint);\n const url = `${storageBucketEndpoint}/value/${encodeURIComponent(key)}`;\n\n await fetchWithErrorHandling(url, {\n method: \"DELETE\",\n headers: {\n Authorization: `Bearer ${authorizationToken}`,\n },\n });\n }\n\n async get(\n storageBucketEndpoint: string,\n key: string,\n maxBytes?: number,\n ): Promise<Uint8Array> {\n verifyHTTPSEndpoint(storageBucketEndpoint);\n const url = `${storageBucketEndpoint}/value/${encodeURIComponent(key)}`;\n\n const response = await fetchWithErrorHandling(url);\n\n const reader = response.body?.getReader();\n if (!reader) {\n throw new Error(\"Failed to read value from storage bucket\");\n }\n\n const contentLengthHeader = response.headers.get(\"Content-Length\");\n const parsedContentLength = contentLengthHeader\n ? Number(contentLengthHeader)\n : undefined;\n\n const hasValidContentLength =\n !!parsedContentLength &&\n !!Number.isFinite(parsedContentLength) &&\n parsedContentLength >= 0;\n\n // Fast path: Content-Length exists and is valid\n if (hasValidContentLength) {\n const contentLength = parsedContentLength!;\n if (maxBytes !== undefined && contentLength > maxBytes) {\n throw new Error(\"Value exceeds maximum byte limit\");\n }\n\n const out = new Uint8Array(contentLength);\n let offset = 0;\n let completed = false;\n\n try {\n while (offset <= out.length) {\n const { done, value } = await reader.read();\n\n if (done) {\n completed = true;\n break;\n }\n if (!value || value.length === 0) continue;\n\n const nextOffset = offset + value.length;\n if (nextOffset > out.length) {\n throw new Error(\"Received more data than expected\");\n }\n\n out.set(value, offset);\n offset = nextOffset;\n }\n } finally {\n reader.releaseLock();\n }\n\n if (!completed) {\n throw new Error(\"Failed to read complete value from storage bucket\");\n }\n\n return offset === contentLength ? out : out.slice(0, offset);\n }\n\n // Fallback path: no (usable) Content-Length\n const chunks: Uint8Array[] = [];\n let total = 0;\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n\n if (done) break;\n if (!value || value.length === 0) continue;\n\n total += value.length;\n if (maxBytes !== undefined && total > maxBytes) {\n throw new Error(\"Value exceeds maximum byte limit\");\n }\n\n // Copy because some implementations reuse the underlying buffer\n chunks.push(value.slice());\n }\n } finally {\n reader.releaseLock();\n }\n\n // Concatenate chunks into one Uint8Array\n const out = new Uint8Array(total);\n let offset = 0;\n for (const c of chunks) {\n out.set(c, offset);\n offset += c.length;\n }\n return out;\n }\n\n async *export(\n storageBucketEndpoint: string,\n authorizationToken: string,\n ): AsyncGenerator<{ key: string }> {\n verifyHTTPSEndpoint(storageBucketEndpoint);\n const url = `${storageBucketEndpoint}/export`;\n\n let cursor: string | undefined = undefined;\n while (true) {\n const response = await fetchWithErrorHandling(\n cursor ? `${url}?cursor=${encodeURIComponent(cursor)}` : url,\n {\n headers: {\n Authorization: `Bearer ${authorizationToken}`,\n },\n },\n );\n\n const blob = await response.blob();\n const cbor = dagCborDecode(await blob.arrayBuffer());\n const data = ExportSchema.parse(cbor);\n\n for (const key of data.keys) {\n yield { key };\n }\n\n if (data.cursor) {\n cursor = data.cursor;\n } else {\n break;\n }\n }\n }\n}\n\nconst ExportSchema = object({\n keys: array(string()),\n cursor: optional(nullable(string())),\n});\n"],
|
|
5
|
-
"mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,QAAQ,OAAO,QAAQ,UAAU,gBAAgB;AAC1D,SAAS,UAAU,qBAAqB;
|
|
4
|
+
"sourcesContent": ["import {\n fetchWithErrorHandling,\n getAuthorizationEndpoint,\n verifyHTTPSEndpoint,\n} from \"./utilities\";\nimport { string, array, object, optional, nullable } from \"zod/mini\";\nimport { encode as dagCborEncode, decode as dagCborDecode } from \"@ipld/dag-cbor\";\nimport { GraffitiErrorNotFound, GraffitiErrorTooLarge } from \"@graffiti-garden/api\";\n\n// All batch-capable bucket servers accept these transport limits.\nconst BATCH_KEYS = 32; // total number of keys in a batch request\nconst BATCH_TOTAL_VALUE_BYTES = 1024 * 1024; // total response limit\n\n// This is a heuristic for how long to wait to fill a batch request\n// before sending it. Just enough waiting to capture a few more requests,\n// but not so long that it adds noticeable latency.\nconst BATCH_WAIT_MS = 2;\n\nfunction batchWidth(maxBytes: number) {\n return Math.min(BATCH_KEYS, Math.floor(BATCH_TOTAL_VALUE_BYTES / maxBytes));\n}\n\ntype PendingGet = {\n key: string;\n resolve: (value: Uint8Array) => void;\n reject: (reason: unknown) => void;\n};\ntype BatchQueue = { requests: PendingGet[]; timer?: ReturnType<typeof setTimeout> };\n\nexport class StorageBuckets {\n getAuthorizationEndpoint = getAuthorizationEndpoint;\n private readonly queues = new Map<string, Map<number, BatchQueue>>();\n private readonly unsupportedBatchEndpoints = new Set<string>();\n\n async put(\n storageBucketEndpoint: string,\n key: string,\n value: Uint8Array,\n authorizationToken: string,\n ): Promise<void> {\n verifyHTTPSEndpoint(storageBucketEndpoint);\n const url = `${storageBucketEndpoint}/value/${encodeURIComponent(key)}`;\n\n await fetchWithErrorHandling(url, {\n method: \"PUT\",\n headers: {\n \"Content-Type\": \"application/octet-stream\",\n Authorization: `Bearer ${authorizationToken}`,\n },\n body: value.slice(),\n });\n }\n\n async delete(\n storageBucketEndpoint: string,\n key: string,\n authorizationToken: string,\n ): Promise<void> {\n verifyHTTPSEndpoint(storageBucketEndpoint);\n const url = `${storageBucketEndpoint}/value/${encodeURIComponent(key)}`;\n\n await fetchWithErrorHandling(url, {\n method: \"DELETE\",\n headers: {\n Authorization: `Bearer ${authorizationToken}`,\n },\n });\n }\n\n async get(\n storageBucketEndpoint: string,\n key: string,\n maxBytes?: number,\n ): Promise<Uint8Array> {\n verifyHTTPSEndpoint(storageBucketEndpoint);\n if (\n maxBytes !== undefined &&\n Number.isSafeInteger(maxBytes) &&\n maxBytes > 0 &&\n batchWidth(maxBytes) >= 2 &&\n !this.unsupportedBatchEndpoints.has(storageBucketEndpoint)\n ) {\n return new Promise<Uint8Array>((resolve, reject) => {\n let queuesByMaxBytes = this.queues.get(storageBucketEndpoint);\n if (!queuesByMaxBytes) {\n queuesByMaxBytes = new Map();\n this.queues.set(storageBucketEndpoint, queuesByMaxBytes);\n }\n let queue = queuesByMaxBytes.get(maxBytes);\n if (!queue) {\n queue = { requests: [] };\n queuesByMaxBytes.set(maxBytes, queue);\n }\n queue.requests.push({ key, resolve, reject });\n if (queue.requests.length === batchWidth(maxBytes)) {\n this.flush(storageBucketEndpoint, maxBytes, queue);\n } else if (queue.timer === undefined) {\n queue.timer = setTimeout(\n () => this.flush(storageBucketEndpoint, maxBytes, queue),\n BATCH_WAIT_MS,\n );\n }\n });\n }\n return this.getOne(storageBucketEndpoint, key, maxBytes);\n }\n\n private flush(endpoint: string, maxBytes: number, queue: BatchQueue) {\n if (queue.timer !== undefined) clearTimeout(queue.timer);\n const requests = queue.requests;\n const queuesByMaxBytes = this.queues.get(endpoint)!;\n queuesByMaxBytes.delete(maxBytes);\n if (queuesByMaxBytes.size === 0) this.queues.delete(endpoint);\n if (requests.length === 1 || this.unsupportedBatchEndpoints.has(endpoint)) {\n requests.forEach(({ key, resolve, reject }) => {\n void this.getOne(endpoint, key, maxBytes).then(resolve, reject);\n });\n } else {\n void this.getBatch(endpoint, maxBytes, requests);\n }\n }\n\n private async getBatch(endpoint: string, maxBytes: number, requests: PendingGet[]) {\n try {\n const response = await fetch(`${endpoint}/values`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/cbor\", Accept: \"application/cbor\" },\n body: dagCborEncode({\n keys: requests.map(({ key }) => key),\n maxValueBytes: maxBytes,\n }).slice(),\n });\n if (response.status === 404) {\n // An older bucket server has no /values route but still serves GETs.\n this.unsupportedBatchEndpoints.add(endpoint);\n await Promise.all(requests.map(async ({ key, resolve, reject }) => {\n try {\n resolve(await this.getOne(endpoint, key, maxBytes));\n } catch (error) {\n reject(error);\n }\n }));\n return;\n }\n if (!response.ok) throw new Error(await response.text());\n const decoded = dagCborDecode(await response.arrayBuffer());\n if (\n !decoded || typeof decoded !== \"object\" ||\n !(\"results\" in decoded) || !Array.isArray(decoded.results) ||\n decoded.results.length !== requests.length\n ) throw new Error(\"Invalid storage batch response\");\n\n decoded.results.forEach((result: unknown, index: number) => {\n const { resolve, reject } = requests[index];\n if (!result || typeof result !== \"object\" || !(\"status\" in result)) {\n reject(new Error(\"Invalid storage batch result\"));\n } else if (\n result.status === 200 && \"value\" in result &&\n result.value instanceof Uint8Array &&\n result.value.byteLength <= maxBytes\n ) {\n resolve(result.value);\n } else if (result.status === 404) {\n reject(new GraffitiErrorNotFound(\"Value not found\"));\n } else if (result.status === 413) {\n reject(new GraffitiErrorTooLarge(\"Value exceeds maximum byte limit\"));\n } else {\n reject(new Error(\"Invalid storage batch result\"));\n }\n });\n } catch (error) {\n requests.forEach(({ reject }) => reject(error));\n }\n }\n\n private async getOne(\n storageBucketEndpoint: string,\n key: string,\n maxBytes?: number,\n ): Promise<Uint8Array> {\n const url = `${storageBucketEndpoint}/value/${encodeURIComponent(key)}`;\n\n const response = await fetchWithErrorHandling(url);\n\n const reader = response.body?.getReader();\n if (!reader) {\n throw new Error(\"Failed to read value from storage bucket\");\n }\n\n const contentLengthHeader = response.headers.get(\"Content-Length\");\n const parsedContentLength = contentLengthHeader\n ? Number(contentLengthHeader)\n : undefined;\n\n const hasValidContentLength =\n !!parsedContentLength &&\n !!Number.isFinite(parsedContentLength) &&\n parsedContentLength >= 0;\n\n // Fast path: Content-Length exists and is valid\n if (hasValidContentLength) {\n const contentLength = parsedContentLength!;\n if (maxBytes !== undefined && contentLength > maxBytes) {\n throw new Error(\"Value exceeds maximum byte limit\");\n }\n\n const out = new Uint8Array(contentLength);\n let offset = 0;\n let completed = false;\n\n try {\n while (offset <= out.length) {\n const { done, value } = await reader.read();\n\n if (done) {\n completed = true;\n break;\n }\n if (!value || value.length === 0) continue;\n\n const nextOffset = offset + value.length;\n if (nextOffset > out.length) {\n throw new Error(\"Received more data than expected\");\n }\n\n out.set(value, offset);\n offset = nextOffset;\n }\n } finally {\n reader.releaseLock();\n }\n\n if (!completed) {\n throw new Error(\"Failed to read complete value from storage bucket\");\n }\n\n return offset === contentLength ? out : out.slice(0, offset);\n }\n\n // Fallback path: no (usable) Content-Length\n const chunks: Uint8Array[] = [];\n let total = 0;\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n\n if (done) break;\n if (!value || value.length === 0) continue;\n\n total += value.length;\n if (maxBytes !== undefined && total > maxBytes) {\n throw new Error(\"Value exceeds maximum byte limit\");\n }\n\n // Copy because some implementations reuse the underlying buffer\n chunks.push(value.slice());\n }\n } finally {\n reader.releaseLock();\n }\n\n // Concatenate chunks into one Uint8Array\n const out = new Uint8Array(total);\n let offset = 0;\n for (const c of chunks) {\n out.set(c, offset);\n offset += c.length;\n }\n return out;\n }\n\n async *export(\n storageBucketEndpoint: string,\n authorizationToken: string,\n ): AsyncGenerator<{ key: string }> {\n verifyHTTPSEndpoint(storageBucketEndpoint);\n const url = `${storageBucketEndpoint}/export`;\n\n let cursor: string | undefined = undefined;\n while (true) {\n const response = await fetchWithErrorHandling(\n cursor ? `${url}?cursor=${encodeURIComponent(cursor)}` : url,\n {\n headers: {\n Authorization: `Bearer ${authorizationToken}`,\n },\n },\n );\n\n const blob = await response.blob();\n const cbor = dagCborDecode(await blob.arrayBuffer());\n const data = ExportSchema.parse(cbor);\n\n for (const key of data.keys) {\n yield { key };\n }\n\n if (data.cursor) {\n cursor = data.cursor;\n } else {\n break;\n }\n }\n }\n}\n\nconst ExportSchema = object({\n keys: array(string()),\n cursor: optional(nullable(string())),\n});\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["out", "offset"]
|
|
7
7
|
}
|
|
@@ -2,6 +2,7 @@ import { assert, describe, expect, test } from "vitest";
|
|
|
2
2
|
import { Inboxes, LABELED_MESSAGE_LABEL_KEY } from "./4-inboxes";
|
|
3
3
|
import { GraffitiErrorUnauthorized } from "./utilities";
|
|
4
4
|
import { randomBytes } from "@noble/hashes/utils.js";
|
|
5
|
+
import { encode as dagCborEncode, decode as dagCborDecode } from "@ipld/dag-cbor";
|
|
5
6
|
function inboxTests(inboxEndpoint, inboxToken) {
|
|
6
7
|
describe("Inboxes", async () => {
|
|
7
8
|
const inboxes = new Inboxes();
|
|
@@ -107,6 +108,25 @@ function inboxTests(inboxEndpoint, inboxToken) {
|
|
|
107
108
|
}
|
|
108
109
|
});
|
|
109
110
|
}
|
|
111
|
+
let cursor;
|
|
112
|
+
for (const [page, expectedCount] of [10, 100, 101].entries()) {
|
|
113
|
+
const response = await fetch(
|
|
114
|
+
`${inboxEndpoint}/query${cursor ? `?cursor=${encodeURIComponent(cursor)}` : ""}`,
|
|
115
|
+
{
|
|
116
|
+
method: "POST",
|
|
117
|
+
headers: {
|
|
118
|
+
"Content-Type": "application/cbor",
|
|
119
|
+
Authorization: `Bearer ${inboxToken}`
|
|
120
|
+
},
|
|
121
|
+
body: cursor ? void 0 : dagCborEncode({ tags: [tags[1]], schema: {} }).slice()
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
expect(response.status).toBe(200);
|
|
125
|
+
const pageResult = dagCborDecode(new Uint8Array(await response.arrayBuffer()));
|
|
126
|
+
expect(pageResult.results).toHaveLength(expectedCount);
|
|
127
|
+
expect(pageResult.hasMore).toBe(page < 2);
|
|
128
|
+
cursor = pageResult.cursor;
|
|
129
|
+
}
|
|
110
130
|
const iterator = inboxes.query(
|
|
111
131
|
inboxEndpoint,
|
|
112
132
|
[randomBytes(), tags[1], randomBytes()],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/1-services/4-inboxes-tests.ts"],
|
|
4
|
-
"sourcesContent": ["import { assert, describe, expect, test } from \"vitest\";\nimport { Inboxes, LABELED_MESSAGE_LABEL_KEY } from \"./4-inboxes\";\nimport { GraffitiErrorUnauthorized } from \"./utilities\";\nimport { randomBytes } from \"@noble/hashes/utils.js\";\nimport type { GraffitiObjectBase } from \"@graffiti-garden/api\";\n\nexport function inboxTests(inboxEndpoint: string, inboxToken: string) {\n describe(\"Inboxes\", async () => {\n const inboxes = new Inboxes();\n\n test(\"send, get\", async () => {\n const tags = [randomBytes(), randomBytes()];\n const metadata = randomBytes();\n const object: GraffitiObjectBase = {\n url: \"url:example\",\n actor: \"did:example\",\n channels: [\"example\", \"something\"],\n value: {\n nested: {\n property: [1, \"askdfj\", null],\n },\n },\n allowed: [\"did:example2\"],\n };\n\n const sending = {\n m: metadata,\n o: object,\n t: tags,\n };\n const messageId = await inboxes.send(inboxEndpoint, sending);\n\n // Get the message back\n const message = await inboxes.get(inboxEndpoint, messageId, inboxToken);\n assert(message !== null);\n expect(message.m).toEqual(sending);\n expect(message.l).toEqual(0);\n\n const iterator = inboxes.query<{}>(inboxEndpoint, tags, {}, inboxToken);\n\n const result = await iterator.next();\n assert(!result.done);\n\n // No label yet so it must be zero\n expect(result.value.l).toEqual(0);\n\n expect(result.value.m.t).toEqual(tags);\n expect(result.value.m.o).toEqual(object);\n expect(result.value.id).toEqual(messageId);\n\n const endResult = await iterator.next();\n expect(endResult.done).toBe(true);\n\n // Label the message\n await inboxes.label(inboxEndpoint, messageId, 42, inboxToken);\n\n const iterator2 = inboxes.query<{}>(inboxEndpoint, tags, {}, inboxToken);\n\n const result2 = await iterator2.next();\n assert(!result2.done);\n expect(result2.value.l).toEqual(42);\n const endResult2 = await iterator2.next();\n expect(endResult2.done).toBe(true);\n\n const message2 = await inboxes.get(inboxEndpoint, messageId, inboxToken);\n assert(message2 !== null);\n expect(message2.m).toEqual(sending);\n expect(message2.l).toEqual(42);\n });\n\n test(\"query with continue\", async () => {\n const tags = [randomBytes(), randomBytes()];\n\n const nullResult = await inboxes\n .query<{}>(inboxEndpoint, tags, {}, inboxToken)\n .next();\n assert(nullResult.done);\n const cursor = nullResult.value;\n\n const metadata = randomBytes();\n\n const messageId = await inboxes.send(inboxEndpoint, {\n o: {\n url: \"url:example\",\n actor: \"did:example\",\n channels: [\"example\", \"something\"],\n value: {\n nested: {\n property: [1, \"askdfj\", null],\n },\n },\n allowed: [\"did:example2\"],\n },\n t: [randomBytes(), tags[0]],\n m: metadata,\n });\n\n const result = await inboxes\n .continueQuery(inboxEndpoint, cursor, inboxToken)\n .next();\n assert(!result.done);\n expect(result.value.id).toEqual(messageId);\n });\n\n test(\"unauthorized access\", async () => {\n const tags = [randomBytes()];\n\n await expect(\n inboxes.query(inboxEndpoint, tags, {}, \"invalid-token\").next(),\n ).rejects.toThrowError(GraffitiErrorUnauthorized);\n await expect(\n inboxes.label(inboxEndpoint, \"1\", 1, \"invalid-token\"),\n ).rejects.toThrowError(GraffitiErrorUnauthorized);\n await expect(\n inboxes.export(inboxEndpoint, \"invalid-token\").next(),\n ).rejects.toThrowError(GraffitiErrorUnauthorized);\n }, 30000);\n\n test(\"query paged\", async () => {\n const tags = [randomBytes(), randomBytes()];\n\n const numSends = 211;\n for (let i = 0; i < numSends; i++) {\n await inboxes.send(inboxEndpoint, {\n t: tags,\n m: randomBytes(),\n o: {\n url: \"url:example\",\n actor: \"did:example\",\n channels: [\"example\", \"something\"],\n value: {\n nested: {\n property: [1, \"askdfj\", null],\n },\n },\n allowed: [\"did:example2\"],\n },\n });\n }\n\n const iterator = inboxes.query(\n inboxEndpoint,\n [randomBytes(), tags[1], randomBytes()],\n {},\n inboxToken,\n );\n\n let count = 0;\n for await (const _ of iterator) {\n count++;\n }\n\n expect(count).toBe(numSends);\n }, 100000);\n });\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,QAAQ,UAAU,QAAQ,YAAY;AAC/C,SAAS,SAAS,iCAAiC;AACnD,SAAS,iCAAiC;AAC1C,SAAS,mBAAmB;
|
|
4
|
+
"sourcesContent": ["import { assert, describe, expect, test } from \"vitest\";\nimport { Inboxes, LABELED_MESSAGE_LABEL_KEY } from \"./4-inboxes\";\nimport { GraffitiErrorUnauthorized } from \"./utilities\";\nimport { randomBytes } from \"@noble/hashes/utils.js\";\nimport type { GraffitiObjectBase } from \"@graffiti-garden/api\";\nimport { encode as dagCborEncode, decode as dagCborDecode } from \"@ipld/dag-cbor\";\n\nexport function inboxTests(inboxEndpoint: string, inboxToken: string) {\n describe(\"Inboxes\", async () => {\n const inboxes = new Inboxes();\n\n test(\"send, get\", async () => {\n const tags = [randomBytes(), randomBytes()];\n const metadata = randomBytes();\n const object: GraffitiObjectBase = {\n url: \"url:example\",\n actor: \"did:example\",\n channels: [\"example\", \"something\"],\n value: {\n nested: {\n property: [1, \"askdfj\", null],\n },\n },\n allowed: [\"did:example2\"],\n };\n\n const sending = {\n m: metadata,\n o: object,\n t: tags,\n };\n const messageId = await inboxes.send(inboxEndpoint, sending);\n\n // Get the message back\n const message = await inboxes.get(inboxEndpoint, messageId, inboxToken);\n assert(message !== null);\n expect(message.m).toEqual(sending);\n expect(message.l).toEqual(0);\n\n const iterator = inboxes.query<{}>(inboxEndpoint, tags, {}, inboxToken);\n\n const result = await iterator.next();\n assert(!result.done);\n\n // No label yet so it must be zero\n expect(result.value.l).toEqual(0);\n\n expect(result.value.m.t).toEqual(tags);\n expect(result.value.m.o).toEqual(object);\n expect(result.value.id).toEqual(messageId);\n\n const endResult = await iterator.next();\n expect(endResult.done).toBe(true);\n\n // Label the message\n await inboxes.label(inboxEndpoint, messageId, 42, inboxToken);\n\n const iterator2 = inboxes.query<{}>(inboxEndpoint, tags, {}, inboxToken);\n\n const result2 = await iterator2.next();\n assert(!result2.done);\n expect(result2.value.l).toEqual(42);\n const endResult2 = await iterator2.next();\n expect(endResult2.done).toBe(true);\n\n const message2 = await inboxes.get(inboxEndpoint, messageId, inboxToken);\n assert(message2 !== null);\n expect(message2.m).toEqual(sending);\n expect(message2.l).toEqual(42);\n });\n\n test(\"query with continue\", async () => {\n const tags = [randomBytes(), randomBytes()];\n\n const nullResult = await inboxes\n .query<{}>(inboxEndpoint, tags, {}, inboxToken)\n .next();\n assert(nullResult.done);\n const cursor = nullResult.value;\n\n const metadata = randomBytes();\n\n const messageId = await inboxes.send(inboxEndpoint, {\n o: {\n url: \"url:example\",\n actor: \"did:example\",\n channels: [\"example\", \"something\"],\n value: {\n nested: {\n property: [1, \"askdfj\", null],\n },\n },\n allowed: [\"did:example2\"],\n },\n t: [randomBytes(), tags[0]],\n m: metadata,\n });\n\n const result = await inboxes\n .continueQuery(inboxEndpoint, cursor, inboxToken)\n .next();\n assert(!result.done);\n expect(result.value.id).toEqual(messageId);\n });\n\n test(\"unauthorized access\", async () => {\n const tags = [randomBytes()];\n\n await expect(\n inboxes.query(inboxEndpoint, tags, {}, \"invalid-token\").next(),\n ).rejects.toThrowError(GraffitiErrorUnauthorized);\n await expect(\n inboxes.label(inboxEndpoint, \"1\", 1, \"invalid-token\"),\n ).rejects.toThrowError(GraffitiErrorUnauthorized);\n await expect(\n inboxes.export(inboxEndpoint, \"invalid-token\").next(),\n ).rejects.toThrowError(GraffitiErrorUnauthorized);\n }, 30000);\n\n test(\"query paged\", async () => {\n const tags = [randomBytes(), randomBytes()];\n\n const numSends = 211;\n for (let i = 0; i < numSends; i++) {\n await inboxes.send(inboxEndpoint, {\n t: tags,\n m: randomBytes(),\n o: {\n url: \"url:example\",\n actor: \"did:example\",\n channels: [\"example\", \"something\"],\n value: {\n nested: {\n property: [1, \"askdfj\", null],\n },\n },\n allowed: [\"did:example2\"],\n },\n });\n }\n\n let cursor: string | undefined;\n for (const [page, expectedCount] of [10, 100, 101].entries()) {\n const response = await fetch(\n `${inboxEndpoint}/query${cursor ? `?cursor=${encodeURIComponent(cursor)}` : \"\"}`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/cbor\",\n Authorization: `Bearer ${inboxToken}`,\n },\n body: cursor ? undefined : dagCborEncode({ tags: [tags[1]], schema: {} }).slice(),\n },\n );\n expect(response.status).toBe(200);\n const pageResult = dagCborDecode(new Uint8Array(await response.arrayBuffer())) as {\n results: unknown[];\n hasMore: boolean;\n cursor: string;\n };\n expect(pageResult.results).toHaveLength(expectedCount);\n expect(pageResult.hasMore).toBe(page < 2);\n cursor = pageResult.cursor;\n }\n\n const iterator = inboxes.query(\n inboxEndpoint,\n [randomBytes(), tags[1], randomBytes()],\n {},\n inboxToken,\n );\n\n let count = 0;\n for await (const _ of iterator) {\n count++;\n }\n\n expect(count).toBe(numSends);\n }, 100000);\n });\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -103,7 +103,7 @@ class Inboxes {
|
|
|
103
103
|
});
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
|
-
async fetchMessageBatch(inboxUrl, type, body, inboxToken, cursor) {
|
|
106
|
+
async fetchMessageBatch(inboxUrl, type, body, inboxToken, cursor, signal) {
|
|
107
107
|
const response = await fetchWithErrorHandling(
|
|
108
108
|
`${inboxUrl}/${type}${cursor ? `?cursor=${encodeURIComponent(cursor)}` : ""}`,
|
|
109
109
|
{
|
|
@@ -114,7 +114,8 @@ class Inboxes {
|
|
|
114
114
|
Authorization: `Bearer ${inboxToken}`
|
|
115
115
|
} : {}
|
|
116
116
|
},
|
|
117
|
-
body
|
|
117
|
+
body,
|
|
118
|
+
signal
|
|
118
119
|
}
|
|
119
120
|
);
|
|
120
121
|
const retryAfterHeader = response.headers.get("Retry-After");
|
|
@@ -297,13 +298,11 @@ class Inboxes {
|
|
|
297
298
|
};
|
|
298
299
|
}
|
|
299
300
|
);
|
|
300
|
-
await
|
|
301
|
-
labeledMessages.map(
|
|
302
|
-
(m)
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
)
|
|
306
|
-
)
|
|
301
|
+
await cache.messages.setMany(
|
|
302
|
+
labeledMessages.map((m) => [
|
|
303
|
+
getMessageCacheKey(inboxUrl, m[LABELED_MESSAGE_ID_KEY]),
|
|
304
|
+
m
|
|
305
|
+
])
|
|
307
306
|
);
|
|
308
307
|
messageIds = [
|
|
309
308
|
...messageIds,
|
|
@@ -318,19 +317,28 @@ class Inboxes {
|
|
|
318
317
|
waitTil
|
|
319
318
|
});
|
|
320
319
|
cacheNumSeen += labeledMessages.length;
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
await waitFor(waitTil);
|
|
324
|
-
const out = await this.fetchMessageBatch(
|
|
320
|
+
const prefetchController = new AbortController();
|
|
321
|
+
const nextPage = hasMore ? waitFor(waitTil).then(() => this.fetchMessageBatch(
|
|
325
322
|
inboxUrl,
|
|
326
323
|
type,
|
|
327
324
|
void 0,
|
|
328
325
|
// Body is never past the first time
|
|
329
326
|
inboxToken,
|
|
330
|
-
cursor
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
327
|
+
cursor,
|
|
328
|
+
prefetchController.signal
|
|
329
|
+
)).then((value) => ({ value }), (error) => ({ error })) : null;
|
|
330
|
+
let consumedPage = false;
|
|
331
|
+
try {
|
|
332
|
+
for (const m of labeledMessages) yield m;
|
|
333
|
+
consumedPage = true;
|
|
334
|
+
} finally {
|
|
335
|
+
if (!consumedPage) prefetchController.abort();
|
|
336
|
+
}
|
|
337
|
+
if (!hasMore) break;
|
|
338
|
+
const out = await nextPage;
|
|
339
|
+
if ("error" in out) throw out.error;
|
|
340
|
+
response = out.value.response;
|
|
341
|
+
waitTil = out.value.waitTil;
|
|
334
342
|
}
|
|
335
343
|
const outputCursor = {
|
|
336
344
|
numSeen: cacheNumSeen,
|
|
@@ -484,6 +492,14 @@ async function createCache() {
|
|
|
484
492
|
set: async (k, v) => {
|
|
485
493
|
await db.put("m", v, k);
|
|
486
494
|
},
|
|
495
|
+
setMany: async (entries) => {
|
|
496
|
+
if (entries.length === 0) return;
|
|
497
|
+
const tx = db.transaction("m", "readwrite");
|
|
498
|
+
await Promise.all([
|
|
499
|
+
...entries.map(([k, v]) => tx.store.put(v, k)),
|
|
500
|
+
tx.done
|
|
501
|
+
]);
|
|
502
|
+
},
|
|
487
503
|
del: (k) => db.delete("m", k)
|
|
488
504
|
},
|
|
489
505
|
messageIds: {
|
|
@@ -510,6 +526,9 @@ async function createCache() {
|
|
|
510
526
|
messages: {
|
|
511
527
|
get: async (k) => m.get(k),
|
|
512
528
|
set: async (k, v) => void m.set(k, v),
|
|
529
|
+
setMany: async (entries) => {
|
|
530
|
+
for (const [k, v] of entries) m.set(k, v);
|
|
531
|
+
},
|
|
513
532
|
del: async (k) => void m.delete(k)
|
|
514
533
|
},
|
|
515
534
|
messageIds: {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/1-services/4-inboxes.ts"],
|
|
4
|
-
"sourcesContent": ["import type { JSONSchema, GraffitiObject } from \"@graffiti-garden/api\";\nimport {\n getAuthorizationEndpoint,\n fetchWithErrorHandling,\n verifyHTTPSEndpoint,\n} from \"./utilities\";\nimport {\n compileGraffitiObjectSchema,\n GraffitiErrorCursorExpired,\n GraffitiErrorNotFound,\n} from \"@graffiti-garden/api\";\nimport {\n encode as dagCborEncode,\n decode as dagCborDecode,\n} from \"@ipld/dag-cbor\";\nimport {\n type infer as infer_,\n string,\n url,\n array,\n optional,\n nullable,\n strictObject,\n looseObject,\n nonnegative,\n int,\n boolean,\n custom,\n number,\n union,\n} from \"zod/mini\";\n\nexport class Inboxes {\n getAuthorizationEndpoint = getAuthorizationEndpoint;\n protected cache_: Promise<Cache> | null = null;\n protected get cache() {\n if (!this.cache_) {\n this.cache_ = createCache();\n }\n return this.cache_;\n }\n\n async send(inboxUrl: string, message: Message<{}>): Promise<string> {\n verifyHTTPSEndpoint(inboxUrl);\n const url = `${inboxUrl}/send`;\n\n const response = await fetchWithErrorHandling(url, {\n method: \"PUT\",\n headers: {\n \"Content-Type\": \"application/cbor\",\n },\n body: new Uint8Array(dagCborEncode({ m: message })),\n });\n\n const blob = await response.blob();\n const cbor = dagCborDecode(await blob.arrayBuffer());\n const parsed = SendResponseSchema.parse(cbor);\n return parsed.id;\n }\n\n async get(\n inboxUrl: string,\n messageId: string,\n inboxToken?: string | null,\n ): Promise<LabeledMessageBase | null> {\n const messageCacheKey = getMessageCacheKey(inboxUrl, messageId);\n const cache = await this.cache;\n const cached = await cache.messages.get(messageCacheKey);\n if (cached !== undefined) return cached;\n\n const url = `${inboxUrl}/message/${messageId}`;\n let response: Response | null = null;\n try {\n response = await fetchWithErrorHandling(url, {\n method: \"GET\",\n headers: {\n ...(inboxToken\n ? {\n Authorization: `Bearer ${inboxToken}`,\n }\n : {}),\n },\n });\n } catch (e) {\n if (e instanceof GraffitiErrorNotFound) {\n await cache.messages.set(messageCacheKey, null);\n return null;\n }\n throw e;\n }\n\n const blob = await response.blob();\n const cbor = dagCborDecode(await blob.arrayBuffer());\n const parsed = LabeledMessageBaseSchema.parse(cbor);\n\n await cache.messages.set(messageCacheKey, parsed);\n return parsed;\n }\n\n async label(\n inboxUrl: string,\n messageId: string,\n label: number,\n inboxToken?: string | null,\n ): Promise<void> {\n verifyHTTPSEndpoint(inboxUrl);\n\n if (inboxToken) {\n const url = `${inboxUrl}/label/${messageId}`;\n\n await fetchWithErrorHandling(url, {\n method: \"PUT\",\n headers: {\n \"Content-Type\": \"application/cbor\",\n Authorization: `Bearer ${inboxToken}`,\n },\n body: new Uint8Array(dagCborEncode({ l: label })),\n });\n }\n\n // Update the cache, even if no token.\n // Therefore people not logged in do not need to\n // repeatedly re-validate objects.\n const cache = await this.cache;\n const messageCacheKey = getMessageCacheKey(inboxUrl, messageId);\n const result = await cache.messages.get(messageCacheKey);\n if (result) {\n await cache.messages.set(messageCacheKey, {\n ...result,\n l: label,\n });\n }\n }\n\n protected async fetchMessageBatch(\n inboxUrl: string,\n type: \"query\" | \"export\",\n body: Uint8Array<ArrayBuffer> | undefined,\n inboxToken?: string | null,\n cursor?: string,\n ) {\n const response = await fetchWithErrorHandling(\n `${inboxUrl}/${type}${cursor ? `?cursor=${encodeURIComponent(cursor)}` : \"\"}`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/cbor\",\n ...(inboxToken\n ? {\n Authorization: `Bearer ${inboxToken}`,\n }\n : {}),\n },\n body,\n },\n );\n const retryAfterHeader = response.headers.get(\"Retry-After\");\n const retryAfter = retryAfterHeader\n ? parseInt(retryAfterHeader)\n : undefined;\n\n const waitTil =\n retryAfter && Number.isFinite(retryAfter)\n ? Date.now() + retryAfter * 1000\n : undefined;\n\n return { response, waitTil };\n }\n\n protected async *yieldFromCache(\n cache: Cache,\n inboxUrl: string,\n messageIdsCacheKey: string,\n cachedMessageIds: CacheQueryValue,\n cacheNumSeen: number = 0,\n ): AsyncGenerator<LabeledMessageBase> {\n // Filter out all messageIds before\n // the number already seen\n const messageIds = cachedMessageIds.messageIds.slice(cacheNumSeen);\n\n // Get all the messages pointed to in the cache\n const messages = await Promise.all(\n messageIds.map(async (id) => {\n const message = await cache.messages.get(\n getMessageCacheKey(inboxUrl, id),\n );\n if (!message) {\n // Something is very wrong with the cache,\n // it refers to message IDs that are not cached\n try {\n await cache.messageIds.del(messageIdsCacheKey);\n } catch {}\n throw new Error(\"Cache out of sync - perhaps clear browser storage\");\n }\n return message;\n }),\n );\n\n yield* messages;\n }\n\n protected async *lockedMessageStreamer<Schema extends JSONSchema>(\n ...args: Parameters<typeof this.messageStreamer<Schema>>\n ): MessageStream<Schema> {\n if (typeof window === \"undefined\" || !(await canUseIDB())) {\n // TODO: implement locking in node as well, but not\n // high priority since most use will be in browser\n const streamer = this.messageStreamer<Schema>(...args);\n while (true) {\n const next = await streamer.next();\n if (next.done) return next.value;\n yield next.value;\n }\n }\n\n // Request the lock\n const messageIdsCacheKey = await args[0];\n const lockKey = `graffiti:inbox:${messageIdsCacheKey}`;\n let releaseLock = () => {};\n let hasLock: boolean = false;\n await new Promise<void>((resolvehasLock) => {\n window.navigator.locks.request(\n lockKey,\n {\n mode: \"exclusive\",\n ifAvailable: true,\n },\n async (lock) => {\n // Immediately return whether we\n // acquired the lock or not\n hasLock = !!lock;\n resolvehasLock();\n\n // Then wait for the release to be called\n await new Promise<void>((r) => (releaseLock = r));\n },\n );\n });\n if (hasLock) {\n // If we have the lock, simply proceed with the regular streamer\n try {\n const streamer = this.messageStreamer<Schema>(...args);\n while (true) {\n const next = await streamer.next();\n if (next.done) return next.value;\n yield next.value;\n }\n } finally {\n // Release the lock when all done\n releaseLock();\n }\n }\n\n // Someone else has the lock,\n // so wait until the lock is released,\n // then just return from the cache\n releaseLock();\n await window.navigator.locks.request(lockKey, () => {});\n\n // TODO: the arguments here are brittle\n // at some point, refactor things\n const inboxUrl = args[1];\n const objectSchema = args[5] ?? {};\n const cacheVersion = args[6];\n const cacheNumSeen = args[7];\n\n const cache = await this.cache;\n const cachedMessageIds = await cache.messageIds.get(messageIdsCacheKey);\n if (!cachedMessageIds) {\n throw new Error(\"Cache not found\");\n }\n if (\n cacheVersion !== undefined &&\n cacheVersion !== cachedMessageIds.version\n ) {\n throw new GraffitiErrorCursorExpired(\"Cursor is stale\");\n }\n\n const iterator = this.yieldFromCache(\n cache,\n inboxUrl,\n messageIdsCacheKey,\n cachedMessageIds,\n cacheNumSeen,\n );\n for await (const m of iterator) yield m as LabeledMessage<Schema>;\n\n const outputCursor: infer_<typeof CursorSchema> = {\n numSeen: cachedMessageIds.messageIds.length,\n version: cachedMessageIds.version,\n messageIdsCacheKey,\n objectSchema,\n };\n\n return JSON.stringify(outputCursor);\n }\n\n protected async *messageStreamer<Schema extends JSONSchema>(\n messageIdsCacheKey_: Promise<string>,\n inboxUrl: string,\n type: \"export\" | \"query\",\n body: Uint8Array<ArrayBuffer> | undefined,\n inboxToken?: string | null,\n objectSchema: Schema = {} as Schema,\n cacheVersion?: string,\n cacheNumSeen: number = 0,\n ): MessageStream<Schema> {\n const validator = await compileGraffitiObjectSchema(objectSchema);\n const messageIdsCacheKey = await messageIdsCacheKey_;\n const cache = await this.cache;\n\n let cachedMessageIds = await cache.messageIds.get(messageIdsCacheKey);\n if (\n cacheVersion !== undefined &&\n cacheVersion !== cachedMessageIds?.version\n ) {\n throw new GraffitiErrorCursorExpired(\"Cursor is stale\");\n }\n\n // If we are rate-limited, wait\n let waitTil = cachedMessageIds?.waitTil;\n await waitFor(waitTil);\n\n // See if the cursor is still active by\n // requesting an initial batch of messages\n const cachedCursor = cachedMessageIds?.cursor;\n let firstResponse: Response | undefined = undefined;\n try {\n const out = await this.fetchMessageBatch(\n inboxUrl,\n type,\n body,\n inboxToken,\n cachedCursor,\n );\n firstResponse = out.response;\n waitTil = out.waitTil;\n } catch (e) {\n if (!(e instanceof GraffitiErrorCursorExpired && cachedCursor)) {\n console.error(\n \"Unexpected error in stream, waiting 5 seconds before continuing...\",\n );\n await new Promise((resolve) => setTimeout(resolve, 5000));\n throw e;\n }\n\n // The cursor is stale\n await cache.messageIds.del(messageIdsCacheKey);\n if (cacheVersion === undefined) {\n // The query is not a continuation\n // so we can effectively ignore the error\n cachedMessageIds = undefined;\n } else {\n // Otherwise propogate it up so the\n // consumer can clear their message history\n throw e;\n }\n }\n\n if (firstResponse !== undefined && cachedMessageIds) {\n // Cursor is valid! Yield from the cache\n const iterator = this.yieldFromCache(\n cache,\n inboxUrl,\n messageIdsCacheKey,\n cachedMessageIds,\n cacheNumSeen,\n );\n for await (const m of iterator) yield m as LabeledMessage<Schema>;\n }\n\n if (firstResponse === undefined) {\n // The cursor was stale: try again\n const out = await this.fetchMessageBatch(\n inboxUrl,\n type,\n body,\n inboxToken,\n );\n firstResponse = out.response;\n waitTil = out.waitTil;\n }\n\n // Continue streaming results\n let response = firstResponse;\n let cursor: string;\n const version = cachedMessageIds?.version ?? crypto.randomUUID();\n let messageIds = cachedMessageIds?.messageIds ?? [];\n while (true) {\n const blob = await response.blob();\n const decoded = dagCborDecode(await blob.arrayBuffer());\n const {\n results,\n hasMore,\n cursor: nextCursor,\n } = MessageResultSchema.parse(decoded);\n cursor = nextCursor;\n\n const labeledMessages: LabeledMessage<Schema>[] = results.map(\n (result) => {\n const object =\n result[LABELED_MESSAGE_MESSAGE_KEY][MESSAGE_OBJECT_KEY];\n if (!validator(object)) {\n throw new Error(\"Server returned data that does not match schema\");\n }\n return {\n ...result,\n [LABELED_MESSAGE_MESSAGE_KEY]: {\n ...result[LABELED_MESSAGE_MESSAGE_KEY],\n [MESSAGE_OBJECT_KEY]: object,\n },\n };\n },\n );\n\n // First cache the messages with their labels\n await Promise.all(\n labeledMessages.map((m: LabeledMessageBase) =>\n cache.messages.set(\n getMessageCacheKey(inboxUrl, m[LABELED_MESSAGE_ID_KEY]),\n m,\n ),\n ),\n );\n // Then store all the messageids\n messageIds = [\n ...messageIds,\n ...labeledMessages.map(\n (m: LabeledMessageBase) => m[LABELED_MESSAGE_ID_KEY],\n ),\n ];\n await cache.messageIds.set(messageIdsCacheKey, {\n cursor,\n version,\n messageIds,\n waitTil,\n });\n\n // Update how many we've seen\n cacheNumSeen += labeledMessages.length;\n\n // Return the values\n for (const m of labeledMessages) yield m;\n\n if (!hasMore) break;\n\n // Otherwise get another response (after waiting for rate-limit)\n await waitFor(waitTil);\n const out = await this.fetchMessageBatch(\n inboxUrl,\n type,\n undefined, // Body is never past the first time\n inboxToken,\n cursor,\n );\n response = out.response;\n waitTil = out.waitTil;\n }\n\n const outputCursor: infer_<typeof CursorSchema> = {\n numSeen: cacheNumSeen,\n version,\n messageIdsCacheKey,\n objectSchema,\n };\n\n return JSON.stringify(outputCursor);\n }\n\n query<Schema extends JSONSchema>(\n inboxUrl: string,\n tags: Uint8Array[],\n objectSchema: Schema,\n inboxToken?: string | null,\n ): MessageStream<Schema> {\n verifyHTTPSEndpoint(inboxUrl);\n\n const body = dagCborEncode({\n tags,\n schema: objectSchema,\n });\n\n const messageIdsCacheKey = getMessageIdsCacheKey(inboxUrl, \"query\", body);\n return this.lockedMessageStreamer<Schema>(\n messageIdsCacheKey,\n inboxUrl,\n \"query\",\n new Uint8Array(body),\n inboxToken,\n objectSchema,\n );\n }\n\n continueQuery(\n inboxUrl: string,\n cursor: string,\n inboxToken?: string | null,\n ): MessageStream<{}> {\n verifyHTTPSEndpoint(inboxUrl);\n\n const decodedCursor = JSON.parse(cursor);\n const { messageIdsCacheKey, numSeen, objectSchema, version } =\n CursorSchema.parse(decodedCursor);\n\n return this.lockedMessageStreamer<{}>(\n Promise.resolve(messageIdsCacheKey),\n inboxUrl,\n \"query\",\n undefined,\n inboxToken,\n objectSchema,\n version,\n numSeen,\n );\n }\n\n export(inboxUrl: string, inboxToken: string): MessageStream<{}> {\n verifyHTTPSEndpoint(inboxUrl);\n const messageIdsCacheKey = getMessageIdsCacheKey(inboxUrl, \"export\");\n return this.lockedMessageStreamer<{}>(\n messageIdsCacheKey,\n inboxUrl,\n \"export\",\n undefined,\n inboxToken,\n );\n }\n}\n\nconst GraffitiObjectSchema = strictObject({\n value: looseObject({}),\n channels: array(string()),\n allowed: optional(nullable(array(url()))),\n url: url(),\n actor: url(),\n});\nexport const Uint8ArraySchema = custom<Uint8Array>(\n (v): v is Uint8Array => v instanceof Uint8Array,\n);\nexport const TagsSchema = array(Uint8ArraySchema);\n\nexport const MESSAGE_TAGS_KEY = \"t\";\nexport const MESSAGE_OBJECT_KEY = \"o\";\nexport const MESSAGE_METADATA_KEY = \"m\";\nexport const MessageBaseSchema = strictObject({\n [MESSAGE_TAGS_KEY]: TagsSchema,\n [MESSAGE_OBJECT_KEY]: GraffitiObjectSchema,\n [MESSAGE_METADATA_KEY]: Uint8ArraySchema,\n});\ntype MessageBase = infer_<typeof MessageBaseSchema>;\n\nexport const LABELED_MESSAGE_ID_KEY = \"id\";\nexport const LABELED_MESSAGE_MESSAGE_KEY = \"m\";\nexport const LABELED_MESSAGE_LABEL_KEY = \"l\";\nexport const LabeledMessageBaseSchema = strictObject({\n [LABELED_MESSAGE_ID_KEY]: string(),\n [LABELED_MESSAGE_MESSAGE_KEY]: MessageBaseSchema,\n [LABELED_MESSAGE_LABEL_KEY]: number(),\n});\ntype LabeledMessageBase = infer_<typeof LabeledMessageBaseSchema>;\n\nexport type Message<Schema extends JSONSchema> = MessageBase & {\n [MESSAGE_OBJECT_KEY]: GraffitiObject<Schema>;\n};\nexport type LabeledMessage<Schema extends JSONSchema> = LabeledMessageBase & {\n [LABELED_MESSAGE_MESSAGE_KEY]: {\n [MESSAGE_OBJECT_KEY]: GraffitiObject<Schema>;\n };\n};\n\nconst SendResponseSchema = strictObject({ id: string() });\n\nconst MessageResultSchema = strictObject({\n results: array(LabeledMessageBaseSchema),\n hasMore: boolean(),\n cursor: string(),\n});\n\nconst CursorSchema = strictObject({\n messageIdsCacheKey: string(),\n version: string(),\n numSeen: int().check(nonnegative()),\n objectSchema: union([looseObject({}), boolean()]),\n});\n\nexport interface MessageStream<\n Schema extends JSONSchema,\n> extends AsyncGenerator<LabeledMessage<Schema>, string> {}\n\ntype CacheQueryValue = {\n cursor: string;\n version: string;\n messageIds: string[];\n waitTil?: number;\n};\n\nasync function canUseIDB(): Promise<boolean> {\n try {\n if (!globalThis.indexedDB) return false;\n\n // Small probe database\n await new Promise<void>((resolve, reject) => {\n const req = indexedDB.open(\"__idb_probe__\", 1);\n req.onupgradeneeded = () => req.result.createObjectStore(\"k\");\n req.onsuccess = () => {\n req.result.close();\n resolve();\n };\n req.onerror = () => reject(req.error);\n });\n\n return true;\n } catch {\n return false;\n }\n}\n\ntype Cache = {\n messages: {\n get(k: string): Promise<LabeledMessageBase | null | undefined>;\n set(k: string, value: LabeledMessageBase | null): Promise<void>;\n del(k: string): Promise<void>;\n };\n messageIds: {\n get(k: string): Promise<CacheQueryValue | undefined>;\n set(k: string, value: CacheQueryValue): Promise<void>;\n del(k: string): Promise<void>;\n };\n};\n\nfunction getMessageCacheKey(inboxUrl: string, messageId: string) {\n return `${encodeURIComponent(inboxUrl)}:${encodeURIComponent(messageId)}`;\n}\nasync function getMessageIdsCacheKey(\n inboxUrl: string,\n type: \"query\" | \"export\",\n body?: Uint8Array,\n): Promise<string> {\n const cacheIdData = dagCborEncode({\n inboxUrl,\n type,\n body: body ?? null,\n });\n return crypto.subtle\n .digest(\"SHA-256\", new Uint8Array(cacheIdData))\n .then((bytes) =>\n Array.from(new Uint8Array(bytes))\n .map((byte) => byte.toString(16).padStart(2, \"0\"))\n .join(\"\"),\n );\n}\n\nasync function resetCacheDB() {\n await new Promise<void>((resolve) => {\n const req = indexedDB.deleteDatabase(\"graffiti-inbox-cache\");\n req.onsuccess = () => resolve();\n req.onerror = () => resolve(); // best effort\n req.onblocked = () => resolve(); // best effort\n });\n}\n\nasync function createCache(): Promise<Cache> {\n if (await canUseIDB()) {\n const { openDB } = await import(\"idb\");\n const db = await openDB(\"graffiti-inbox-cache\", 1, {\n upgrade(db) {\n if (!db.objectStoreNames.contains(\"m\")) db.createObjectStore(\"m\");\n if (!db.objectStoreNames.contains(\"q\")) db.createObjectStore(\"q\");\n },\n });\n\n return {\n messages: {\n get: async (k) => {\n try {\n return db.get(\"m\", k);\n } catch (error) {\n console.error(\"Error getting message from cache:\", error);\n console.error(\"resetting cache...\");\n await resetCacheDB();\n return undefined;\n }\n },\n set: async (k, v) => {\n await db.put(\"m\", v, k);\n },\n del: (k) => db.delete(\"m\", k),\n },\n messageIds: {\n get: async (k) => {\n try {\n return await db.get(\"q\", k);\n } catch (error) {\n console.error(\"Error getting message IDs from cache:\", error);\n console.error(\"resetting cache...\");\n await resetCacheDB();\n return undefined;\n }\n },\n set: async (k, v) => {\n await db.put(\"q\", v, k);\n },\n del: (k) => db.delete(\"q\", k),\n },\n };\n }\n\n const m = new Map<string, LabeledMessageBase | null>();\n const q = new Map<string, CacheQueryValue>();\n\n return {\n messages: {\n get: async (k) => m.get(k),\n set: async (k, v) => void m.set(k, v),\n del: async (k) => void m.delete(k),\n },\n messageIds: {\n get: async (k) => q.get(k),\n set: async (k, v) => void q.set(k, v),\n del: async (k) => void q.delete(k),\n },\n };\n}\n\nasync function waitFor(waitTil?: number) {\n if (waitTil !== undefined) {\n const waitFor = waitTil - Date.now();\n if (waitFor > 0) {\n await new Promise((resolve) => setTimeout(resolve, waitFor));\n }\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import type { JSONSchema, GraffitiObject } from \"@graffiti-garden/api\";\nimport {\n getAuthorizationEndpoint,\n fetchWithErrorHandling,\n verifyHTTPSEndpoint,\n} from \"./utilities\";\nimport {\n compileGraffitiObjectSchema,\n GraffitiErrorCursorExpired,\n GraffitiErrorNotFound,\n} from \"@graffiti-garden/api\";\nimport {\n encode as dagCborEncode,\n decode as dagCborDecode,\n} from \"@ipld/dag-cbor\";\nimport {\n type infer as infer_,\n string,\n url,\n array,\n optional,\n nullable,\n strictObject,\n looseObject,\n nonnegative,\n int,\n boolean,\n custom,\n number,\n union,\n} from \"zod/mini\";\n\nexport class Inboxes {\n getAuthorizationEndpoint = getAuthorizationEndpoint;\n protected cache_: Promise<Cache> | null = null;\n protected get cache() {\n if (!this.cache_) {\n this.cache_ = createCache();\n }\n return this.cache_;\n }\n\n async send(inboxUrl: string, message: Message<{}>): Promise<string> {\n verifyHTTPSEndpoint(inboxUrl);\n const url = `${inboxUrl}/send`;\n\n const response = await fetchWithErrorHandling(url, {\n method: \"PUT\",\n headers: {\n \"Content-Type\": \"application/cbor\",\n },\n body: new Uint8Array(dagCborEncode({ m: message })),\n });\n\n const blob = await response.blob();\n const cbor = dagCborDecode(await blob.arrayBuffer());\n const parsed = SendResponseSchema.parse(cbor);\n return parsed.id;\n }\n\n async get(\n inboxUrl: string,\n messageId: string,\n inboxToken?: string | null,\n ): Promise<LabeledMessageBase | null> {\n const messageCacheKey = getMessageCacheKey(inboxUrl, messageId);\n const cache = await this.cache;\n const cached = await cache.messages.get(messageCacheKey);\n if (cached !== undefined) return cached;\n\n const url = `${inboxUrl}/message/${messageId}`;\n let response: Response | null = null;\n try {\n response = await fetchWithErrorHandling(url, {\n method: \"GET\",\n headers: {\n ...(inboxToken\n ? {\n Authorization: `Bearer ${inboxToken}`,\n }\n : {}),\n },\n });\n } catch (e) {\n if (e instanceof GraffitiErrorNotFound) {\n await cache.messages.set(messageCacheKey, null);\n return null;\n }\n throw e;\n }\n\n const blob = await response.blob();\n const cbor = dagCborDecode(await blob.arrayBuffer());\n const parsed = LabeledMessageBaseSchema.parse(cbor);\n\n await cache.messages.set(messageCacheKey, parsed);\n return parsed;\n }\n\n async label(\n inboxUrl: string,\n messageId: string,\n label: number,\n inboxToken?: string | null,\n ): Promise<void> {\n verifyHTTPSEndpoint(inboxUrl);\n\n if (inboxToken) {\n const url = `${inboxUrl}/label/${messageId}`;\n\n await fetchWithErrorHandling(url, {\n method: \"PUT\",\n headers: {\n \"Content-Type\": \"application/cbor\",\n Authorization: `Bearer ${inboxToken}`,\n },\n body: new Uint8Array(dagCborEncode({ l: label })),\n });\n }\n\n // Update the cache, even if no token.\n // Therefore people not logged in do not need to\n // repeatedly re-validate objects.\n const cache = await this.cache;\n const messageCacheKey = getMessageCacheKey(inboxUrl, messageId);\n const result = await cache.messages.get(messageCacheKey);\n if (result) {\n await cache.messages.set(messageCacheKey, {\n ...result,\n l: label,\n });\n }\n }\n\n protected async fetchMessageBatch(\n inboxUrl: string,\n type: \"query\" | \"export\",\n body: Uint8Array<ArrayBuffer> | undefined,\n inboxToken?: string | null,\n cursor?: string,\n signal?: AbortSignal,\n ) {\n const response = await fetchWithErrorHandling(\n `${inboxUrl}/${type}${cursor ? `?cursor=${encodeURIComponent(cursor)}` : \"\"}`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/cbor\",\n ...(inboxToken\n ? {\n Authorization: `Bearer ${inboxToken}`,\n }\n : {}),\n },\n body,\n signal,\n },\n );\n const retryAfterHeader = response.headers.get(\"Retry-After\");\n const retryAfter = retryAfterHeader\n ? parseInt(retryAfterHeader)\n : undefined;\n\n const waitTil =\n retryAfter && Number.isFinite(retryAfter)\n ? Date.now() + retryAfter * 1000\n : undefined;\n\n return { response, waitTil };\n }\n\n protected async *yieldFromCache(\n cache: Cache,\n inboxUrl: string,\n messageIdsCacheKey: string,\n cachedMessageIds: CacheQueryValue,\n cacheNumSeen: number = 0,\n ): AsyncGenerator<LabeledMessageBase> {\n // Filter out all messageIds before\n // the number already seen\n const messageIds = cachedMessageIds.messageIds.slice(cacheNumSeen);\n\n // Get all the messages pointed to in the cache\n const messages = await Promise.all(\n messageIds.map(async (id) => {\n const message = await cache.messages.get(\n getMessageCacheKey(inboxUrl, id),\n );\n if (!message) {\n // Something is very wrong with the cache,\n // it refers to message IDs that are not cached\n try {\n await cache.messageIds.del(messageIdsCacheKey);\n } catch {}\n throw new Error(\"Cache out of sync - perhaps clear browser storage\");\n }\n return message;\n }),\n );\n\n yield* messages;\n }\n\n protected async *lockedMessageStreamer<Schema extends JSONSchema>(\n ...args: Parameters<typeof this.messageStreamer<Schema>>\n ): MessageStream<Schema> {\n if (typeof window === \"undefined\" || !(await canUseIDB())) {\n // TODO: implement locking in node as well, but not\n // high priority since most use will be in browser\n const streamer = this.messageStreamer<Schema>(...args);\n while (true) {\n const next = await streamer.next();\n if (next.done) return next.value;\n yield next.value;\n }\n }\n\n // Request the lock\n const messageIdsCacheKey = await args[0];\n const lockKey = `graffiti:inbox:${messageIdsCacheKey}`;\n let releaseLock = () => {};\n let hasLock: boolean = false;\n await new Promise<void>((resolvehasLock) => {\n window.navigator.locks.request(\n lockKey,\n {\n mode: \"exclusive\",\n ifAvailable: true,\n },\n async (lock) => {\n // Immediately return whether we\n // acquired the lock or not\n hasLock = !!lock;\n resolvehasLock();\n\n // Then wait for the release to be called\n await new Promise<void>((r) => (releaseLock = r));\n },\n );\n });\n if (hasLock) {\n // If we have the lock, simply proceed with the regular streamer\n try {\n const streamer = this.messageStreamer<Schema>(...args);\n while (true) {\n const next = await streamer.next();\n if (next.done) return next.value;\n yield next.value;\n }\n } finally {\n // Release the lock when all done\n releaseLock();\n }\n }\n\n // Someone else has the lock,\n // so wait until the lock is released,\n // then just return from the cache\n releaseLock();\n await window.navigator.locks.request(lockKey, () => {});\n\n // TODO: the arguments here are brittle\n // at some point, refactor things\n const inboxUrl = args[1];\n const objectSchema = args[5] ?? {};\n const cacheVersion = args[6];\n const cacheNumSeen = args[7];\n\n const cache = await this.cache;\n const cachedMessageIds = await cache.messageIds.get(messageIdsCacheKey);\n if (!cachedMessageIds) {\n throw new Error(\"Cache not found\");\n }\n if (\n cacheVersion !== undefined &&\n cacheVersion !== cachedMessageIds.version\n ) {\n throw new GraffitiErrorCursorExpired(\"Cursor is stale\");\n }\n\n const iterator = this.yieldFromCache(\n cache,\n inboxUrl,\n messageIdsCacheKey,\n cachedMessageIds,\n cacheNumSeen,\n );\n for await (const m of iterator) yield m as LabeledMessage<Schema>;\n\n const outputCursor: infer_<typeof CursorSchema> = {\n numSeen: cachedMessageIds.messageIds.length,\n version: cachedMessageIds.version,\n messageIdsCacheKey,\n objectSchema,\n };\n\n return JSON.stringify(outputCursor);\n }\n\n protected async *messageStreamer<Schema extends JSONSchema>(\n messageIdsCacheKey_: Promise<string>,\n inboxUrl: string,\n type: \"export\" | \"query\",\n body: Uint8Array<ArrayBuffer> | undefined,\n inboxToken?: string | null,\n objectSchema: Schema = {} as Schema,\n cacheVersion?: string,\n cacheNumSeen: number = 0,\n ): MessageStream<Schema> {\n const validator = await compileGraffitiObjectSchema(objectSchema);\n const messageIdsCacheKey = await messageIdsCacheKey_;\n const cache = await this.cache;\n\n let cachedMessageIds = await cache.messageIds.get(messageIdsCacheKey);\n if (\n cacheVersion !== undefined &&\n cacheVersion !== cachedMessageIds?.version\n ) {\n throw new GraffitiErrorCursorExpired(\"Cursor is stale\");\n }\n\n // If we are rate-limited, wait\n let waitTil = cachedMessageIds?.waitTil;\n await waitFor(waitTil);\n\n // See if the cursor is still active by\n // requesting an initial batch of messages\n const cachedCursor = cachedMessageIds?.cursor;\n let firstResponse: Response | undefined = undefined;\n try {\n const out = await this.fetchMessageBatch(\n inboxUrl,\n type,\n body,\n inboxToken,\n cachedCursor,\n );\n firstResponse = out.response;\n waitTil = out.waitTil;\n } catch (e) {\n if (!(e instanceof GraffitiErrorCursorExpired && cachedCursor)) {\n console.error(\n \"Unexpected error in stream, waiting 5 seconds before continuing...\",\n );\n await new Promise((resolve) => setTimeout(resolve, 5000));\n throw e;\n }\n\n // The cursor is stale\n await cache.messageIds.del(messageIdsCacheKey);\n if (cacheVersion === undefined) {\n // The query is not a continuation\n // so we can effectively ignore the error\n cachedMessageIds = undefined;\n } else {\n // Otherwise propogate it up so the\n // consumer can clear their message history\n throw e;\n }\n }\n\n if (firstResponse !== undefined && cachedMessageIds) {\n // Cursor is valid! Yield from the cache\n const iterator = this.yieldFromCache(\n cache,\n inboxUrl,\n messageIdsCacheKey,\n cachedMessageIds,\n cacheNumSeen,\n );\n for await (const m of iterator) yield m as LabeledMessage<Schema>;\n }\n\n if (firstResponse === undefined) {\n // The cursor was stale: try again\n const out = await this.fetchMessageBatch(\n inboxUrl,\n type,\n body,\n inboxToken,\n );\n firstResponse = out.response;\n waitTil = out.waitTil;\n }\n\n // Continue streaming results\n let response = firstResponse;\n let cursor: string;\n const version = cachedMessageIds?.version ?? crypto.randomUUID();\n let messageIds = cachedMessageIds?.messageIds ?? [];\n while (true) {\n const blob = await response.blob();\n const decoded = dagCborDecode(await blob.arrayBuffer());\n const {\n results,\n hasMore,\n cursor: nextCursor,\n } = MessageResultSchema.parse(decoded);\n cursor = nextCursor;\n\n const labeledMessages: LabeledMessage<Schema>[] = results.map(\n (result) => {\n const object =\n result[LABELED_MESSAGE_MESSAGE_KEY][MESSAGE_OBJECT_KEY];\n if (!validator(object)) {\n throw new Error(\"Server returned data that does not match schema\");\n }\n return {\n ...result,\n [LABELED_MESSAGE_MESSAGE_KEY]: {\n ...result[LABELED_MESSAGE_MESSAGE_KEY],\n [MESSAGE_OBJECT_KEY]: object,\n },\n };\n },\n );\n\n // First cache the messages with their labels\n await cache.messages.setMany(\n labeledMessages.map((m: LabeledMessageBase) => [\n getMessageCacheKey(inboxUrl, m[LABELED_MESSAGE_ID_KEY]),\n m,\n ]),\n );\n // Then store all the messageids\n messageIds = [\n ...messageIds,\n ...labeledMessages.map(\n (m: LabeledMessageBase) => m[LABELED_MESSAGE_ID_KEY],\n ),\n ];\n await cache.messageIds.set(messageIdsCacheKey, {\n cursor,\n version,\n messageIds,\n waitTil,\n });\n\n // Update how many we've seen\n cacheNumSeen += labeledMessages.length;\n\n // Fetch the next page while the caller processes the current page.\n const prefetchController = new AbortController();\n // Otherwise get another response (after waiting for rate-limit)\n const nextPage = hasMore\n ? waitFor(waitTil)\n .then(() => this.fetchMessageBatch(\n inboxUrl,\n type,\n undefined, // Body is never past the first time\n inboxToken,\n cursor,\n prefetchController.signal,\n ))\n // A consumer may stop before awaiting this request.\n .then((value) => ({ value }), (error) => ({ error }))\n : null;\n\n // Return the values\n let consumedPage = false;\n try {\n for (const m of labeledMessages) yield m;\n consumedPage = true;\n } finally {\n if (!consumedPage) prefetchController.abort();\n }\n\n if (!hasMore) break;\n\n const out = await nextPage!;\n if (\"error\" in out) throw out.error;\n response = out.value.response;\n waitTil = out.value.waitTil;\n }\n\n const outputCursor: infer_<typeof CursorSchema> = {\n numSeen: cacheNumSeen,\n version,\n messageIdsCacheKey,\n objectSchema,\n };\n\n return JSON.stringify(outputCursor);\n }\n\n query<Schema extends JSONSchema>(\n inboxUrl: string,\n tags: Uint8Array[],\n objectSchema: Schema,\n inboxToken?: string | null,\n ): MessageStream<Schema> {\n verifyHTTPSEndpoint(inboxUrl);\n\n const body = dagCborEncode({\n tags,\n schema: objectSchema,\n });\n\n const messageIdsCacheKey = getMessageIdsCacheKey(inboxUrl, \"query\", body);\n return this.lockedMessageStreamer<Schema>(\n messageIdsCacheKey,\n inboxUrl,\n \"query\",\n new Uint8Array(body),\n inboxToken,\n objectSchema,\n );\n }\n\n continueQuery(\n inboxUrl: string,\n cursor: string,\n inboxToken?: string | null,\n ): MessageStream<{}> {\n verifyHTTPSEndpoint(inboxUrl);\n\n const decodedCursor = JSON.parse(cursor);\n const { messageIdsCacheKey, numSeen, objectSchema, version } =\n CursorSchema.parse(decodedCursor);\n\n return this.lockedMessageStreamer<{}>(\n Promise.resolve(messageIdsCacheKey),\n inboxUrl,\n \"query\",\n undefined,\n inboxToken,\n objectSchema,\n version,\n numSeen,\n );\n }\n\n export(inboxUrl: string, inboxToken: string): MessageStream<{}> {\n verifyHTTPSEndpoint(inboxUrl);\n const messageIdsCacheKey = getMessageIdsCacheKey(inboxUrl, \"export\");\n return this.lockedMessageStreamer<{}>(\n messageIdsCacheKey,\n inboxUrl,\n \"export\",\n undefined,\n inboxToken,\n );\n }\n}\n\nconst GraffitiObjectSchema = strictObject({\n value: looseObject({}),\n channels: array(string()),\n allowed: optional(nullable(array(url()))),\n url: url(),\n actor: url(),\n});\nexport const Uint8ArraySchema = custom<Uint8Array>(\n (v): v is Uint8Array => v instanceof Uint8Array,\n);\nexport const TagsSchema = array(Uint8ArraySchema);\n\nexport const MESSAGE_TAGS_KEY = \"t\";\nexport const MESSAGE_OBJECT_KEY = \"o\";\nexport const MESSAGE_METADATA_KEY = \"m\";\nexport const MessageBaseSchema = strictObject({\n [MESSAGE_TAGS_KEY]: TagsSchema,\n [MESSAGE_OBJECT_KEY]: GraffitiObjectSchema,\n [MESSAGE_METADATA_KEY]: Uint8ArraySchema,\n});\ntype MessageBase = infer_<typeof MessageBaseSchema>;\n\nexport const LABELED_MESSAGE_ID_KEY = \"id\";\nexport const LABELED_MESSAGE_MESSAGE_KEY = \"m\";\nexport const LABELED_MESSAGE_LABEL_KEY = \"l\";\nexport const LabeledMessageBaseSchema = strictObject({\n [LABELED_MESSAGE_ID_KEY]: string(),\n [LABELED_MESSAGE_MESSAGE_KEY]: MessageBaseSchema,\n [LABELED_MESSAGE_LABEL_KEY]: number(),\n});\ntype LabeledMessageBase = infer_<typeof LabeledMessageBaseSchema>;\n\nexport type Message<Schema extends JSONSchema> = MessageBase & {\n [MESSAGE_OBJECT_KEY]: GraffitiObject<Schema>;\n};\nexport type LabeledMessage<Schema extends JSONSchema> = LabeledMessageBase & {\n [LABELED_MESSAGE_MESSAGE_KEY]: {\n [MESSAGE_OBJECT_KEY]: GraffitiObject<Schema>;\n };\n};\n\nconst SendResponseSchema = strictObject({ id: string() });\n\nconst MessageResultSchema = strictObject({\n results: array(LabeledMessageBaseSchema),\n hasMore: boolean(),\n cursor: string(),\n});\n\nconst CursorSchema = strictObject({\n messageIdsCacheKey: string(),\n version: string(),\n numSeen: int().check(nonnegative()),\n objectSchema: union([looseObject({}), boolean()]),\n});\n\nexport interface MessageStream<\n Schema extends JSONSchema,\n> extends AsyncGenerator<LabeledMessage<Schema>, string> {}\n\ntype CacheQueryValue = {\n cursor: string;\n version: string;\n messageIds: string[];\n waitTil?: number;\n};\n\nasync function canUseIDB(): Promise<boolean> {\n try {\n if (!globalThis.indexedDB) return false;\n\n // Small probe database\n await new Promise<void>((resolve, reject) => {\n const req = indexedDB.open(\"__idb_probe__\", 1);\n req.onupgradeneeded = () => req.result.createObjectStore(\"k\");\n req.onsuccess = () => {\n req.result.close();\n resolve();\n };\n req.onerror = () => reject(req.error);\n });\n\n return true;\n } catch {\n return false;\n }\n}\n\ntype Cache = {\n messages: {\n get(k: string): Promise<LabeledMessageBase | null | undefined>;\n set(k: string, value: LabeledMessageBase | null): Promise<void>;\n setMany(entries: [string, LabeledMessageBase][]): Promise<void>;\n del(k: string): Promise<void>;\n };\n messageIds: {\n get(k: string): Promise<CacheQueryValue | undefined>;\n set(k: string, value: CacheQueryValue): Promise<void>;\n del(k: string): Promise<void>;\n };\n};\n\nfunction getMessageCacheKey(inboxUrl: string, messageId: string) {\n return `${encodeURIComponent(inboxUrl)}:${encodeURIComponent(messageId)}`;\n}\nasync function getMessageIdsCacheKey(\n inboxUrl: string,\n type: \"query\" | \"export\",\n body?: Uint8Array,\n): Promise<string> {\n const cacheIdData = dagCborEncode({\n inboxUrl,\n type,\n body: body ?? null,\n });\n return crypto.subtle\n .digest(\"SHA-256\", new Uint8Array(cacheIdData))\n .then((bytes) =>\n Array.from(new Uint8Array(bytes))\n .map((byte) => byte.toString(16).padStart(2, \"0\"))\n .join(\"\"),\n );\n}\n\nasync function resetCacheDB() {\n await new Promise<void>((resolve) => {\n const req = indexedDB.deleteDatabase(\"graffiti-inbox-cache\");\n req.onsuccess = () => resolve();\n req.onerror = () => resolve(); // best effort\n req.onblocked = () => resolve(); // best effort\n });\n}\n\nasync function createCache(): Promise<Cache> {\n if (await canUseIDB()) {\n const { openDB } = await import(\"idb\");\n const db = await openDB(\"graffiti-inbox-cache\", 1, {\n upgrade(db) {\n if (!db.objectStoreNames.contains(\"m\")) db.createObjectStore(\"m\");\n if (!db.objectStoreNames.contains(\"q\")) db.createObjectStore(\"q\");\n },\n });\n\n return {\n messages: {\n get: async (k) => {\n try {\n return db.get(\"m\", k);\n } catch (error) {\n console.error(\"Error getting message from cache:\", error);\n console.error(\"resetting cache...\");\n await resetCacheDB();\n return undefined;\n }\n },\n set: async (k, v) => {\n await db.put(\"m\", v, k);\n },\n setMany: async (entries) => {\n if (entries.length === 0) return;\n const tx = db.transaction(\"m\", \"readwrite\");\n await Promise.all([\n ...entries.map(([k, v]) => tx.store.put(v, k)),\n tx.done,\n ]);\n },\n del: (k) => db.delete(\"m\", k),\n },\n messageIds: {\n get: async (k) => {\n try {\n return await db.get(\"q\", k);\n } catch (error) {\n console.error(\"Error getting message IDs from cache:\", error);\n console.error(\"resetting cache...\");\n await resetCacheDB();\n return undefined;\n }\n },\n set: async (k, v) => {\n await db.put(\"q\", v, k);\n },\n del: (k) => db.delete(\"q\", k),\n },\n };\n }\n\n const m = new Map<string, LabeledMessageBase | null>();\n const q = new Map<string, CacheQueryValue>();\n\n return {\n messages: {\n get: async (k) => m.get(k),\n set: async (k, v) => void m.set(k, v),\n setMany: async (entries) => {\n for (const [k, v] of entries) m.set(k, v);\n },\n del: async (k) => void m.delete(k),\n },\n messageIds: {\n get: async (k) => q.get(k),\n set: async (k, v) => void q.set(k, v),\n del: async (k) => void q.delete(k),\n },\n };\n}\n\nasync function waitFor(waitTil?: number) {\n if (waitTil !== undefined) {\n const waitFor = waitTil - Date.now();\n if (waitFor > 0) {\n await new Promise((resolve) => setTimeout(resolve, waitFor));\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["url", "db", "waitFor"]
|
|
7
7
|
}
|
|
@@ -106,7 +106,7 @@ const MESSAGE_LABEL_UNLABELED = 0;
|
|
|
106
106
|
const MESSAGE_LABEL_VALID = 1;
|
|
107
107
|
const MESSAGE_LABEL_TRASH = 2;
|
|
108
108
|
const MESSAGE_LABEL_INVALID = 3;
|
|
109
|
-
const CONCURRENCY =
|
|
109
|
+
const CONCURRENCY = 128;
|
|
110
110
|
const LOCAL_STORAGE_REMEMBERED_HANDLE_KEY = "graffiti-login-remembered-handle";
|
|
111
111
|
class GraffitiDecentralized {
|
|
112
112
|
dids = new DecentralizedIdentifiers();
|