r2.tsannotatedr2.tssource43 lines · 1.7 KB · raw
1// An R2 bucket in memory, for the Worker's tests: the part of R2Bucket
2// the Worker uses (put, get, delete, list), holding each object's bytes and its
3// content type. `objects` is the bucket itself, for a test to look into.
4export function memoryR2() {
5  const objects = new Map<string, { bytes: Uint8Array; contentType: string | undefined }>();
6  let etags = 0;
7  const bucket = {
8    objects,
9    async put(key: string, value: Uint8Array | ArrayBuffer, options?: { httpMetadata?: { contentType?: string } }) {
10      const bytes = value instanceof Uint8Array ? value.slice() : new Uint8Array(value);
11      objects.set(key, { bytes, contentType: options?.httpMetadata?.contentType });
12      return { key, size: bytes.byteLength };
13    },
14    async get(key: string) {
15      const o = objects.get(key);
16      if (!o) return null;
17      etags += 1;
18      return {
19        key,
20        size: o.bytes.byteLength,
21        httpEtag: `"etag-${etags}"`,
22        body: new Response(o.bytes).body,
23        httpMetadata: { contentType: o.contentType },
24      };
25    },
26    async delete(key: string) {
27      objects.delete(key);
28    },
29    // by prefix, in key order, paged by `limit` and `cursor` (an offset here)
30    async list({ prefix = '', limit = 1000, cursor }: { prefix?: string; limit?: number; cursor?: string } = {}) {
31      const keys = [...objects.keys()].filter((k) => k.startsWith(prefix)).sort();
32      const from = cursor ? Number(cursor) : 0;
33      const page = keys.slice(from, from + limit);
34      const truncated = from + limit < keys.length;
35      return {
36        objects: page.map((key) => ({ key, size: objects.get(key)!.bytes.byteLength })),
37        truncated,
38        ...(truncated ? { cursor: String(from + limit) } : {}),
39      };
40    },
41  };
42  return bucket;
43}