@skydiveai/git-cache 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Create, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/cli.d.mts ADDED
@@ -0,0 +1 @@
1
+ export { };
package/dist/cli.mjs ADDED
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ import { f as startServer } from "./server-B5u5DETT.mjs";
3
+ //#region src/cli.ts
4
+ /**
5
+ * `git-cache` CLI — the published binary for @skydiveai/git-cache.
6
+ *
7
+ * Subcommands:
8
+ * serve start the loopback cache daemon (the long-running process the
9
+ * sandbox launches at boot via supervisord). Config comes from env
10
+ * (GIT_CACHE_PORT, GIT_CACHE_DIR, GIT_CACHE_CREDENTIAL_HELPER, …).
11
+ * version print the package version.
12
+ *
13
+ * The in-sandbox on/off wiring (git-config rewrites, opt-out, daemon start/stop)
14
+ * lives in the thin shell layer that ships with the sandbox image
15
+ * (git-cache-apply.sh / git-cache-ctl.sh); this binary is the daemon itself.
16
+ */
17
+ async function main() {
18
+ const cmd = process.argv[2] ?? "serve";
19
+ switch (cmd) {
20
+ case "serve":
21
+ await startServer();
22
+ return;
23
+ case "version":
24
+ case "--version":
25
+ case "-v":
26
+ process.stdout.write(`${process.env.npm_package_version ?? "unknown"}\n`);
27
+ return;
28
+ case "help":
29
+ case "--help":
30
+ case "-h":
31
+ process.stdout.write("usage: git-cache-daemon <serve|version>\n serve start the loopback git cache daemon (default)\n version print the daemon version\n");
32
+ return;
33
+ default:
34
+ process.stderr.write(`git-cache: unknown command '${cmd}'\n`);
35
+ process.exit(2);
36
+ }
37
+ }
38
+ main().catch((error) => {
39
+ process.stderr.write(`git-cache: fatal: ${String(error)}\n`);
40
+ process.exit(1);
41
+ });
42
+ //#endregion
43
+ export {};
@@ -0,0 +1,179 @@
1
+ import * as _$node_stream0 from "node:stream";
2
+
3
+ //#region src/server.d.ts
4
+ /** The counter payload `/metrics` serves. Copied so callers can't mutate it. */
5
+ declare function metricsSnapshot(): {
6
+ cold_requests: number;
7
+ cold_followers: number;
8
+ warm_hits: number;
9
+ refreshes: number;
10
+ publications: number;
11
+ abandoned_ineligible: number;
12
+ abandoned_oversize: number;
13
+ abandoned_queue_overflow: number;
14
+ abandoned_import_error: number;
15
+ abandoned_disconnect: number;
16
+ passthrough_ineligible: number;
17
+ passthrough_missing_want: number;
18
+ invalidations: number;
19
+ evictions: number;
20
+ refresh_failures: number;
21
+ refresh_deferred: number;
22
+ publish_deferred: number;
23
+ credential_url_redirects: number;
24
+ lfs_redirects: number;
25
+ upstream_errors: number;
26
+ bad_requests: number;
27
+ cold_proxy_bytes: number;
28
+ captured_pack_bytes: number;
29
+ cold_build_failures: number;
30
+ backfills: number;
31
+ backfill_failures: number;
32
+ auth_checks: number;
33
+ auth_memo_hits: number;
34
+ auth_denials: number;
35
+ };
36
+ /**
37
+ * Record the outcome of a finished cold build. A failure leaves the mirror
38
+ * unpublished, so every later clone silently pays full upstream cost — it must
39
+ * move a counter and not just write a log line, or a cache that never publishes
40
+ * reads as healthy on /metrics.
41
+ */
42
+ declare function recordColdBuildOutcome(error: unknown): void;
43
+ /** A ref line from a smart-HTTP advertisement (peeled tags excluded). */
44
+ type AdvertisedRef = {
45
+ oid: string;
46
+ ref: string;
47
+ };
48
+ /** Parsed `info/refs` advertisement: the fetchable refs + object-format flag. */
49
+ type Advertisement = {
50
+ refs: AdvertisedRef[];
51
+ sha256: boolean;
52
+ };
53
+ /**
54
+ * Guard against non-numeric env values explicitly: NaN would only be
55
+ * fail-safe through the accident of `elapsed < NaN` being false, and any
56
+ * later refactor to a `>= ttl` comparison would silently flip that into
57
+ * memoize-forever.
58
+ */
59
+ /**
60
+ * Turn a rewritten cache path into a validated upstream identity, or null.
61
+ * The first path segment is the authority (host or host:port); the rest is the
62
+ * repo path. We reject anything that isn't a plain public https git host.
63
+ */
64
+ /** parseUpstream result: either a validated upstream identity to serve, or a
65
+ * redirect bouncing not-ours traffic (creds-in-URL, LFS API) to its true
66
+ * upstream. Discriminated by `redirect` so callers narrow with one check. */
67
+ type ParsedUpstream = {
68
+ redirect?: undefined;
69
+ repo: string;
70
+ upstreamUrl: string;
71
+ operation: string;
72
+ } | {
73
+ redirect: string;
74
+ operation: string;
75
+ };
76
+ declare function parseUpstream(rawPathname: any, rawSearch?: string): ParsedUpstream | null;
77
+ /**
78
+ * Best-effort human explanation for a request parseUpstream refused, used
79
+ * ONLY in the 404 body so an agent whose (legitimate but unproxyable) remote
80
+ * hits a security rejection gets an actionable error instead of an opaque
81
+ * "git cache endpoint". Diagnostic only — the actual gate is parseUpstream;
82
+ * this never influences serving decisions, so a miss here is harmless.
83
+ */
84
+ declare function explainRejection(rawPathname: string): string | null;
85
+ /**
86
+ * Run `fn` while holding a named lock for `repo`, or return `null` without
87
+ * running it if another writer holds that lock.
88
+ *
89
+ * The in-memory guards elsewhere in this file (`inflightRefresh`,
90
+ * `coldLeaders`, `backfilling`) only serialize writers inside ONE process,
91
+ * which is the whole story when each process owns its own disk. On a mirror
92
+ * store shared between tasks they are blind to each other, so two tasks will
93
+ * happily fetch into, publish over, or evict the same mirror at the same
94
+ * time. This lock is what makes those operations mutually exclusive across
95
+ * every writer of the store.
96
+ *
97
+ * `mkdir` is the primitive because its create-or-fail is atomic on a single
98
+ * disk and over NFS alike. A lock whose mtime has stopped advancing is
99
+ * assumed abandoned and broken, so a task that dies mid-operation cannot
100
+ * wedge a repo forever.
101
+ */
102
+ declare function copyRequestHeaders(headers: any): {};
103
+ declare function parseAdvertisement(buffer: Buffer): Advertisement;
104
+ /**
105
+ * Inspect a client's git-upload-pack request body to decide cache eligibility.
106
+ * We only ingest packs that can form a COMPLETE standalone mirror: no filter,
107
+ * no shallow/deepen, and no client-provided `have` lines (which would make the
108
+ * server omit objects the client already had). Everything else is passed
109
+ * through with ingestion disabled — correctness over coverage.
110
+ *
111
+ * Returns { eligible, reason }.
112
+ */
113
+ /**
114
+ * True when a `git-upload-pack` POST body speaks protocol v2.
115
+ *
116
+ * v2 replaces v0's bare want/have lines with a leading `command=<verb>` line,
117
+ * so the first pkt-line identifies the dialect unambiguously.
118
+ */
119
+ declare function isProtocolV2Request(buffer: any): boolean;
120
+ declare function classifyUploadRequest(buffer: any): {
121
+ eligible: boolean;
122
+ reason: string;
123
+ wants: string[];
124
+ };
125
+ declare class SidebandPackExtractor {
126
+ writable: _$node_stream0.Writable;
127
+ buffer: Buffer;
128
+ sawPack: boolean;
129
+ inPackSection: boolean;
130
+ needsDrain: boolean;
131
+ errored: boolean;
132
+ constructor(writable: _$node_stream0.Writable);
133
+ push(chunk: any): boolean | undefined;
134
+ drained(): void;
135
+ end(): void;
136
+ }
137
+ /**
138
+ * Whether a request that could NOT be cached is worth building a mirror for
139
+ * out-of-band. Only uncacheable requests need it — a cacheable one already
140
+ * populated the cache — and a sha256 repo is excluded because the
141
+ * advertisement parser cannot read its 64-hex OIDs, so no correct mirror can
142
+ * be completed for it at all.
143
+ */
144
+ declare function shouldBackfillAfterPassthrough({
145
+ cacheable,
146
+ sha256
147
+ }: {
148
+ cacheable: any;
149
+ sha256: any;
150
+ }): boolean;
151
+ /**
152
+ * True when every OID is already present in the mirror.
153
+ *
154
+ * `git-upload-pack` refuses the whole request with `not our ref <oid>` if a
155
+ * single wanted object is missing, so this is the difference between serving
156
+ * a fetch and failing it. A mirror can legitimately lack a wanted object: the
157
+ * client negotiated against a fresher advertisement — from a sibling task
158
+ * holding its own mirror set, or from the sandbox-local cache tier — and asks
159
+ * for a commit this mirror has not fetched yet.
160
+ */
161
+ declare function mirrorHasObjects(local: any, oids: string[]): Promise<boolean>;
162
+ /**
163
+ * Explicit configuration overrides for startServer. Takes precedence over the
164
+ * environment snapshot taken at module load, so embedders (the shared service
165
+ * wrapper) configure the engine through this API instead of mutating
166
+ * process.env before import — an ordering contract that import hoisting made
167
+ * easy to violate silently.
168
+ */
169
+ type StartServerOptions = {
170
+ authMode?: 'trusted-client' | 'check-through';
171
+ bindHost?: string;
172
+ port?: number;
173
+ cacheDir?: string;
174
+ maxCacheBytes?: number;
175
+ credentialHelper?: string;
176
+ };
177
+ declare function startServer(options?: StartServerOptions): Promise<void>;
178
+ //#endregion
179
+ export { SidebandPackExtractor, classifyUploadRequest, copyRequestHeaders, explainRejection, isProtocolV2Request, metricsSnapshot, mirrorHasObjects, parseAdvertisement, parseUpstream, recordColdBuildOutcome, shouldBackfillAfterPassthrough, startServer };
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import { a as isProtocolV2Request, c as parseAdvertisement, d as shouldBackfillAfterPassthrough, f as startServer, i as explainRejection, l as parseUpstream, n as classifyUploadRequest, o as metricsSnapshot, r as copyRequestHeaders, s as mirrorHasObjects, t as SidebandPackExtractor, u as recordColdBuildOutcome } from "./server-B5u5DETT.mjs";
2
+ export { SidebandPackExtractor, classifyUploadRequest, copyRequestHeaders, explainRejection, isProtocolV2Request, metricsSnapshot, mirrorHasObjects, parseAdvertisement, parseUpstream, recordColdBuildOutcome, shouldBackfillAfterPassthrough, startServer };