1/**
2 * R2 key scheme (DESIGN.md §3.2).
3 *
4 * All R2 keys are **immutable and content-addressed** — this sidesteps the
5 * 1-write/s/key limit, makes conditional-put re-runs (idempotent alarm jobs)
6 * safe, and makes cross-repo fork references safe: a fork's `objects.r2_key`
7 * may point into the parent repo's prefix, since the bytes at a key never
8 * change once written.
9 *
10 * ```
11 * {repoId}/objects/{oid} oversize loose objects, zlib(content)
12 * {repoId}/packs/pack-{sha1}.pack v1.1 compaction output
13 * {repoId}/incoming/{pushId}.pack v1.x streaming-push tee (reserved)
14 * {repoId}/lfs/{sha256} LFS objects (reserved)
15 * ```
16 *
17 * `list()` is never used on a hot path — the `objects` table is the sole
18 * inventory. The prefix helpers exist for the delete-purge and GC alarm jobs
19 * only.
20 */
21
22/**
23 * R2 key for an oversize loose object: `{repoId}/objects/{oid}`.
24 *
25 * The stored bytes are `zlib(content)` without the loose header — byte-equal
26 * to what an `objects.location = 'row'` row would hold in `zdata`.
27 */
28export const objectKey = (repoId: string, oid: string): string =>
29 `${repoId}/objects/${oid}`;
30
31/**
32 * R2 key for a compacted pack (v1.1): `{repoId}/packs/pack-{sha1}.pack`.
33 * The `sha1` is the pack's trailer checksum, making the key content-addressed.
34 */
35export const packKey = (repoId: string, sha: string): string =>
36 `${repoId}/packs/pack-${sha}.pack`;
37
38/**
39 * R2 key of a clone bundle for a ref snapshot (DESIGN.md §12.2):
40 * `{repoId}/bundles/bundle-{refsHash}.pack`. Immutable and
41 * content-addressed by the refs it covers, so it is safe to cache at the
42 * edge and safe to write concurrently.
43 */
44export const bundleKey = (repoId: string, refsHash: string): string =>
45 `${repoId}/bundles/bundle-${refsHash}.pack`;
46
47/**
48 * The bundle's **pre-framed** twin (DESIGN §22.4): the same pack already
49 * cut into band-1 sideband frames of exactly 65515 data bytes. Real git
50 * always negotiates `side-band-64k`, and bytes that cross into JS to be
51 * framed measured a hard ~15 MiB/s ceiling regardless of how the framing
52 * was written; this twin lets the sideband case be a platform-to-platform
53 * pipe like the raw case. Written alongside the bundle, immutable, same
54 * lifecycle (purged with the prefix).
55 */
56export const bundleSidebandKey = (repoId: string, refsHash: string): string =>
57 `${repoId}/bundles/bundle-${refsHash}.sideband`;
58
59/**
60 * R2 key for a streamed incoming push pack (v1.x upgrade seam, DESIGN.md
61 * §3.6): `{repoId}/incoming/{pushId}.pack`. Reserved — unused in v1's
62 * buffered ingest.
63 */
64export const incomingKey = (repoId: string, pushId: string): string =>
65 `${repoId}/incoming/${pushId}.pack`;
66
67/**
68 * Pack-id prefix of a **promoted wire pack** (DESIGN §22.5): a push whose
69 * body spilled to blob storage already holds every non-delta blob in the
70 * compaction layout (`typeSize header + zdata`), so ingest points those
71 * rows straight into the incoming object instead of copying 40 MiB of
72 * blobs through SQLite and compacting them out again later. The pack id
73 * is `wire-<receiveId>`; the object stays at its `incomingKey`.
74 */
75export const WIRE_PACK_PREFIX = "wire-";
76export const wirePackId = (receiveId: string): string =>
77 `${WIRE_PACK_PREFIX}${receiveId}`;
78export const isWirePackId = (packId: string): boolean =>
79 packId.startsWith(WIRE_PACK_PREFIX);
80
81/** R2 key of any pack by id — compacted (`packKey`) or promoted wire pack. */
82export const packKeyOf = (repoId: string, packId: string): string =>
83 isWirePackId(packId)
84 ? incomingKey(repoId, packId.slice(WIRE_PACK_PREFIX.length))
85 : packKey(repoId, packId);
86
87/**
88 * R2 key reserved for a future LFS object (DESIGN.md §10):
89 * `{repoId}/lfs/{sha256}`.
90 */
91export const lfsKey = (repoId: string, sha256: string): string =>
92 `${repoId}/lfs/${sha256}`;
93
94/**
95 * The whole-repo R2 prefix (`{repoId}/`) — the unit the delete-purge alarm
96 * job lists and deletes (retained while the Registry reports
97 * `fork_count > 0`, since forks reference keys under it).
98 */
99export const repoPrefix = (repoId: string): string => `${repoId}/`;
100
101/**
102 * Prefix of all oversize loose objects for a repo: `{repoId}/objects/`.
103 */
104export const objectsPrefix = (repoId: string): string => `${repoId}/objects/`;
105
106/**
107 * Prefix of all compacted packs for a repo (v1.1): `{repoId}/packs/`.
108 */
109export const packsPrefix = (repoId: string): string => `${repoId}/packs/`;
110
111/**
112 * Prefix of all in-flight streamed push packs for a repo (reserved):
113 * `{repoId}/incoming/`.
114 */
115export const incomingPrefix = (repoId: string): string => `${repoId}/incoming/`;
116
117/**
118 * Prefix of all LFS objects for a repo (reserved): `{repoId}/lfs/`.
119 */
120export const lfsPrefix = (repoId: string): string => `${repoId}/lfs/`;
121
122/**
123 * R2 key of a repo's head snapshot (DESIGN.md §21): a small JSON object
124 * describing the current refs, visibility, and clone bundle, written by
125 * the Repo DO after every mutation commit. Lets the Worker serve
126 * anonymous public reads (advertisement + bundle clones) without waking
127 * the DO. Lives under the repo prefix so purge drains it.
128 */
129export const headKey = (repoId: string): string => `${repoId}/head`;