just-git 1.3.1 → 1.3.3

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.
@@ -1,62 +1,139 @@
1
- import { a3 as RawObject, a5 as Ref, f as GitRepo, X as Rejection, N as NetworkPolicy } from '../hooks-BtbyLyYE.js';
1
+ import { _ as RawObject, $ as Ref, g as GitRepo, a3 as Rejection, N as NetworkPolicy } from '../hooks-t3k-y0u_.js';
2
2
 
3
+ /**
4
+ * A value that may be synchronous or asynchronous.
5
+ *
6
+ * Storage methods use this return type so that sync backends (e.g. SQLite)
7
+ * can avoid unnecessary `async`/`await` overhead while async backends
8
+ * (e.g. PostgreSQL) return promises naturally.
9
+ */
3
10
  type MaybeAsync<T> = T | Promise<T>;
4
- /** Options for {@link StorageAdapter.createRepo}. */
11
+ /** Options for creating a new repo via `GitServer.createRepo`. */
5
12
  interface CreateRepoOptions {
6
13
  /** Name of the default branch (default: `"main"`). Used for HEAD initialization. */
7
14
  defaultBranch?: string;
8
15
  }
9
- /** Unresolved ref entry as stored by the storage backend. */
16
+ /**
17
+ * A ref entry as stored by the storage backend, without symref resolution.
18
+ *
19
+ * Symbolic refs (like HEAD → refs/heads/main) are returned as-is — the
20
+ * adapter layer handles resolution. Storage backends should store and
21
+ * return the exact {@link Ref} value that was written via `putRef`.
22
+ */
10
23
  interface RawRefEntry {
24
+ /** Full ref name, e.g. `"HEAD"` or `"refs/heads/main"`. */
11
25
  name: string;
26
+ /** The ref value — either a direct hash or a symbolic pointer. */
12
27
  ref: Ref;
13
28
  }
14
29
  /**
15
- * Ref operations available inside an {@link Storage.atomicRefUpdate} callback.
16
- * The storage backend provides isolation; the shared adapter runs git-aware CAS logic inside.
30
+ * Ref operations available inside a {@link Storage.atomicRefUpdate} callback.
31
+ *
32
+ * The storage backend wraps the callback in a transaction (or lock), and the
33
+ * adapter layer uses these operations to implement compare-and-swap with
34
+ * symref resolution. Implementations should route these to the same
35
+ * underlying store as the top-level ref methods, but within the
36
+ * transaction/lock scope.
17
37
  */
18
38
  interface RefOps {
39
+ /** Read a single ref within the transaction. */
19
40
  getRef(name: string): MaybeAsync<Ref | null>;
41
+ /** Write a ref within the transaction. */
20
42
  putRef(name: string, ref: Ref): MaybeAsync<void>;
43
+ /** Delete a ref within the transaction. */
21
44
  removeRef(name: string): MaybeAsync<void>;
22
45
  }
23
46
  /**
24
- * Storage backend interface. Implementations provide raw key-value
25
- * CRUD for objects and refs, plus an atomic ref operation primitive.
47
+ * Storage backend interface for multi-repo git object and ref persistence.
48
+ *
49
+ * Implementations provide raw key-value CRUD for objects and refs, plus an
50
+ * atomic ref operation primitive. All git-aware logic — object hashing,
51
+ * pack ingestion, symref resolution, compare-and-swap semantics — lives
52
+ * in the internal adapter and does not need to be implemented by backends.
26
53
  *
27
- * All git-aware logic (object hashing, pack ingestion, symref resolution,
28
- * CAS semantics) lives in the shared adapter built by {@link createStorageAdapter}.
54
+ * All methods use {@link MaybeAsync} return types: sync backends (SQLite)
55
+ * can return values directly, async backends (PostgreSQL) return promises.
29
56
  *
30
- * All methods may return synchronously or asynchronously.
57
+ * See `MemoryStorage` for a minimal reference implementation.
31
58
  */
