git-fs-s3 0.3.5

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/dist/index.cjs ADDED
@@ -0,0 +1,801 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ CircuitOpenError: () => CircuitOpenError,
24
+ FsError: () => FsError,
25
+ GitAuthenticationError: () => GitAuthenticationError,
26
+ GitAuthorizationError: () => GitAuthorizationError,
27
+ GitConflictError: () => GitConflictError,
28
+ GitError: () => GitError,
29
+ GitInvalidRequestError: () => GitInvalidRequestError,
30
+ GitObjectNotFoundError: () => GitObjectNotFoundError,
31
+ GitPathNotFoundError: () => GitPathNotFoundError,
32
+ GitProtocolError: () => GitProtocolError,
33
+ GitRateLimitError: () => GitRateLimitError,
34
+ GitRefNotFoundError: () => GitRefNotFoundError,
35
+ GitRepositoryNotFoundError: () => GitRepositoryNotFoundError,
36
+ MemoryObjectStore: () => MemoryObjectStore,
37
+ concat: () => concat,
38
+ createCachedStore: () => createCachedStore,
39
+ createGitFs: () => createGitFs,
40
+ createRetryStore: () => createRetryStore,
41
+ decodeAscii: () => decodeAscii,
42
+ decodeUtf8: () => decodeUtf8,
43
+ deflate: () => deflate,
44
+ encodeUtf8: () => encodeUtf8,
45
+ formatErrorResponse: () => formatErrorResponse,
46
+ fromHex: () => fromHex,
47
+ hasNullByte: () => hasNullByte,
48
+ isFullSha: () => isFullSha,
49
+ isSafeBranchName: () => isSafeBranchName,
50
+ isSafeFullRefName: () => isSafeFullRefName,
51
+ isSafeRefName: () => isSafeRefName,
52
+ isSafeRepoPath: () => isSafeRepoPath,
53
+ qualifyBranchRef: () => qualifyBranchRef,
54
+ readBlobContent: () => readBlobContent,
55
+ sha1: () => sha1,
56
+ toBase64: () => toBase64,
57
+ toHex: () => toHex
58
+ });
59
+ module.exports = __toCommonJS(src_exports);
60
+
61
+ // src/cache.ts
62
+ var import_lru_cache = require("lru-cache");
63
+ var MISS = /* @__PURE__ */ Symbol("miss");
64
+ function listEntrySize(entry) {
65
+ let size = entry.prefix.length + 16;
66
+ for (const o of entry.result.objects) size += o.key.length + 8;
67
+ for (const p of entry.result.prefixes) size += p.length;
68
+ return size;
69
+ }
70
+ function copyListResult(result) {
71
+ return {
72
+ objects: result.objects.map((o) => ({ ...o })),
73
+ prefixes: [...result.prefixes]
74
+ };
75
+ }
76
+ function createCachedStore(store, options = {}) {
77
+ const maxBytes = options.maxBytes ?? 50 * 1024 * 1024;
78
+ const maxEntryBytes = options.maxEntryBytes ?? Math.ceil(maxBytes / 10);
79
+ const ttl = options.ttlMs ?? 6e4;
80
+ const ttlForKey = options.ttlForKey;
81
+ const cacheMisses = options.cacheMisses ?? false;
82
+ const cacheLists = options.cacheLists ?? false;
83
+ const coalesce = options.coalesce ?? true;
84
+ const onHit = options.onHit;
85
+ const onMiss = options.onMiss;
86
+ const cache = new import_lru_cache.LRUCache({
87
+ maxSize: maxBytes,
88
+ sizeCalculation: (value) => value === MISS ? 1 : value.byteLength || 1,
89
+ ttl
90
+ });
91
+ const listCache = new import_lru_cache.LRUCache({
92
+ maxSize: Math.max(1, Math.ceil(maxBytes / 10)),
93
+ sizeCalculation: listEntrySize,
94
+ ttl
95
+ });
96
+ const pendingGets = /* @__PURE__ */ new Map();
97
+ const pendingHeads = /* @__PURE__ */ new Map();
98
+ const pendingLists = /* @__PURE__ */ new Map();
99
+ const admit = (key, data) => {
100
+ if (data.byteLength <= maxEntryBytes) {
101
+ cache.set(key, data.slice(), { ttl: ttlForKey?.(key) });
102
+ }
103
+ };
104
+ function clearStaleListEntries(key) {
105
+ for (const [listKey, entry] of listCache.entries()) {
106
+ if (!key.startsWith(entry.prefix)) continue;
107
+ if (entry.probe && !entry.empty) continue;
108
+ listCache.delete(listKey);
109
+ }
110
+ }
111
+ function coalesced(pending, key, fn) {
112
+ if (!coalesce) return fn();
113
+ const inflight = pending.get(key);
114
+ if (inflight !== void 0) return inflight;
115
+ const p = fn().finally(() => pending.delete(key));
116
+ pending.set(key, p);
117
+ return p;
118
+ }
119
+ return {
120
+ async get(key) {
121
+ const cached = cache.get(key);
122
+ if (cached !== void 0) {
123
+ onHit?.(key);
124
+ return cached === MISS ? null : cached.slice();
125
+ }
126
+ const data = await coalesced(pendingGets, key, async () => {
127
+ onMiss?.(key);
128
+ const fetched = await store.get(key);
129
+ if (fetched !== null) {
130
+ admit(key, fetched);
131
+ } else if (cacheMisses) {
132
+ cache.set(key, MISS, { ttl: ttlForKey?.(key) });
133
+ }
134
+ return fetched;
135
+ });
136
+ return data === null ? null : data.slice();
137
+ },
138
+ async put(key, data) {
139
+ await store.put(key, data);
140
+ admit(key, data);
141
+ if (data.byteLength > maxEntryBytes) cache.delete(key);
142
+ clearStaleListEntries(key);
143
+ },
144
+ async delete(key) {
145
+ await store.delete(key);
146
+ if (cacheMisses) {
147
+ cache.set(key, MISS, { ttl: ttlForKey?.(key) });
148
+ } else {
149
+ cache.delete(key);
150
+ }
151
+ clearStaleListEntries(key);
152
+ },
153
+ async head(key) {
154
+ const cached = cache.get(key);
155
+ if (cached !== void 0) {
156
+ onHit?.(key);
157
+ return cached === MISS ? null : { size: cached.byteLength };
158
+ }
159
+ return coalesced(pendingHeads, key, async () => {
160
+ onMiss?.(key);
161
+ const stat = await store.head(key);
162
+ if (stat === null && cacheMisses) {
163
+ cache.set(key, MISS, { ttl: ttlForKey?.(key) });
164
+ }
165
+ return stat;
166
+ });
167
+ },
168
+ async list(prefix, listOptions) {
169
+ if (!cacheLists) return store.list(prefix, listOptions);
170
+ const listKey = `${listOptions?.delimiter ?? ""}|${listOptions?.limit ?? ""}|${prefix}`;
171
+ const cached = listCache.get(listKey);
172
+ if (cached !== void 0) {
173
+ onHit?.(prefix);
174
+ return copyListResult(cached.result);
175
+ }
176
+ const result = await coalesced(pendingLists, listKey, async () => {
177
+ onMiss?.(prefix);
178
+ const fetched = await store.list(prefix, listOptions);
179
+ listCache.set(
180
+ listKey,
181
+ {
182
+ result: copyListResult(fetched),
183
+ prefix,
184
+ probe: listOptions?.limit === 1,
185
+ empty: fetched.objects.length === 0 && fetched.prefixes.length === 0
186
+ },
187
+ { ttl: ttlForKey?.(prefix) }
188
+ );
189
+ return fetched;
190
+ });
191
+ return copyListResult(result);
192
+ },
193
+ invalidate(prefix) {
194
+ for (const key of cache.keys()) {
195
+ if (key.startsWith(prefix)) cache.delete(key);
196
+ }
197
+ for (const [listKey, entry] of listCache.entries()) {
198
+ if (entry.prefix.startsWith(prefix) || prefix.startsWith(entry.prefix)) {
199
+ listCache.delete(listKey);
200
+ }
201
+ }
202
+ }
203
+ };
204
+ }
205
+
206
+ // src/edge-utils.ts
207
+ var textEncoder = new TextEncoder();
208
+ var textDecoder = new TextDecoder();
209
+ function encodeUtf8(data) {
210
+ return textEncoder.encode(data);
211
+ }
212
+ function decodeUtf8(data) {
213
+ return textDecoder.decode(data);
214
+ }
215
+ function decodeAscii(data) {
216
+ let s = "";
217
+ for (let i = 0; i < data.length; i++)
218
+ s += String.fromCharCode(data[i]);
219
+ return s;
220
+ }
221
+ function concat(...parts) {
222
+ let total = 0;
223
+ for (const p of parts) total += p.length;
224
+ const out = new Uint8Array(total);
225
+ let offset = 0;
226
+ for (const p of parts) {
227
+ out.set(p, offset);
228
+ offset += p.length;
229
+ }
230
+ return out;
231
+ }
232
+ function toHex(data) {
233
+ let hex = "";
234
+ for (let i = 0; i < data.length; i++)
235
+ hex += data[i].toString(16).padStart(2, "0");
236
+ return hex;
237
+ }
238
+ function toBase64(data) {
239
+ let binary = "";
240
+ for (let i = 0; i < data.length; i++)
241
+ binary += String.fromCharCode(data[i]);
242
+ return btoa(binary);
243
+ }
244
+ function fromHex(hex) {
245
+ const bytes = new Uint8Array(hex.length / 2);
246
+ for (let i = 0; i < bytes.length; i++) {
247
+ bytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
248
+ }
249
+ return bytes;
250
+ }
251
+ async function sha1(data) {
252
+ const bytes = typeof data === "string" ? encodeUtf8(data) : data;
253
+ const hash = await globalThis.crypto.subtle.digest("SHA-1", bytes);
254
+ return toHex(new Uint8Array(hash));
255
+ }
256
+ async function deflate(data) {
257
+ const stream = new Blob([data]).stream().pipeThrough(new CompressionStream("deflate"));
258
+ return new Uint8Array(await new Response(stream).arrayBuffer());
259
+ }
260
+ function hasNullByte(data) {
261
+ return data.includes(0);
262
+ }
263
+ function readBlobContent(blob) {
264
+ const isBinary = hasNullByte(blob);
265
+ return {
266
+ isBinary,
267
+ text: isBinary ? "" : decodeUtf8(blob),
268
+ bytes: blob
269
+ };
270
+ }
271
+
272
+ // src/errors.ts
273
+ var FsError = class extends Error {
274
+ code;
275
+ syscall;
276
+ path;
277
+ constructor(code, syscall, path) {
278
+ super(`${code}: ${syscall} '${path}'`);
279
+ this.name = "FsError";
280
+ this.code = code;
281
+ this.syscall = syscall;
282
+ this.path = path;
283
+ }
284
+ };
285
+ var enoent = (syscall, path) => new FsError("ENOENT", syscall, path);
286
+ var enotdir = (syscall, path) => new FsError("ENOTDIR", syscall, path);
287
+ var enotempty = (syscall, path) => new FsError("ENOTEMPTY", syscall, path);
288
+ var einval = (syscall, path) => new FsError("EINVAL", syscall, path);
289
+ var eperm = (syscall, path) => new FsError("EPERM", syscall, path);
290
+
291
+ // src/git-errors.ts
292
+ var GitError = class extends Error {
293
+ statusCode;
294
+ retryable;
295
+ constructor(message, statusCode = 500, retryable = false) {
296
+ super(message);
297
+ this.name = this.constructor.name;
298
+ this.statusCode = statusCode;
299
+ this.retryable = retryable;
300
+ Error.captureStackTrace?.(this, this.constructor);
301
+ }
302
+ toJSON() {
303
+ return {
304
+ error: this.name,
305
+ message: this.message,
306
+ statusCode: this.statusCode,
307
+ retryable: this.retryable
308
+ };
309
+ }
310
+ };
311
+ var GitPathNotFoundError = class extends GitError {
312
+ constructor(message) {
313
+ super(message, 404, false);
314
+ }
315
+ };
316
+ var GitObjectNotFoundError = class extends GitError {
317
+ constructor(message) {
318
+ super(message, 404, false);
319
+ }
320
+ };
321
+ var GitRefNotFoundError = class extends GitError {
322
+ constructor(message) {
323
+ super(message, 404, false);
324
+ }
325
+ };
326
+ var GitRepositoryNotFoundError = class extends GitError {
327
+ constructor(message) {
328
+ super(message, 404, false);
329
+ }
330
+ };
331
+ var GitConflictError = class extends GitError {
332
+ conflicts;
333
+ constructor(message, conflicts = []) {
334
+ super(message, 409, false);
335
+ this.conflicts = conflicts;
336
+ }
337
+ toJSON() {
338
+ return { ...super.toJSON(), conflicts: this.conflicts };
339
+ }
340
+ };
341
+ var GitAuthenticationError = class extends GitError {
342
+ constructor(message) {
343
+ super(message, 401, false);
344
+ }
345
+ };
346
+ var GitAuthorizationError = class extends GitError {
347
+ constructor(message) {
348
+ super(message, 403, false);
349
+ }
350
+ };
351
+ var GitRateLimitError = class extends GitError {
352
+ constructor(message) {
353
+ super(message, 429, false);
354
+ }
355
+ };
356
+ var GitInvalidRequestError = class extends GitError {
357
+ constructor(message) {
358
+ super(message, 400, false);
359
+ }
360
+ };
361
+ var GitProtocolError = class extends GitError {
362
+ constructor(message) {
363
+ super(message, 400, false);
364
+ }
365
+ };
366
+ function formatErrorResponse(error) {
367
+ if (error instanceof GitError) {
368
+ return {
369
+ status: error.statusCode,
370
+ body: error.toJSON(),
371
+ headers: error.statusCode === 401 ? { "WWW-Authenticate": 'Basic realm="Git Repository"' } : void 0
372
+ };
373
+ }
374
+ if (error instanceof Error) {
375
+ return {
376
+ status: 500,
377
+ body: {
378
+ error: "InternalServerError",
379
+ message: "An internal error occurred",
380
+ retryable: true
381
+ }
382
+ };
383
+ }
384
+ return {
385
+ status: 500,
386
+ body: {
387
+ error: "UnknownError",
388
+ message: "An unknown error occurred",
389
+ retryable: true
390
+ }
391
+ };
392
+ }
393
+
394
+ // src/git-fs.ts
395
+ var import_lru_cache2 = require("lru-cache");
396
+
397
+ // src/path.ts
398
+ function normalizePath(filepath) {
399
+ const segments = filepath.split("/");
400
+ const out = [];
401
+ for (const segment of segments) {
402
+ if (segment === "" || segment === ".") continue;
403
+ if (segment === "..") {
404
+ if (out.length === 0) throw einval("resolve", filepath);
405
+ out.pop();
406
+ continue;
407
+ }
408
+ out.push(segment);
409
+ }
410
+ return out.join("/");
411
+ }
412
+
413
+ // src/git-fs.ts
414
+ var FILE_MODE = 33188;
415
+ var DIR_MODE = 16384;
416
+ var LOOSE_OBJECT_RE = /(^|\/)objects\/[0-9a-f]{2}\/[0-9a-f]{38}$/;
417
+ var textEncoder2 = new TextEncoder();
418
+ var textDecoder2 = new TextDecoder();
419
+ function makeStat(type, size) {
420
+ const epoch = /* @__PURE__ */ new Date(0);
421
+ return {
422
+ type,
423
+ mode: type === "file" ? FILE_MODE : DIR_MODE,
424
+ size,
425
+ ino: 0,
426
+ mtimeMs: 0,
427
+ ctimeMs: 0,
428
+ uid: 0,
429
+ gid: 0,
430
+ dev: 0,
431
+ mtime: epoch,
432
+ ctime: epoch,
433
+ isFile: () => type === "file",
434
+ isDirectory: () => type === "dir",
435
+ isSymbolicLink: () => false
436
+ };
437
+ }
438
+ function resolveEncoding(options) {
439
+ if (typeof options === "string") return options;
440
+ return options?.encoding;
441
+ }
442
+ function createGitFs(store, options = {}) {
443
+ const prefix = options.prefix ?? "";
444
+ const structurallyAbsent = options.isStructurallyAbsent;
445
+ const useLooseHints = options.looseObjectHints ?? false;
446
+ const onNote = options.onNote;
447
+ const toKey = (path) => {
448
+ if (prefix === "") return path;
449
+ return path === "" ? prefix : `${prefix}/${path}`;
450
+ };
451
+ const looseHints = new import_lru_cache2.LRUCache({
452
+ max: 1024,
453
+ ttl: options.hintTtlMs ?? 36e5
454
+ });
455
+ function looseScope(path) {
456
+ const match = LOOSE_OBJECT_RE.exec(path);
457
+ if (match === null) return null;
458
+ return path.slice(0, match.index);
459
+ }
460
+ function knownAbsent(path) {
461
+ if (structurallyAbsent?.(path)) return true;
462
+ if (!useLooseHints) return false;
463
+ const scope = looseScope(path);
464
+ return scope !== null && looseHints.get(scope) === "none";
465
+ }
466
+ async function isDirectory(dirKey) {
467
+ const { objects, prefixes } = await store.list(`${dirKey}/`, {
468
+ limit: 1
469
+ });
470
+ return objects.length > 0 || prefixes.length > 0;
471
+ }
472
+ async function stat(filepath, syscall) {
473
+ const path = normalizePath(filepath);
474
+ if (knownAbsent(path)) throw enoent(syscall, filepath);
475
+ const k = toKey(path);
476
+ if (k === prefix || k === "") return makeStat("dir", 0);
477
+ const fileStat = await store.head(k);
478
+ if (fileStat) return makeStat("file", fileStat.size);
479
+ if (useLooseHints && looseScope(path) !== null) {
480
+ throw enoent(syscall, filepath);
481
+ }
482
+ if (await isDirectory(k)) return makeStat("dir", 0);
483
+ throw enoent(syscall, filepath);
484
+ }
485
+ const promises = {
486
+ async readFile(filepath, opts) {
487
+ const path = normalizePath(filepath);
488
+ if (knownAbsent(path)) throw enoent("open", filepath);
489
+ const data = await store.get(toKey(path));
490
+ if (data === null) throw enoent("open", filepath);
491
+ return resolveEncoding(opts) === "utf8" ? textDecoder2.decode(data) : data;
492
+ },
493
+ async writeFile(filepath, data, _opts) {
494
+ const path = normalizePath(filepath);
495
+ if (useLooseHints) {
496
+ const scope = looseScope(path);
497
+ if (scope !== null) looseHints.set(scope, "present");
498
+ }
499
+ const bytes = typeof data === "string" ? textEncoder2.encode(data) : data;
500
+ await store.put(toKey(path), bytes);
501
+ },
502
+ async unlink(filepath) {
503
+ const k = toKey(normalizePath(filepath));
504
+ if (await store.head(k) === null) throw enoent("unlink", filepath);
505
+ await store.delete(k);
506
+ },
507
+ async readdir(dirpath) {
508
+ const k = toKey(normalizePath(dirpath));
509
+ const isRoot = k === prefix || k === "";
510
+ const listPrefix = isRoot && k === "" ? "" : `${k}/`;
511
+ const { objects, prefixes } = await store.list(listPrefix, {
512
+ delimiter: "/"
513
+ });
514
+ if (objects.length === 0 && prefixes.length === 0) {
515
+ if (!isRoot && await store.head(k) !== null) {
516
+ throw enotdir("scandir", dirpath);
517
+ }
518
+ if (!isRoot) throw enoent("scandir", dirpath);
519
+ }
520
+ const names = objects.map((o) => o.key.slice(listPrefix.length));
521
+ const dirNames = prefixes.map(
522
+ (p) => p.slice(listPrefix.length).replace(/\/$/, "")
523
+ );
524
+ return [...names, ...dirNames].sort();
525
+ },
526
+ async mkdir(_dirpath, _opts) {
527
+ },
528
+ async rmdir(dirpath) {
529
+ const k = toKey(normalizePath(dirpath));
530
+ const { objects, prefixes } = await store.list(`${k}/`, { limit: 1 });
531
+ if (objects.length > 0 || prefixes.length > 0) {
532
+ throw enotempty("rmdir", dirpath);
533
+ }
534
+ },
535
+ stat: (filepath) => stat(filepath, "stat"),
536
+ lstat: (filepath) => stat(filepath, "lstat"),
537
+ async readlink(filepath) {
538
+ throw enoent("readlink", filepath);
539
+ },
540
+ async symlink(_target, filepath) {
541
+ throw eperm("symlink", filepath);
542
+ },
543
+ async chmod(_filepath, _mode) {
544
+ }
545
+ };
546
+ async function detectLooseObjects(gitdir) {
547
+ if (!useLooseHints) return;
548
+ const scope = normalizePath(gitdir);
549
+ if (looseHints.has(scope)) return;
550
+ try {
551
+ const { objects } = await store.list(`${toKey(scope)}/objects/`, {
552
+ limit: 1
553
+ });
554
+ const first = objects[0]?.key;
555
+ const hint = first !== void 0 && LOOSE_OBJECT_RE.test(first) ? "present" : "none";
556
+ looseHints.set(scope, hint);
557
+ onNote?.(`loose objects ${hint} under ${scope}`);
558
+ } catch {
559
+ }
560
+ }
561
+ async function prefetchPacks(gitdir, prefetchOptions) {
562
+ const maxPacks = prefetchOptions?.maxPacks ?? 30;
563
+ const packDir = `${normalizePath(gitdir)}/objects/pack`;
564
+ const entries = await promises.readdir(packDir).catch(() => []);
565
+ if (entries.length > maxPacks * 2) {
566
+ await detectLooseObjects(gitdir);
567
+ return;
568
+ }
569
+ await Promise.all([
570
+ detectLooseObjects(gitdir),
571
+ ...entries.map(
572
+ (name) => promises.readFile(`${packDir}/${name}`).catch(() => void 0)
573
+ )
574
+ ]);
575
+ }
576
+ function invalidate(pathPrefix) {
577
+ const normalized = normalizePath(pathPrefix);
578
+ for (const scope of looseHints.keys()) {
579
+ if (scope.startsWith(normalized)) looseHints.delete(scope);
580
+ }
581
+ const maybe = store;
582
+ maybe.invalidate?.(toKey(normalized));
583
+ }
584
+ return { promises, detectLooseObjects, prefetchPacks, invalidate };
585
+ }
586
+
587
+ // src/refs.ts
588
+ var BAD_REF_COMPONENT = (
589
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: control chars are exactly what git's own ref-name rules reject — this needs to match the same range.
590
+ /(^|[/.])([/.]|$)|^@$|@\{|[\x00-\x20\x7f~^:?*[\\]|\.lock(\/|$)/
591
+ );
592
+ var FULL_SHA_RE = /^[0-9a-f]{40}$/i;
593
+ function isSafeFullRefName(ref) {
594
+ if (!ref.startsWith("refs/heads/") && !ref.startsWith("refs/tags/")) {
595
+ return false;
596
+ }
597
+ return !BAD_REF_COMPONENT.test(ref);
598
+ }
599
+ function isSafeBranchName(name) {
600
+ if (!name || name.startsWith("refs/") || name === "HEAD") return false;
601
+ if (FULL_SHA_RE.test(name)) return false;
602
+ return !BAD_REF_COMPONENT.test(name);
603
+ }
604
+ function isFullSha(value) {
605
+ return FULL_SHA_RE.test(value);
606
+ }
607
+ function isSafeRefName(value) {
608
+ return isSafeBranchName(value) || isFullSha(value);
609
+ }
610
+ function isSafeRepoPath(p) {
611
+ if (p.startsWith("/")) return false;
612
+ if (p.split("/").some((segment) => segment === "..")) return false;
613
+ if (/^\.git(\/|$)/i.test(p)) return false;
614
+ if (p.includes("\0")) return false;
615
+ return true;
616
+ }
617
+ function qualifyBranchRef(ref) {
618
+ if (ref.startsWith("refs/") || ref === "HEAD" || FULL_SHA_RE.test(ref)) {
619
+ return ref;
620
+ }
621
+ return `refs/heads/${ref}`;
622
+ }
623
+
624
+ // src/retry.ts
625
+ var CircuitOpenError = class extends Error {
626
+ code = "EUNAVAILABLE";
627
+ constructor() {
628
+ super("Circuit breaker is open, object store unavailable");
629
+ this.name = "CircuitOpenError";
630
+ }
631
+ };
632
+ var RETRYABLE_NAMES = /* @__PURE__ */ new Set([
633
+ "TimeoutError",
634
+ "RequestTimeout",
635
+ "RequestTimeoutException",
636
+ "SlowDown",
637
+ "ThrottlingException",
638
+ "TooManyRequestsException"
639
+ ]);
640
+ var RETRYABLE_CODES = /* @__PURE__ */ new Set([
641
+ "ECONNRESET",
642
+ "ECONNREFUSED",
643
+ "EPIPE",
644
+ "ETIMEDOUT",
645
+ "ENOTFOUND",
646
+ "EAI_AGAIN",
647
+ "EPROTO"
648
+ ]);
649
+ function defaultIsRetryable(error) {
650
+ if (typeof error !== "object" || error === null) return false;
651
+ const err = error;
652
+ if (err.name !== void 0 && RETRYABLE_NAMES.has(err.name)) return true;
653
+ if (err.code !== void 0 && RETRYABLE_CODES.has(err.code)) return true;
654
+ const status = err.$metadata?.httpStatusCode;
655
+ return status !== void 0 && (status >= 500 || status === 429);
656
+ }
657
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
658
+ function createRetryStore(store, options = {}) {
659
+ const retries = options.retries ?? 3;
660
+ const initialDelayMs = options.initialDelayMs ?? 100;
661
+ const maxDelayMs = options.maxDelayMs ?? 5e3;
662
+ const jitter = options.jitter ?? 0.3;
663
+ const isRetryable = options.isRetryable ?? defaultIsRetryable;
664
+ const breaker = options.breaker === false ? null : {
665
+ threshold: options.breaker?.threshold ?? 5,
666
+ resetMs: options.breaker?.resetMs ?? 3e4
667
+ };
668
+ let failures = 0;
669
+ let lastFailureAt = 0;
670
+ let state = "closed";
671
+ async function guarded(fn) {
672
+ if (breaker === null) return fn();
673
+ if (state === "open") {
674
+ if (Date.now() - lastFailureAt < breaker.resetMs) {
675
+ throw new CircuitOpenError();
676
+ }
677
+ state = "half-open";
678
+ }
679
+ try {
680
+ const result = await fn();
681
+ if (state === "half-open") {
682
+ state = "closed";
683
+ failures = 0;
684
+ }
685
+ return result;
686
+ } catch (error) {
687
+ failures++;
688
+ lastFailureAt = Date.now();
689
+ if (failures >= breaker.threshold) state = "open";
690
+ throw error;
691
+ }
692
+ }
693
+ async function run(op, key, fn) {
694
+ let lastError;
695
+ for (let attempt = 0; attempt <= retries; attempt++) {
696
+ try {
697
+ return await guarded(fn);
698
+ } catch (error) {
699
+ lastError = error;
700
+ if (error instanceof CircuitOpenError) throw error;
701
+ if (!isRetryable(error) || attempt === retries) throw error;
702
+ const base = Math.min(initialDelayMs * 2 ** attempt, maxDelayMs);
703
+ const delayMs = Math.round(base + Math.random() * base * jitter);
704
+ options.onRetry?.({ key, op, attempt: attempt + 1, delayMs });
705
+ await sleep(delayMs);
706
+ }
707
+ }
708
+ throw lastError;
709
+ }
710
+ return {
711
+ get: (key) => run("get", key, () => store.get(key)),
712
+ put: (key, data) => run("put", key, () => store.put(key, data)),
713
+ delete: (key) => run("delete", key, () => store.delete(key)),
714
+ head: (key) => run("head", key, () => store.head(key)),
715
+ list: (prefix, listOptions) => run("list", prefix, () => store.list(prefix, listOptions))
716
+ };
717
+ }
718
+
719
+ // src/stores/memory.ts
720
+ var MemoryObjectStore = class {
721
+ objects = /* @__PURE__ */ new Map();
722
+ async get(key) {
723
+ const data = this.objects.get(key);
724
+ return data ? data.slice() : null;
725
+ }
726
+ async put(key, data) {
727
+ this.objects.set(key, data.slice());
728
+ }
729
+ async delete(key) {
730
+ this.objects.delete(key);
731
+ }
732
+ async head(key) {
733
+ const data = this.objects.get(key);
734
+ return data ? { size: data.byteLength } : null;
735
+ }
736
+ async list(prefix, options) {
737
+ const delimiter = options?.delimiter;
738
+ const limit = options?.limit ?? Number.POSITIVE_INFINITY;
739
+ const objects = [];
740
+ const prefixes = /* @__PURE__ */ new Set();
741
+ for (const [key, data] of this.objects) {
742
+ if (!key.startsWith(prefix)) continue;
743
+ const rest = key.slice(prefix.length);
744
+ if (delimiter !== void 0) {
745
+ const idx = rest.indexOf(delimiter);
746
+ if (idx !== -1) {
747
+ prefixes.add(prefix + rest.slice(0, idx + delimiter.length));
748
+ } else {
749
+ objects.push({ key, size: data.byteLength });
750
+ }
751
+ } else {
752
+ objects.push({ key, size: data.byteLength });
753
+ }
754
+ if (objects.length + prefixes.size >= limit) break;
755
+ }
756
+ return { objects, prefixes: [...prefixes] };
757
+ }
758
+ /** Number of stored objects (test convenience, not part of ObjectStore). */
759
+ get size() {
760
+ return this.objects.size;
761
+ }
762
+ };
763
+ // Annotate the CommonJS export names for ESM import in node:
764
+ 0 && (module.exports = {
765
+ CircuitOpenError,
766
+ FsError,
767
+ GitAuthenticationError,
768
+ GitAuthorizationError,
769
+ GitConflictError,
770
+ GitError,
771
+ GitInvalidRequestError,
772
+ GitObjectNotFoundError,
773
+ GitPathNotFoundError,
774
+ GitProtocolError,
775
+ GitRateLimitError,
776
+ GitRefNotFoundError,
777
+ GitRepositoryNotFoundError,
778
+ MemoryObjectStore,
779
+ concat,
780
+ createCachedStore,
781
+ createGitFs,
782
+ createRetryStore,
783
+ decodeAscii,
784
+ decodeUtf8,
785
+ deflate,
786
+ encodeUtf8,
787
+ formatErrorResponse,
788
+ fromHex,
789
+ hasNullByte,
790
+ isFullSha,
791
+ isSafeBranchName,
792
+ isSafeFullRefName,
793
+ isSafeRefName,
794
+ isSafeRepoPath,
795
+ qualifyBranchRef,
796
+ readBlobContent,
797
+ sha1,
798
+ toBase64,
799
+ toHex
800
+ });
801
+ //# sourceMappingURL=index.cjs.map