src/r2.ts
65 lines and 6 functions in DrDustinEdwards/d1-dump, at commit e15f966 on 2026-10-23. This file on GitHub.
Functions
| Function | Lines | Complexity | Covered | CRAP | Mutation |
|---|---|---|---|---|---|
| putJsonStreamed | 13 to 54 | 7 | 94% | WatchCRAP 7 | Watch77.3% |
| flush | 20 to 24 | 1 | 100% | OkCRAP 1 | Ok100% |
| write | 25 to 34 | 3 | 100% | OkCRAP 3 | Ok100% |
| <anonymous:49> | 49 to 51 | 1 | 0% | OkCRAP 2 | Critical0% |
| listAllKeys | 56 to 65 | 4 | 100% | OkCRAP 4 | Critical40% |
| deleteInChunks | 67 to 70 | 2 | 100% | OkCRAP 2 | Watch71.4% |
Imports
It imports src/types.ts. Imported by src/dump.ts, src/runs.ts.
Source
import type { R2Like, R2UploadedPartLike } from "./types.js";
// R2 requires every part except the last to be the same size, and at least 5 MiB.
const MULTIPART_PART_BYTES = 8 * 1024 * 1024;
// R2's bulk delete refuses more than 1000 keys in one call rather than truncating.
const R2_DELETE_MAX = 1000;
export const JSON_TYPE = { httpMetadata: { contentType: "application/json" } };
// Writes `head`, then the rows a page at a time, then "]}", as one multipart object.
// The bytes equal JSON.stringify of the whole object, holding at most one part and one
// page in memory.
export async function putJsonStreamed(bucket: R2Like, key: string, head: string, pages: AsyncIterable<unknown[]>): Promise<number> {
const upload = await bucket.createMultipartUpload(key, JSON_TYPE);
const parts: R2UploadedPartLike[] = [];
const encoder = new TextEncoder();
let part = new Uint8Array(MULTIPART_PART_BYTES);
let filled = 0;
let rows = 0;
const flush = async () => {
parts.push(await upload.uploadPart(parts.length + 1, part.slice(0, filled)));
part = new Uint8Array(MULTIPART_PART_BYTES);
filled = 0;
};
const write = async (text: string) => {
let bytes = encoder.encode(text);
while (bytes.length > 0) {
const n = Math.min(bytes.length, MULTIPART_PART_BYTES - filled);
part.set(bytes.subarray(0, n), filled);
filled += n;
bytes = bytes.subarray(n);
if (filled === MULTIPART_PART_BYTES) await flush();
}
};
try {
await write(head);
for await (const page of pages) {
for (const row of page) {
await write((rows === 0 ? "" : ",") + JSON.stringify(row));
rows++;
}
}
await write("]}");
if (filled > 0 || parts.length === 0) await flush();
await upload.complete(parts);
return rows;
} catch (err) {
// The abort's own failure is reported with the cause, never in place of it.
await upload.abort().catch((abortErr: unknown) => {
throw new AggregateError([err, abortErr], `d1-dump: writing ${key} failed, and so did aborting its upload`);
});
throw err;
}
}
export async function listAllKeys(bucket: R2Like, prefix: string): Promise<string[]> {
const keys: string[] = [];
let cursor: string | undefined;
do {
const page = await bucket.list({ prefix, cursor });
for (const obj of page.objects) keys.push(obj.key);
cursor = page.truncated ? page.cursor : undefined;
} while (cursor);
return keys;
}
export async function deleteInChunks(bucket: R2Like, keys: string[]): Promise<number> {
for (let i = 0; i < keys.length; i += R2_DELETE_MAX) await bucket.delete(keys.slice(i, i + R2_DELETE_MAX));
return keys.length;
}