32
59
  interface Storage {
60
+ /** Check whether a repo with this ID has been created. */
33
61
  hasRepo(repoId: string): MaybeAsync<boolean>;
62
+ /** Register a new repo ID. Does not need to create any initial data. */
34
63
  insertRepo(repoId: string): MaybeAsync<void>;
35
64
  /** Delete the repo record and all associated objects and refs. */
36
65
  deleteRepo(repoId: string): MaybeAsync<void>;
66
+ /**
67
+ * Read a raw git object by hash.
68
+ * Returns `null` when the object does not exist.
69
+ */
37
70
  getObject(repoId: string, hash: string): MaybeAsync<RawObject | null>;
71
+ /** Store a single git object. `content` is the uncompressed object body (no git header). */
38
72
  putObject(repoId: string, hash: string, type: string, content: Uint8Array): MaybeAsync<void>;
39
- /** Bulk insert. Implementations should use their optimal batch strategy. */
73
+ /**
74
+ * Bulk-insert objects. Called during pack ingestion (push, fetch).
75
+ * Implementations should use their optimal batch strategy (e.g. a
76
+ * single transaction for SQL backends).
77
+ */
40
78
  putObjects(repoId: string, objects: ReadonlyArray<{
41
79
  hash: string;
42
80
  type: string;
43
81
  content: Uint8Array;
44
82
  }>): MaybeAsync<void>;
83
+ /** Check whether an object exists without reading its content. */
45
84
  hasObject(repoId: string, hash: string): MaybeAsync<boolean>;
85
+ /**
86
+ * Find all object hashes starting with `prefix` (for short-hash resolution).
87
+ * `prefix` is at least 4 hex characters.
88
+ */
46
89
  findObjectsByPrefix(repoId: string, prefix: string): MaybeAsync<string[]>;
90
+ /** Return all object hashes stored for a repo. */
91
+ listObjectHashes(repoId: string): MaybeAsync<string[]>;
92
+ /**
93
+ * Delete specific objects by hash.
94
+ * Returns the number of objects actually deleted.
95
+ */
96
+ deleteObjects(repoId: string, hashes: ReadonlyArray<string>): MaybeAsync<number>;
97
+ /**
98
+ * Read a single ref. Returns the stored {@link Ref} value (direct hash
99
+ * or symbolic pointer) without following symrefs — the adapter handles
100
+ * resolution.
101
+ */
47
102
  getRef(repoId: string, name: string): MaybeAsync<Ref | null>;
103
+ /** Write a ref (direct or symbolic). */
48
104
  putRef(repoId: string, name: string, ref: Ref): MaybeAsync<void>;
105
+ /** Delete a ref. */
49
106
  removeRef(repoId: string, name: string): MaybeAsync<void>;
50
- /** Return all refs under a prefix, unresolved (symrefs not followed). */
107
+ /**
108
+ * List all refs, optionally filtered by a prefix (e.g. `"refs/heads/"`).
109
+ * Returns unresolved entries — symrefs are not followed.
110
+ */
51
111
  listRefs(repoId: string, prefix?: string): MaybeAsync<RawRefEntry[]>;
52
112
  /**
53
- * Run ref operations atomically. The storage backend wraps the callback in
54
- * whatever isolation mechanism it supports (transaction, lock, etc.).
55
- * The shared adapter uses this for compare-and-swap with symref resolution.
113
+ * Run ref operations atomically.
114
+ *
115
+ * The storage backend wraps the callback in whatever isolation
116
+ * mechanism it supports (SQL transaction, in-memory lock, etc.).
117
+ * The adapter uses this for compare-and-swap with symref resolution.
56
118
  */
57
119
  atomicRefUpdate<T>(repoId: string, fn: (ops: RefOps) => MaybeAsync<T>): MaybeAsync<T>;
58
120
  }
59
121
 
