git-fs-s3 0.13.0 → 0.15.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/README.md CHANGED
@@ -34,6 +34,8 @@ Use it to build git-backed CMSes, notes apps with real version history, lightwei
34
34
  npm install git-fs-s3 isomorphic-git # ESM only, Node.js 22+
35
35
  # for the S3 store (any S3-compatible provider):
36
36
  npm install @aws-sdk/client-s3
37
+ # or, for the fetch-based store (no AWS SDK):
38
+ npm install aws4fetch
37
39
  ```
38
40
 
39
41
  ## Quick start
@@ -175,6 +177,14 @@ range reads of the pack.
175
177
 
176
178
  `ObjectStore` over `@aws-sdk/client-s3` (optional peer dependency, loaded only via the `/s3` subpath). Implements the optional `getRange(key, start, end)` (S3's `Range` header, inclusive at both ends), which `getObjectSize` uses. Reuse one `S3Client` across stores so HTTP connections are pooled — a busy repo page can mean hundreds of object reads. `contentType(key)` optionally derives a `Content-Type` header for uploads.
177
179
 
180
+ ### `createFetchS3Store({ endpoint, bucket, accessKeyId, secretAccessKey, region?, contentType?, fetch? })`
181
+
182
+ From `git-fs-s3/fetch-s3`. An S3/R2/MinIO store over plain `fetch`, signed with [`aws4fetch`](https://github.com/mhart/aws4fetch) (optional peer dependency: `npm install aws4fetch`) — no AWS SDK, so it runs on Node, Vercel Functions and Workers alike. Implements `getRange`, ListObjectsV2 pagination and delimiters, and a server-side `copy(from, to)` (pass it to `movePrefix`). A 404 maps to `null`; any other failure throws `S3HttpError` with `$metadata.httpStatusCode` (429 is named `SlowDown`), which `isRetryableError` classifies. `region` defaults to `"auto"` (R2); timeouts are `bodyTimeoutMs` (30 s) and `metadataTimeoutMs` (10 s) per attempt. Retries are not built in — wrap it in `createRetryStore`.
183
+
184
+ ### `FileObjectStore(root)`
185
+
186
+ From `git-fs-s3/node` (Node only, so edge bundles stay clean). A disk-backed store for local development: reads are served from memory, every write is mirrored to a file under `root`, and the tree is loaded back on first use, so repositories survive restarts. Keys that would escape `root` are rejected. Not for production.
187
+
178
188
  ### `MemoryObjectStore()`
179
189
 
180
190
  In-memory store; also the reference implementation of list/delimiter semantics.
@@ -198,10 +208,14 @@ Wraps any store with an in-process LRU read cache (git objects are content-addre
198
208
  Wraps any store with exponential-backoff retries and a per-instance circuit breaker. Place it directly above the network store, under the cache.
199
209
 
200
210
  - `retries` (3), `initialDelayMs` (100), `maxDelayMs` (5000), `jitter` (0.3)
201
- - `isRetryable(error)` — default retries network faults, throttling, and HTTP 5xx/429
211
+ - `isRetryable(error)` — default is the exported `isRetryableError`: network faults (including `fetch`'s bare `TypeError`), timeouts, throttling, and HTTP 5xx/429. Compose it in your own classifier.
202
212
  - `breaker` — `{ threshold: 5, resetMs: 30_000 }` by default, or `false` to disable. While open, calls fail fast with `CircuitOpenError` (`code: "EUNAVAILABLE"`).
203
213
  - `onRetry(info)` — logging hook.
204
214
 
215
+ ### Prefix helpers and error classification
216
+
217
+ `deletePrefix(store, prefix)` lists a prefix and deletes it in batches. `movePrefix(store, from, to, { copy? })` copies everything, then deletes the old keys only once every copy succeeded (a failed copy leaves the source untouched); pass a store's server-side `copy` to avoid pulling bytes through the process. Both return the object count, and neither touches caches above the store — call `invalidate(prefix)` yourself and hold your own lock. `isNotFoundError(error)` is true for the missing repository/ref/path/object errors, the ones to map to a 404.
218
+
205
219
  ## Git smart-HTTP
206
220
 
207
221
  `git-fs-s3/http` implements the git smart-HTTP protocol (`info/refs`, `upload-pack`, `receive-pack`) as plain functions over a `Repo` — no framework assumptions, Fetch-API-shaped inputs/outputs. Extracted from the same production git-hosting service's HTTP layer, so it's what actually serves `git clone`/`git push` over HTTPS: pkt-line framing, side-band-64k packfile chunking (required once a client like isomorphic-git negotiates it — it always demuxes the response), CAS-checked ref updates, and pack consolidation.
@@ -258,6 +272,9 @@ Every client-supplied ref name in a push is validated with `isSafeFullRefName` (
258
272
  `git-fs-s3/ops` is a higher-level layer for building a git-hosting UI on top of `createGitFs` — the operations a repo browser / PR flow actually needs, each taking a `Repo` (`{ fs, gitdir, cache? }`) plus an optional `OpsHooks` (`{ resultCache?, step?, onNote?, prefetchPacks? }`) for the same timing/caching seam `GitFs` and `/http` use:
259
273
 
260
274
  - **Branches** — `listBranches`, `createBranchFrom`, `deleteBranchByName`.
275
+ - **Tags** — `listTags` (annotated tags peeled; `peelToCommit` for one oid).
276
+ Every read that takes a `ref` accepts a tag name too; a branch wins over a
277
+ same-named tag.
261
278
  - **Commits** — `commitFilesToBare` (write one or more files as a single commit against a branch, creating it if new), `deleteFileFromBare`, `authorNow`.
262
279
  - **Trees** — `upsertTree`/`deleteFromTree` (overlay blobs onto a tree, return the new root oid), `listTreeEntries`, `findTreeEntry`.
263
280
  - **History** — `getCommitLog`/`getCommitHistory` (memoized commit-chain walks; pages are slices of the deepest walk), `getFileContent`/`getTreeFromRef`, `getFileHistory` (per-file commit history), `getLastCommitsForTree` (the "last commit" column on a directory listing), `summarizeCommit`.
@@ -0,0 +1,53 @@
1
+ // src/stores/memory.ts
2
+ var MemoryObjectStore = class {
3
+ objects = /* @__PURE__ */ new Map();
4
+ async get(key) {
5
+ const data = this.objects.get(key);
6
+ return data ? data.slice() : null;
7
+ }
8
+ async getRange(key, start, end) {
9
+ const data = this.objects.get(key);
10
+ return data ? data.slice(start, end + 1) : null;
11
+ }
12
+ async put(key, data) {
13
+ this.objects.set(key, data.slice());
14
+ }
15
+ async delete(key) {
16
+ this.objects.delete(key);
17
+ }
18
+ async head(key) {
19
+ const data = this.objects.get(key);
20
+ return data ? { size: data.byteLength } : null;
21
+ }
22
+ async list(prefix, options) {
23
+ const delimiter = options?.delimiter;
24
+ const limit = options?.limit ?? Number.POSITIVE_INFINITY;
25
+ const objects = [];
26
+ const prefixes = /* @__PURE__ */ new Set();
27
+ for (const [key, data] of this.objects) {
28
+ if (!key.startsWith(prefix)) continue;
29
+ const rest = key.slice(prefix.length);
30
+ if (delimiter !== void 0) {
31
+ const idx = rest.indexOf(delimiter);
32
+ if (idx !== -1) {
33
+ prefixes.add(prefix + rest.slice(0, idx + delimiter.length));
34
+ } else {
35
+ objects.push({ key, size: data.byteLength });
36
+ }
37
+ } else {
38
+ objects.push({ key, size: data.byteLength });
39
+ }
40
+ if (objects.length + prefixes.size >= limit) break;
41
+ }
42
+ return { objects, prefixes: [...prefixes] };
43
+ }
44
+ /** Number of stored objects (test convenience, not part of ObjectStore). */
45
+ get size() {
46
+ return this.objects.size;
47
+ }
48
+ };
49
+
50
+ export {
51
+ MemoryObjectStore
52
+ };
53
+ //# sourceMappingURL=chunk-HLYCSNJ2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/stores/memory.ts"],"sourcesContent":["import type {\n\tListOptions,\n\tListResult,\n\tObjectStat,\n\tObjectStore,\n} from \"../types.js\";\n\n/**\n * In-memory {@link ObjectStore}. Useful for tests, examples, and ephemeral\n * repositories; also the reference implementation for the list/delimiter\n * semantics other stores must match.\n */\nexport class MemoryObjectStore implements ObjectStore {\n\tprivate readonly objects = new Map<string, Uint8Array>();\n\n\tasync get(key: string): Promise<Uint8Array | null> {\n\t\tconst data = this.objects.get(key);\n\t\treturn data ? data.slice() : null;\n\t}\n\n\tasync getRange(\n\t\tkey: string,\n\t\tstart: number,\n\t\tend: number,\n\t): Promise<Uint8Array | null> {\n\t\tconst data = this.objects.get(key);\n\t\treturn data ? data.slice(start, end + 1) : null;\n\t}\n\n\tasync put(key: string, data: Uint8Array): Promise<void> {\n\t\tthis.objects.set(key, data.slice());\n\t}\n\n\tasync delete(key: string): Promise<void> {\n\t\tthis.objects.delete(key);\n\t}\n\n\tasync head(key: string): Promise<ObjectStat | null> {\n\t\tconst data = this.objects.get(key);\n\t\treturn data ? { size: data.byteLength } : null;\n\t}\n\n\tasync list(prefix: string, options?: ListOptions): Promise<ListResult> {\n\t\tconst delimiter = options?.delimiter;\n\t\tconst limit = options?.limit ?? Number.POSITIVE_INFINITY;\n\t\tconst objects: ListResult[\"objects\"] = [];\n\t\tconst prefixes = new Set<string>();\n\n\t\tfor (const [key, data] of this.objects) {\n\t\t\tif (!key.startsWith(prefix)) continue;\n\t\t\tconst rest = key.slice(prefix.length);\n\t\t\tif (delimiter !== undefined) {\n\t\t\t\tconst idx = rest.indexOf(delimiter);\n\t\t\t\tif (idx !== -1) {\n\t\t\t\t\tprefixes.add(prefix + rest.slice(0, idx + delimiter.length));\n\t\t\t\t} else {\n\t\t\t\t\tobjects.push({ key, size: data.byteLength });\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tobjects.push({ key, size: data.byteLength });\n\t\t\t}\n\t\t\tif (objects.length + prefixes.size >= limit) break;\n\t\t}\n\n\t\treturn { objects, prefixes: [...prefixes] };\n\t}\n\n\t/** Number of stored objects (test convenience, not part of ObjectStore). */\n\tget size(): number {\n\t\treturn this.objects.size;\n\t}\n}\n"],"mappings":";AAYO,IAAM,oBAAN,MAA+C;AAAA,EACpC,UAAU,oBAAI,IAAwB;AAAA,EAEvD,MAAM,IAAI,KAAyC;AAClD,UAAM,OAAO,KAAK,QAAQ,IAAI,GAAG;AACjC,WAAO,OAAO,KAAK,MAAM,IAAI;AAAA,EAC9B;AAAA,EAEA,MAAM,SACL,KACA,OACA,KAC6B;AAC7B,UAAM,OAAO,KAAK,QAAQ,IAAI,GAAG;AACjC,WAAO,OAAO,KAAK,MAAM,OAAO,MAAM,CAAC,IAAI;AAAA,EAC5C;AAAA,EAEA,MAAM,IAAI,KAAa,MAAiC;AACvD,SAAK,QAAQ,IAAI,KAAK,KAAK,MAAM,CAAC;AAAA,EACnC;AAAA,EAEA,MAAM,OAAO,KAA4B;AACxC,SAAK,QAAQ,OAAO,GAAG;AAAA,EACxB;AAAA,EAEA,MAAM,KAAK,KAAyC;AACnD,UAAM,OAAO,KAAK,QAAQ,IAAI,GAAG;AACjC,WAAO,OAAO,EAAE,MAAM,KAAK,WAAW,IAAI;AAAA,EAC3C;AAAA,EAEA,MAAM,KAAK,QAAgB,SAA4C;AACtE,UAAM,YAAY,SAAS;AAC3B,UAAM,QAAQ,SAAS,SAAS,OAAO;AACvC,UAAM,UAAiC,CAAC;AACxC,UAAM,WAAW,oBAAI,IAAY;AAEjC,eAAW,CAAC,KAAK,IAAI,KAAK,KAAK,SAAS;AACvC,UAAI,CAAC,IAAI,WAAW,MAAM,EAAG;AAC7B,YAAM,OAAO,IAAI,MAAM,OAAO,MAAM;AACpC,UAAI,cAAc,QAAW;AAC5B,cAAM,MAAM,KAAK,QAAQ,SAAS;AAClC,YAAI,QAAQ,IAAI;AACf,mBAAS,IAAI,SAAS,KAAK,MAAM,GAAG,MAAM,UAAU,MAAM,CAAC;AAAA,QAC5D,OAAO;AACN,kBAAQ,KAAK,EAAE,KAAK,MAAM,KAAK,WAAW,CAAC;AAAA,QAC5C;AAAA,MACD,OAAO;AACN,gBAAQ,KAAK,EAAE,KAAK,MAAM,KAAK,WAAW,CAAC;AAAA,MAC5C;AACA,UAAI,QAAQ,SAAS,SAAS,QAAQ,MAAO;AAAA,IAC9C;AAEA,WAAO,EAAE,SAAS,UAAU,CAAC,GAAG,QAAQ,EAAE;AAAA,EAC3C;AAAA;AAAA,EAGA,IAAI,OAAe;AAClB,WAAO,KAAK,QAAQ;AAAA,EACrB;AACD;","names":[]}