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}