122
+ /** Options for {@link gcRepo}. */
123
+ interface GcOptions {
124
+ /** Report what would be deleted without actually deleting. Default: false. */
125
+ dryRun?: boolean;
126
+ }
127
+ /** Result of a {@link gcRepo} call. */
128
+ interface GcResult {
129
+ /** Number of unreachable objects deleted (or that would be deleted in dry-run mode). */
130
+ deleted: number;
131
+ /** Number of reachable objects retained. */
132
+ retained: number;
133
+ /** True if GC was aborted because refs changed during the walk (concurrent modification detected). */
134
+ aborted?: boolean;
135
+ }
136
+
60
137
  /**
61
138
  * Default session type, produced by the built-in session builder when
62
139
  * no custom `session` config is provided to `createServer`.
@@ -75,10 +152,14 @@ interface Session {
75
152
  * User-provided session builder that transforms raw transport input
76
153
  * into a typed session object threaded through all hooks.
77
154
  *
78
- * TypeScript infers `S` from the return types of the builder functions,
79
- * so hooks receive the custom type without explicit generic annotations.
155
+ * Both properties are optional provide only the transports you use.
156
+ * TypeScript infers `S` from whichever builders are present.
157
+ *
158
+ * If a transport is used at runtime but its builder is missing, the
159
+ * server returns an error (HTTP 501 / SSH exit 128).
80
160
  *
81
161
  * ```ts
162
+ * // HTTP-only — no need to provide ssh
82
163
  * const server = createServer({
83
164
  * storage: new BunSqliteStorage(db),
84
165
  * session: {
@@ -86,10 +167,6 @@ interface Session {
86
167
  * userId: parseJwt(req).sub,
87
168
  * roles: parseJwt(req).roles,
88
169
  * }),
89
- * ssh: (info) => ({
90
- * userId: info.username ?? "anonymous",
91
- * roles: (info.metadata?.roles as string[]) ?? [],
92
- * }),
93
170
  * },
94
171
  * hooks: {
95
172
  * preReceive: ({ session }) => {
@@ -108,10 +185,17 @@ interface SessionBuilder<S> {
108
185
  * Return `S` to proceed, or return a `Response` to short-circuit
109
186
  * the request (e.g. 401 with `WWW-Authenticate` header). This is
110
187
  * the primary mechanism for HTTP auth — no separate middleware needed.
188
+ *
189
+ * When omitted, HTTP requests receive a 501 response.
190
+ */
191
+ http?: (request: Request) => S | Response | Promise<S | Response>;
192
+ /**
193
+ * Build a session from SSH session info.
194
+ *
195
+ * When omitted, SSH sessions receive exit code 128 with a
196
+ * diagnostic message.
111
197
  */
112
- http: (request: Request) => S | Response | Promise<S | Response>;
113
- /** Build a session from SSH session info. */
114
- ssh: (info: SshSessionInfo) => S | Promise<S>;
198
+ ssh?: (info: SshSessionInfo) => S | Promise<S>;
115
199
  }
116
200
  /** Information about the SSH session passed to `handleSession`. */
