pr_01m47d24b0e6n91zwymwxg0vpx/services/repos/src/artifacts-git-store.ts
Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Initial g1t: services, event bus, intents and attempts | 1 | import type { Commit, GitAccess, TreeEntry } from "@g1t/contracts"; |
| 2 | ||
| 3 | import type { GitStore } from "./git-store"; | |
| 4 | ||
| 5 | const TOKEN_TTL_SECONDS = 300; | |
| 6 | ||
| 7 | function isCode(error: unknown, code: ArtifactsErrorCode): boolean { | |
| 8 | return (error as Partial<ArtifactsError> | null)?.code === code; | |
| 9 | } | |
| 10 | ||
| 11 | function toCommit(commit: ArtifactsCommitMetadata): Commit { | |
| 12 | return { | |
| 13 | hash: commit.hash, | |
| 14 | treeHash: commit.treeHash, | |
| 15 | message: commit.message, | |
| 16 | author: commit.author, | |
| 17 | parents: commit.parents, | |
| 18 | authoredAt: commit.authoredAt * 1000, | |
| 19 | }; | |
| 20 | } | |
| 21 | ||
| 22 | /** {@link GitStore} backed by Cloudflare Artifacts. */ | |
| 23 | export class ArtifactsGitStore implements GitStore { | |
| 24 | constructor(private readonly artifacts: Artifacts) {} | |
| 25 | ||
| 26 | private async use<T>(key: string, fn: (repo: ArtifactsRepo) => Promise<T>): Promise<T> { | |
| 27 | const repo = await this.artifacts.get(key); | |
| 28 | try { | |
| 29 | return await fn(repo); | |
| 30 | } finally { | |
| 31 | repo[Symbol.dispose](); | |
| 32 | } | |
| 33 | } | |
| 34 | ||
| 35 | async create( | |
| 36 | key: string, | |
| 37 | options: { description?: string; defaultBranch: string }, | |
| 38 | ): Promise<void> { | |
| 39 | try { | |
| 40 | await this.artifacts.create(key, { | |
| 41 | description: options.description, | |
| 42 | setDefaultBranch: options.defaultBranch, | |
| 43 | }); | |
| 44 | } catch (error) { | |
| 45 | // Left behind by an earlier failed attempt; adopt it. | |
| 46 | if (!isCode(error, "ALREADY_EXISTS")) throw error; | |
| 47 | } | |
| 48 | } | |
| 49 | ||
| 50 | async fork(sourceKey: string, targetKey: string): Promise<void> { | |
| 51 | await this.use(sourceKey, async (repo) => { | |
| 52 | try { | |
| 53 | await repo.fork(targetKey, { defaultBranchOnly: true }); | |
| 54 | } catch (error) { | |
| 55 | if (!isCode(error, "ALREADY_EXISTS")) throw error; | |
| 56 | } | |
| 57 | }); | |
| 58 | } | |
| 59 | ||
| 60 | async access(key: string, scope: "read" | "write"): Promise<GitAccess> { | |
| 61 | return this.use(key, async (repo) => { | |
| 62 | const [info, token] = await Promise.all([ | |
| 63 | repo.info(), | |
| 64 | repo.createToken(scope, TOKEN_TTL_SECONDS), | |
| 65 | ]); | |
| 66 | return { remote: info.remote, token: token.plaintext }; | |
| 67 | }); | |
| 68 | } | |
| 69 | ||
| 70 | async log(key: string, ref: string, limit: number): Promise<Commit[]> { | |
| 71 | return this.use(key, async (repo) => | |
| 72 | (await repo.log({ ref, limit })).map(toCommit), | |
| 73 | ); | |
| 74 | } | |
| 75 | ||
| 76 | async readTree(key: string, treeHash: string): Promise<TreeEntry[] | null> { | |
| 77 | return this.use(key, async (repo) => { | |
| 78 | const entries = await repo.readTree(treeHash); | |
| 79 | return ( | |
| 80 | entries?.map((entry) => ({ | |
| 81 | name: entry.name, | |
| 82 | hash: entry.hash, | |
| 83 | kind: entry.type, | |
| 84 | })) ?? null | |
| 85 | ); | |
| 86 | }); | |
| 87 | } | |
| 88 | ||
| 89 | async readBlob(key: string, blobHash: string): Promise<Blob | null> { | |
| 90 | return this.use(key, (repo) => repo.readBlob(blobHash)); | |
| 91 | } | |
| 92 | ||
| 93 | async readFile(key: string, ref: string, path: string): Promise<Blob | null> { | |
| 94 | return this.use(key, (repo) => repo.readFile({ ref, path })); | |
| 95 | } | |
| 96 | } |