@usecontextlayer/pggit 1.0.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":["ZERO_OID","GITLINK_MODE"],"sources":["../src/instrument.ts","../src/protocol/errors.ts","../src/protocol/pkt-line.ts","../src/protocol/capabilities.ts","../src/protocol/sideband.ts","../src/protocol/receive-pack.ts","../src/protocol/v2.ts","../src/protocol/upload-pack.ts","../src/object/format-error.ts","../src/object/object.ts","../src/repo-view/build-file-list.ts","../src/repo-view/config.ts","../src/repo-view/rebuild.ts","../src/database/postgres.ts","../src/database/copy-insert.ts","../src/object/edges.ts","../src/pack/object-header.ts","../src/store/reachability.ts","../src/store/repo-resolver.ts","../src/store/gc.ts","../src/gc-scheduler.ts","../src/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\"\nimport { performance } from \"node:perf_hooks\"\n\n/**\n * Request-scoped performance instrumentation. The perf harness activates a\n * per-request {@link Collector} via {@link runRequest}; everything else\n * (`withPhase`/`count`/`label`/`recordQuery`) reads the active collector from an\n * AsyncLocalStorage and is a **no-op when none is active**. Production and the\n * oracle tests never call `runRequest`, so they pay nothing but a `Map.get`.\n *\n * Concurrency note: phase attribution uses a single mutable `current` per\n * collector. That is correct because a single git request runs its phases\n * (graph-walk → read-objects → write-pack) sequentially, never overlapping.\n */\n\nexport type QueryRecord = { sql: string; durationMs: number; phase: string }\n\nexport type Collector = {\n\t/** What the request turned out to be (`fetch` / `ls-refs`), set by the handler. */\n\tlabel: string\n\tmethod: string\n\tpath: string\n\t/** The phase a query/counter is currently attributed to. */\n\tcurrent: string\n\t/** Phase name → total wall ms spent in that phase. */\n\tphaseMs: Map<string, number>\n\t/** Counter name → accumulated value. */\n\tcounters: Map<string, number>\n\tqueries: QueryRecord[]\n}\n\nconst als = new AsyncLocalStorage<Collector>()\nconst collected: Collector[] = []\n\n/** Every collector recorded since the last {@link resetCollected}. */\nexport function collectedRuns(): readonly Collector[] {\n\treturn collected\n}\n\nexport function resetCollected(): void {\n\tcollected.length = 0\n}\n\nfunction newCollector(method: string, path: string): Collector {\n\treturn {\n\t\tcounters: new Map(),\n\t\tcurrent: \"request\",\n\t\tlabel: \"\",\n\t\tmethod,\n\t\tpath,\n\t\tphaseMs: new Map(),\n\t\tqueries: [],\n\t}\n}\n\n/** Run `fn` inside a fresh per-request collector; record the collector when done. */\nexport async function runRequest<T>(\n\tmeta: { method: string; path: string },\n\tfn: () => Promise<T>,\n): Promise<T> {\n\tconst collector = newCollector(meta.method, meta.path)\n\treturn als.run(collector, async () => {\n\t\ttry {\n\t\t\treturn await fn()\n\t\t} finally {\n\t\t\tcollected.push(collector)\n\t\t}\n\t})\n}\n\n/** Measure `fn`'s wall time into the active collector under `name`; no-op when inactive. */\nexport async function withPhase<T>(name: string, fn: () => Promise<T>): Promise<T> {\n\tconst collector = als.getStore()\n\tif (!collector) return fn()\n\tconst previous = collector.current\n\tcollector.current = name\n\tconst start = performance.now()\n\ttry {\n\t\treturn await fn()\n\t} finally {\n\t\tconst elapsed = performance.now() - start\n\t\tcollector.phaseMs.set(name, (collector.phaseMs.get(name) ?? 0) + elapsed)\n\t\tcollector.current = previous\n\t}\n}\n\nexport function count(metric: string, n = 1): void {\n\tconst collector = als.getStore()\n\tif (!collector) return\n\tcollector.counters.set(metric, (collector.counters.get(metric) ?? 0) + n)\n}\n\nexport function label(name: string): void {\n\tconst collector = als.getStore()\n\tif (collector) collector.label = name\n}\n\nexport function recordQuery(sql: string, durationMs: number): void {\n\tconst collector = als.getStore()\n\tif (!collector) return\n\tcollector.queries.push({ durationMs, phase: collector.current, sql })\n}\n","/**\n * A malformed-request / unsupported-capability error detected at the git wire\n * boundary (bad command list, unknown command, unsupported object-format or\n * filter, a request body in an encoding we don't accept). It is the CLIENT's\n * fault, so the HTTP layer maps it to a 400 with the message — distinct from an\n * internal failure (a missing object mid-serve, a DB error), which stays a 500.\n * Validate at the boundary, fail loud, and let the type carry the status.\n */\nexport class GitProtocolError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message)\n\t\tthis.name = \"GitProtocolError\"\n\t}\n}\n\n/**\n * A fetch `want` names an object this repo does not have — a CLIENT condition (a\n * stale/force-pushed tip, a lost promisor blob), not an internal failure. Real git\n * upload-pack answers it IN-BAND with `ERR upload-pack: not our ref <oid>` (an HTTP\n * 200 protocol error the client reads), so it must NOT escape as a 500. Carries the\n * absent OIDs; `handleFetch` maps it to the ERR pkt-line. Distinct from a generic\n * `Error` out of the serve path (a real backend fault), which still propagates → 500.\n */\nexport class WantNotFoundError extends Error {\n\tconstructor(readonly oids: string[]) {\n\t\tsuper(`upload-pack: not our ref ${oids.join(\" \")}`)\n\t\tthis.name = \"WantNotFoundError\"\n\t}\n}\n","/**\n * pkt-line framing (git wire protocol). A pkt-line is a 4-byte hex length prefix\n * (the length INCLUDES the 4 prefix bytes) followed by `length - 4` payload bytes.\n * Three special zero-payload packets: flush `0000`, delim `0001`, response-end\n * `0002`. See gitprotocol-common + design spec §5.\n */\n\nimport { GitProtocolError } from \"@/protocol/errors\"\n\nexport type Pkt =\n\t| { type: \"data\"; payload: Buffer }\n\t| { type: \"flush\" }\n\t| { type: \"delim\" }\n\t| { type: \"response-end\" }\n\nconst FLUSH_PKT = Buffer.from(\"0000\", \"latin1\")\nconst DELIM_PKT = Buffer.from(\"0001\", \"latin1\")\nconst RESPONSE_END_PKT = Buffer.from(\"0002\", \"latin1\")\n\n/** Largest payload we will emit (git's conservative writer cap). */\nexport const WRITER_MAX_PAYLOAD = 65515\n/** Largest payload we will accept on read (git's LARGE_PACKET_DATA_MAX). */\nexport const READER_MAX_PAYLOAD = 65516\n\n/** Frame a data payload as a pkt-line: `<4-hex len><payload>`. */\nexport function encodePktLine(payload: Buffer): Buffer {\n\tif (payload.length > WRITER_MAX_PAYLOAD) {\n\t\tthrow new Error(\n\t\t\t`pkt-line: payload ${payload.length} exceeds writer cap ${WRITER_MAX_PAYLOAD}`,\n\t\t)\n\t}\n\tconst len = payload.length + 4\n\tconst prefix = len.toString(16).padStart(4, \"0\")\n\treturn Buffer.concat([Buffer.from(prefix, \"latin1\"), payload])\n}\n\n/** Frame any packet — data or one of the three special zero-payload markers. */\nexport function encodePkt(pkt: Pkt): Buffer {\n\tswitch (pkt.type) {\n\t\tcase \"data\":\n\t\t\treturn encodePktLine(pkt.payload)\n\t\tcase \"flush\":\n\t\t\treturn FLUSH_PKT\n\t\tcase \"delim\":\n\t\t\treturn DELIM_PKT\n\t\tcase \"response-end\":\n\t\t\treturn RESPONSE_END_PKT\n\t}\n}\n\nfunction parseLen(buf: Buffer, offset: number): number {\n\tconst hex = buf.toString(\"latin1\", offset, offset + 4)\n\tif (!/^[0-9a-f]{4}$/i.test(hex)) {\n\t\t// Malformed framing in a client request body — a wire-boundary fault (400),\n\t\t// not a server error.\n\t\tthrow new GitProtocolError(`pkt-line: invalid length prefix ${JSON.stringify(hex)}`)\n\t}\n\treturn Number.parseInt(hex, 16)\n}\n\n/**\n * Decode a buffer into a sequence of packets. Streaming-safe: a trailing\n * partial packet is left in `rest` for the caller to prepend to the next chunk.\n *\n * With `stopAtFlush`, decoding returns at the first flush (which is NOT included\n * in `packets`), leaving the bytes after it in `rest`. The receive-pack request\n * splits here: a pkt-line command list, a flush, then the raw (un-framed) pack.\n *\n * `flushed` reports whether a flush actually terminated the stream in\n * `stopAtFlush` mode — the parser uses it to reject an unterminated command list\n * on a COMPLETE request body (where \"more bytes coming\" is not an option).\n */\nexport function decodePktStream(\n\tbuf: Buffer,\n\topts: { stopAtFlush?: boolean } = {},\n): { packets: Pkt[]; rest: Buffer; flushed: boolean } {\n\tconst packets: Pkt[] = []\n\tlet offset = 0\n\twhile (offset + 4 <= buf.length) {\n\t\tconst len = parseLen(buf, offset)\n\t\tif (len === 0) {\n\t\t\toffset += 4\n\t\t\tif (opts.stopAtFlush) return { flushed: true, packets, rest: buf.subarray(offset) }\n\t\t\tpackets.push({ type: \"flush\" })\n\t\t\tcontinue\n\t\t}\n\t\tif (len === 1) {\n\t\t\tpackets.push({ type: \"delim\" })\n\t\t\toffset += 4\n\t\t\tcontinue\n\t\t}\n\t\tif (len === 2) {\n\t\t\tpackets.push({ type: \"response-end\" })\n\t\t\toffset += 4\n\t\t\tcontinue\n\t\t}\n\t\tif (len === 3) {\n\t\t\tthrow new GitProtocolError(\"pkt-line: reserved length 0003\")\n\t\t}\n\t\tconst payloadLen = len - 4\n\t\tif (payloadLen > READER_MAX_PAYLOAD) {\n\t\t\tthrow new GitProtocolError(\n\t\t\t\t`pkt-line: declared payload ${payloadLen} exceeds reader bound ${READER_MAX_PAYLOAD}`,\n\t\t\t)\n\t\t}\n\t\t// Incomplete data packet — leave it (and everything after) in `rest`.\n\t\tif (offset + len > buf.length) break\n\t\tconst payload = buf.subarray(offset + 4, offset + len)\n\t\tpackets.push({ payload, type: \"data\" })\n\t\toffset += len\n\t}\n\treturn { flushed: false, packets, rest: buf.subarray(offset) }\n}\n","import { GitProtocolError } from \"@/protocol/errors\"\n\n// Protocol capabilities shared by both services — the agent string and the\n// object-format guard are version-agnostic (push is v0, fetch is v2), so they\n// live here rather than in the v2-named module.\n\nexport const AGENT = \"pggit/0.0.0\"\n\n/**\n * Reject a client negotiating a non-sha1 object hash. pggit is SHA-1 only (the\n * charter) and assumes 40-hex / 20-byte OIDs everywhere; a sha256 client would\n * otherwise fail deep in the parser on a 64-hex OID. Catch it at the boundary\n * with a clear message. An absent `object-format` cap defaults to sha1 (git's\n * default), so it is accepted.\n */\nexport function assertSupportedObjectFormat(caps: string[]): void {\n\tconst fmt = caps.find((c) => c.startsWith(\"object-format=\"))\n\tif (fmt !== undefined && fmt !== \"object-format=sha1\") {\n\t\tthrow new GitProtocolError(\n\t\t\t`unsupported ${fmt} — only object-format=sha1 is supported`,\n\t\t)\n\t}\n}\n","import { encodePktLine } from \"@/protocol/pkt-line\"\n\n/** The sideband-64k data channel (band 1) — carries pack/report payload. */\nexport const SIDEBAND_DATA = 0x01\n\n// One band pkt-line is a band byte + payload, together within the pkt-line writer\n// cap (65515) — so a single payload slice is at most 65514 bytes.\nconst MAX_BAND_DATA = 65514\n\n/**\n * Multiplex `data` onto sideband `band`: each ≤MAX_BAND_DATA slice becomes a\n * pkt-line of `[band byte | slice]`. Returns the concatenated band pkt-lines with\n * NO trailing flush — the caller owns the section framing (the `packfile\\n` header\n * for fetch, the bare report for push) and appends its own flush.\n */\nexport function encodeSideband(band: number, data: Buffer): Buffer {\n\tconst parts: Buffer[] = []\n\tfor (let i = 0; i < data.length; i += MAX_BAND_DATA) {\n\t\tconst chunk = data.subarray(i, i + MAX_BAND_DATA)\n\t\tparts.push(encodePktLine(Buffer.concat([Buffer.from([band]), chunk])))\n\t}\n\treturn Buffer.concat(parts)\n}\n","import { AGENT, assertSupportedObjectFormat } from \"@/protocol/capabilities\"\nimport { GitProtocolError } from \"@/protocol/errors\"\nimport { decodePktStream, encodePkt, encodePktLine } from \"@/protocol/pkt-line\"\nimport { encodeSideband, SIDEBAND_DATA } from \"@/protocol/sideband\"\n\nconst ZERO_OID = \"0\".repeat(40)\n\n/** A ref name longer than this (bytes) is rejected at the boundary: `git_ref`'s PK is\n * a btree on (repo_id, name) whose index entry overflows past ~2704 bytes, which\n * Postgres raises as an opaque storage error. The cap sits far above any real ref name\n * and safely under the btree limit, so a too-long name fails loud + in-band (`ng`),\n * never as an HTTP 500 that has already orphaned the ingested pack. */\nconst MAX_REF_NAME_BYTES = 2000\n\n// The push capabilities we advertise AND honor (spec §4): report-status over\n// side-band, ref deletion, atomic mode, sha1. We pick plain `report-status`\n// (not `-v2`) — we do not emit its extra option lines.\nconst RECEIVE_CAPS = [\n\t\"report-status\",\n\t\"delete-refs\",\n\t\"side-band-64k\",\n\t\"atomic\",\n\t\"object-format=sha1\",\n\t`agent=${AGENT}`,\n]\n\nexport type RefCommand = { oldOid: string; newOid: string; ref: string }\nexport type ReceiveRequest = { commands: RefCommand[]; caps: string[]; pack: Buffer }\nexport type CommandResult = { ref: string; ok: boolean; reason?: string }\n\n/**\n * v0 ref advertisement for receive-pack (push). An empty repo — the dominant\n * first-push state — emits the synthetic `0{40} capabilities^{}` line so the\n * client has somewhere to read the push capabilities.\n */\nexport function encodeReceivePackAdvertisement(\n\trefs: { name: string; oid: string }[],\n): Buffer {\n\tconst capStr = RECEIVE_CAPS.join(\" \")\n\tconst lines: Buffer[] = []\n\tif (refs.length === 0) {\n\t\tlines.push(encodePktLine(Buffer.from(`${ZERO_OID} capabilities^{}\\0${capStr}\\n`)))\n\t} else {\n\t\trefs.forEach((r, i) => {\n\t\t\tconst base = `${r.oid} ${r.name}`\n\t\t\tlines.push(\n\t\t\t\tencodePktLine(Buffer.from(i === 0 ? `${base}\\0${capStr}\\n` : `${base}\\n`)),\n\t\t\t)\n\t\t})\n\t}\n\tlines.push(encodePkt({ type: \"flush\" }))\n\treturn Buffer.concat(lines)\n}\n\n/**\n * Parse the receive-pack POST body: a pkt-line command list (`<old> <new> <ref>`,\n * caps after a NUL on the first line), a flush, then the raw packfile.\n */\nexport function parseReceivePack(body: Buffer): ReceiveRequest {\n\tconst { packets, rest, flushed } = decodePktStream(body, { stopAtFlush: true })\n\t// A non-empty command list MUST be terminated by a flush before the pack. Without\n\t// it, decodePktStream falls off the end (a truncated/length-overrunning command\n\t// pkt-line) and hands the framing garbage back as `rest` — which would otherwise\n\t// be mis-fed to the pack reader. Reject the framing fault loudly. An empty body is\n\t// the legitimate zero-command no-op and is left alone.\n\tif (!flushed && body.length > 0) {\n\t\tthrow new GitProtocolError(\n\t\t\t\"receive-pack: command list not terminated by a flush (truncated or length-overrunning pkt-line)\",\n\t\t)\n\t}\n\tconst commands: RefCommand[] = []\n\tlet caps: string[] = []\n\tfor (const p of packets) {\n\t\tif (p.type !== \"data\") continue\n\t\tlet line = p.payload.toString(\"utf8\").replace(/\\n$/, \"\")\n\t\tconst nul = line.indexOf(\"\\0\")\n\t\tif (nul >= 0) {\n\t\t\tcaps = line\n\t\t\t\t.slice(nul + 1)\n\t\t\t\t.split(\" \")\n\t\t\t\t.filter(Boolean)\n\t\t\tline = line.slice(0, nul)\n\t\t}\n\t\t// Fail loud: a command line is exactly `<old> <new> <ref>`. Anything else is\n\t\t// malformed — reject it rather than silently drop it (which would apply a\n\t\t// partial command set with no diagnostic).\n\t\tconst parts = line.split(\" \")\n\t\tconst [oldOid, newOid, ref] = parts\n\t\tif (parts.length !== 3 || !oldOid || !newOid || !ref) {\n\t\t\tthrow new GitProtocolError(\n\t\t\t\t`receive-pack: malformed command line ${JSON.stringify(line)}`,\n\t\t\t)\n\t\t}\n\t\tcommands.push({ newOid, oldOid, ref })\n\t}\n\treturn { caps, commands, pack: rest }\n}\n\n/**\n * report-status: `unpack <status>` then `ok <ref>` / `ng <ref> <reason>` per\n * command, flush. When side-band-64k is negotiated the whole stream rides band 1.\n */\nexport function encodeReportStatus(\n\tunpack: string,\n\tresults: CommandResult[],\n\tuseSideband: boolean,\n): Buffer {\n\tconst lines: Buffer[] = [encodePktLine(Buffer.from(`unpack ${unpack}\\n`))]\n\tfor (const r of results) {\n\t\tconst line = r.ok ? `ok ${r.ref}\\n` : `ng ${r.ref} ${r.reason ?? \"failed\"}\\n`\n\t\tlines.push(encodePktLine(Buffer.from(line)))\n\t}\n\tlines.push(encodePkt({ type: \"flush\" }))\n\tconst report = Buffer.concat(lines)\n\tif (!useSideband) return report\n\treturn Buffer.concat([\n\t\tencodeSideband(SIDEBAND_DATA, report),\n\t\tencodePkt({ type: \"flush\" }),\n\t])\n}\n\n/** Everything receive-pack needs from a single repo's storage. */\nexport type ReceiveBackend = {\n\tingest: (pack: Buffer) => Promise<void>\n\t/** Apply ref CAS updates; `atomic` ⇒ all-or-nothing. Per-command success flags. */\n\tapplyRefUpdates: (commands: RefCommand[], atomic: boolean) => Promise<boolean[]>\n\t/** Is every object reachable from `oid` present? (connectivity, spec §10). */\n\tisConnected: (oid: string) => Promise<boolean>\n\t/** Refresh the queryable file projection for a just-applied ref. Present only\n\t * when the (optional) queryable-view layer is wired; a plain remote omits it. */\n\tsyncRefSnapshot?: (ref: string, newOid: string) => Promise<void>\n}\n\n/**\n * Handle a receive-pack POST: ingest the pack (if any), then apply the ref\n * commands under CAS — atomically when the client negotiated `atomic` — and\n * report status. A failed unpack fails every ref; an atomic failure ng's every\n * ref (none applied). Non-ff is accepted by default (CAS guards concurrency, not\n * ancestry — spec §3.6).\n */\nexport async function handleReceivePack(\n\tbody: Buffer,\n\tbackend: ReceiveBackend,\n): Promise<Buffer> {\n\tconst { commands, caps, pack } = parseReceivePack(body)\n\tassertSupportedObjectFormat(caps)\n\tconst useSideband = caps.includes(\"side-band-64k\")\n\tconst atomic = caps.includes(\"atomic\")\n\n\t// rc3 boundary check: a ref name too long to store is rejected BEFORE ingest — so\n\t// an all-unstorable push never ingests a pack (no orphaned objects), and the raw\n\t// btree error never escapes as a 500.\n\tconst nameTooLong = commands.map(\n\t\t(c) => Buffer.byteLength(c.ref, \"utf8\") > MAX_REF_NAME_BYTES,\n\t)\n\tconst anyApplicable = nameTooLong.length === 0 || nameTooLong.some((t) => !t)\n\n\tlet unpackStatus = \"ok\"\n\tif (pack.length > 0 && anyApplicable) {\n\t\ttry {\n\t\t\tawait backend.ingest(pack)\n\t\t} catch (e) {\n\t\t\tunpackStatus = (e instanceof Error ? e.message : \"unpack failed\").replace(\n\t\t\t\t/\\n/g,\n\t\t\t\t\" \",\n\t\t\t)\n\t\t}\n\t}\n\n\tif (unpackStatus !== \"ok\") {\n\t\tconst failed = commands.map((c) => ({\n\t\t\tok: false,\n\t\t\treason: \"unpacker error\",\n\t\t\tref: c.ref,\n\t\t}))\n\t\treturn encodeReportStatus(unpackStatus, failed, useSideband)\n\t}\n\n\t// Connectivity (spec §10): a create/update must leave its new tip fully reachable\n\t// in the store; a delete (newOid zero) needs no objects. A too-long ref is already\n\t// disqualified, so it skips the closure walk.\n\tconst connected = await Promise.all(\n\t\tcommands.map((c, i) =>\n\t\t\tnameTooLong[i] || c.newOid === ZERO_OID\n\t\t\t\t? Promise.resolve(true)\n\t\t\t\t: backend.isConnected(c.newOid),\n\t\t),\n\t)\n\t// Per-command disqualification reason (null ⇒ applicable): a too-long name fails\n\t// the storage boundary, a disconnected tip fails connectivity. A disqualified\n\t// command never touches a ref.\n\tconst reasons = commands.map((_, i) =>\n\t\tnameTooLong[i]\n\t\t\t? \"funny refname (too long to store)\"\n\t\t\t: connected[i]\n\t\t\t\t? null\n\t\t\t\t: \"missing necessary objects\",\n\t)\n\tif (atomic && reasons.some((r) => r !== null)) {\n\t\tconst failed = commands.map((c, i) => ({\n\t\t\tok: false,\n\t\t\treason: reasons[i] ?? \"atomic transaction failed\",\n\t\t\tref: c.ref,\n\t\t}))\n\t\treturn encodeReportStatus(unpackStatus, failed, useSideband)\n\t}\n\n\t// Apply only the applicable commands; a disqualified one never touches a ref.\n\tconst oks = await backend.applyRefUpdates(\n\t\tcommands.filter((_, i) => reasons[i] === null),\n\t\tatomic,\n\t)\n\tlet applied = 0\n\tconst results: CommandResult[] = commands.map((c, i) => {\n\t\tconst reason = reasons[i]\n\t\tif (reason !== null) return { ok: false, reason, ref: c.ref }\n\t\treturn oks[applied++]\n\t\t\t? { ok: true, ref: c.ref }\n\t\t\t: {\n\t\t\t\t\tok: false,\n\t\t\t\t\treason: atomic\n\t\t\t\t\t\t? \"atomic transaction failed\"\n\t\t\t\t\t\t: \"stale ref (compare-and-swap failed)\",\n\t\t\t\t\tref: c.ref,\n\t\t\t\t}\n\t})\n\n\t// Post-commit: refresh the queryable file projection for each applied ref. The\n\t// view layer decides branch-filtering and build-vs-drop. Sequential — same-repo\n\t// rebuilds must not race the shared-blob reaper.\n\tfor (const [i, c] of commands.entries()) {\n\t\tif (!results[i]?.ok) continue\n\t\t// rc2: the queryable view is a DERIVED projection — a rebuild failure (e.g. a\n\t\t// tip that is not a commit, which buildFileList cannot walk) must NEVER roll\n\t\t// back or 500 an already-applied push (rebuild.ts's standing contract). Absorb\n\t\t// it loudly to the log; the projection is rebuilt on the next push to the ref.\n\t\ttry {\n\t\t\tawait backend.syncRefSnapshot?.(c.ref, c.newOid)\n\t\t} catch (err) {\n\t\t\tconsole.error(\n\t\t\t\t`pggit: snapshot refresh failed for ${c.ref} (the push is already applied):`,\n\t\t\t\terr,\n\t\t\t)\n\t\t}\n\t}\n\treturn encodeReportStatus(unpackStatus, results, useSideband)\n}\n","import { AGENT } from \"@/protocol/capabilities\"\nimport { GitProtocolError } from \"@/protocol/errors\"\nimport { decodePktStream, encodePkt, encodePktLine } from \"@/protocol/pkt-line\"\nimport { encodeSideband, SIDEBAND_DATA } from \"@/protocol/sideband\"\n\n/**\n * The v2 capability advertisement (GET info/refs body, minus HTTP framing).\n * We advertise ONLY what we honor (spec §4): ls-refs (with `unborn`) and fetch\n * with the `filter` (partial clone) and `include-tag` (auto-follow annotated tags)\n * features. No shallow / ref-in-want — those have no milestone owner and\n * advertising them flips clients onto unimplemented paths.\n */\nexport function encodeAdvertisement(): Buffer {\n\tconst caps = [\n\t\t\"version 2\",\n\t\t`agent=${AGENT}`,\n\t\t\"ls-refs=unborn\",\n\t\t\"fetch=filter include-tag\",\n\t\t\"object-format=sha1\",\n\t]\n\treturn Buffer.concat([\n\t\t...caps.map((c) => encodePktLine(Buffer.from(`${c}\\n`))),\n\t\tencodePkt({ type: \"flush\" }),\n\t])\n}\n\nexport type V2Request = {\n\tcommand: string\n\tcapabilities: string[]\n\targs: string[]\n}\n\n/** Decode a `command=… <caps> 0001 <args> 0000` v2 request body. */\nexport function parseV2Request(body: Buffer): V2Request {\n\tconst { packets, rest } = decodePktStream(body)\n\t// The body is a COMPLETE request: any leftover bytes are an incomplete or\n\t// length-overrunning packet (decodePktStream leaves a partial packet in `rest`).\n\t// On a complete body that is a framing fault, not a streaming boundary — reject\n\t// it loudly rather than silently dropping the truncated args.\n\tif (rest.length > 0) {\n\t\tthrow new GitProtocolError(\n\t\t\t`pkt-line: ${rest.length} trailing bytes after the request — incomplete or length-overrunning packet`,\n\t\t)\n\t}\n\tlet command = \"\"\n\tconst capabilities: string[] = []\n\tconst args: string[] = []\n\tlet afterDelim = false\n\tfor (const p of packets) {\n\t\tif (p.type === \"delim\") {\n\t\t\tafterDelim = true\n\t\t\tcontinue\n\t\t}\n\t\tif (p.type !== \"data\") continue\n\t\tconst line = p.payload.toString(\"utf8\").replace(/\\n$/, \"\")\n\t\tif (afterDelim) args.push(line)\n\t\telse if (line.startsWith(\"command=\")) command = line.slice(\"command=\".length)\n\t\telse capabilities.push(line)\n\t}\n\treturn { args, capabilities, command }\n}\n\nexport type FetchRequest = {\n\twants: string[]\n\thaves: string[]\n\tdone: boolean\n\t/** Partial-clone filter spec (e.g. `blob:none`), if the client sent one. */\n\tfilter?: string\n\t/** Client asked the server to auto-include annotated tags pointing into the\n\t * fetched set (the `include-tag` capability). */\n\tincludeTag: boolean\n}\n\n/** Fetch features pggit deliberately does NOT advertise (encodeAdvertisement): a\n * client that drives one anyway must FAIL LOUDLY, never be silently dropped to an\n * empty result (the charter). `ref-in-want` (`want-ref`) and the `shallow`/`deepen`\n * family are the unimplemented ones. */\nconst UNSUPPORTED_FETCH_ARG = /^(want-ref|deepen|shallow)\\b/\n\nconst OID = /^[0-9a-f]{40}$/\n\nexport function parseFetch(req: V2Request): FetchRequest {\n\tconst wants: string[] = []\n\tconst haves: string[] = []\n\tlet done = false\n\tlet filter: string | undefined\n\tlet includeTag = false\n\tfor (const arg of req.args) {\n\t\t// Reject an unadvertised feature request loudly before parsing wants — else a\n\t\t// `want-ref` line falls through every branch below and silently leaves wants=[]\n\t\t// (a no-op empty pack the client misreads as a successful empty clone).\n\t\tif (UNSUPPORTED_FETCH_ARG.test(arg)) {\n\t\t\tthrow new GitProtocolError(\n\t\t\t\t`fetch: unsupported feature ${JSON.stringify(arg.split(\" \")[0])} — pggit does not advertise it`,\n\t\t\t)\n\t\t}\n\t\tif (arg.startsWith(\"want \")) {\n\t\t\tconst oid = arg.slice(5)\n\t\t\t// A want OID is coerced to `bytea` downstream via Buffer.from(oid, \"hex\"),\n\t\t\t// which SILENTLY yields a short/empty buffer for a non-hex value and then\n\t\t\t// fails deep in buildPack. Validate the wire shape at the boundary instead.\n\t\t\tif (!OID.test(oid)) {\n\t\t\t\tthrow new GitProtocolError(\n\t\t\t\t\t`fetch: malformed want object id ${JSON.stringify(oid)}`,\n\t\t\t\t)\n\t\t\t}\n\t\t\twants.push(oid)\n\t\t} else if (arg.startsWith(\"have \")) haves.push(arg.slice(5))\n\t\telse if (arg.startsWith(\"filter \")) filter = arg.slice(\"filter \".length)\n\t\telse if (arg === \"include-tag\") includeTag = true\n\t\telse if (arg === \"done\") done = true\n\t}\n\treturn { done, filter, haves, includeTag, wants }\n}\n\n/**\n * One ls-refs line. A normal ref leads with its oid; an `unborn` ref (an empty\n * repo's HEAD, which has no commit yet) leads with the literal `unborn` token\n * instead — exactly git's `ls-refs.c send_ref` shape.\n */\nexport type LsRefEntry =\n\t| { name: string; oid: string; symrefTarget?: string; peeled?: string }\n\t| { name: string; unborn: true; symrefTarget?: string }\n\n/** ls-refs response: one line per ref (+ symref-target / peeled), then flush. */\nexport function encodeLsRefsResponse(entries: LsRefEntry[]): Buffer {\n\tconst lines = entries.map((e) => {\n\t\tlet line = \"unborn\" in e ? `unborn ${e.name}` : `${e.oid} ${e.name}`\n\t\tif (e.symrefTarget) line += ` symref-target:${e.symrefTarget}`\n\t\tif (\"peeled\" in e && e.peeled) line += ` peeled:${e.peeled}`\n\t\treturn encodePktLine(Buffer.from(`${line}\\n`))\n\t})\n\treturn Buffer.concat([...lines, encodePkt({ type: \"flush\" })])\n}\n\n/** The `acknowledgments` section lines: header, ACKs / NAK, optional `ready`. */\nfunction acknowledgmentLines(common: string[], ready: boolean): Buffer {\n\tconst lines: Buffer[] = [encodePktLine(Buffer.from(\"acknowledgments\\n\"))]\n\tif (common.length === 0 && !ready) {\n\t\tlines.push(encodePktLine(Buffer.from(\"NAK\\n\")))\n\t} else {\n\t\tfor (const oid of common) lines.push(encodePktLine(Buffer.from(`ACK ${oid}\\n`)))\n\t\tif (ready) lines.push(encodePktLine(Buffer.from(\"ready\\n\")))\n\t}\n\treturn Buffer.concat(lines)\n}\n\n/**\n * fetch `acknowledgments` response for a negotiation round that is NOT yet ready\n * (no `done`): the section + flush, no pack. The client sends more haves or\n * `done` (spec §4 shape b).\n */\nexport function encodeAcknowledgments(common: string[], ready: boolean): Buffer {\n\treturn Buffer.concat([acknowledgmentLines(common, ready), encodePkt({ type: \"flush\" })])\n}\n\n/**\n * fetch response when the server becomes `ready` mid-negotiation: the\n * acknowledgments section (with `ready`), a delim-pkt, then the packfile — git\n * requires the pack to follow `ready` in the same response (not a later round).\n */\nexport function encodeReadyWithPack(common: string[], pack: Buffer): Buffer {\n\treturn Buffer.concat([\n\t\tacknowledgmentLines(common, true),\n\t\tencodePkt({ type: \"delim\" }),\n\t\tencodePackfileResponse(pack),\n\t])\n}\n\n/**\n * A v2 error response: a single `ERR <message>` pkt-line. git's packet reader\n * recognizes the `ERR ` prefix and the client dies with `remote error: <message>`\n * — the in-band channel for a request that cannot be served (e.g. a `want` the repo\n * does not have): an HTTP-200 protocol error the client can read, NOT a transport 500.\n */\nexport function encodeErr(message: string): Buffer {\n\treturn encodePktLine(Buffer.from(`ERR ${message}\\n`))\n}\n\n/**\n * fetch response for the clone path (client sent `done`, no haves): the\n * `packfile` section header, the pack multiplexed over sideband band-1, flush.\n */\nexport function encodePackfileResponse(pack: Buffer): Buffer {\n\treturn Buffer.concat([\n\t\tencodePktLine(Buffer.from(\"packfile\\n\")),\n\t\tencodeSideband(SIDEBAND_DATA, pack),\n\t\tencodePkt({ type: \"flush\" }),\n\t])\n}\n","import { label, withPhase } from \"@/instrument\"\nimport { assertSupportedObjectFormat } from \"@/protocol/capabilities\"\nimport { GitProtocolError, WantNotFoundError } from \"@/protocol/errors\"\nimport {\n\tencodeAcknowledgments,\n\tencodeErr,\n\tencodeLsRefsResponse,\n\tencodePackfileResponse,\n\tencodeReadyWithPack,\n\ttype LsRefEntry,\n\tparseFetch,\n\tparseV2Request,\n\ttype V2Request,\n} from \"@/protocol/v2\"\n\n/** A ref advertised by the store: its oid, plus the peeled tag target when it is\n * an annotated tag (computed at ref-write, §5.3). */\nexport type AdvertisedRef = { name: string; oid: string; peeled?: string }\n\n/**\n * Everything the upload-pack service needs from a single repo's storage. The graph\n * logic lives in the store now (set-based SQL over the row+edge model), so this is\n * a thin set-oriented interface — no object-at-a-time walk. Tag peeling is read\n * straight off the ref (`peeled`), so there is no object-fetch on the serve path.\n */\nexport type RepoBackend = {\n\tlistRefs: () => Promise<AdvertisedRef[]>\n\tgetSymref: (name: string) => Promise<string | null>\n\t/** The subset of `haves` the repo has — the negotiation common set. */\n\tcommonHaves: (haves: string[]) => Promise<string[]>\n\t/** git's ok_to_give_up: does every want reach a common have by ancestry? */\n\treadyToGiveUp: (wants: string[], common: string[]) => Promise<boolean>\n\t/** The served pack: want-closure minus have-closure, plus the explicit wants\n\t * (and, when `includeTag`, annotated tags pointing into the served set). */\n\tbuildPack: (\n\t\twants: string[],\n\t\thaves: string[],\n\t\tomitBlobs: boolean,\n\t\tincludeTag: boolean,\n\t) => Promise<Buffer>\n}\n\nasync function handleLsRefs(req: V2Request, backend: RepoBackend): Promise<Buffer> {\n\tlabel(\"ls-refs\")\n\treturn withPhase(\"ref-advertise\", async () => {\n\t\tconst wantPeel = req.args.includes(\"peel\")\n\t\tconst wantSymrefs = req.args.includes(\"symrefs\")\n\t\tconst prefixes = req.args\n\t\t\t.filter((a) => a.startsWith(\"ref-prefix \"))\n\t\t\t.map((a) => a.slice(\"ref-prefix \".length))\n\t\tconst matches = (name: string) =>\n\t\t\tprefixes.length === 0 || prefixes.some((p) => name.startsWith(p))\n\n\t\tconst refs = await backend.listRefs()\n\t\tconst byName = new Map(refs.map((r) => [r.name, r.oid]))\n\t\tconst entries: LsRefEntry[] = []\n\n\t\tconst wantUnborn = req.args.includes(\"unborn\")\n\t\tconst headTarget = await backend.getSymref(\"HEAD\")\n\t\tif (headTarget && matches(\"HEAD\")) {\n\t\t\tconst headOid = byName.get(headTarget)\n\t\t\tif (headOid) {\n\t\t\t\tentries.push({\n\t\t\t\t\tname: \"HEAD\",\n\t\t\t\t\toid: headOid,\n\t\t\t\t\tsymrefTarget: wantSymrefs ? headTarget : undefined,\n\t\t\t\t})\n\t\t\t} else if (wantUnborn && wantSymrefs) {\n\t\t\t\t// Empty repo: HEAD points at a branch with no commit yet. git emits the\n\t\t\t\t// unborn HEAD only when the client asked for both `unborn` and `symrefs`\n\t\t\t\t// (ls-refs.c send_possibly_unborn_head), propagating the default branch.\n\t\t\t\tentries.push({ name: \"HEAD\", symrefTarget: headTarget, unborn: true })\n\t\t\t}\n\t\t}\n\n\t\tfor (const ref of refs) {\n\t\t\tif (!matches(ref.name)) continue\n\t\t\tconst entry: LsRefEntry = { name: ref.name, oid: ref.oid }\n\t\t\t// Peeled target is precomputed at ref-write (§5.3) — no per-request walk.\n\t\t\tif (wantPeel && ref.peeled) entry.peeled = ref.peeled\n\t\t\tentries.push(entry)\n\t\t}\n\n\t\treturn encodeLsRefsResponse(entries)\n\t})\n}\n\n/**\n * Translate the wire filter spec to a walk option. We optimize the common\n * `blob:none` (blobless partial clone) by omitting blobs; any other filter\n * (`tree:0`, `blob:limit=…`, …) serves the FULL closure. The protocol lets a\n * server send more than a filter requests — the client accepts the superset and\n * has nothing to lazily fetch — so over-serving completes the clone that a hard\n * rejection would abort, without implementing every filter spec.\n */\nfunction filterOmitsBlobs(filter: string | undefined): boolean {\n\treturn filter === \"blob:none\"\n}\n\nasync function handleFetch(req: V2Request, backend: RepoBackend): Promise<Buffer> {\n\tlabel(\"fetch\")\n\t// parseFetch validates wire shape (malformed/unsupported args → GitProtocolError\n\t// → 400) BEFORE the serve attempt — kept outside the try so it stays a 400.\n\tconst { wants, haves, done, filter, includeTag } = parseFetch(req)\n\t// A zero-want fetch is NOT an error: git's upload-pack treats it as a no-op\n\t// (upload-pack.c) and returns an empty pack — buildPack produces one, so we let\n\t// it fall through rather than rejecting.\n\tconst omitBlobs = filterOmitsBlobs(filter)\n\tconst common = await backend.commonHaves(haves)\n\n\ttry {\n\t\tif (!done) {\n\t\t\t// Negotiation round (spec §4 shape b): until the haves cut every want we\n\t\t\t// ACK/NAK and flush, no pack — the client sends more haves. Once ready, git\n\t\t\t// requires the pack in this same response, after the `ready` line.\n\t\t\tif (!(await backend.readyToGiveUp(wants, common))) {\n\t\t\t\treturn encodeAcknowledgments(common, false)\n\t\t\t}\n\t\t\treturn encodeReadyWithPack(\n\t\t\t\tcommon,\n\t\t\t\tawait backend.buildPack(wants, common, omitBlobs, includeTag),\n\t\t\t)\n\t\t}\n\n\t\t// `done` (spec §4 shapes a/c): pack the delta directly. A clone has no haves, so\n\t\t// the subtrahend is empty and we pack the whole want-closure.\n\t\treturn encodePackfileResponse(\n\t\t\tawait backend.buildPack(wants, common, omitBlobs, includeTag),\n\t\t)\n\t} catch (err) {\n\t\t// A `want` the repo does not have is a client condition, not a server fault:\n\t\t// answer it in-band like canonical upload-pack (`ERR … not our ref`) so the\n\t\t// client reads a clean protocol error, not an HTTP 500. A genuine serve failure\n\t\t// (any other error) still propagates → 500 (§10, no partial pack is emitted).\n\t\tif (err instanceof WantNotFoundError) return encodeErr(err.message)\n\t\tthrow err\n\t}\n}\n\n/** Dispatch a v2 upload-pack POST body to ls-refs or fetch. */\nexport async function handleUploadPack(\n\tbody: Buffer,\n\tbackend: RepoBackend,\n): Promise<Buffer> {\n\tconst req = parseV2Request(body)\n\tassertSupportedObjectFormat(req.capabilities)\n\tif (req.command === \"ls-refs\") return handleLsRefs(req, backend)\n\tif (req.command === \"fetch\") return handleFetch(req, backend)\n\tthrow new GitProtocolError(\n\t\t`upload-pack: unsupported command ${JSON.stringify(req.command)}`,\n\t)\n}\n","/**\n * A \"the git data we were handed is not well-formed\" error — raised while parsing\n * a packfile, applying a delta, or reading a git object's bytes. It is distinct\n * from `GitProtocolError` (a malformed *request* at the wire boundary, → HTTP\n * 400): a `GitFormatError` is about the *content* (a corrupt/truncated pack or\n * object), which on push surfaces as the `unpack <reason>` report-status line and\n * otherwise propagates as an internal failure.\n *\n * The `code` is the stable, assertable identity of the failure; the `message` is\n * free-form prose for humans (the `unpack` line, logs) and may be reworded\n * without breaking callers or tests. Tests assert `code`, never the message text.\n */\nexport type GitFormatErrorCode =\n\t// packfile framing / codec (read-pack)\n\t| \"bad-magic\"\n\t| \"unsupported-version\"\n\t| \"trailer-mismatch\"\n\t| \"unknown-object-type\"\n\t| \"size-mismatch\"\n\t| \"trailing-bytes\"\n\t| \"unresolved-base\"\n\t| \"inflate-failed\"\n\t// delta application\n\t| \"delta-base-size-mismatch\"\n\t| \"delta-reserved-opcode\"\n\t| \"delta-target-size-mismatch\"\n\t// git object content\n\t| \"malformed-tree\"\n\t| \"malformed-oid\"\n\t| \"missing-tree-header\"\n\t| \"multiple-tree-headers\"\n\t| \"missing-tag-object\"\n\t| \"multiple-tag-objects\"\n\nexport class GitFormatError extends Error {\n\treadonly code: GitFormatErrorCode\n\n\tconstructor(code: GitFormatErrorCode, message: string) {\n\t\tsuper(message)\n\t\tthis.name = \"GitFormatError\"\n\t\tthis.code = code\n\t}\n}\n","import { createHash } from \"node:crypto\"\nimport { GitFormatError } from \"@/object/format-error\"\n\n/** The four addressable git object types (deltas resolve into one of these). */\nexport type GitObjectType = \"blob\" | \"commit\" | \"tree\" | \"tag\"\n\n/**\n * The git object ID: SHA-1 of the loose-object representation\n * `\"<type> <byteLength>\\0\" + content`. Returns the 40-char lowercase hex digest.\n */\nexport function computeOid(type: GitObjectType, content: Buffer): string {\n\tconst header = Buffer.from(`${type} ${content.length}\\0`, \"latin1\")\n\treturn createHash(\"sha1\").update(header).update(content).digest(\"hex\")\n}\n\n/** OIDs in the leading `key <oid>` headers (up to the blank line) for given keys. */\nfunction headerOids(content: Buffer, keys: Set<string>): string[] {\n\tconst oids: string[] = []\n\tfor (const line of content.toString(\"latin1\").split(\"\\n\")) {\n\t\tif (line === \"\") break // headers end at the blank line\n\t\tconst sp = line.indexOf(\" \")\n\t\tif (sp > 0 && keys.has(line.slice(0, sp))) oids.push(line.slice(sp + 1))\n\t}\n\treturn oids\n}\n\n/** One entry of a tree. `mode` is the raw stored value (`\"40000\"` for a subtree —\n * git zero-pads to `\"040000\"` only for display); `name` is the entry's own path\n * segment, not a full path. */\nexport type TreeEntry = { mode: string; name: string; oid: string }\n\n/** A tree's entries — `<mode> <name>\\0<20-byte oid>` repeated. */\nexport function treeEntries(content: Buffer): TreeEntry[] {\n\tconst entries: TreeEntry[] = []\n\tlet pos = 0\n\twhile (pos < content.length) {\n\t\tconst space = content.indexOf(0x20, pos)\n\t\tconst nul = content.indexOf(0x00, pos)\n\t\t// Fail loud: a tree is `<mode> <name>\\0<20-byte oid>` repeated exactly. Any\n\t\t// missing separator or a trailing OID shorter than 20 bytes is corruption —\n\t\t// throw rather than return a short list (which would let `isConnected` report\n\t\t// a truncated object as connected and silently accept bad data).\n\t\tif (space < 0 || nul < 0 || space > nul || nul + 21 > content.length) {\n\t\t\tthrow new GitFormatError(\"malformed-tree\", `tree: malformed entry at offset ${pos}`)\n\t\t}\n\t\tconst mode = content.subarray(pos, space).toString(\"latin1\")\n\t\tconst name = content.subarray(space + 1, nul).toString(\"utf8\")\n\t\tconst oid = content.subarray(nul + 1, nul + 21).toString(\"hex\")\n\t\tentries.push({ mode, name, oid })\n\t\tpos = nul + 21\n\t}\n\treturn entries\n}\n\n/** A tree entry's mode marks a subtree (directory), not a blob or gitlink. */\nexport function isTreeEntryMode(mode: string): boolean {\n\treturn mode === \"40000\"\n}\n\n/** OIDs of a tree's entries (all kinds), in tree order. */\nfunction treeEntryOids(content: Buffer): string[] {\n\treturn treeEntries(content).map((e) => e.oid)\n}\n\n/** A commit's parent OIDs only (ancestry walk; excludes its tree). */\nexport function commitParents(content: Buffer): string[] {\n\treturn headerOids(content, new Set([\"parent\"]))\n}\n\n/** A commit's root tree OID. Every commit has exactly one `tree` header. */\nexport function commitTreeOid(content: Buffer): string {\n\tconst [tree] = headerOids(content, new Set([\"tree\"]))\n\tif (!tree) {\n\t\tthrow new GitFormatError(\n\t\t\t\"missing-tree-header\",\n\t\t\t\"commitTreeOid: commit has no tree header\",\n\t\t)\n\t}\n\treturn tree\n}\n\n/**\n * The OIDs an object directly references: a commit → its tree + parents, a tree\n * → its entries, a tag → its target, a blob → nothing. The basis of reachability\n * enumeration (fetch, connectivity).\n */\nexport function referencedOids(type: GitObjectType, content: Buffer): string[] {\n\tswitch (type) {\n\t\tcase \"blob\":\n\t\t\treturn []\n\t\tcase \"commit\":\n\t\t\treturn headerOids(content, new Set([\"tree\", \"parent\"]))\n\t\tcase \"tag\":\n\t\t\treturn headerOids(content, new Set([\"object\"]))\n\t\tcase \"tree\":\n\t\t\treturn treeEntryOids(content)\n\t}\n}\n","import {\n\tcommitTreeOid,\n\ttype GitObjectType,\n\tisTreeEntryMode,\n\ttreeEntries,\n} from \"@/object/object\"\n\n/** Reads a stored object's type + content by OID — the snapshot builder's view of\n * the object store. */\nexport type ObjectReader = (\n\toid: string,\n) => Promise<{ type: GitObjectType; content: Buffer }>\n\nexport type FileEntry = { path: string; mode: string; blobOid: string }\nexport type FileList = { files: FileEntry[] }\n\n/** Gitlink/submodule entries point at a commit in another repo — no blob here. */\nconst GITLINK_MODE = \"160000\"\n\n/**\n * The flat path→blob index of a commit's tree (the `git ls-tree -r` of a commit,\n * read straight from the object store): one FileEntry per blob — full path from the\n * root, raw mode, blob oid. Subtrees are recursed; gitlinks (submodules) are skipped\n * (no blob in this repo). Blob CONTENT is NOT read — it lives in git_object and is\n * joined at query time (§4.5 collapse), so this walk touches only commits + trees.\n */\nexport async function buildFileList(\n\tread: ObjectReader,\n\tcommitOid: string,\n): Promise<FileList> {\n\tconst commit = await read(commitOid)\n\tconst files: FileEntry[] = []\n\n\tconst walk = async (treeOid: string, prefix: string): Promise<void> => {\n\t\tconst tree = await read(treeOid)\n\t\tfor (const entry of treeEntries(tree.content)) {\n\t\t\tconst path = prefix + entry.name\n\t\t\tif (isTreeEntryMode(entry.mode)) {\n\t\t\t\tawait walk(entry.oid, `${path}/`)\n\t\t\t} else if (entry.mode !== GITLINK_MODE) {\n\t\t\t\tfiles.push({ blobOid: entry.oid, mode: entry.mode, path })\n\t\t\t}\n\t\t}\n\t}\n\n\tawait walk(commitTreeOid(commit.content), \"\")\n\treturn { files }\n}\n","/**\n * Which refs get a queryable file snapshot. Branches only — tags, notes, and\n * `refs/pull/*` are skipped. One edit to widen the projection later.\n */\nexport const SNAPSHOT_REFS = (refName: string): boolean =>\n\trefName.startsWith(\"refs/heads/\")\n","import { buildFileList } from \"@/repo-view/build-file-list\"\nimport { SNAPSHOT_REFS } from \"@/repo-view/config\"\nimport type { RepoFileProjection } from \"@/repo-view/repo-file-projection\"\nimport type { ObjectStore } from \"@/store/object-store\"\nimport type { RefStore } from \"@/store/refs-store\"\n\nconst ZERO_OID = \"0\".repeat(40)\n\nexport type SnapshotDeps = {\n\tobjects: ObjectStore\n\tsnapshots: RepoFileProjection\n}\n\n/**\n * Refresh `refName`'s file snapshot after a push applied it. Non-branch refs are\n * ignored (§ SNAPSHOT_REFS); a delete (zero oid) drops the snapshot; otherwise\n * the new tip's tree is walked — objects are already present post-ingest — into a\n * fresh snapshot. Runs after the push commits, so a failure here never rolls back\n * the git operation (the projection is rebuildable from the packs).\n */\nexport async function syncRefSnapshot(\n\tdeps: SnapshotDeps,\n\trepoId: string,\n\trefName: string,\n\tnewOid: string,\n): Promise<void> {\n\tif (!SNAPSHOT_REFS(refName)) return\n\tif (newOid === ZERO_OID) {\n\t\tawait deps.snapshots.dropRefSnapshot(repoId, refName)\n\t\treturn\n\t}\n\tconst read = async (oid: string) => {\n\t\tconst obj = await deps.objects.getObject(repoId, oid)\n\t\tif (!obj)\n\t\t\tthrow new Error(`repo-view: object ${oid} missing while building ${refName}`)\n\t\treturn obj\n\t}\n\tawait deps.snapshots.rebuildRefSnapshot(\n\t\trepoId,\n\t\trefName,\n\t\tawait buildFileList(read, newOid),\n\t)\n}\n\n/**\n * Rebuild a repo's entire projection from its current branch tips — the backfill\n * for an existing repo, and the \"nuke and rebuild\" backstop if the cache ever\n * drifts. Everything is re-derived from the canonical packs.\n */\nexport async function rebuildAllSnapshots(\n\tdeps: SnapshotDeps & { refs: RefStore },\n\trepoId: string,\n): Promise<void> {\n\tawait deps.snapshots.clearRepo(repoId)\n\tfor (const ref of await deps.refs.listRefs(repoId)) {\n\t\tawait syncRefSnapshot(deps, repoId, ref.name, ref.oid)\n\t}\n}\n","import { Kysely } from \"kysely\"\nimport { PostgresJSDialect } from \"kysely-postgres-js\"\nimport type { Sql } from \"postgres\"\nimport { recordQuery } from \"@/instrument\"\n\n// Mirrors web/postgres.ts: Kysely over the porsager `postgres` driver via\n// PostgresJSDialect. Unlike web, pggit does NOT keep a module-level singleton —\n// the caller owns the porsager client and injects it; each store builds its own\n// Kysely from it (so the app stays a mountable sub-app and per-schema test\n// isolation works), and keeps the raw client for the COPY ingest path.\nconst EVENT_SIGNS = { error: \"🔴\", query: \"🟢\" } as const\n\n/** Wrap a porsager client in a typed Kysely. Dev builds log query/error events. */\nexport function initKysely<T>(pg: Sql): Kysely<T> {\n\treturn new Kysely<T>({\n\t\tdialect: new PostgresJSDialect({ postgres: pg }),\n\t\tlog(event) {\n\t\t\tif (event.level === \"query\" || event.level === \"error\") {\n\t\t\t\trecordQuery(event.query.sql, event.queryDurationMillis)\n\t\t\t\tif (process.env.NODE_ENV === \"development\") {\n\t\t\t\t\tconsole.debug(\n\t\t\t\t\t\t`${EVENT_SIGNS[event.level]} ${event.queryDurationMillis}ms ${event.query.sql}`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t})\n}\n","import type { TransactionSql } from \"postgres\"\n\n/**\n * Bulk binary insert via `COPY … FROM STDIN (FORMAT binary)` into a staging temp\n * table, then `INSERT … SELECT … ON CONFLICT DO NOTHING`. This is the one bulk-row\n * insert path for the ingest spine, and it exists because a multi-row `INSERT`\n * cannot carry git's data correctly:\n *\n * - The porsager driver serializes a `bytea` parameter as the text `'\\x'+hex`,\n * DOUBLING the byte length; a blob over ~256MiB therefore overruns V8's max\n * string length and the insert throws. COPY binary streams the bytea as RAW\n * bytes — content never lands on the JS string heap, so blob size is bounded\n * only by Postgres (`bytea` ~1GB), not by the string cap.\n * - The wire protocol caps a statement at 65534 bind parameters (≈13k object rows\n * at 5 columns); COPY binds none, so any row count goes in a single statement —\n * no chunking, whatever the column count.\n *\n * The staging hop keeps ingest idempotent (a re-sent object skips on the primary\n * key), exactly as the prior `onConflict().doNothing()` did. Caller runs it on a\n * transaction-scoped `Sql` so the staging COPY and the final insert commit\n * together; the temp table drops on commit.\n */\n\n/** One COPY field, tagged with the destination column's Postgres type so it\n * encodes to the correct binary wire form. */\nexport type CopyValue =\n\t| { t: \"int2\"; v: number }\n\t| { t: \"int4\"; v: number }\n\t| { t: \"int8\"; v: number | bigint | string }\n\t| { t: \"bytea\"; v: Buffer }\n\t| { t: \"text\"; v: string }\n\n// PGCOPY binary signature + the two zero header words (flags, header extension).\nconst HEADER = Buffer.concat([\n\tBuffer.from([0x50, 0x47, 0x43, 0x4f, 0x50, 0x59, 0x0a, 0xff, 0x0d, 0x0a, 0x00]),\n\tBuffer.alloc(8),\n])\n// File trailer: a -1 field count.\nconst TRAILER = (() => {\n\tconst b = Buffer.alloc(2)\n\tb.writeInt16BE(-1)\n\treturn b\n})()\n\nfunction encodeValue(field: CopyValue): Buffer {\n\tswitch (field.t) {\n\t\tcase \"int2\": {\n\t\t\tconst b = Buffer.alloc(2)\n\t\t\tb.writeInt16BE(field.v)\n\t\t\treturn b\n\t\t}\n\t\tcase \"int4\": {\n\t\t\tconst b = Buffer.alloc(4)\n\t\t\tb.writeInt32BE(field.v)\n\t\t\treturn b\n\t\t}\n\t\tcase \"int8\": {\n\t\t\tconst b = Buffer.alloc(8)\n\t\t\tb.writeBigInt64BE(BigInt(field.v))\n\t\t\treturn b\n\t\t}\n\t\tcase \"bytea\":\n\t\t\treturn field.v\n\t\tcase \"text\":\n\t\t\treturn Buffer.from(field.v, \"utf8\")\n\t}\n}\n\n/** Encode rows as one PGCOPY binary payload: header, then per row a field count\n * and each field as `<int32 length><raw bytes>`, then the trailer. */\nfunction encodeBinaryCopy(rows: CopyValue[][]): Buffer {\n\tconst parts: Buffer[] = [HEADER]\n\tfor (const row of rows) {\n\t\tconst fieldCount = Buffer.alloc(2)\n\t\tfieldCount.writeInt16BE(row.length)\n\t\tparts.push(fieldCount)\n\t\tfor (const field of row) {\n\t\t\tconst value = encodeValue(field)\n\t\t\tconst len = Buffer.alloc(4)\n\t\t\tlen.writeInt32BE(value.length)\n\t\t\tparts.push(len, value)\n\t\t}\n\t}\n\tparts.push(TRAILER)\n\treturn Buffer.concat(parts)\n}\n\n/**\n * COPY `rows` into `target` (a temp staging table shaped from `target`'s columns,\n * then `INSERT … SELECT … ON CONFLICT DO NOTHING`). `tx` must be a\n * transaction-scoped porsager `Sql`. `target` and `columns` are internal constants\n * (never client input), interpolated as SQL identifiers.\n */\nexport async function copyInsert(\n\ttx: TransactionSql,\n\ttarget: string,\n\tcolumns: readonly string[],\n\trows: CopyValue[][],\n): Promise<void> {\n\tif (rows.length === 0) return\n\tconst cols = columns.join(\", \")\n\tconst staging = `copy_stg_${target}`\n\t// `CREATE TABLE AS … WITH NO DATA` shapes the staging table from exactly the\n\t// inserted columns (their types, no NOT NULL / defaults / constraints), so COPY\n\t// fills every column it declares and the final insert lets `target` apply its\n\t// own defaults (e.g. git_object.created_at) to the unlisted ones.\n\tawait tx.unsafe(\n\t\t`create temp table ${staging} on commit drop as select ${cols} from ${target} with no data`,\n\t)\n\tconst writable =\n\t\tawait tx`copy ${tx(staging)} (${tx.unsafe(cols)}) from stdin (format binary)`.writable()\n\tawait new Promise<void>((resolve, reject) => {\n\t\twritable.on(\"error\", reject)\n\t\twritable.on(\"finish\", () => resolve())\n\t\twritable.write(encodeBinaryCopy(rows), (err) => {\n\t\t\tif (err) reject(err)\n\t\t\telse writable.end()\n\t\t})\n\t})\n\tawait tx.unsafe(\n\t\t`insert into ${target} (${cols}) select ${cols} from ${staging} on conflict do nothing`,\n\t)\n}\n","import { GitFormatError } from \"@/object/format-error\"\nimport {\n\tcommitParents,\n\tcommitTreeOid,\n\ttype GitObjectType,\n\tisTreeEntryMode,\n\treferencedOids,\n\ttreeEntries,\n} from \"@/object/object\"\n\n/**\n * Edge kinds stored in `git_edge.kind`. tree→blob (would be `4`) is deliberately\n * NOT a kind: blobs are enumerated from tree content, never stored as edges (§4.3),\n * so `4` is reserved/unused.\n */\nexport const EDGE_KIND = {\n\tCOMMIT_PARENT: 2,\n\tCOMMIT_TREE: 1,\n\tTAG_TARGET: 5,\n\tTREE_SUBTREE: 3,\n} as const\n\nexport type DerivedEdge = { child: string; kind: number }\n\n/** A tree entry pointing at a commit in *another* repo — no blob, no edge here. */\nconst GITLINK_MODE = \"160000\"\n\nconst WELL_FORMED_OID = /^[0-9a-f]{40}$/\n\n/**\n * Validate an OID parsed from a commit/tag header. `commitParents`/`commitTreeOid`/\n * `referencedOids` take whatever follows the header key verbatim — a forged object\n * could carry a non-OID there and yield a bogus edge child — so reject it loudly at\n * the ingest boundary (§5.1). Tree-entry OIDs are exempt: `treeEntries` already\n * guarantees a 20-byte value, and the `bytea CHECK(length(child)=20)` is the\n * database-level backstop for every edge.\n */\nfunction assertOid(oid: string, context: string): string {\n\tif (!WELL_FORMED_OID.test(oid)) {\n\t\tthrow new GitFormatError(\n\t\t\t\"malformed-oid\",\n\t\t\t`${context}: not a well-formed object id: ${JSON.stringify(oid)}`,\n\t\t)\n\t}\n\treturn oid\n}\n\n/** Count the leading `key value` header lines (up to the blank line that ends a\n * commit/tag's header block). */\nfunction countHeader(content: Buffer, key: string): number {\n\tconst prefix = `${key} `\n\tlet n = 0\n\tfor (const line of content.toString(\"latin1\").split(\"\\n\")) {\n\t\tif (line === \"\") break // headers end at the blank line\n\t\tif (line.startsWith(prefix)) n++\n\t}\n\treturn n\n}\n\n/**\n * fsck-grade structural validation at the ingest boundary (§5.1, invariant §10.2):\n * reject the malformed objects that OID-wellformedness and tree parsing do not\n * catch. A commit must not carry more than one `tree` header (git fsck:\n * multipleTrees — `commitTreeOid` would otherwise silently take the first and drop\n * the rest, recording an edge to a tree the object does not actually root). An\n * annotated tag must carry exactly one `object` header (git fsck: missingObject /\n * an extra object line): zero yields no `kind=5` edge and silently breaks peeling\n * and connectivity; more than one yields multiple divergent `kind=5` edges and a\n * nondeterministic `peeled_oid`. The other structural guarantees are already\n * enforced downstream: `assertOid` on every referenced OID (below), a present root\n * `tree` (`commitTreeOid`, which also rejects a zero-tree commit), and a well-formed\n * tree body (`treeEntries` throws). Called by the store once per object before\n * derivation, in the ingest transaction, so a malformed push aborts before any row\n * lands.\n */\nexport function validateObject(type: GitObjectType, content: Buffer): void {\n\tif (type === \"commit\" && countHeader(content, \"tree\") > 1) {\n\t\tthrow new GitFormatError(\n\t\t\t\"multiple-tree-headers\",\n\t\t\t\"commit carries more than one tree header\",\n\t\t)\n\t}\n\tif (type === \"tag\") {\n\t\tconst objects = countHeader(content, \"object\")\n\t\tif (objects < 1) {\n\t\t\tthrow new GitFormatError(\"missing-tag-object\", \"annotated tag has no object header\")\n\t\t}\n\t\tif (objects > 1) {\n\t\t\tthrow new GitFormatError(\n\t\t\t\t\"multiple-tag-objects\",\n\t\t\t\t\"annotated tag carries more than one object header\",\n\t\t\t)\n\t\t}\n\t}\n}\n\n/**\n * The edges an object contributes to `git_edge`, with the object's own OID as the\n * parent — the §4.3 standing rule, mode-aware:\n * - commit → its tree (kind 1) then each parent (kind 2);\n * - tree → its **subtrees only** (mode `40000` → kind 3). Blobs and gitlinks\n * (`160000`, a commit living in another repo) are NOT edges — `isTreeEntryMode`\n * admits only `40000`, so both are dropped;\n * - tag → its target (kind 5);\n * - blob → nothing.\n *\n * This is the single derivation the store inserts alongside the object row, in the\n * same transaction (§10.1), so edges are a validated total function of content.\n */\nexport function deriveEdges(type: GitObjectType, content: Buffer): DerivedEdge[] {\n\tswitch (type) {\n\t\tcase \"blob\":\n\t\t\treturn []\n\t\tcase \"commit\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tchild: assertOid(commitTreeOid(content), \"commit tree\"),\n\t\t\t\t\tkind: EDGE_KIND.COMMIT_TREE,\n\t\t\t\t},\n\t\t\t\t...commitParents(content).map((p) => ({\n\t\t\t\t\tchild: assertOid(p, \"commit parent\"),\n\t\t\t\t\tkind: EDGE_KIND.COMMIT_PARENT,\n\t\t\t\t})),\n\t\t\t]\n\t\tcase \"tag\":\n\t\t\treturn referencedOids(\"tag\", content).map((t) => ({\n\t\t\t\tchild: assertOid(t, \"tag target\"),\n\t\t\t\tkind: EDGE_KIND.TAG_TARGET,\n\t\t\t}))\n\t\tcase \"tree\":\n\t\t\treturn treeEntries(content)\n\t\t\t\t.filter((e) => isTreeEntryMode(e.mode))\n\t\t\t\t.map((e) => ({ child: e.oid, kind: EDGE_KIND.TREE_SUBTREE }))\n\t}\n}\n\n/**\n * The blob OIDs directly in a tree — the §4.3 standing rule's other half: blobs\n * are enumerated from tree content, never stored as edges. A tree entry is a blob\n * unless it is a subtree (`deriveEdges` covers those as kind-3 edges) or a gitlink\n * (`160000`, a submodule commit living in another repo — neither blob nor edge).\n * Connectivity uses this to find the blobs a present tree requires, since no\n * tree→blob edge exists to anchor a missing one.\n */\nexport function treeBlobOids(content: Buffer): string[] {\n\treturn treeEntries(content)\n\t\t.filter((e) => !isTreeEntryMode(e.mode) && e.mode !== GITLINK_MODE)\n\t\t.map((e) => e.oid)\n}\n","/**\n * Pack object header: a variable-length encoding of (type, uncompressed size)\n * that prefixes every object entry in a packfile.\n *\n * First byte: `[c|ttt|ssss]` — continuation bit `c`, 3-bit type `ttt`, low 4 bits\n * of size. Each continuation byte contributes 7 more size bits, least-significant\n * group first. See gitformat-pack.\n *\n * Size arithmetic uses `*`/`Math.floor`, NOT `<<`/`>>` — JS bitwise ops are\n * 32-bit and would corrupt object sizes ≥ 2³¹.\n */\n\nexport const PACK_OBJ_TYPE = {\n\tBLOB: 3,\n\tCOMMIT: 1,\n\tOFS_DELTA: 6,\n\tREF_DELTA: 7,\n\tTAG: 4,\n\tTREE: 2,\n} as const\n\nexport type PackObjType = (typeof PACK_OBJ_TYPE)[keyof typeof PACK_OBJ_TYPE]\n\nexport type DecodedObjectHeader = {\n\ttype: number\n\tsize: number\n\tbytesRead: number\n}\n\nexport function encodeObjectHeader(type: number, size: number): Buffer {\n\tlet rest = Math.floor(size / 16)\n\tlet first = (type << 4) | (size % 16)\n\tif (rest > 0) first |= 0x80\n\tconst bytes = [first]\n\twhile (rest > 0) {\n\t\tlet byte = rest % 128\n\t\trest = Math.floor(rest / 128)\n\t\tif (rest > 0) byte |= 0x80\n\t\tbytes.push(byte)\n\t}\n\treturn Buffer.from(bytes)\n}\n\nexport function decodeObjectHeader(buf: Buffer, offset: number): DecodedObjectHeader {\n\tlet b = buf.readUInt8(offset)\n\tlet bytesRead = 1\n\tconst type = (b >> 4) & 0x07\n\tlet size = b & 0x0f\n\tlet mult = 16\n\twhile (b & 0x80) {\n\t\tb = buf.readUInt8(offset + bytesRead)\n\t\tbytesRead++\n\t\tsize += (b & 0x7f) * mult\n\t\tmult *= 128\n\t}\n\treturn { bytesRead, size, type }\n}\n","import { type Kysely, sql } from \"kysely\"\nimport type { Database } from \"@/database\"\nimport type { ReposId } from \"@/database/models/public/Repos\"\nimport { EDGE_KIND, treeBlobOids } from \"@/object/edges\"\nimport { PACK_OBJ_TYPE } from \"@/pack/object-header\"\n\n/** Objects looked up per round-trip when chunking tree/blob existence queries. */\nconst LOOKUP_BATCH = 1000\n\n/** Split `items` into consecutive batches of at most `size`. */\nfunction batches<T>(items: T[], size: number): T[][] {\n\tconst out: T[][] = []\n\tfor (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size))\n\treturn out\n}\n\n/**\n * The objects reachable from `roots` over the stored DAG — the ONE reachability\n * engine shared by connectivity, clone, and incremental fetch (so they can never\n * disagree). A recursive CTE walks `git_edge` (all stored kinds 1,2,3,5) for the\n * commit/tree/tag closure; the LEFT JOIN marks which are present. Blobs are not\n * edges (§4.3), so unless `omitBlobs` they are enumerated from each present tree's\n * content (mode-aware) and their presence checked. Returns the reachable set\n * partitioned into present / missing. `::bigint`/`::bytea` casts and the\n * `VALUES (…::bytea)` seed pin types in the raw CTE (the porsager driver can't\n * bind a raw `bytea[]`, OQ-13); array lookups use Kysely's `in`-expansion.\n */\nexport async function reachableClosure(\n\tdb: Kysely<Database>,\n\tid: ReposId,\n\troots: string[],\n\tomitBlobs: boolean,\n): Promise<{ present: Set<string>; missing: Set<string> }> {\n\tconst present = new Set<string>()\n\tconst missing = new Set<string>()\n\tif (roots.length === 0) return { missing, present }\n\n\tconst seed = sql.join(roots.map((r) => sql`(${Buffer.from(r, \"hex\")}::bytea)`))\n\tconst closure = await sql<{ oid: Buffer; type: number | null }>`\n\t\twith recursive closure(oid) as (\n\t\t\tselect oid from (values ${seed}) as roots(oid)\n\t\t\tunion\n\t\t\tselect e.child from git_edge e\n\t\t\t\tjoin closure c on e.parent = c.oid\n\t\t\t\twhere e.repo_id = ${id}::bigint\n\t\t)\n\t\tselect c.oid, o.type\n\t\tfrom closure c\n\t\tleft join git_object o on o.repo_id = ${id}::bigint and o.oid = c.oid\n\t`.execute(db)\n\n\tconst treeOids: Buffer[] = []\n\tfor (const r of closure.rows) {\n\t\tconst hex = r.oid.toString(\"hex\")\n\t\tif (r.type === null) {\n\t\t\tmissing.add(hex)\n\t\t} else {\n\t\t\tpresent.add(hex)\n\t\t\tif (r.type === PACK_OBJ_TYPE.TREE) treeOids.push(r.oid)\n\t\t}\n\t}\n\tif (omitBlobs || treeOids.length === 0) return { missing, present }\n\n\tconst blobCandidates = new Set<string>()\n\tfor (const batch of batches(treeOids, LOOKUP_BATCH)) {\n\t\tconst trees = await db\n\t\t\t.selectFrom(\"git_object\")\n\t\t\t.select(\"content\")\n\t\t\t.where(\"repo_id\", \"=\", id)\n\t\t\t.where(\"oid\", \"in\", batch)\n\t\t\t.execute()\n\t\tfor (const t of trees) {\n\t\t\tfor (const blob of treeBlobOids(t.content)) blobCandidates.add(blob)\n\t\t}\n\t}\n\tif (blobCandidates.size === 0) return { missing, present }\n\n\tconst presentBlobs = new Set<string>()\n\tfor (const batch of batches([...blobCandidates], LOOKUP_BATCH)) {\n\t\tconst rows = await db\n\t\t\t.selectFrom(\"git_object\")\n\t\t\t.select(\"oid\")\n\t\t\t.where(\"repo_id\", \"=\", id)\n\t\t\t.where(\n\t\t\t\t\"oid\",\n\t\t\t\t\"in\",\n\t\t\t\tbatch.map((h) => Buffer.from(h, \"hex\")),\n\t\t\t)\n\t\t\t.execute()\n\t\tfor (const r of rows) presentBlobs.add(r.oid.toString(\"hex\"))\n\t}\n\tfor (const b of blobCandidates) (presentBlobs.has(b) ? present : missing).add(b)\n\treturn { missing, present }\n}\n\n/** Does `want`'s commit/tag ancestry (edge kinds 2,5) reach any oid in `common`?\n * The ancestry-only CTE that underpins `readyToGiveUp`. */\nexport async function ancestryReachesCommon(\n\tdb: Kysely<Database>,\n\tid: ReposId,\n\twant: string,\n\tcommonBufs: Buffer[],\n): Promise<boolean> {\n\tif (commonBufs.length === 0) return false\n\tconst commons = sql.join(commonBufs.map((b) => sql`(${b}::bytea)`))\n\tconst result = await sql<{ reached: boolean }>`\n\t\twith recursive anc(oid) as (\n\t\t\tselect ${Buffer.from(want, \"hex\")}::bytea\n\t\t\tunion\n\t\t\tselect e.child from git_edge e\n\t\t\t\tjoin anc a on e.parent = a.oid\n\t\t\t\twhere e.repo_id = ${id}::bigint\n\t\t\t\t\tand e.kind in (${EDGE_KIND.COMMIT_PARENT}, ${EDGE_KIND.TAG_TARGET})\n\t\t)\n\t\tselect exists (\n\t\t\tselect 1 from anc join (values ${commons}) as c(oid) on c.oid = anc.oid\n\t\t) as reached\n\t`.execute(db)\n\treturn result.rows[0]?.reached ?? false\n}\n","import type { Kysely } from \"kysely\"\nimport type { Database } from \"@/database\"\nimport type { ReposId } from \"@/database/models/public/Repos\"\n\nexport type RepoResolver = ReturnType<typeof createRepoResolver>\n\n/**\n * Resolves a wire repo name to its `repos.id` surrogate, memoized. The object and\n * ref stores both key on the bigint `repo_id`, so each builds one of these as its\n * name→id boundary.\n *\n * The mapping is immutable once a repo exists (ids are `generated always`, names\n * are unique), so a found id is cached for the resolver's lifetime — keeping the\n * per-object hot path (getObject) at one point-read, not a join. Misses are NEVER\n * cached: a name the lookup didn't find may be created by a later push, and a\n * cached `null` would mask it.\n *\n * Reads resolve (lookup; `null` ⇒ the repo has never been written, i.e. empty).\n * Writes ensure (race-safe get-or-create).\n */\nexport function createRepoResolver(db: Kysely<Database>) {\n\tconst cache = new Map<string, ReposId>()\n\n\treturn {\n\t\t/** The repo's id, creating the row if absent. Race-safe under concurrent\n\t\t * first-pushes, and avoids a no-op UPDATE on the common (exists) path. */\n\t\tasync ensureRepoId(name: string): Promise<ReposId> {\n\t\t\tconst cached = cache.get(name)\n\t\t\tif (cached !== undefined) return cached\n\t\t\tconst existing = await db\n\t\t\t\t.selectFrom(\"repos\")\n\t\t\t\t.select(\"id\")\n\t\t\t\t.where(\"name\", \"=\", name)\n\t\t\t\t.executeTakeFirst()\n\t\t\tif (existing) {\n\t\t\t\tcache.set(name, existing.id)\n\t\t\t\treturn existing.id\n\t\t\t}\n\t\t\tconst inserted = await db\n\t\t\t\t.insertInto(\"repos\")\n\t\t\t\t.values({ name })\n\t\t\t\t.onConflict((oc) => oc.doNothing())\n\t\t\t\t.returning(\"id\")\n\t\t\t\t.executeTakeFirst()\n\t\t\t// `inserted` is undefined iff a concurrent push won the insert race; the\n\t\t\t// row is guaranteed present now, so re-select it.\n\t\t\tconst id =\n\t\t\t\tinserted?.id ??\n\t\t\t\t(\n\t\t\t\t\tawait db\n\t\t\t\t\t\t.selectFrom(\"repos\")\n\t\t\t\t\t\t.select(\"id\")\n\t\t\t\t\t\t.where(\"name\", \"=\", name)\n\t\t\t\t\t\t.executeTakeFirstOrThrow()\n\t\t\t\t).id\n\t\t\tcache.set(name, id)\n\t\t\treturn id\n\t\t},\n\t\t/** The repo's id, or `null` if it has never been written to. */\n\t\tasync resolveRepoId(name: string): Promise<ReposId | null> {\n\t\t\tconst cached = cache.get(name)\n\t\t\tif (cached !== undefined) return cached\n\t\t\tconst row = await db\n\t\t\t\t.selectFrom(\"repos\")\n\t\t\t\t.select(\"id\")\n\t\t\t\t.where(\"name\", \"=\", name)\n\t\t\t\t.executeTakeFirst()\n\t\t\tif (!row) return null\n\t\t\tcache.set(name, row.id)\n\t\t\treturn row.id\n\t\t},\n\t}\n}\n","import type { Kysely } from \"kysely\"\nimport type { ReservedSql, Sql } from \"postgres\"\nimport { type Database, initKysely } from \"@/database\"\nimport { copyInsert } from \"@/database/copy-insert\"\nimport type { ReposId } from \"@/database/models/public/Repos\"\nimport { reachableClosure } from \"@/store/reachability\"\nimport { createRepoResolver } from \"@/store/repo-resolver\"\n\n/**\n * Per-repo reachability GC — the one piece of the Postgres-native redesign (§7)\n * that the rest of the spine deferred. See `docs/2026-06-24-force-commit-gc-design.md`\n * for the design; the observable contract is §4 of that doc, and the authoritative\n * algorithm is §7 of `internal/archived/2026-06-22-pggit-postgres-native-storage-\n * redesign.md`.\n *\n * The mechanism (data-structures-first): materialize the LIVE set — the reachable\n * closure from every ref tip — into an UNLOGGED table, then sweep `git_object` in\n * batched short transactions with a server-side anti-join (`NOT EXISTS`) against\n * that table plus a `created_at` grace cutoff. Reachability itself is NOT re-derived\n * here: it is exactly `reachableClosure(omitBlobs=false)`, the one engine clone /\n * fetch / connectivity already share, so GC can never disagree with them about what\n * is reachable.\n */\n\n/** Tunables for one GC pass. `graceSeconds` is REQUIRED — no silent default: an\n * object is reclaimed iff it is unreachable from every ref AND its `created_at`\n * is older than `graceSeconds` (0 ⇒ reclaim all unreachable; a huge value ⇒\n * retain). `batchLimit` caps the per-batch DELETE size (sweep tuning only — it\n * never changes the final observable state). `maintain` (default true) runs the\n * post-sweep VACUUM/REINDEX; the self-scheduling drain passes `false` so a\n * frequent per-repo pass never triggers a full-table VACUUM on the hot cadence\n * (autovacuum reclaims the GC churn instead). Maintenance is observable-neutral —\n * it changes dead-tuple bloat, never the row/clone state. */\nexport type GcOptions = { graceSeconds: number; batchLimit?: number; maintain?: boolean }\n\n/**\n * Internal-only test seam (NOT part of the public `GcOptions` contract): hooks the\n * GC pass at the one point §5 in-flight safety depends on. `afterLiveSet` is awaited\n * AFTER the live set is materialized and BEFORE the object sweep begins, so a test\n * can deterministically interpose a concurrent push there and assert the just-pushed\n * tip is never partially reclaimed. Test-only; do not document or use in production.\n */\ntype GcHooks = { afterLiveSet?: () => Promise<void> }\ntype InternalGcOptions = GcOptions & { _hooks?: GcHooks }\n\n/** What one GC pass reclaimed: the deleted `git_object` / `git_edge` row counts. */\nexport type GcResult = { deletedObjects: number; deletedEdges: number }\n\nexport type Gc = ReturnType<typeof createGc>\n\n/** Default per-batch DELETE cap when the caller omits `batchLimit`. Large enough to\n * sweep a typical force-commit orphan set in one or two batches, small enough to\n * bound the dead-tuple burst and lock duration per transaction (§7). */\nconst DEFAULT_BATCH_LIMIT = 10_000\n\n/** OIDs loaded per COPY round-trip into the live table (the live set can be the whole\n * reachable tree, so it streams in bounded batches, never one giant payload). */\nconst LIVE_LOAD_BATCH = 10_000\n\n/**\n * Build the GC over a porsager client (the same wire→DB boundary the object and ref\n * stores take). `gc(repo, opts)` reclaims a single repo's unreachable-and-old-enough\n * objects offline; reachable objects are always retained.\n */\nexport function createGc(pg: Sql) {\n\tconst db = initKysely<Database>(pg)\n\tconst repos = createRepoResolver(db)\n\n\treturn {\n\t\tasync gc(repo: string, opts: InternalGcOptions): Promise<GcResult> {\n\t\t\t// 1. Resolve the repo. A name never written has nothing to reclaim.\n\t\t\tconst id = await repos.resolveRepoId(repo)\n\t\t\tif (id === null) return { deletedEdges: 0, deletedObjects: 0 }\n\n\t\t\tconst batchLimit = opts.batchLimit ?? DEFAULT_BATCH_LIMIT\n\n\t\t\t// 2 + 3. Materialize the live set under a consistent snapshot.\n\t\t\t//\n\t\t\t// CONCURRENCY: the write/ingest path (`object-store.insertObjects`) does NOT\n\t\t\t// yet take a per-repo `pg_advisory_xact_lock` — that lock was deferred to this\n\t\t\t// GC chunk (redesign §5.4 / §12) and has no other consumer. So GC takes its\n\t\t\t// safety from two defenses (§5): (a) a REPEATABLE READ transaction, so the\n\t\t\t// ref-tip read and the closure walk see ONE consistent MVCC snapshot that\n\t\t\t// hides any push not yet committed when the snapshot opened; and (b) the\n\t\t\t// `created_at` grace below, which protects the present-but-unreachable window\n\t\t\t// (just-ingested objects a ref does not yet reach). When the write path adopts\n\t\t\t// the same per-repo advisory lock, GC should take that SAME key around this\n\t\t\t// read for full §5 mutual exclusion — DO NOT add a key here that the write path\n\t\t\t// does not also hold, or the lock would guard nothing.\n\t\t\t//\n\t\t\t// The live OIDs land in a per-repo UNLOGGED table (named by repo id, so two\n\t\t\t// DIFFERENT repos' GC passes never collide) — server-side so the sweep's\n\t\t\t// anti-join scales to a ~30k-orphan repo without pulling the orphan set through\n\t\t\t// JS. SINGLE-INSTANCE ONLY: two pggit processes GC'ing the SAME repo would share\n\t\t\t// this id-named table — B's `truncate`/`drop` could wipe A's live set mid-sweep,\n\t\t\t// and A's anti-join would then match (and delete) the whole reachable set. So the\n\t\t\t// deferred multi-instance advisory lock (redesign §5.4; scheduler design §8) MUST\n\t\t\t// wrap the ENTIRE pass (create→load→sweep→drop) AND the staging table be\n\t\t\t// instance-scoped, before a second instance is ever run.\n\t\t\tconst live = `gc_live_${id}`\n\t\t\tawait pg.unsafe(\n\t\t\t\t`create unlogged table if not exists ${live} (oid bytea primary key)`,\n\t\t\t)\n\t\t\ttry {\n\t\t\t\tawait pg.unsafe(`truncate ${live}`)\n\n\t\t\t\tconst roots = await liveSet(id)\n\t\t\t\tawait loadLive(live, roots)\n\n\t\t\t\t// TEST SEAM (§5 in-flight safety): interpose a concurrent push here, between\n\t\t\t\t// the live-set materialization and the object sweep.\n\t\t\t\tawait opts._hooks?.afterLiveSet?.()\n\n\t\t\t\t// 4. SWEEP objects: batched DELETE, each batch its own short transaction,\n\t\t\t\t// anti-join `NOT EXISTS` against the live set, `created_at` past the grace\n\t\t\t\t// cutoff. `clock_timestamp()` (not `now()`) so the cutoff advances per batch.\n\t\t\t\tconst deletedObjects = await sweepObjects(id, live, opts.graceSeconds, batchLimit)\n\n\t\t\t\t// 5. SWEEP edges: drop every edge whose PARENT object no longer survives —\n\t\t\t\t// run AFTER the object sweep, so a grace-retained object keeps its edges and a\n\t\t\t\t// surviving (reachable) parent's edges (which point only at reachable children)\n\t\t\t\t// never dangle.\n\t\t\t\tconst deletedEdges = await sweepEdges(id, batchLimit)\n\n\t\t\t\t// 6. MAINTENANCE (best-effort, not part of the counted deletion): reclaim the\n\t\t\t\t// dead tuples + reindex the walk index. VACUUM cannot run in a txn block, so\n\t\t\t\t// these are standalone statements run outside any transaction. Skipped when\n\t\t\t\t// the pass reclaimed nothing (no dead tuples to chase) or the caller opted out\n\t\t\t\t// (`maintain: false`, the drain's choice) — so a frequent per-repo drain never\n\t\t\t\t// triggers a full-table VACUUM/REINDEX on its hot cadence; the leaf partitions'\n\t\t\t\t// autovacuum (0001_init.ts) reclaims the GC churn. Observable-neutral either way.\n\t\t\t\tif (opts.maintain !== false && deletedObjects + deletedEdges > 0) {\n\t\t\t\t\tawait maintain()\n\t\t\t\t}\n\n\t\t\t\treturn { deletedEdges, deletedObjects }\n\t\t\t} finally {\n\t\t\t\tawait pg.unsafe(`drop table if exists ${live}`)\n\t\t\t}\n\t\t},\n\t}\n\n\t/**\n\t * The live set: the reachable closure from every ref tip, read under ONE\n\t * REPEATABLE READ snapshot so the ref-tip read and the multi-statement closure\n\t * walk cannot interleave with a concurrent push's ref update (§5 defense (a)).\n\t *\n\t * `reachableClosure` is the shared engine and takes a `Kysely`, but the\n\t * kysely-postgres-js dialect drives queries by calling `.reserve()` on its\n\t * `postgres` client for EACH query — so a plain pooled Kysely would scatter the\n\t * closure's statements across connections (no shared snapshot), and a\n\t * transaction-scoped `Sql` has no `.reserve()` at all. So we pin ONE porsager\n\t * connection, open a REPEATABLE READ transaction on it, and back a Kysely with a\n\t * shim whose `reserve()` always returns that pinned connection with a no-op\n\t * `release()` — every closure statement then runs on the one snapshotted\n\t * connection. The transaction is read-only; it commits (releasing the snapshot)\n\t * before the sweep's own short write transactions begin.\n\t */\n\tasync function liveSet(id: ReposId): Promise<Set<string>> {\n\t\tconst conn = await pg.reserve()\n\t\ttry {\n\t\t\tawait conn`begin isolation level repeatable read`\n\t\t\tconst pinned = pinnedKysely(conn)\n\t\t\tconst rows = await conn<{ oid: Buffer | null; peeled_oid: Buffer | null }[]>`\n\t\t\t\tselect oid, peeled_oid from git_ref where repo_id = ${id} and oid is not null\n\t\t\t`\n\t\t\t// Roots: every direct ref tip plus each annotated tag's peeled target. The\n\t\t\t// closure over kinds (1,2,3,5) already descends tag→target, so the peeled\n\t\t\t// target is redundant for the walk, but it is included to match §7 and stay\n\t\t\t// correct even if a tag ref's edge were ever absent.\n\t\t\tconst tips = new Set<string>()\n\t\t\tfor (const r of rows) {\n\t\t\t\tif (r.oid) tips.add(r.oid.toString(\"hex\"))\n\t\t\t\tif (r.peeled_oid) tips.add(r.peeled_oid.toString(\"hex\"))\n\t\t\t}\n\t\t\tconst { present } = await reachableClosure(pinned, id, [...tips], false)\n\t\t\tawait conn`commit`\n\t\t\treturn present\n\t\t} finally {\n\t\t\tconn.release()\n\t\t}\n\t}\n\n\t/** A Kysely pinned to a single porsager connection: its dialect `reserve()`s the\n\t * same connection for every statement (so a multi-statement read shares one MVCC\n\t * snapshot) and `release()` is a no-op (the caller owns the connection's lifetime).\n\t * The shim is a callable with a `reserve` property, the shape the dialect probes\n\t * for (`isPostgresJSSql`). */\n\tfunction pinnedKysely(conn: ReservedSql): Kysely<Database> {\n\t\t// The dialect only ever calls `.unsafe(sql, params)` then `.release()` on the\n\t\t// reserved connection — so hand it the real `conn` for `.unsafe` but swallow\n\t\t// `.release()` (a no-op), keeping the connection pinned across every closure\n\t\t// statement. The caller releases `conn` exactly once when the snapshot is done.\n\t\tconst nonReleasing = new Proxy(conn, {\n\t\t\tget: (target, prop) =>\n\t\t\t\tprop === \"release\" ? () => {} : Reflect.get(target, prop, target),\n\t\t})\n\t\tconst handle = Object.assign(\n\t\t\t() => {\n\t\t\t\tthrow new Error(\"pggit gc: pinned client used as a tagged template\")\n\t\t\t},\n\t\t\t{ reserve: async () => nonReleasing },\n\t\t)\n\t\treturn initKysely<Database>(handle as unknown as Sql)\n\t}\n\n\t/** Bulk-load the live OID set into the UNLOGGED `live` table via binary COPY (the\n\t * one bytea-safe bulk path, copy-insert.ts), batched so the payload stays bounded.\n\t * Each COPY runs in its own transaction so the staging temp table drops on commit. */\n\tasync function loadLive(live: string, oids: Set<string>): Promise<void> {\n\t\tif (oids.size === 0) return\n\t\tconst all = [...oids]\n\t\tfor (let i = 0; i < all.length; i += LIVE_LOAD_BATCH) {\n\t\t\tconst chunk = all.slice(i, i + LIVE_LOAD_BATCH)\n\t\t\tawait pg.begin(async (tx) => {\n\t\t\t\tawait copyInsert(\n\t\t\t\t\ttx,\n\t\t\t\t\tlive,\n\t\t\t\t\t[\"oid\"],\n\t\t\t\t\tchunk.map((hex) => [{ t: \"bytea\", v: Buffer.from(hex, \"hex\") }]),\n\t\t\t\t)\n\t\t\t})\n\t\t}\n\t}\n\n\t/** Batched object sweep. Postgres `DELETE` has no `LIMIT`, so each batch picks a\n\t * `LIMIT`-bounded set of victim OIDs then deletes them by PRIMARY KEY `(repo_id,\n\t * oid)`. The match is on the PK — NOT `ctid`: `ctid` is per-partition-relative, so\n\t * matching `ctid` across the HASH-partitioned table would delete same-ctid rows in\n\t * OTHER partitions (other tenants). The loop ends when a batch deletes nothing.\n\t * Each batch is its own (implicit) transaction, so `clock_timestamp()` re-evaluates\n\t * per batch and the grace cutoff advances. Returns total rows deleted. */\n\tasync function sweepObjects(\n\t\tid: ReposId,\n\t\tlive: string,\n\t\tgraceSeconds: number,\n\t\tbatchLimit: number,\n\t): Promise<number> {\n\t\tlet total = 0\n\t\tfor (;;) {\n\t\t\tconst deleted = await pg.unsafe<{ n: number }[]>(\n\t\t\t\t`with victims as (\n\t\t\t\t\tselect o.oid from git_object o\n\t\t\t\t\twhere o.repo_id = $1::bigint\n\t\t\t\t\t\tand not exists (select 1 from ${live} l where l.oid = o.oid)\n\t\t\t\t\t\tand o.created_at < clock_timestamp() - make_interval(secs => $2::float8)\n\t\t\t\t\tlimit $3::int\n\t\t\t\t)\n\t\t\t\tdelete from git_object o using victims v\n\t\t\t\twhere o.repo_id = $1::bigint and o.oid = v.oid returning 1 as n`,\n\t\t\t\t[String(id), String(graceSeconds), String(batchLimit)],\n\t\t\t)\n\t\t\tif (deleted.length === 0) break\n\t\t\ttotal += deleted.length\n\t\t}\n\t\treturn total\n\t}\n\n\t/** Batched edge sweep: delete every `git_edge` row whose PARENT object no longer\n\t * exists in `git_object` (a deleted object's outgoing edges). No FK cascade exists\n\t * (0003_git_edge.ts), so dangling edges must be swept explicitly. Anti-join on the\n\t * parent only: a surviving parent is reachable, so all its children are reachable\n\t * and present — its edges never dangle. Like the object sweep, each batch picks a\n\t * `LIMIT`-bounded victim set then deletes by PRIMARY KEY `(repo_id, parent, child)`\n\t * — never `ctid`, which is per-partition and would reach into other tenants. */\n\tasync function sweepEdges(id: ReposId, batchLimit: number): Promise<number> {\n\t\tlet total = 0\n\t\tfor (;;) {\n\t\t\tconst deleted = await pg.unsafe<{ n: number }[]>(\n\t\t\t\t`with victims as (\n\t\t\t\t\tselect e.parent, e.child from git_edge e\n\t\t\t\t\twhere e.repo_id = $1::bigint\n\t\t\t\t\t\tand not exists (\n\t\t\t\t\t\t\tselect 1 from git_object o where o.repo_id = e.repo_id and o.oid = e.parent\n\t\t\t\t\t\t)\n\t\t\t\t\tlimit $2::int\n\t\t\t\t)\n\t\t\t\tdelete from git_edge e using victims v\n\t\t\t\twhere e.repo_id = $1::bigint and e.parent = v.parent and e.child = v.child\n\t\t\t\treturning 1 as n`,\n\t\t\t\t[String(id), String(batchLimit)],\n\t\t\t)\n\t\t\tif (deleted.length === 0) break\n\t\t\ttotal += deleted.length\n\t\t}\n\t\treturn total\n\t}\n\n\t/** Post-sweep maintenance (best-effort): reclaim the dead tuples GC produced in\n\t * the heap + TOAST and refresh planner stats, then reindex the walk index.\n\t * `VACUUM` cannot run inside a transaction block, so these are standalone\n\t * statements run outside any txn. */\n\tasync function maintain(): Promise<void> {\n\t\tawait pg.unsafe(`vacuum (analyze) git_object`)\n\t\tawait pg.unsafe(`vacuum (analyze) git_edge`)\n\t\tawait pg.unsafe(`reindex index git_edge_walk`)\n\t}\n}\n","import type { Sql } from \"postgres\"\nimport { createGc } from \"@/store/gc\"\n\n/**\n * Self-scheduling GC — the background drain that decides WHEN the per-repo\n * reachability GC (`store/gc.ts`) runs, off the push/fetch hot path. See\n * `docs/2026-06-24-gc-scheduler-design.md`; the observable contract is §6 of that\n * doc (SCH-1 … SCH-11 / PBT-S1).\n *\n * Mechanism (data-structures-first): every storage-mutating push stamps\n * `repos.last_pushed_at` in its own transaction (the store), so the scheduler is a\n * pure poll loop over Postgres with NO coupling to the request path. One pass\n * (`drainOnce`) selects the eligible repos — `last_pushed_at > last_gc_at`\n * (or `last_gc_at is null`) — and runs `gc()` on each (per-repo serialized,\n * bounded concurrency), then advances `last_gc_at` to the pass's start time so a\n * push landing mid-pass re-qualifies the repo next loop (no lost garbage). `start`\n * is just `drainOnce` on a `setInterval`; all correctness lives in `drainOnce`,\n * which the tests drive directly (the timer is never in a test's critical path).\n */\n\n/** One repo's outcome in a drain pass: the repo and what its GC reclaimed.\n * Emitted for EVERY repo the pass judged eligible (including zero-reclaim), so the\n * eligible set itself is observable (SCH-3). */\nexport type DrainEntry = { repo: string; deletedObjects: number; deletedEdges: number }\n\n/** What one `drainOnce()` reclaimed, one entry per eligible repo. */\nexport type DrainSummary = DrainEntry[]\n\n/** Scheduler tunables (resolved from `env` / `startServer` opts). `graceSeconds`\n * is passed straight to `gc()`; `intervalMs` is the drain cadence (the debounce\n * window); `concurrency` caps repos GC'd at once per pass so one large-orphan repo\n * cannot head-of-line-block the rest. */\nexport type GcSchedulerOptions = {\n\tgraceSeconds: number\n\tintervalMs: number\n\tconcurrency: number\n}\n\nexport type GcScheduler = ReturnType<typeof createGcScheduler>\n\n/** A candidate repo for one drain pass: its id + wire name. The pass-start\n * watermark is captured per-repo (in `drainRepo`, before that repo's GC snapshot)\n * and written back as `last_gc_at`. */\ntype Candidate = { id: string; name: string }\n\n/**\n * Build the GC scheduler over a porsager client (the same wire→DB boundary the\n * stores take). `drainOnce()` runs one poll+sweep pass; `start()`/`stop()` drive\n * it on `intervalMs`. Reachable objects are never touched — it only invokes the\n * per-repo GC primitive, which is reachability-safe.\n */\nexport function createGcScheduler(pg: Sql, opts: GcSchedulerOptions) {\n\tconst gc = createGc(pg)\n\tlet timer: ReturnType<typeof setInterval> | undefined\n\t// The in-flight pass (if any). Doubles as the overlap guard (a tick skips while a\n\t// pass runs, so two passes never touch the same repo at once) and the shutdown\n\t// barrier (`stop()` awaits it).\n\tlet inFlight: Promise<unknown> | undefined\n\n\t/** The eligible repos for this pass — the §2 predicate. */\n\tasync function selectCandidates(): Promise<Candidate[]> {\n\t\treturn pg<Candidate[]>`\n\t\t\tselect r.id::text as id, r.name\n\t\t\tfrom repos r\n\t\t\twhere r.last_pushed_at is not null\n\t\t\t\tand (r.last_gc_at is null or r.last_pushed_at > r.last_gc_at)\n\t\t`\n\t}\n\n\t/**\n\t * GC one candidate. `t0 = clock_timestamp()` is captured BEFORE `gc()` opens its\n\t * snapshot, then written as `last_gc_at` after the sweep: any push committing\n\t * after t0 re-stamps `last_pushed_at > t0` (the store stamps after commit) and\n\t * re-qualifies the repo next pass (no lost garbage). A per-repo failure is\n\t * ISOLATED — logged and skipped (the repo keeps its old `last_gc_at`, so it\n\t * re-qualifies and is retried next pass) — so one poison repo never aborts the\n\t * rest of the pass. `maintain: false`: the drain leans on autovacuum, never a\n\t * per-pass full-table VACUUM (gc.ts).\n\t */\n\tasync function drainRepo(c: Candidate): Promise<DrainEntry | null> {\n\t\ttry {\n\t\t\tconst [t] = await pg<{ t0: string }[]>`select clock_timestamp()::text as t0`\n\t\t\tif (!t) throw new Error(\"pggit gc-scheduler: clock_timestamp() returned no row\")\n\t\t\tconst { deletedObjects, deletedEdges } = await gc.gc(c.name, {\n\t\t\t\tgraceSeconds: opts.graceSeconds,\n\t\t\t\tmaintain: false,\n\t\t\t})\n\t\t\tawait pg`update repos set last_gc_at = ${t.t0}::timestamptz where id = ${c.id}::bigint`\n\t\t\treturn { deletedEdges, deletedObjects, repo: c.name }\n\t\t} catch (err) {\n\t\t\tconsole.error(\n\t\t\t\t`pggit gc-scheduler: GC of repo ${JSON.stringify(c.name)} failed (retried next pass):`,\n\t\t\t\terr,\n\t\t\t)\n\t\t\treturn null\n\t\t}\n\t}\n\n\t/** One drain pass: GC every eligible repo (bounded concurrency, distinct repos so\n\t * a pass never double-GCs one). Returns an entry per repo GC'd this pass — a repo\n\t * whose GC threw is skipped (not in the summary) and retried next pass. */\n\tasync function drainOnce(): Promise<DrainSummary> {\n\t\tconst candidates = await selectCandidates()\n\t\tconst results = await mapPool(candidates, Math.max(1, opts.concurrency), drainRepo)\n\t\treturn results.filter((e): e is DrainEntry => e !== null)\n\t}\n\n\t/** Run the drain on `intervalMs`. The `inFlight` guard ensures passes never\n\t * overlap — so two passes can never touch the same repo at once — and a slow pass\n\t * simply skips the next tick. A pass failure is logged, never thrown into the\n\t * timer. The timer is `unref`'d so it alone does not keep the process alive (the\n\t * server's socket does). */\n\tfunction start(): void {\n\t\tif (timer) return\n\t\ttimer = setInterval(() => {\n\t\t\tif (inFlight) return\n\t\t\tinFlight = drainOnce()\n\t\t\t\t.catch((err) => {\n\t\t\t\t\tconsole.error(\"pggit gc-scheduler: drain pass failed:\", err)\n\t\t\t\t})\n\t\t\t\t.finally(() => {\n\t\t\t\t\tinFlight = undefined\n\t\t\t\t})\n\t\t}, opts.intervalMs)\n\t\ttimer.unref?.()\n\t}\n\n\t/** Halt the background drain and AWAIT any pass already in flight, so a caller may\n\t * safely tear the connection pool down afterwards (no query runs into a closed\n\t * pool). Idempotent. */\n\tasync function stop(): Promise<void> {\n\t\tif (timer) {\n\t\t\tclearInterval(timer)\n\t\t\ttimer = undefined\n\t\t}\n\t\tawait inFlight\n\t}\n\n\treturn { drainOnce, start, stop }\n}\n\n/** Run `fn` over `items` with at most `limit` concurrent, preserving result order.\n * A bounded worker pool — `limit` workers pull from a shared cursor — so one\n * large-orphan repo cannot head-of-line-block the rest of a pass. */\nasync function mapPool<T, R>(\n\titems: T[],\n\tlimit: number,\n\tfn: (item: T) => Promise<R>,\n): Promise<R[]> {\n\tconst results = new Array<R>(items.length)\n\tlet cursor = 0\n\tasync function worker(): Promise<void> {\n\t\tfor (;;) {\n\t\t\tconst i = cursor++\n\t\t\tif (i >= items.length) return\n\t\t\tresults[i] = await fn(items[i] as T)\n\t\t}\n\t}\n\tconst workers = Array.from({ length: Math.min(limit, items.length) }, worker)\n\tawait Promise.all(workers)\n\treturn results\n}\n","import { gunzipSync } from \"node:zlib\"\nimport { type Context, Hono } from \"hono\"\nimport { cors } from \"hono/cors\"\nimport { count, runRequest } from \"@/instrument\"\nimport { GitProtocolError } from \"@/protocol/errors\"\nimport { encodePkt, encodePktLine } from \"@/protocol/pkt-line\"\nimport {\n\tencodeReceivePackAdvertisement,\n\thandleReceivePack,\n\ttype ReceiveBackend,\n} from \"@/protocol/receive-pack\"\nimport { handleUploadPack, type RepoBackend } from \"@/protocol/upload-pack\"\nimport { encodeAdvertisement } from \"@/protocol/v2\"\nimport { syncRefSnapshot } from \"@/repo-view/rebuild\"\nimport type { RepoFileProjection } from \"@/repo-view/repo-file-projection\"\nimport type { ObjectStore } from \"@/store/object-store\"\nimport type { RefStore } from \"@/store/refs-store\"\n\nexport type GitAppDeps = {\n\tobjects: ObjectStore\n\trefs: RefStore\n\t/** Optional queryable-view layer. When provided, push maintains a `repo_file`\n\t * path→blob index per branch; when omitted, this is a plain git remote. */\n\tsnapshots?: RepoFileProjection\n}\n\n// Smart-HTTP info/refs body: the `# service` preamble + flush, then the v2\n// capability advertisement.\nconst UPLOAD_PACK_ADVERTISEMENT = Buffer.concat([\n\tencodePktLine(Buffer.from(\"# service=git-upload-pack\\n\")),\n\tencodePkt({ type: \"flush\" }),\n\tencodeAdvertisement(),\n])\n\n// Hono's body types want an ArrayBuffer, not a Node Buffer (a Uint8Array view).\nfunction toArrayBuffer(buf: Buffer): ArrayBuffer {\n\treturn buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength) as ArrayBuffer\n}\n\nconst ADVERTISEMENT_BODY = toArrayBuffer(UPLOAD_PACK_ADVERTISEMENT)\n\n/**\n * Read a smart-HTTP POST body, honoring `Content-Encoding`. Git compresses the\n * upload-pack/receive-pack request body with gzip once it is large enough\n * (`remote-curl.c`), exactly as `git http-backend` decompresses on the server\n * side — so we must too. Any other declared encoding is a hard error, never fed\n * raw to the pkt-line parser.\n */\nasync function readRequestBody(c: Context): Promise<Buffer> {\n\tconst raw = Buffer.from(await c.req.arrayBuffer())\n\tconst encoding = c.req.header(\"content-encoding\")?.toLowerCase()\n\tif (encoding === undefined || encoding === \"identity\") return raw\n\tif (encoding === \"gzip\" || encoding === \"x-gzip\") {\n\t\t// A body that declares gzip but fails to inflate is a CLIENT wire fault, not a\n\t\t// server error — surface it on the same clean 400 path as the unsupported\n\t\t// encodings below, never let the ZlibError escape to a 500.\n\t\ttry {\n\t\t\treturn gunzipSync(raw)\n\t\t} catch (err) {\n\t\t\tthrow new GitProtocolError(\n\t\t\t\t`request body declared Content-Encoding ${JSON.stringify(encoding)} but failed to gunzip: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t)\n\t\t}\n\t}\n\tthrow new GitProtocolError(\n\t\t`unsupported request Content-Encoding: ${JSON.stringify(encoding)}`,\n\t)\n}\n\n/**\n * Fetch is served over git protocol v2 ONLY (the charter). git requests v2 with a\n * `Git-Protocol: version=2` header (a `:`-joined key list; git ≥ 2.26 sends it by\n * default). A v0/v1 client sends no such header and cannot parse the v2\n * advertisement — it would read the `version 2` line + flush as an empty repo and\n * silently clone nothing. So reject the unnegotiated case loudly at the boundary\n * (400) instead of handing back an advertisement it will misread.\n */\nfunction assertProtocolV2(header: string | undefined): void {\n\tconst requested = (header ?? \"\").split(\":\").map((s) => s.trim())\n\tif (!requested.includes(\"version=2\")) {\n\t\tthrow new GitProtocolError(\n\t\t\t\"pggit serves fetch over git protocol v2 only; set protocol.version=2 (git ≥ 2.26 negotiates it by default)\",\n\t\t)\n\t}\n}\n\nfunction backendFor(deps: GitAppDeps, repoId: string): RepoBackend {\n\treturn {\n\t\tbuildPack: (wants, haves, omitBlobs, includeTag) =>\n\t\t\tdeps.objects.buildPack(repoId, wants, haves, omitBlobs, includeTag),\n\t\tcommonHaves: (haves) => deps.objects.commonHaves(repoId, haves),\n\t\tgetSymref: (name) => deps.refs.getSymref(repoId, name),\n\t\tlistRefs: () => deps.refs.listRefs(repoId),\n\t\treadyToGiveUp: (wants, common) => deps.objects.readyToGiveUp(repoId, wants, common),\n\t}\n}\n\nfunction receiveBackendFor(deps: GitAppDeps, repoId: string): ReceiveBackend {\n\tconst backend: ReceiveBackend = {\n\t\tapplyRefUpdates: (commands, atomic) =>\n\t\t\tdeps.refs.applyRefUpdates(repoId, commands, atomic),\n\t\tingest: async (pack) => {\n\t\t\tawait deps.objects.ingestPack(repoId, pack)\n\t\t},\n\t\tisConnected: (oid) => deps.objects.isConnected(repoId, oid),\n\t}\n\tif (deps.snapshots) {\n\t\tconst sdeps = { objects: deps.objects, snapshots: deps.snapshots }\n\t\tbackend.syncRefSnapshot = (ref, newOid) => syncRefSnapshot(sdeps, repoId, ref, newOid)\n\t}\n\treturn backend\n}\n\n/** v0 receive-pack ref advertisement body: the `# service` preamble + ref list. */\nasync function receivePackAdvertBody(deps: GitAppDeps, repoId: string): Promise<Buffer> {\n\tconst refs = await deps.refs.listRefs(repoId)\n\treturn Buffer.concat([\n\t\tencodePktLine(Buffer.from(\"# service=git-receive-pack\\n\")),\n\t\tencodePkt({ type: \"flush\" }),\n\t\tencodeReceivePackAdvertisement(refs),\n\t])\n}\n\n/**\n * Build the git-remote Hono app (smart-HTTP, protocol v2 fetch). Mountable into\n * a host app via `host.route(\"/git\", createGitApp(deps))`; the host owns the\n * Postgres lifecycle behind `deps`.\n */\nexport function createGitApp(\n\tdeps: GitAppDeps,\n\topts: { instrument?: boolean } = {},\n): Hono {\n\tconst app = new Hono()\n\tif (opts.instrument) {\n\t\tapp.use((c, next) =>\n\t\t\trunRequest({ method: c.req.method, path: c.req.path }, () => next()),\n\t\t)\n\t}\n\tapp.use(cors())\n\n\t// A client-caused boundary error (malformed request, unsupported capability) is\n\t// a clean 400 with its message; anything else is an internal 500, logged loud.\n\tapp.onError((err, c) => {\n\t\tif (err instanceof GitProtocolError) return c.text(err.message, 400)\n\t\tconsole.error(err)\n\t\treturn c.text(\"internal server error\", 500)\n\t})\n\n\tapp.get(\"/health\", (c) => c.text(\"ok\"))\n\n\tapp.get(\"/:repo/info/refs\", async (c) => {\n\t\tconst service = c.req.query(\"service\")\n\t\tif (service === \"git-upload-pack\") {\n\t\t\tassertProtocolV2(c.req.header(\"git-protocol\"))\n\t\t\treturn c.body(ADVERTISEMENT_BODY, 200, {\n\t\t\t\t\"Cache-Control\": \"no-cache\",\n\t\t\t\t\"Content-Type\": \"application/x-git-upload-pack-advertisement\",\n\t\t\t})\n\t\t}\n\t\tif (service === \"git-receive-pack\") {\n\t\t\tconst body = await receivePackAdvertBody(deps, c.req.param(\"repo\"))\n\t\t\treturn c.body(toArrayBuffer(body), 200, {\n\t\t\t\t\"Cache-Control\": \"no-cache\",\n\t\t\t\t\"Content-Type\": \"application/x-git-receive-pack-advertisement\",\n\t\t\t})\n\t\t}\n\t\treturn c.text(`unsupported service ${JSON.stringify(service)}`, 403)\n\t})\n\n\tapp.post(\"/:repo/git-upload-pack\", async (c) => {\n\t\tconst body = await readRequestBody(c)\n\t\tconst out = await handleUploadPack(body, backendFor(deps, c.req.param(\"repo\")))\n\t\t// Layer-1 wire size, measured at the boundary where bytes actually leave —\n\t\t// a no-op unless the perf harness activated a collector, so production pays\n\t\t// nothing and the metric survives any restructure of the core.\n\t\tcount(\"wireBytes\", out.length)\n\t\treturn c.body(toArrayBuffer(out), 200, {\n\t\t\t\"Cache-Control\": \"no-cache\",\n\t\t\t\"Content-Type\": \"application/x-git-upload-pack-result\",\n\t\t})\n\t})\n\n\tapp.post(\"/:repo/git-receive-pack\", async (c) => {\n\t\tconst body = await readRequestBody(c)\n\t\tconst out = await handleReceivePack(\n\t\t\tbody,\n\t\t\treceiveBackendFor(deps, c.req.param(\"repo\")),\n\t\t)\n\t\treturn c.body(toArrayBuffer(out), 200, {\n\t\t\t\"Cache-Control\": \"no-cache\",\n\t\t\t\"Content-Type\": \"application/x-git-receive-pack-result\",\n\t\t})\n\t})\n\n\treturn app\n}\n\nexport type {\n\tDrainEntry,\n\tDrainSummary,\n\tGcScheduler,\n\tGcSchedulerOptions,\n} from \"@/gc-scheduler\"\n// Public GC surface: the per-repo reachability GC primitive (a host may drive it\n// directly on its own schedule) and the self-scheduling background drain that\n// decides when to run it (docs/2026-06-24-gc-scheduler-design.md §4).\nexport { createGcScheduler } from \"@/gc-scheduler\"\nexport type { Gc, GcOptions, GcResult } from \"@/store/gc\"\nexport { createGc } from \"@/store/gc\"\n"],"mappings":"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"}
@@ -0,0 +1,100 @@
1
+ import { ColumnType, Selectable } from "kysely";
2
+
3
+ //#region src/database/models/public/Repos.d.ts
4
+ /** Identifier type for public.repos */
5
+ type ReposId = string & {
6
+ __brand: "public.repos";
7
+ };
8
+ /** Represents the table public.repos */
9
+ interface ReposTable {
10
+ id: ColumnType<ReposId, never, never>;
11
+ name: ColumnType<string, string, string>;
12
+ last_pushed_at: ColumnType<Date | null, Date | string | null, Date | string | null>;
13
+ last_gc_at: ColumnType<Date | null, Date | string | null, Date | string | null>;
14
+ }
15
+ type Repos = Selectable<ReposTable>;
16
+ //#endregion
17
+ //#region src/database/models/public/GitEdge.d.ts
18
+ /** Represents the table public.git_edge */
19
+ interface GitEdgeTable {
20
+ repo_id: ColumnType<ReposId, ReposId, ReposId>;
21
+ parent: ColumnType<Buffer, Buffer, Buffer>;
22
+ child: ColumnType<Buffer, Buffer, Buffer>;
23
+ kind: ColumnType<number, number, number>;
24
+ }
25
+ //#endregion
26
+ //#region src/database/models/public/GitObject.d.ts
27
+ /** Represents the table public.git_object */
28
+ interface GitObjectTable {
29
+ repo_id: ColumnType<ReposId, ReposId, ReposId>;
30
+ oid: ColumnType<Buffer, Buffer, Buffer>;
31
+ type: ColumnType<number, number, number>;
32
+ size: ColumnType<number, number, number>;
33
+ content: ColumnType<Buffer, Buffer, Buffer>;
34
+ created_at: ColumnType<Date, Date | string | undefined, Date | string>;
35
+ }
36
+ type GitObject = Selectable<GitObjectTable>;
37
+ //#endregion
38
+ //#region src/database/models/public/GitRef.d.ts
39
+ /** Identifier type for public.git_ref */
40
+ type GitRefName = string & {
41
+ __brand: "public.git_ref";
42
+ };
43
+ /** Represents the table public.git_ref */
44
+ interface GitRefTable {
45
+ repo_id: ColumnType<ReposId, ReposId, ReposId>;
46
+ name: ColumnType<GitRefName, GitRefName, GitRefName>;
47
+ oid: ColumnType<Buffer | null, Buffer | null, Buffer | null>;
48
+ peeled_oid: ColumnType<Buffer | null, Buffer | null, Buffer | null>;
49
+ symref_target: ColumnType<string | null, string | null, string | null>;
50
+ }
51
+ //#endregion
52
+ //#region src/database/models/public/KyselyMigration.d.ts
53
+ /** Identifier type for public.kysely_migration */
54
+ type KyselyMigrationName = string & {
55
+ __brand: "public.kysely_migration";
56
+ };
57
+ /** Represents the table public.kysely_migration */
58
+ interface KyselyMigrationTable {
59
+ name: ColumnType<KyselyMigrationName, KyselyMigrationName, KyselyMigrationName>;
60
+ timestamp: ColumnType<string, string, string>;
61
+ }
62
+ //#endregion
63
+ //#region src/database/models/public/KyselyMigrationLock.d.ts
64
+ /** Identifier type for public.kysely_migration_lock */
65
+ type KyselyMigrationLockId = string & {
66
+ __brand: "public.kysely_migration_lock";
67
+ };
68
+ /** Represents the table public.kysely_migration_lock */
69
+ interface KyselyMigrationLockTable {
70
+ id: ColumnType<KyselyMigrationLockId, KyselyMigrationLockId, KyselyMigrationLockId>;
71
+ is_locked: ColumnType<number, number | undefined, number>;
72
+ }
73
+ //#endregion
74
+ //#region src/database/models/public/RepoFile.d.ts
75
+ /** Represents the table public.repo_file */
76
+ interface RepoFileTable {
77
+ repo_id: ColumnType<ReposId, ReposId, ReposId>;
78
+ ref_name: ColumnType<string, string, string>;
79
+ path: ColumnType<string, string, string>;
80
+ mode: ColumnType<string, string, string>;
81
+ blob_oid: ColumnType<Buffer, Buffer, Buffer>;
82
+ }
83
+ type RepoFile = Selectable<RepoFileTable>;
84
+ //#endregion
85
+ //#region src/database/models/public/PublicSchema.d.ts
86
+ interface PublicSchema {
87
+ kysely_migration_lock: KyselyMigrationLockTable;
88
+ git_edge: GitEdgeTable;
89
+ git_ref: GitRefTable;
90
+ repo_file: RepoFileTable;
91
+ kysely_migration: KyselyMigrationTable;
92
+ git_object: GitObjectTable;
93
+ repos: ReposTable;
94
+ }
95
+ //#endregion
96
+ //#region src/database/models/Database.d.ts
97
+ type Database = PublicSchema;
98
+ //#endregion
99
+ export type { GitObject, GitObjectTable, Database as PggitSchema, RepoFile, RepoFileTable, Repos, ReposId, ReposTable };
100
+ //# sourceMappingURL=schema.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.mts","names":[],"sources":["../src/database/models/public/Repos.ts","../src/database/models/public/GitEdge.ts","../src/database/models/public/GitObject.ts","../src/database/models/public/GitRef.ts","../src/database/models/public/KyselyMigration.ts","../src/database/models/public/KyselyMigrationLock.ts","../src/database/models/public/RepoFile.ts","../src/database/models/public/PublicSchema.ts","../src/database/models/Database.ts"],"mappings":";;;;KAMY,OAAA;EAAqB,OAAO;AAAA;;UAGf,UAAA;EACxB,EAAA,EAAI,UAAA,CAAW,OAAA;EAEf,IAAA,EAAM,UAAA;EAEN,cAAA,EAAgB,UAAA,CAAW,IAAA,SAAa,IAAA,kBAAsB,IAAA;EAE9D,UAAA,EAAY,UAAA,CAAW,IAAA,SAAa,IAAA,kBAAsB,IAAA;AAAA;AAAA,KAG/C,KAAA,GAAQ,UAAU,CAAC,UAAA;;;;UCZN,YAAA;EACxB,OAAA,EAAS,UAAA,CAAW,OAAA,EAAS,OAAA,EAAS,OAAA;EAEtC,MAAA,EAAQ,UAAA,CAAW,MAAA,EAAQ,MAAA,EAAQ,MAAA;EAEnC,KAAA,EAAO,UAAA,CAAW,MAAA,EAAQ,MAAA,EAAQ,MAAA;EAElC,IAAA,EAAM,UAAA;AAAA;;;;UCPkB,cAAA;EACxB,OAAA,EAAS,UAAA,CAAW,OAAA,EAAS,OAAA,EAAS,OAAA;EAEtC,GAAA,EAAK,UAAA,CAAW,MAAA,EAAQ,MAAA,EAAQ,MAAA;EAEhC,IAAA,EAAM,UAAA;EAEN,IAAA,EAAM,UAAA;EAEN,OAAA,EAAS,UAAA,CAAW,MAAA,EAAQ,MAAA,EAAQ,MAAA;EAEpC,UAAA,EAAY,UAAA,CAAW,IAAA,EAAM,IAAA,uBAA2B,IAAA;AAAA;AAAA,KAG7C,SAAA,GAAY,UAAU,CAAC,cAAA;;;;KCdvB,UAAA;EAAwB,OAAO;AAAA;;UAGlB,WAAA;EACxB,OAAA,EAAS,UAAA,CAAW,OAAA,EAAS,OAAA,EAAS,OAAA;EAEtC,IAAA,EAAM,UAAA,CAAW,UAAA,EAAY,UAAA,EAAY,UAAA;EAEzC,GAAA,EAAK,UAAA,CAAW,MAAA,SAAe,MAAA,SAAe,MAAA;EAE9C,UAAA,EAAY,UAAA,CAAW,MAAA,SAAe,MAAA,SAAe,MAAA;EAErD,aAAA,EAAe,UAAA;AAAA;;;;KCbJ,mBAAA;EAAiC,OAAO;AAAA;;UAG3B,oBAAA;EACxB,IAAA,EAAM,UAAA,CAAW,mBAAA,EAAqB,mBAAA,EAAqB,mBAAA;EAE3D,SAAA,EAAW,UAAA;AAAA;;;;KCNA,qBAAA;EAAmC,OAAO;AAAA;;UAG7B,wBAAA;EACxB,EAAA,EAAI,UAAA,CAAW,qBAAA,EAAuB,qBAAA,EAAuB,qBAAA;EAE7D,SAAA,EAAW,UAAA;AAAA;;;;UCLa,aAAA;EACxB,OAAA,EAAS,UAAA,CAAW,OAAA,EAAS,OAAA,EAAS,OAAA;EAEtC,QAAA,EAAU,UAAA;EAEV,IAAA,EAAM,UAAA;EAEN,IAAA,EAAM,UAAA;EAEN,QAAA,EAAU,UAAA,CAAW,MAAA,EAAQ,MAAA,EAAQ,MAAA;AAAA;AAAA,KAG1B,QAAA,GAAW,UAAU,CAAC,aAAA;;;UCRT,YAAA;EACxB,qBAAA,EAAuB,wBAAA;EAEvB,QAAA,EAAU,YAAA;EAEV,OAAA,EAAS,WAAA;EAET,SAAA,EAAW,aAAA;EAEX,gBAAA,EAAkB,oBAAA;EAElB,UAAA,EAAY,cAAA;EAEZ,KAAA,EAAO,UAAA;AAAA;;;KCnBH,QAAA,GAAW,YAAY"}
@@ -0,0 +1 @@
1
+ export { };
package/package.json ADDED
@@ -0,0 +1,91 @@
1
+ {
2
+ "bugs": {
3
+ "url": "https://github.com/usecontextlayer/pggit/issues"
4
+ },
5
+ "dependencies": {
6
+ "@hono/node-server": "^1.19.14",
7
+ "hono": "^4.12.23",
8
+ "kysely": "^0.29.2",
9
+ "kysely-postgres-js": "^3.0.0",
10
+ "postgres": "^3.4.9",
11
+ "zod": "^4.4.3"
12
+ },
13
+ "description": "A battle-tested git server that runs on Postgres.",
14
+ "devDependencies": {
15
+ "@biomejs/biome": "latest",
16
+ "@datadog/pprof": "^5.15.0",
17
+ "@platformatic/flame": "^1.6.0",
18
+ "@testcontainers/postgresql": "^12.0.3",
19
+ "@testcontainers/toxiproxy": "^12.0.3",
20
+ "@types/node": "^25.9.1",
21
+ "commander": "^15.0.0",
22
+ "fast-check": "latest",
23
+ "kanel": "^4.0.2",
24
+ "kanel-kysely": "^4.0.0",
25
+ "semantic-release": "latest",
26
+ "testcontainers": "12.0.3",
27
+ "toxiproxy-node-client": "^4.1.0",
28
+ "tsdown": "latest",
29
+ "tsx": "latest",
30
+ "typescript": "latest",
31
+ "vitest": "latest"
32
+ },
33
+ "engines": {
34
+ "node": ">=20.0.0"
35
+ },
36
+ "exports": {
37
+ ".": {
38
+ "import": "./dist/index.mjs",
39
+ "types": "./dist/index.d.mts"
40
+ },
41
+ "./schema": {
42
+ "import": "./dist/schema.mjs",
43
+ "types": "./dist/schema.d.mts"
44
+ }
45
+ },
46
+ "files": [
47
+ "dist"
48
+ ],
49
+ "homepage": "https://github.com/usecontextlayer/pggit#readme",
50
+ "keywords": [
51
+ "git",
52
+ "git-server",
53
+ "smart-http",
54
+ "postgres",
55
+ "postgresql",
56
+ "hono",
57
+ "kysely"
58
+ ],
59
+ "license": "MIT",
60
+ "main": "./dist/index.mjs",
61
+ "name": "@usecontextlayer/pggit",
62
+ "packageManager": "pnpm@10.34.1",
63
+ "pnpm": {
64
+ "onlyBuiltDependencies": [
65
+ "esbuild"
66
+ ]
67
+ },
68
+ "publishConfig": {
69
+ "access": "public"
70
+ },
71
+ "repository": {
72
+ "type": "git",
73
+ "url": "git+https://github.com/usecontextlayer/pggit.git"
74
+ },
75
+ "scripts": {
76
+ "build": "npx tsdown",
77
+ "check": "pnpm run format.fix && pnpm run tsc && pnpm run test",
78
+ "clean": "rm -rf dist dist-types *.tsbuildinfo coverage .vitest-attachments node_modules",
79
+ "db.manage": "npx tsx ./manage.ts --sub=database",
80
+ "dev": "npx tsx src/main.ts",
81
+ "format.fix": "npx @biomejs/biome check --fix --error-on-warnings .",
82
+ "format.verify": "npx @biomejs/biome check --error-on-warnings .",
83
+ "perf": "NODE_OPTIONS=--expose-gc npx tsx perf/run.ts",
84
+ "release": "npx semantic-release",
85
+ "test": "npx vitest run",
86
+ "tsc": "npx tsc -b tsconfig.json"
87
+ },
88
+ "type": "module",
89
+ "types": "./dist/index.d.mts",
90
+ "version": "1.0.0"
91
+ }