just-git 1.2.13 → 1.3.1
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 +14 -11
- package/dist/{hooks-4DvkF2xT.d.ts → hooks-BtbyLyYE.d.ts} +14 -1
- package/dist/index.d.ts +16 -9
- package/dist/index.js +345 -338
- package/dist/repo/index.d.ts +111 -7
- package/dist/repo/index.js +16 -13
- package/dist/server/index.d.ts +610 -139
- package/dist/server/index.js +50 -32
- package/package.json +4 -1
package/dist/server/index.d.ts
CHANGED
|
@@ -1,19 +1,222 @@
|
|
|
1
|
-
import { f as GitRepo, X as Rejection } from '../hooks-
|
|
1
|
+
import { a3 as RawObject, a5 as Ref, f as GitRepo, X as Rejection, N as NetworkPolicy } from '../hooks-BtbyLyYE.js';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
type MaybeAsync<T> = T | Promise<T>;
|
|
4
|
+
/** Options for {@link StorageAdapter.createRepo}. */
|
|
5
|
+
interface CreateRepoOptions {
|
|
6
|
+
/** Name of the default branch (default: `"main"`). Used for HEAD initialization. */
|
|
7
|
+
defaultBranch?: string;
|
|
8
|
+
}
|
|
9
|
+
/** Unresolved ref entry as stored by the storage backend. */
|
|
10
|
+
interface RawRefEntry {
|
|
11
|
+
name: string;
|
|
12
|
+
ref: Ref;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
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.
|
|
17
|
+
*/
|
|
18
|
+
interface RefOps {
|
|
19
|
+
getRef(name: string): MaybeAsync<Ref | null>;
|
|
20
|
+
putRef(name: string, ref: Ref): MaybeAsync<void>;
|
|
21
|
+
removeRef(name: string): MaybeAsync<void>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Storage backend interface. Implementations provide raw key-value
|
|
25
|
+
* CRUD for objects and refs, plus an atomic ref operation primitive.
|
|
26
|
+
*
|
|
27
|
+
* All git-aware logic (object hashing, pack ingestion, symref resolution,
|
|
28
|
+
* CAS semantics) lives in the shared adapter built by {@link createStorageAdapter}.
|
|
29
|
+
*
|
|
30
|
+
* All methods may return synchronously or asynchronously.
|
|
31
|
+
*/
|
|
32
|
+
interface Storage {
|
|
33
|
+
hasRepo(repoId: string): MaybeAsync<boolean>;
|
|
34
|
+
insertRepo(repoId: string): MaybeAsync<void>;
|
|
35
|
+
/** Delete the repo record and all associated objects and refs. */
|
|
36
|
+
deleteRepo(repoId: string): MaybeAsync<void>;
|
|
37
|
+
getObject(repoId: string, hash: string): MaybeAsync<RawObject | null>;
|
|
38
|
+
putObject(repoId: string, hash: string, type: string, content: Uint8Array): MaybeAsync<void>;
|
|
39
|
+
/** Bulk insert. Implementations should use their optimal batch strategy. */
|
|
40
|
+
putObjects(repoId: string, objects: ReadonlyArray<{
|
|
41
|
+
hash: string;
|
|
42
|
+
type: string;
|
|
43
|
+
content: Uint8Array;
|
|
44
|
+
}>): MaybeAsync<void>;
|
|
45
|
+
hasObject(repoId: string, hash: string): MaybeAsync<boolean>;
|
|
46
|
+
findObjectsByPrefix(repoId: string, prefix: string): MaybeAsync<string[]>;
|
|
47
|
+
getRef(repoId: string, name: string): MaybeAsync<Ref | null>;
|
|
48
|
+
putRef(repoId: string, name: string, ref: Ref): MaybeAsync<void>;
|
|
49
|
+
removeRef(repoId: string, name: string): MaybeAsync<void>;
|
|
50
|
+
/** Return all refs under a prefix, unresolved (symrefs not followed). */
|
|
51
|
+
listRefs(repoId: string, prefix?: string): MaybeAsync<RawRefEntry[]>;
|
|
52
|
+
/**
|
|
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.
|
|
56
|
+
*/
|
|
57
|
+
atomicRefUpdate<T>(repoId: string, fn: (ops: RefOps) => MaybeAsync<T>): MaybeAsync<T>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Default session type, produced by the built-in session builder when
|
|
62
|
+
* no custom `session` config is provided to `createServer`.
|
|
63
|
+
*
|
|
64
|
+
* HTTP requests produce `{ transport: "http", request }`.
|
|
65
|
+
* SSH sessions produce `{ transport: "ssh", username }`.
|
|
66
|
+
*/
|
|
67
|
+
interface Session {
|
|
68
|
+
transport: "http" | "ssh";
|
|
69
|
+
/** Authenticated username, when available. */
|
|
70
|
+
username?: string;
|
|
71
|
+
/** The HTTP request, present only when `transport` is `"http"`. */
|
|
72
|
+
request?: Request;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* User-provided session builder that transforms raw transport input
|
|
76
|
+
* into a typed session object threaded through all hooks.
|
|
77
|
+
*
|
|
78
|
+
* TypeScript infers `S` from the return types of the builder functions,
|
|
79
|
+
* so hooks receive the custom type without explicit generic annotations.
|
|
80
|
+
*
|
|
81
|
+
* ```ts
|
|
82
|
+
* const server = createServer({
|
|
83
|
+
* storage: new BunSqliteStorage(db),
|
|
84
|
+
* session: {
|
|
85
|
+
* http: (req) => ({
|
|
86
|
+
* userId: parseJwt(req).sub,
|
|
87
|
+
* roles: parseJwt(req).roles,
|
|
88
|
+
* }),
|
|
89
|
+
* ssh: (info) => ({
|
|
90
|
+
* userId: info.username ?? "anonymous",
|
|
91
|
+
* roles: (info.metadata?.roles as string[]) ?? [],
|
|
92
|
+
* }),
|
|
93
|
+
* },
|
|
94
|
+
* hooks: {
|
|
95
|
+
* preReceive: ({ session }) => {
|
|
96
|
+
* // session is { userId: string, roles: string[] } — inferred!
|
|
97
|
+
* if (!session?.roles.includes("push"))
|
|
98
|
+
* return { reject: true, message: "forbidden" };
|
|
99
|
+
* },
|
|
100
|
+
* },
|
|
101
|
+
* });
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
interface SessionBuilder<S> {
|
|
105
|
+
/**
|
|
106
|
+
* Build a session from an HTTP request.
|
|
107
|
+
*
|
|
108
|
+
* Return `S` to proceed, or return a `Response` to short-circuit
|
|
109
|
+
* the request (e.g. 401 with `WWW-Authenticate` header). This is
|
|
110
|
+
* the primary mechanism for HTTP auth — no separate middleware needed.
|
|
111
|
+
*/
|
|
112
|
+
http: (request: Request) => S | Response | Promise<S | Response>;
|
|
113
|
+
/** Build a session from SSH session info. */
|
|
114
|
+
ssh: (info: SshSessionInfo) => S | Promise<S>;
|
|
115
|
+
}
|
|
116
|
+
/** Information about the SSH session passed to `handleSession`. */
|
|
117
|
+
interface SshSessionInfo {
|
|
118
|
+
/** SSH username from authentication. */
|
|
119
|
+
username?: string;
|
|
120
|
+
/**
|
|
121
|
+
* Arbitrary metadata from the SSH auth layer.
|
|
122
|
+
* Stash key fingerprints, client IPs, roles, etc. here —
|
|
123
|
+
* the session builder can extract and type them.
|
|
124
|
+
*/
|
|
125
|
+
metadata?: Record<string, unknown>;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Bidirectional channel for SSH session I/O.
|
|
129
|
+
*
|
|
130
|
+
* Adapters create this from their SSH library's channel/stream.
|
|
131
|
+
* The handler reads the client request from `readable` and writes
|
|
132
|
+
* the server response to `writable`.
|
|
133
|
+
*
|
|
134
|
+
* For receive-pack (push), `readable` must close when the client
|
|
135
|
+
* finishes sending. For upload-pack (fetch/clone), the handler
|
|
136
|
+
* reads protocol-aware pkt-lines and does not require EOF.
|
|
137
|
+
*/
|
|
138
|
+
interface SshChannel {
|
|
139
|
+
/** Client data (from client stdout via SSH channel). */
|
|
140
|
+
readonly readable: ReadableStream<Uint8Array>;
|
|
141
|
+
/** Server response (to client stdin via SSH channel). */
|
|
142
|
+
readonly writable: WritableStream<Uint8Array>;
|
|
143
|
+
/** Write a diagnostic/error message to the client's stderr. */
|
|
144
|
+
writeStderr?(data: Uint8Array): void;
|
|
145
|
+
}
|
|
146
|
+
/** Node.js `http.IncomingMessage`-compatible request interface. */
|
|
147
|
+
interface NodeHttpRequest {
|
|
148
|
+
method?: string;
|
|
149
|
+
url?: string;
|
|
150
|
+
headers: Record<string, string | string[] | undefined>;
|
|
151
|
+
on(event: string, listener: (...args: any[]) => void): any;
|
|
152
|
+
}
|
|
153
|
+
/** Node.js `http.ServerResponse`-compatible response interface. */
|
|
154
|
+
interface NodeHttpResponse {
|
|
155
|
+
writeHead(statusCode: number, headers?: Record<string, string | string[]>): any;
|
|
156
|
+
write(chunk: any): any;
|
|
157
|
+
end(data?: string): any;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Declarative push rules applied before user-provided hooks.
|
|
161
|
+
*
|
|
162
|
+
* These are git-level constraints that don't depend on the session.
|
|
163
|
+
* For session-dependent logic (auth, logging), use hooks directly.
|
|
164
|
+
*/
|
|
165
|
+
interface ServerPolicy {
|
|
166
|
+
/** Branches that cannot be force-pushed to or deleted. */
|
|
167
|
+
protectedBranches?: string[];
|
|
168
|
+
/** Reject all non-fast-forward pushes globally. */
|
|
169
|
+
denyNonFastForward?: boolean;
|
|
170
|
+
/** Reject all ref deletions globally. */
|
|
171
|
+
denyDeletes?: boolean;
|
|
172
|
+
/** Tags are immutable — no deletion, no overwrite once created. */
|
|
173
|
+
immutableTags?: boolean;
|
|
174
|
+
}
|
|
175
|
+
interface GitServerConfig<S = Session> {
|
|
176
|
+
/**
|
|
177
|
+
* Storage backend for git object and ref persistence.
|
|
178
|
+
*
|
|
179
|
+
* The server calls `createStorageAdapter(storage)` internally to build the
|
|
180
|
+
* git-aware adapter. Users provide the storage backend; they never see
|
|
181
|
+
* the `StorageAdapter` interface.
|
|
182
|
+
*/
|
|
183
|
+
storage: Storage;
|
|
4
184
|
/**
|
|
5
|
-
*
|
|
185
|
+
* Map a request path to a repo ID.
|
|
186
|
+
*
|
|
187
|
+
* Called for both HTTP and SSH requests. Return a string repo ID
|
|
188
|
+
* to serve, or `null` to respond with 404 / reject.
|
|
6
189
|
*
|
|
7
|
-
*
|
|
8
|
-
* - `GitRepo` — use this repo for the request
|
|
9
|
-
* - `null` — respond with 404
|
|
10
|
-
* - `Response` — send this response as-is (useful for 401/403 with
|
|
11
|
-
* custom headers like `WWW-Authenticate`)
|
|
190
|
+
* Default: identity — the URL path segment is the repo ID.
|
|
12
191
|
*/
|
|
13
|
-
|
|
192
|
+
resolve?: (path: string) => string | null | Promise<string | null>;
|
|
193
|
+
/**
|
|
194
|
+
* Automatically create repos on first access.
|
|
195
|
+
*
|
|
196
|
+
* When `true`, uses `"main"` as the default branch.
|
|
197
|
+
* When `{ defaultBranch }`, uses the specified branch name.
|
|
198
|
+
* When `false` or omitted, unknown repos return 404.
|
|
199
|
+
*/
|
|
200
|
+
autoCreate?: boolean | {
|
|
201
|
+
defaultBranch?: string;
|
|
202
|
+
};
|
|
14
203
|
/** Server-side hooks. All optional. */
|
|
15
|
-
hooks?: ServerHooks
|
|
16
|
-
/**
|
|
204
|
+
hooks?: ServerHooks<S>;
|
|
205
|
+
/**
|
|
206
|
+
* Declarative push policy. Rules run before user-provided hooks.
|
|
207
|
+
*
|
|
208
|
+
* For session-dependent logic (auth, post-push actions), use `hooks`.
|
|
209
|
+
*/
|
|
210
|
+
policy?: ServerPolicy;
|
|
211
|
+
/**
|
|
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.
|
|
215
|
+
*
|
|
216
|
+
* When omitted, the built-in `Session` type is used.
|
|
217
|
+
*/
|
|
218
|
+
session?: SessionBuilder<S>;
|
|
219
|
+
/** Base path prefix to strip from HTTP URLs (e.g. "/git"). */
|
|
17
220
|
basePath?: string;
|
|
18
221
|
/**
|
|
19
222
|
* Cache generated packfiles for identical full-clone requests.
|
|
@@ -44,36 +247,116 @@ interface GitServerConfig {
|
|
|
44
247
|
* Override to integrate with your own logging, or set to `false` to
|
|
45
248
|
* suppress all error output.
|
|
46
249
|
*/
|
|
47
|
-
onError?: false | ((err: unknown,
|
|
250
|
+
onError?: false | ((err: unknown, session?: S) => void);
|
|
48
251
|
}
|
|
49
252
|
interface GitServer {
|
|
50
|
-
/** Standard fetch-API handler: (Request) => Response */
|
|
51
|
-
fetch
|
|
253
|
+
/** Standard fetch-API handler for HTTP: (Request) => Response */
|
|
254
|
+
fetch(request: Request): Promise<Response>;
|
|
255
|
+
/**
|
|
256
|
+
* Handle a single git-over-SSH session.
|
|
257
|
+
*
|
|
258
|
+
* Call this when the SSH client execs a git command (typically
|
|
259
|
+
* `git-upload-pack` or `git-receive-pack`). Returns the exit code
|
|
260
|
+
* to send to the client.
|
|
261
|
+
*
|
|
262
|
+
* ```ts
|
|
263
|
+
* import { Server } from "ssh2";
|
|
264
|
+
*
|
|
265
|
+
* new Server({ hostKeys: [key] }, (client) => {
|
|
266
|
+
* client.on("authentication", (ctx) => { ctx.accept(); });
|
|
267
|
+
* client.on("session", (accept) => {
|
|
268
|
+
* accept().on("exec", (accept, reject, info) => {
|
|
269
|
+
* const stream = accept();
|
|
270
|
+
* const channel: SshChannel = {
|
|
271
|
+
* readable: new ReadableStream({
|
|
272
|
+
* start(c) {
|
|
273
|
+
* stream.on("data", (d: Buffer) => c.enqueue(new Uint8Array(d)));
|
|
274
|
+
* stream.on("end", () => c.close());
|
|
275
|
+
* },
|
|
276
|
+
* }),
|
|
277
|
+
* writable: new WritableStream({ write(chunk) { stream.write(chunk); } }),
|
|
278
|
+
* writeStderr(data) { stream.stderr.write(data); },
|
|
279
|
+
* };
|
|
280
|
+
* server.handleSession(info.command, channel, { username: ctx.username })
|
|
281
|
+
* .then((code) => { stream.exit(code); stream.close(); });
|
|
282
|
+
* });
|
|
283
|
+
* });
|
|
284
|
+
* });
|
|
285
|
+
* ```
|
|
286
|
+
*/
|
|
287
|
+
handleSession(command: string, channel: SshChannel, session?: SshSessionInfo): Promise<number>;
|
|
288
|
+
/**
|
|
289
|
+
* Node.js `http.createServer` compatible handler.
|
|
290
|
+
*
|
|
291
|
+
* ```ts
|
|
292
|
+
* import http from "node:http";
|
|
293
|
+
* http.createServer(server.nodeHandler).listen(4280);
|
|
294
|
+
* ```
|
|
295
|
+
*/
|
|
296
|
+
nodeHandler(req: NodeHttpRequest, res: NodeHttpResponse): void;
|
|
297
|
+
/** Create a new repo. Throws if the repo already exists. */
|
|
298
|
+
createRepo(id: string, options?: CreateRepoOptions): Promise<GitRepo>;
|
|
299
|
+
/** Get a repo by ID, or `null` if it doesn't exist. */
|
|
300
|
+
repo(id: string): Promise<GitRepo | null>;
|
|
301
|
+
/** Delete a repo and all its data. */
|
|
302
|
+
deleteRepo(id: string): Promise<void>;
|
|
303
|
+
/**
|
|
304
|
+
* Graceful shutdown. After calling, new HTTP requests receive 503
|
|
305
|
+
* and new SSH sessions get exit 128. Resolves when all in-flight
|
|
306
|
+
* operations complete and the pack cache is released.
|
|
307
|
+
*
|
|
308
|
+
* Pass an `AbortSignal` to set a timeout — when aborted, the
|
|
309
|
+
* promise resolves immediately even if operations are still running.
|
|
310
|
+
* Idempotent: subsequent calls return the same drain promise.
|
|
311
|
+
*/
|
|
312
|
+
close(options?: {
|
|
313
|
+
signal?: AbortSignal;
|
|
314
|
+
}): Promise<void>;
|
|
315
|
+
/** Whether `close()` has been called. */
|
|
316
|
+
readonly closed: boolean;
|
|
317
|
+
/**
|
|
318
|
+
* Build a {@link NetworkPolicy} that routes HTTP requests to this
|
|
319
|
+
* server in-process, bypassing the network stack entirely.
|
|
320
|
+
*
|
|
321
|
+
* Pass the returned policy as `network` to {@link createGit}:
|
|
322
|
+
*
|
|
323
|
+
* ```ts
|
|
324
|
+
* const git = createGit({ network: server.asNetwork() });
|
|
325
|
+
* await git.exec("clone http://git/my-repo /work");
|
|
326
|
+
* ```
|
|
327
|
+
*
|
|
328
|
+
* @param baseUrl - Base URL used in clone/push/fetch commands.
|
|
329
|
+
* Only the hostname matters (for the `allowed` list). The URL
|
|
330
|
+
* never hits the network — it's resolved by the server's
|
|
331
|
+
* `resolve` function. Defaults to `"http://git"`.
|
|
332
|
+
*/
|
|
333
|
+
asNetwork(baseUrl?: string): NetworkPolicy;
|
|
52
334
|
}
|
|
53
|
-
interface ServerHooks {
|
|
335
|
+
interface ServerHooks<S = Session> {
|
|
54
336
|
/**
|
|
55
337
|
* Called after objects are unpacked but before any refs update.
|
|
56
338
|
* Receives ALL ref updates as a batch. Return a Rejection to abort
|
|
57
339
|
* the entire push. Auth, branch protection, and repo-wide policy
|
|
58
340
|
* belong here.
|
|
59
341
|
*/
|
|
60
|
-
preReceive?: (event: PreReceiveEvent) => void | Rejection | Promise<void | Rejection>;
|
|
342
|
+
preReceive?: (event: PreReceiveEvent<S>) => void | Rejection | Promise<void | Rejection>;
|
|
61
343
|
/**
|
|
62
344
|
* Called per-ref, after preReceive passes.
|
|
63
345
|
* Return a Rejection to block this specific ref update while
|
|
64
346
|
* allowing others. Per-branch rules belong here.
|
|
65
347
|
*/
|
|
66
|
-
update?: (event: UpdateEvent) => void | Rejection | Promise<void | Rejection>;
|
|
348
|
+
update?: (event: UpdateEvent<S>) => void | Rejection | Promise<void | Rejection>;
|
|
67
349
|
/**
|
|
68
350
|
* Called after all ref updates succeed. Cannot reject.
|
|
69
351
|
* CI triggers, webhooks, notifications belong here.
|
|
70
352
|
*/
|
|
71
|
-
postReceive?: (event: PostReceiveEvent) => void | Promise<void>;
|
|
353
|
+
postReceive?: (event: PostReceiveEvent<S>) => void | Promise<void>;
|
|
72
354
|
/**
|
|
73
355
|
* Called when a client wants to fetch or push (during ref advertisement).
|
|
74
|
-
* Return a filtered ref list to hide branches,
|
|
356
|
+
* Return a filtered ref list to hide branches, a Rejection to deny
|
|
357
|
+
* access entirely, or void to advertise all refs.
|
|
75
358
|
*/
|
|
76
|
-
advertiseRefs?: (event: AdvertiseRefsEvent) => RefAdvertisement[] | void | Promise<RefAdvertisement[] | void>;
|
|
359
|
+
advertiseRefs?: (event: AdvertiseRefsEvent<S>) => RefAdvertisement[] | void | Rejection | Promise<RefAdvertisement[] | void | Rejection>;
|
|
77
360
|
}
|
|
78
361
|
/** A single ref update within a push. */
|
|
79
362
|
interface RefUpdate {
|
|
@@ -91,33 +374,41 @@ interface RefUpdate {
|
|
|
91
374
|
isDelete: boolean;
|
|
92
375
|
}
|
|
93
376
|
/** Fired after objects are unpacked but before refs are updated. */
|
|
94
|
-
interface PreReceiveEvent {
|
|
377
|
+
interface PreReceiveEvent<S = Session> {
|
|
95
378
|
repo: GitRepo;
|
|
96
|
-
|
|
379
|
+
/** Resolved repo ID (the value returned by `resolve`, or the raw path when `resolve` is not set). */
|
|
380
|
+
repoId: string;
|
|
97
381
|
updates: readonly RefUpdate[];
|
|
98
|
-
|
|
382
|
+
/** Session info. Present for HTTP and SSH; absent for in-process pushes. */
|
|
383
|
+
session?: S;
|
|
99
384
|
}
|
|
100
385
|
/** Fired per-ref after preReceive passes. */
|
|
101
|
-
interface UpdateEvent {
|
|
386
|
+
interface UpdateEvent<S = Session> {
|
|
102
387
|
repo: GitRepo;
|
|
103
|
-
|
|
388
|
+
/** Resolved repo ID (the value returned by `resolve`, or the raw path when `resolve` is not set). */
|
|
389
|
+
repoId: string;
|
|
104
390
|
update: RefUpdate;
|
|
105
|
-
|
|
391
|
+
/** Session info. Present for HTTP and SSH; absent for in-process pushes. */
|
|
392
|
+
session?: S;
|
|
106
393
|
}
|
|
107
394
|
/** Fired after all ref updates succeed. */
|
|
108
|
-
interface PostReceiveEvent {
|
|
395
|
+
interface PostReceiveEvent<S = Session> {
|
|
109
396
|
repo: GitRepo;
|
|
110
|
-
|
|
397
|
+
/** Resolved repo ID (the value returned by `resolve`, or the raw path when `resolve` is not set). */
|
|
398
|
+
repoId: string;
|
|
111
399
|
updates: readonly RefUpdate[];
|
|
112
|
-
|
|
400
|
+
/** Session info. Present for HTTP and SSH; absent for in-process pushes. */
|
|
401
|
+
session?: S;
|
|
113
402
|
}
|
|
114
403
|
/** Fired during ref advertisement (info/refs). */
|
|
115
|
-
interface AdvertiseRefsEvent {
|
|
404
|
+
interface AdvertiseRefsEvent<S = Session> {
|
|
116
405
|
repo: GitRepo;
|
|
117
|
-
|
|
406
|
+
/** Resolved repo ID (the value returned by `resolve`, or the raw path when `resolve` is not set). */
|
|
407
|
+
repoId: string;
|
|
118
408
|
refs: RefAdvertisement[];
|
|
119
409
|
service: "git-upload-pack" | "git-receive-pack";
|
|
120
|
-
|
|
410
|
+
/** Session info. Present for HTTP and SSH; absent for in-process requests. */
|
|
411
|
+
session?: S;
|
|
121
412
|
}
|
|
122
413
|
/** A ref name and hash advertised to clients during fetch/push discovery. */
|
|
123
414
|
interface RefAdvertisement {
|
|
@@ -126,47 +417,43 @@ interface RefAdvertisement {
|
|
|
126
417
|
}
|
|
127
418
|
|
|
128
419
|
/**
|
|
129
|
-
*
|
|
420
|
+
* Unified Git server: Smart HTTP + SSH session handling.
|
|
130
421
|
*
|
|
131
|
-
* Uses web-standard Request/Response
|
|
132
|
-
*
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Create a Git Smart HTTP server handler.
|
|
422
|
+
* Uses web-standard Request/Response for HTTP, and web-standard
|
|
423
|
+
* ReadableStream/WritableStream for SSH. Works with Bun.serve, Hono,
|
|
424
|
+
* Cloudflare Workers, or any framework that speaks fetch API. SSH
|
|
425
|
+
* works with any SSH library (ssh2, etc.) through a thin adapter.
|
|
137
426
|
*
|
|
138
427
|
* ```ts
|
|
139
|
-
* const server =
|
|
140
|
-
*
|
|
428
|
+
* const server = createServer({
|
|
429
|
+
* storage: new MemoryStorage(),
|
|
430
|
+
* autoCreate: true,
|
|
141
431
|
* });
|
|
432
|
+
* await server.createRepo("my-repo");
|
|
433
|
+
*
|
|
434
|
+
* // HTTP
|
|
142
435
|
* Bun.serve({ fetch: server.fetch });
|
|
143
436
|
* ```
|
|
144
437
|
*/
|
|
145
|
-
|
|
146
|
-
interface NodeHttpRequest {
|
|
147
|
-
method?: string;
|
|
148
|
-
url?: string;
|
|
149
|
-
headers: Record<string, string | string[] | undefined>;
|
|
150
|
-
on(event: string, listener: (...args: any[]) => void): any;
|
|
151
|
-
}
|
|
152
|
-
interface NodeHttpResponse {
|
|
153
|
-
writeHead(statusCode: number, headers?: Record<string, string | string[]>): any;
|
|
154
|
-
write(chunk: any): any;
|
|
155
|
-
end(data?: string): any;
|
|
156
|
-
}
|
|
438
|
+
|
|
157
439
|
/**
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
* Converts between Node's `IncomingMessage`/`ServerResponse` and the
|
|
161
|
-
* web-standard `Request`/`Response` used by the server handler.
|
|
440
|
+
* Create a unified Git server that handles both HTTP and SSH.
|
|
162
441
|
*
|
|
163
442
|
* ```ts
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
443
|
+
* const server = createServer({
|
|
444
|
+
* storage: new MemoryStorage(),
|
|
445
|
+
* autoCreate: true,
|
|
446
|
+
* });
|
|
447
|
+
* await server.createRepo("my-repo");
|
|
448
|
+
*
|
|
449
|
+
* // HTTP — pass to Bun.serve, Hono, Cloudflare Workers, etc.
|
|
450
|
+
* Bun.serve({ fetch: server.fetch });
|
|
451
|
+
*
|
|
452
|
+
* // SSH — wire up with ssh2 or any SSH library
|
|
453
|
+
* server.handleSession(command, channel, { username });
|
|
167
454
|
* ```
|
|
168
455
|
*/
|
|
169
|
-
declare function
|
|
456
|
+
declare function createServer<S = Session>(config: GitServerConfig<S>): GitServer;
|
|
170
457
|
/**
|
|
171
458
|
* Compose multiple hook sets into a single `ServerHooks` object.
|
|
172
459
|
*
|
|
@@ -175,94 +462,241 @@ declare function toNodeHandler(server: GitServer): (req: NodeHttpRequest, res: N
|
|
|
175
462
|
* - **Post-hooks** (`postReceive`): run all in order. Each is individually
|
|
176
463
|
* try/caught so one failure doesn't prevent the rest from running.
|
|
177
464
|
* - **Filter hooks** (`advertiseRefs`): chain — each hook receives the
|
|
178
|
-
* refs returned by the previous one.
|
|
179
|
-
* unchanged.
|
|
465
|
+
* refs returned by the previous one. Short-circuits on `Rejection`.
|
|
466
|
+
* Returning void passes through unchanged.
|
|
180
467
|
*/
|
|
181
|
-
declare function composeHooks(...hookSets: (ServerHooks | undefined)[]): ServerHooks
|
|
468
|
+
declare function composeHooks<S = Session>(...hookSets: (ServerHooks<S> | undefined)[]): ServerHooks<S>;
|
|
182
469
|
|
|
183
470
|
/**
|
|
184
|
-
*
|
|
471
|
+
* Server-side Git protocol helpers.
|
|
472
|
+
*
|
|
473
|
+
* Transport-agnostic ref advertisement, upload-pack response building,
|
|
474
|
+
* and receive-pack request/response parsing. The HTTP-specific service
|
|
475
|
+
* header wrapping is layered on top of the shared ref list builder.
|
|
185
476
|
*/
|
|
186
477
|
|
|
187
|
-
|
|
478
|
+
interface AdvertisedRef {
|
|
479
|
+
name: string;
|
|
480
|
+
hash: string;
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Build the pkt-line ref list with capabilities. Transport-agnostic —
|
|
484
|
+
* used directly by SSH/in-process transports and wrapped by
|
|
485
|
+
* `buildRefAdvertisement` for HTTP.
|
|
486
|
+
*
|
|
487
|
+
* Format:
|
|
488
|
+
* pkt-line("<hash> <refname>\0<capabilities>\n") // first ref
|
|
489
|
+
* pkt-line("<hash> <refname>\n") // subsequent refs
|
|
490
|
+
* flush
|
|
491
|
+
*/
|
|
492
|
+
declare function buildRefListPktLines(refs: AdvertisedRef[], capabilities: string[], headTarget?: string): Uint8Array;
|
|
493
|
+
interface PushCommand {
|
|
494
|
+
oldHash: string;
|
|
495
|
+
newHash: string;
|
|
496
|
+
refName: string;
|
|
497
|
+
}
|
|
498
|
+
|
|
188
499
|
/**
|
|
189
|
-
*
|
|
190
|
-
* gates **all** access (clone, fetch, and push).
|
|
500
|
+
* High-level server operations.
|
|
191
501
|
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
|
|
502
|
+
* Transport-agnostic: each operation accepts a `GitRepo` and returns
|
|
503
|
+
* structured results. `applyReceivePack` encapsulates the full push
|
|
504
|
+
* lifecycle (hooks + ref application) for use by any transport adapter.
|
|
505
|
+
*/
|
|
506
|
+
|
|
507
|
+
interface PackCacheEntry {
|
|
508
|
+
packData: Uint8Array;
|
|
509
|
+
objectCount: number;
|
|
510
|
+
deltaCount: number;
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Bounded LRU-ish cache for generated packfiles.
|
|
196
514
|
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
515
|
+
* Keyed on `(repoId, sorted wants)` — only caches full clones
|
|
516
|
+
* (requests with no `have` lines). Incremental fetches always
|
|
517
|
+
* compute fresh packs.
|
|
199
518
|
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
* (req) => req.headers.get("Authorization") === `Bearer ${token}`,
|
|
204
|
-
* (repoPath) => storage.repo(repoPath),
|
|
205
|
-
* ),
|
|
206
|
-
* hooks: createStandardHooks({ protectedBranches: ["main"] }),
|
|
207
|
-
* });
|
|
208
|
-
* ```
|
|
519
|
+
* Entries are automatically invalidated when refs change: since the
|
|
520
|
+
* cache key includes the exact want hashes, a ref update changes
|
|
521
|
+
* the want set on the next client request, producing a cache miss.
|
|
209
522
|
*/
|
|
210
|
-
declare
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
523
|
+
declare class PackCache {
|
|
524
|
+
private entries;
|
|
525
|
+
private currentBytes;
|
|
526
|
+
private maxBytes;
|
|
527
|
+
private hits;
|
|
528
|
+
private misses;
|
|
529
|
+
constructor(maxBytes?: number);
|
|
530
|
+
/** Build a cache key. Returns null for requests with haves (not cacheable). */
|
|
531
|
+
static key(repoId: string, wants: string[], haves: string[]): string | null;
|
|
532
|
+
get(key: string): PackCacheEntry | undefined;
|
|
533
|
+
set(key: string, entry: PackCacheEntry): void;
|
|
534
|
+
clear(): void;
|
|
535
|
+
get stats(): {
|
|
536
|
+
entries: number;
|
|
537
|
+
bytes: number;
|
|
538
|
+
hits: number;
|
|
539
|
+
misses: number;
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
interface RefsData {
|
|
543
|
+
refs: RefAdvertisement[];
|
|
544
|
+
headTarget?: string;
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Collect the structured ref list from a repo (no wire encoding).
|
|
548
|
+
* The handler can pass this through an advertiseRefs hook to filter,
|
|
549
|
+
* then call `buildRefAdvertisementBytes` to produce the wire format.
|
|
550
|
+
*/
|
|
551
|
+
declare function collectRefs(repo: GitRepo): Promise<RefsData>;
|
|
552
|
+
/**
|
|
553
|
+
* Build the HTTP-wrapped ref advertisement (includes `# service=...` header).
|
|
554
|
+
*/
|
|
555
|
+
declare function buildRefAdvertisementBytes(refs: RefAdvertisement[], service: "git-upload-pack" | "git-receive-pack", headTarget?: string): Uint8Array;
|
|
556
|
+
/**
|
|
557
|
+
* Build the transport-agnostic ref list (no HTTP service header).
|
|
558
|
+
* Used by SSH and in-process transports.
|
|
559
|
+
*/
|
|
560
|
+
declare function buildRefListBytes(refs: RefAdvertisement[], service: "git-upload-pack" | "git-receive-pack", headTarget?: string): Uint8Array;
|
|
561
|
+
interface AdvertiseResult {
|
|
562
|
+
refs: RefAdvertisement[];
|
|
563
|
+
headTarget?: string;
|
|
224
564
|
}
|
|
225
565
|
/**
|
|
226
|
-
*
|
|
566
|
+
* Collect refs and run the `advertiseRefs` hook. Returns either the
|
|
567
|
+
* (possibly filtered) ref list, or a `Rejection` if the hook denied access.
|
|
227
568
|
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
* have to wire hooks manually for typical setups.
|
|
569
|
+
* Both HTTP and SSH code paths use this — the caller handles the
|
|
570
|
+
* transport-specific response (HTTP 403 vs SSH exit 128).
|
|
231
571
|
*/
|
|
232
|
-
declare function
|
|
233
|
-
|
|
572
|
+
declare function advertiseRefsWithHooks<S>(repo: GitRepo, repoId: string, service: "git-upload-pack" | "git-receive-pack", hooks?: ServerHooks<S>, session?: S): Promise<AdvertiseResult | Rejection>;
|
|
573
|
+
interface UploadPackOptions {
|
|
574
|
+
/** Pack cache instance. When provided, full clones (no haves) are cached. */
|
|
575
|
+
cache?: PackCache;
|
|
576
|
+
/** Repo path used as part of the cache key. Required when cache is set. */
|
|
577
|
+
cacheKey?: string;
|
|
578
|
+
/** Skip delta compression — faster pack generation, larger output. */
|
|
579
|
+
noDelta?: boolean;
|
|
580
|
+
/** Delta window size (default 10). Ignored when noDelta is true. */
|
|
581
|
+
deltaWindow?: number;
|
|
582
|
+
}
|
|
234
583
|
/**
|
|
235
|
-
*
|
|
584
|
+
* Handle a `POST /git-upload-pack` request.
|
|
236
585
|
*
|
|
237
|
-
*
|
|
586
|
+
* Returns `Uint8Array` for buffered responses (cache hits, deltified packs)
|
|
587
|
+
* or `ReadableStream<Uint8Array>` for streaming no-delta responses.
|
|
238
588
|
*/
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
589
|
+
declare function handleUploadPack(repo: GitRepo, requestBody: Uint8Array, options?: UploadPackOptions): Promise<Uint8Array | ReadableStream<Uint8Array>>;
|
|
590
|
+
interface ReceivePackResult {
|
|
591
|
+
updates: RefUpdate[];
|
|
592
|
+
unpackOk: boolean;
|
|
593
|
+
capabilities: string[];
|
|
594
|
+
/** Whether the request body contained a valid pkt-line flush packet. */
|
|
595
|
+
sawFlush: boolean;
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Ingest a receive-pack request: parse commands, ingest the packfile,
|
|
599
|
+
* and compute enriched RefUpdate objects. Does NOT apply ref updates —
|
|
600
|
+
* call `applyReceivePack` to run hooks and apply refs.
|
|
601
|
+
*/
|
|
602
|
+
declare function ingestReceivePack(repo: GitRepo, requestBody: Uint8Array): Promise<ReceivePackResult>;
|
|
603
|
+
/**
|
|
604
|
+
* Streaming variant of `ingestReceivePack`. Accepts pre-parsed push
|
|
605
|
+
* commands and a raw pack byte stream. Uses `readPackStreaming` →
|
|
606
|
+
* `ingestPackStream` so pack bytes are consumed incrementally without
|
|
607
|
+
* buffering the entire pack in memory.
|
|
608
|
+
*
|
|
609
|
+
* The HTTP handler continues using `ingestReceivePack` (runtime buffers
|
|
610
|
+
* POST bodies anyway). The SSH handler calls this directly after parsing
|
|
611
|
+
* pkt-line commands.
|
|
612
|
+
*/
|
|
613
|
+
declare function ingestReceivePackFromStream(repo: GitRepo, commands: PushCommand[], capabilities: string[], packStream: AsyncIterable<Uint8Array>, sawFlush?: boolean): Promise<ReceivePackResult>;
|
|
614
|
+
interface ApplyReceivePackOptions<S = unknown> {
|
|
615
|
+
repo: GitRepo;
|
|
616
|
+
repoId: string;
|
|
617
|
+
ingestResult: ReceivePackResult;
|
|
618
|
+
hooks?: ServerHooks<S>;
|
|
619
|
+
/** Session info threaded through to hooks. */
|
|
620
|
+
session?: S;
|
|
621
|
+
}
|
|
622
|
+
interface RefResult {
|
|
623
|
+
ref: string;
|
|
624
|
+
ok: boolean;
|
|
625
|
+
error?: string;
|
|
626
|
+
}
|
|
627
|
+
interface ApplyReceivePackResult {
|
|
628
|
+
refResults: RefResult[];
|
|
629
|
+
applied: RefUpdate[];
|
|
244
630
|
}
|
|
631
|
+
/**
|
|
632
|
+
* Run the full receive-pack lifecycle: preReceive hook, per-ref update
|
|
633
|
+
* hook with ref format validation, CAS ref application, and postReceive
|
|
634
|
+
* hook. Transport-agnostic — works for HTTP, SSH, or in-process pushes.
|
|
635
|
+
*
|
|
636
|
+
* Returns per-ref results and the list of successfully applied updates.
|
|
637
|
+
* Does NOT handle unpack failures — the caller should check
|
|
638
|
+
* `ingestResult.unpackOk` and short-circuit before calling this.
|
|
639
|
+
*/
|
|
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";
|
|
652
|
+
/**
|
|
653
|
+
* Parse a git SSH exec command into service and repo path.
|
|
654
|
+
*
|
|
655
|
+
* Handles `git-upload-pack '/path'`, `git upload-pack '/path'`,
|
|
656
|
+
* and unquoted variants.
|
|
657
|
+
*/
|
|
658
|
+
declare function parseGitSshCommand(command: string): {
|
|
659
|
+
service: GitSshService;
|
|
660
|
+
repoPath: string;
|
|
661
|
+
} | null;
|
|
245
662
|
|
|
246
663
|
/**
|
|
247
|
-
* In-memory
|
|
664
|
+
* In-memory storage backend with multi-repo support.
|
|
248
665
|
*
|
|
249
666
|
* Useful for tests, ephemeral servers, and benchmarking.
|
|
250
667
|
* Data is lost when the process exits.
|
|
251
668
|
*
|
|
252
669
|
* ```ts
|
|
253
|
-
* const
|
|
254
|
-
*
|
|
255
|
-
* resolveRepo: async (repoPath) => storage.repo(repoPath),
|
|
670
|
+
* const server = createServer({
|
|
671
|
+
* storage: new MemoryStorage(),
|
|
256
672
|
* });
|
|
673
|
+
* await server.createRepo("my-repo");
|
|
257
674
|
* ```
|
|
258
675
|
*/
|
|
259
676
|
declare class MemoryStorage implements Storage {
|
|
677
|
+
private repos;
|
|
260
678
|
private objects;
|
|
261
679
|
private refs;
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
680
|
+
hasRepo(repoId: string): boolean;
|
|
681
|
+
insertRepo(repoId: string): void;
|
|
682
|
+
deleteRepo(repoId: string): void;
|
|
683
|
+
getObject(repoId: string, hash: string): RawObject | null;
|
|
684
|
+
putObject(repoId: string, hash: string, type: string, content: Uint8Array): void;
|
|
685
|
+
putObjects(repoId: string, objects: ReadonlyArray<{
|
|
686
|
+
hash: string;
|
|
687
|
+
type: string;
|
|
688
|
+
content: Uint8Array;
|
|
689
|
+
}>): void;
|
|
690
|
+
hasObject(repoId: string, hash: string): boolean;
|
|
691
|
+
findObjectsByPrefix(repoId: string, prefix: string): string[];
|
|
692
|
+
getRef(repoId: string, name: string): Ref | null;
|
|
693
|
+
putRef(repoId: string, name: string, ref: Ref): void;
|
|
694
|
+
removeRef(repoId: string, name: string): void;
|
|
695
|
+
listRefs(repoId: string, prefix?: string): RawRefEntry[];
|
|
696
|
+
atomicRefUpdate<T>(repoId: string, fn: (ops: RefOps) => T): T;
|
|
697
|
+
repoIds(): string[];
|
|
698
|
+
private getObjMap;
|
|
699
|
+
private getRefMap;
|
|
266
700
|
}
|
|
267
701
|
|
|
268
702
|
/** Minimal prepared statement interface matching `bun:sqlite`. */
|
|
@@ -278,20 +712,35 @@ interface BunSqliteDatabase {
|
|
|
278
712
|
transaction<F extends (...args: any[]) => any>(fn: F): (...args: Parameters<F>) => ReturnType<F>;
|
|
279
713
|
}
|
|
280
714
|
/**
|
|
281
|
-
* SQLite-backed
|
|
715
|
+
* SQLite-backed storage using `bun:sqlite`.
|
|
282
716
|
*
|
|
283
717
|
* ```ts
|
|
284
718
|
* import { Database } from "bun:sqlite";
|
|
285
|
-
* const storage = new BunSqliteStorage(new Database("repos.db"));
|
|
719
|
+
* const storage = createStorageAdapter(new BunSqliteStorage(new Database("repos.db")));
|
|
286
720
|
* ```
|
|
287
721
|
*/
|
|
288
722
|
declare class BunSqliteStorage implements Storage {
|
|
289
723
|
private db;
|
|
290
724
|
private stmts;
|
|
291
|
-
private
|
|
725
|
+
private batchInsertTx;
|
|
292
726
|
constructor(db: BunSqliteDatabase);
|
|
293
|
-
|
|
294
|
-
|
|
727
|
+
hasRepo(repoId: string): boolean;
|
|
728
|
+
insertRepo(repoId: string): void;
|
|
729
|
+
deleteRepo(repoId: string): void;
|
|
730
|
+
getObject(repoId: string, hash: string): RawObject | null;
|
|
731
|
+
putObject(repoId: string, hash: string, type: string, content: Uint8Array): void;
|
|
732
|
+
putObjects(repoId: string, objects: ReadonlyArray<{
|
|
733
|
+
hash: string;
|
|
734
|
+
type: string;
|
|
735
|
+
content: Uint8Array;
|
|
736
|
+
}>): void;
|
|
737
|
+
hasObject(repoId: string, hash: string): boolean;
|
|
738
|
+
findObjectsByPrefix(repoId: string, prefix: string): string[];
|
|
739
|
+
getRef(repoId: string, name: string): Ref | null;
|
|
740
|
+
putRef(repoId: string, name: string, ref: Ref): void;
|
|
741
|
+
removeRef(repoId: string, name: string): void;
|
|
742
|
+
listRefs(repoId: string, prefix?: string): RawRefEntry[];
|
|
743
|
+
atomicRefUpdate<T>(repoId: string, fn: (ops: RefOps) => T): T;
|
|
295
744
|
}
|
|
296
745
|
|
|
297
746
|
/** Minimal prepared statement interface matching `better-sqlite3`. */
|
|
@@ -307,20 +756,35 @@ interface BetterSqlite3Database {
|
|
|
307
756
|
transaction<F extends (...args: any[]) => any>(fn: F): (...args: Parameters<F>) => ReturnType<F>;
|
|
308
757
|
}
|
|
309
758
|
/**
|
|
310
|
-
* SQLite-backed
|
|
759
|
+
* SQLite-backed storage using `better-sqlite3`.
|
|
311
760
|
*
|
|
312
761
|
* ```ts
|
|
313
762
|
* import Database from "better-sqlite3";
|
|
314
|
-
* const storage = new BetterSqlite3Storage(new Database("repos.db"));
|
|
763
|
+
* const storage = createStorageAdapter(new BetterSqlite3Storage(new Database("repos.db")));
|
|
315
764
|
* ```
|
|
316
765
|
*/
|
|
317
766
|
declare class BetterSqlite3Storage implements Storage {
|
|
318
767
|
private db;
|
|
319
768
|
private stmts;
|
|
320
|
-
private
|
|
769
|
+
private batchInsertTx;
|
|
321
770
|
constructor(db: BetterSqlite3Database);
|
|
322
|
-
|
|
323
|
-
|
|
771
|
+
hasRepo(repoId: string): boolean;
|
|
772
|
+
insertRepo(repoId: string): void;
|
|
773
|
+
deleteRepo(repoId: string): void;
|
|
774
|
+
getObject(repoId: string, hash: string): RawObject | null;
|
|
775
|
+
putObject(repoId: string, hash: string, type: string, content: Uint8Array): void;
|
|
776
|
+
putObjects(repoId: string, objects: ReadonlyArray<{
|
|
777
|
+
hash: string;
|
|
778
|
+
type: string;
|
|
779
|
+
content: Uint8Array;
|
|
780
|
+
}>): void;
|
|
781
|
+
hasObject(repoId: string, hash: string): boolean;
|
|
782
|
+
findObjectsByPrefix(repoId: string, prefix: string): string[];
|
|
783
|
+
getRef(repoId: string, name: string): Ref | null;
|
|
784
|
+
putRef(repoId: string, name: string, ref: Ref): void;
|
|
785
|
+
removeRef(repoId: string, name: string): void;
|
|
786
|
+
listRefs(repoId: string, prefix?: string): RawRefEntry[];
|
|
787
|
+
atomicRefUpdate<T>(repoId: string, fn: (ops: RefOps) => T): T;
|
|
324
788
|
}
|
|
325
789
|
|
|
326
790
|
/** Minimal database interface for PostgreSQL. Use {@link wrapPgPool} to adapt a `pg` Pool. */
|
|
@@ -357,10 +821,7 @@ interface PgPoolClient {
|
|
|
357
821
|
*/
|
|
358
822
|
declare function wrapPgPool(pool: PgPool): PgDatabase;
|
|
359
823
|
/**
|
|
360
|
-
* PostgreSQL-backed
|
|
361
|
-
*
|
|
362
|
-
* Creates and manages `git_objects` and `git_refs` tables in the
|
|
363
|
-
* provided database. Multiple repos are partitioned by `repo_id`.
|
|
824
|
+
* PostgreSQL-backed storage.
|
|
364
825
|
*
|
|
365
826
|
* Use the static `create` factory (schema setup is async):
|
|
366
827
|
*
|
|
@@ -368,19 +829,29 @@ declare function wrapPgPool(pool: PgPool): PgDatabase;
|
|
|
368
829
|
* import { Pool } from "pg";
|
|
369
830
|
* const pool = new Pool({ connectionString: "..." });
|
|
370
831
|
* const storage = await PgStorage.create(wrapPgPool(pool));
|
|
371
|
-
* const server = createGitServer({
|
|
372
|
-
* resolveRepo: async (repoPath) => storage.repo(repoPath),
|
|
373
|
-
* });
|
|
374
832
|
* ```
|
|
375
833
|
*/
|
|
376
834
|
declare class PgStorage implements Storage {
|
|
377
835
|
private db;
|
|
378
836
|
private constructor();
|
|
379
837
|
static create(db: PgDatabase): Promise<PgStorage>;
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
/** Delete all objects and refs for a repo. */
|
|
838
|
+
hasRepo(repoId: string): Promise<boolean>;
|
|
839
|
+
insertRepo(repoId: string): Promise<void>;
|
|
383
840
|
deleteRepo(repoId: string): Promise<void>;
|
|
841
|
+
getObject(repoId: string, hash: string): Promise<RawObject | null>;
|
|
842
|
+
putObject(repoId: string, hash: string, type: string, content: Uint8Array): Promise<void>;
|
|
843
|
+
putObjects(repoId: string, objects: ReadonlyArray<{
|
|
844
|
+
hash: string;
|
|
845
|
+
type: string;
|
|
846
|
+
content: Uint8Array;
|
|
847
|
+
}>): Promise<void>;
|
|
848
|
+
hasObject(repoId: string, hash: string): Promise<boolean>;
|
|
849
|
+
findObjectsByPrefix(repoId: string, prefix: string): Promise<string[]>;
|
|
850
|
+
getRef(repoId: string, name: string): Promise<Ref | null>;
|
|
851
|
+
putRef(repoId: string, name: string, ref: Ref): Promise<void>;
|
|
852
|
+
removeRef(repoId: string, name: string): Promise<void>;
|
|
853
|
+
listRefs(repoId: string, prefix?: string): Promise<RawRefEntry[]>;
|
|
854
|
+
atomicRefUpdate<T>(repoId: string, fn: (ops: RefOps) => Promise<T> | T): Promise<T>;
|
|
384
855
|
}
|
|
385
856
|
|
|
386
|
-
export { type AdvertiseRefsEvent, type BetterSqlite3Database, type BetterSqlite3Statement, BetterSqlite3Storage, type BunSqliteDatabase, type BunSqliteStatement, BunSqliteStorage, type GitServer, type GitServerConfig, MemoryStorage, type PgDatabase, type PgPool, type PgPoolClient, PgStorage, type PostReceiveEvent, type PreReceiveEvent, type RefAdvertisement, type RefUpdate, Rejection, type ServerHooks, type
|
|
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 };
|