117
201
  interface SshSessionInfo {
@@ -209,11 +293,12 @@ interface GitServerConfig<S = Session> {
209
293
  */
210
294
  policy?: ServerPolicy;
211
295
  /**
212
- * Custom session builder. When provided, the server calls
213
- * `session.http(request)` for HTTP and `session.ssh(info)` for SSH
214
- * to produce the session object threaded through all hooks.
296
+ * Custom session builder. Provide `http`, `ssh`, or both —
297
+ * the server calls whichever is present for that transport.
298
+ * If a transport is used but its builder is missing, the server
299
+ * returns an error (HTTP 501 / SSH exit 128).
215
300
  *
216
- * When omitted, the built-in `Session` type is used.
301
+ * When omitted entirely, the built-in `Session` type is used.
217
302
  */
218
303
  session?: SessionBuilder<S>;
219
304
  /** Base path prefix to strip from HTTP URLs (e.g. "/git"). */
@@ -249,7 +334,29 @@ interface GitServerConfig<S = Session> {
249
334
  */
250
335
  onError?: false | ((err: unknown, session?: S) => void);
251
336
  }
252
- interface GitServer {
337
+ /**
338
+ * A ref update request for {@link GitServer.updateRefs}.
339
+ *
340
+ * In-process equivalent of a push command — updates a ref with CAS
341
+ * protection and hook enforcement, without transport overhead.
342
+ */
343
+ interface RefUpdateRequest {
344
+ /** Full ref name (e.g. `"refs/heads/main"`, `"refs/tags/v1.0"`). */
345
+ ref: string;
346
+ /** New commit hash, or `null` to delete the ref. */
347
+ newHash: string | null;
348
+ /**
349
+ * Expected current hash for compare-and-swap.
350
+ *
351
+ * - `undefined` (default) — the server reads the current ref state
352
+ * automatically. Still CAS-protected against concurrent updates.
353
+ * - `null` — assert the ref does not exist (create-only).
354
+ * - `string` — explicit CAS: the update fails if the ref's current
355
+ * hash doesn't match.
356
+ */
357
+ oldHash?: string | null;
358
+ }
359
+ interface GitServer<S = Session> {
253
360
  /** Standard fetch-API handler for HTTP: (Request) => Response */
254
361
  fetch(request: Request): Promise<Response>;
255
362
  /**
@@ -285,6 +392,32 @@ interface GitServer {
285
392
  * ```
286
393
  */
287
394
  handleSession(command: string, channel: SshChannel, session?: SshSessionInfo): Promise<number>;
395
+ /**
396
+ * Update refs in-process with hook enforcement and CAS protection.
397
+ *
398
+ * Equivalent to a push, but without transport overhead — no pack
399
+ * negotiation, no object transfer. Objects must already exist in
400
+ * the repo's object store (e.g. via `createCommit` from `just-git/repo`).
401
+ *
402
+ * Runs the full hook lifecycle: `preReceive` → per-ref `update` →
403
+ * CAS application → `postReceive`. Returns per-ref results.
404
+ *
405
+ * ```ts
406
+ * import { createCommit, writeBlob, writeTree } from "just-git/repo";
407
+ *
408
+ * const repo = await server.repo("my-repo");
409
+ * const blob = await writeBlob(repo, "content");
410
+ * const tree = await writeTree(repo, [{ name: "file.txt", hash: blob }]);
411
+ * const commit = await createCommit(repo, { tree, parents: [head], ... });
412
+ *
413
+ * await server.updateRefs("my-repo", [
414
+ * { ref: "refs/heads/auto-fix", newHash: commit },
415
+ * ]);
416
+ * ```
417
+ *
418
+ * @throws If the repo does not exist or the server is shutting down.
419
+ */
420
+ updateRefs(repoId: string, refs: RefUpdateRequest[], session?: S): Promise<RefUpdateResult>;
288
421
  /**
289
422
  * Node.js `http.createServer` compatible handler.
290
423
  *
@@ -300,6 +433,19 @@ interface GitServer {
300
433
  repo(id: string): Promise<GitRepo | null>;
301
434
  /** Delete a repo and all its data. */
302
435
  deleteRepo(id: string): Promise<void>;
436
+ /**
437
+ * Remove unreachable objects from a repo's storage.
438
+ *
439
+ * Walks all objects reachable from the repo's refs, compares against
440
+ * the full set of stored objects, and deletes the difference.
441
+ *
442
+ * If refs change during the walk (e.g. a concurrent push completes),
443
+ * GC aborts and returns `{ aborted: true }` to prevent deleting
444
+ * newly-reachable objects. Callers can retry.
445
+ *
446
+ * @throws If the repo does not exist or the server is shutting down.
447
+ */
448
+ gc(repoId: string, options?: GcOptions): Promise<GcResult>;
303
449
  /**
304
450
  * Graceful shutdown. After calling, new HTTP requests receive 503
305
451
  * and new SSH sessions get exit 128. Resolves when all in-flight
@@ -415,6 +561,17 @@ interface RefAdvertisement {
415
561
  name: string;
416
562
  hash: string;
417
563
  }
564
+ /** Per-ref result from a push or {@link GitServer.updateRefs} call. */
565
+ interface RefResult {
566
+ ref: string;
567
+ ok: boolean;
568
+ error?: string;
569
+ }
570
+ /** Result of a push or {@link GitServer.updateRefs} call. */
571
+ interface RefUpdateResult {
572
+ refResults: RefResult[];
573
+ applied: RefUpdate[];
574
+ }
418
575
 
419
576
  /**
420
577
  * Unified Git server: Smart HTTP + SSH session handling.
@@ -453,7 +610,7 @@ interface RefAdvertisement {
453
610
  * server.handleSession(command, channel, { username });
454
611
  * ```
455
612
  */
456
- declare function createServer<S = Session>(config: GitServerConfig<S>): GitServer;
613
+ declare function createServer<S = Session>(config: GitServerConfig<S>): GitServer<S>;
457
614
  /**
458
615
  * Compose multiple hook sets into a single `ServerHooks` object.
459
616
  *
@@ -619,15 +776,6 @@ interface ApplyReceivePackOptions<S = unknown> {
619
776
  /** Session info threaded through to hooks. */
620
777
  session?: S;
621
778
  }
622
- interface RefResult {
623
- ref: string;
624
- ok: boolean;
625
- error?: string;
626
- }
627
- interface ApplyReceivePackResult {
628
- refResults: RefResult[];
629
- applied: RefUpdate[];
630
- }
631
779
  /**
632
780
  * Run the full receive-pack lifecycle: preReceive hook, per-ref update
633
781
  * hook with ref format validation, CAS ref application, and postReceive
@@ -637,28 +785,14 @@ interface ApplyReceivePackResult {
637
785
  * Does NOT handle unpack failures — the caller should check
638
786
  * `ingestResult.unpackOk` and short-circuit before calling this.
639
787
  */
640
- declare function applyReceivePack<S = unknown>(options: ApplyReceivePackOptions<S>): Promise<ApplyReceivePackResult>;
641
-
642
- /**
643
- * SSH protocol helpers for the unified Git server.
644
- *
645
- * Provides the pkt-line stream reader, command parser, and
646
- * receive-pack streaming logic used by `createServer`'s
647
- * `handleSession` method. Not a public entry point — see
648
- * `createServer` for usage.
649
- */
650
-
651
- type GitSshService = "git-upload-pack" | "git-receive-pack";
788
+ declare function applyReceivePack<S = unknown>(options: ApplyReceivePackOptions<S>): Promise<RefUpdateResult>;
652
789
  /**
653
- * Parse a git SSH exec command into service and repo path.
790
+ * Resolve `RefUpdateRequest[]` into fully computed `RefUpdate[]`.
654
791
  *
655
- * Handles `git-upload-pack '/path'`, `git upload-pack '/path'`,
656
- * and unquoted variants.
792
+ * Reads current ref state when `oldHash` is not provided, and computes
793
+ * `isFF`/`isCreate`/`isDelete` for each entry.
657
794
  */
658
- declare function parseGitSshCommand(command: string): {
659
- service: GitSshService;
660
- repoPath: string;
661
- } | null;
795
+ declare function resolveRefUpdates(repo: GitRepo, requests: RefUpdateRequest[]): Promise<RefUpdate[]>;
662
796
 
663
797
  /**
664
798
  * In-memory storage backend with multi-repo support.
@@ -689,6 +823,8 @@ declare class MemoryStorage implements Storage {
689
823
  }>): void;
690
824
  hasObject(repoId: string, hash: string): boolean;
691
825
  findObjectsByPrefix(repoId: string, prefix: string): string[];
826
+ listObjectHashes(repoId: string): string[];
827
+ deleteObjects(repoId: string, hashes: ReadonlyArray<string>): number;
692
828
  getRef(repoId: string, name: string): Ref | null;
693
829
  putRef(repoId: string, name: string, ref: Ref): void;
694
830
  removeRef(repoId: string, name: string): void;
@@ -723,6 +859,7 @@ declare class BunSqliteStorage implements Storage {
723
859
  private db;
724
860
  private stmts;
725
861
  private batchInsertTx;
862
+ private batchDeleteTx;
726
863
  constructor(db: BunSqliteDatabase);
727
864
  hasRepo(repoId: string): boolean;
728
865
  insertRepo(repoId: string): void;
@@ -736,6 +873,8 @@ declare class BunSqliteStorage implements Storage {
736
873
  }>): void;
737
874
  hasObject(repoId: string, hash: string): boolean;
738
875
  findObjectsByPrefix(repoId: string, prefix: string): string[];
876
+ listObjectHashes(repoId: string): string[];
877
+ deleteObjects(repoId: string, hashes: ReadonlyArray<string>): number;
739
878
  getRef(repoId: string, name: string): Ref | null;
740
879
  putRef(repoId: string, name: string, ref: Ref): void;
741
880
  removeRef(repoId: string, name: string): void;
@@ -767,6 +906,7 @@ declare class BetterSqlite3Storage implements Storage {
767
906
  private db;
768
907
  private stmts;
769
908
  private batchInsertTx;
909
+ private batchDeleteTx;
770
910
  constructor(db: BetterSqlite3Database);
771
911
  hasRepo(repoId: string): boolean;
772
912
  insertRepo(repoId: string): void;
@@ -780,6 +920,8 @@ declare class BetterSqlite3Storage implements Storage {
780
920
  }>): void;
781
921
  hasObject(repoId: string, hash: string): boolean;
782
922
  findObjectsByPrefix(repoId: string, prefix: string): string[];
923
+ listObjectHashes(repoId: string): string[];
924
+ deleteObjects(repoId: string, hashes: ReadonlyArray<string>): number;
783
925
  getRef(repoId: string, name: string): Ref | null;
784
926
  putRef(repoId: string, name: string, ref: Ref): void;
785
927
  removeRef(repoId: string, name: string): void;
@@ -847,6 +989,8 @@ declare class PgStorage implements Storage {
847
989
  }>): Promise<void>;
848
990
  hasObject(repoId: string, hash: string): Promise<boolean>;
849
991
  findObjectsByPrefix(repoId: string, prefix: string): Promise<string[]>;
992
+ listObjectHashes(repoId: string): Promise<string[]>;
993
+ deleteObjects(repoId: string, hashes: ReadonlyArray<string>): Promise<number>;
850
994
  getRef(repoId: string, name: string): Promise<Ref | null>;
851
995
  putRef(repoId: string, name: string, ref: Ref): Promise<void>;
852
996
  removeRef(repoId: string, name: string): Promise<void>;
@@ -854,4 +998,4 @@ declare class PgStorage implements Storage {
854
998
  atomicRefUpdate<T>(repoId: string, fn: (ops: RefOps) => Promise<T> | T): Promise<T>;
855
999
  }
856
1000
 
857
- export { type AdvertiseRefsEvent, type AdvertiseResult, type ApplyReceivePackOptions, type ApplyReceivePackResult, type BetterSqlite3Database, type BetterSqlite3Statement, BetterSqlite3Storage, type BunSqliteDatabase, type BunSqliteStatement, BunSqliteStorage, type CreateRepoOptions, type GitServer, type GitServerConfig, type MaybeAsync, MemoryStorage, type NodeHttpRequest, type NodeHttpResponse, type PgDatabase, type PgPool, type PgPoolClient, PgStorage, type PostReceiveEvent, type PreReceiveEvent, type RawRefEntry, type ReceivePackResult, type RefAdvertisement, type RefOps, type RefResult, type RefUpdate, Rejection, type ServerHooks, type ServerPolicy, type Session, type SessionBuilder, type SshChannel, type SshSessionInfo, type Storage, type UpdateEvent, advertiseRefsWithHooks, applyReceivePack, buildRefAdvertisementBytes, buildRefListBytes, buildRefListPktLines, collectRefs, composeHooks, createServer, handleUploadPack, ingestReceivePack, ingestReceivePackFromStream, parseGitSshCommand, wrapPgPool };
1001
+ export { type AdvertiseRefsEvent, type AdvertiseResult, type ApplyReceivePackOptions, type BetterSqlite3Database, BetterSqlite3Storage, type BunSqliteDatabase, BunSqliteStorage, type CreateRepoOptions, type GcOptions, type GcResult, GitRepo, type GitServer, type GitServerConfig, type MaybeAsync, MemoryStorage, type NodeHttpRequest, type NodeHttpResponse, type PgDatabase, type PgPool, type PgPoolClient, PgStorage, type PostReceiveEvent, type PreReceiveEvent, type PushCommand, RawObject, type RawRefEntry, type ReceivePackResult, Ref, type RefAdvertisement, type RefOps, type RefResult, type RefUpdate, type RefUpdateRequest, type RefUpdateResult, Rejection, type ServerHooks, type ServerPolicy, type Session, type SessionBuilder, type SshChannel, type SshSessionInfo, type Storage, type UpdateEvent, advertiseRefsWithHooks, applyReceivePack, buildRefAdvertisementBytes, buildRefListBytes, buildRefListPktLines, collectRefs, composeHooks, createServer, handleUploadPack, ingestReceivePack, ingestReceivePackFromStream, resolveRefUpdates, wrapPgPool };