| 1 | /** |
| 2 | * The `refs` group: ref reads and transactional (CAS) writes (DESIGN.md §5). |
| 3 | * |
| 4 | * Ref names contain `/`, so the single-ref endpoints address the ref via |
| 5 | * the `name` query parameter, not a path segment. |
| 6 | */ |
| 7 | import * as HttpApiEndpoint from "effect/http-api/HttpApiEndpoint"; |
| 8 | import * as Schema from "effect/Schema"; |
| 9 | import * as HttpApiGroup from "effect/http-api/HttpApiGroup"; |
| 10 | import * as HttpApiSchema from "effect/http-api/HttpApiSchema"; |
| 11 | import { |
| 12 | ObjectNotFound, |
| 13 | Oid, |
| 14 | ReadOnlyRepo, |
| 15 | Ref, |
| 16 | RefConflict, |
| 17 | RefName, |
| 18 | RefNotFound, |
| 19 | RepoNotFound, |
| 20 | RepoPath, |
| 21 | PushDenied, |
| 22 | } from "./Schema.ts"; |
| 23 | |
| 24 | /** Lists refs, optionally filtered by prefix (e.g. `refs/heads/`). */ |
| 25 | export const ListRefs = HttpApiEndpoint.get( |
| 26 | "list", |
| 27 | "/repos/:owner/:repo/refs", |
| 28 | { |
| 29 | params: RepoPath, |
| 30 | query: Schema.Struct({ |
| 31 | /** Filter by prefix, e.g. `refs/heads/`. */ |
| 32 | prefix: Schema.optional(Schema.String), |
| 33 | }), |
| 34 | success: Schema.Struct({ |
| 35 | /** The default branch's full ref name, `null` on an unborn repo. */ |
| 36 | head: Schema.NullOr(Schema.String), |
| 37 | refs: Schema.Array(Ref), |
| 38 | }), |
| 39 | error: [RepoNotFound], |
| 40 | }, |
| 41 | ); |
| 42 | |
| 43 | /** Reads one ref by full name (`?name=refs/heads/main`). */ |
| 44 | export const GetRef = HttpApiEndpoint.get("get", "/repos/:owner/:repo/ref", { |
| 45 | params: RepoPath, |
| 46 | query: Schema.Struct({ name: RefName }), |
| 47 | success: Ref, |
| 48 | error: [RepoNotFound, RefNotFound], |
| 49 | }); |
| 50 | |
| 51 | /** Writes one ref with CAS semantics. */ |
| 52 | export const UpdateRef = HttpApiEndpoint.put( |
| 53 | "update", |
| 54 | "/repos/:owner/:repo/ref", |
| 55 | { |
| 56 | params: RepoPath, |
| 57 | query: Schema.Struct({ name: RefName }), |
| 58 | payload: Schema.Struct({ |
| 59 | newOid: Oid, |
| 60 | /** |
| 61 | * CAS token: current oid the ref must hold. `null` = the ref must |
| 62 | * not exist (create). Absent = unconditional write. |
| 63 | */ |
| 64 | expectedOid: Schema.optional(Schema.NullOr(Oid)), |
| 65 | }), |
| 66 | success: Ref, |
| 67 | error: [ |
| 68 | PushDenied, |
| 69 | RepoNotFound, |
| 70 | RefConflict, |
| 71 | ObjectNotFound, |
| 72 | ReadOnlyRepo, |
| 73 | ], |
| 74 | }, |
| 75 | ); |
| 76 | |
| 77 | /** Deletes one ref with CAS semantics. */ |
| 78 | export const RemoveRef = HttpApiEndpoint.delete( |
| 79 | "remove", |
| 80 | "/repos/:owner/:repo/ref", |
| 81 | { |
| 82 | params: RepoPath, |
| 83 | query: Schema.Struct({ name: RefName }), |
| 84 | payload: Schema.Struct({ |
| 85 | /** CAS token: current oid the ref must hold. Absent = unconditional. */ |
| 86 | expectedOid: Schema.optional(Oid), |
| 87 | }), |
| 88 | success: HttpApiSchema.NoContent, |
| 89 | error: [PushDenied, RepoNotFound, RefNotFound, RefConflict, ReadOnlyRepo], |
| 90 | }, |
| 91 | ); |
| 92 | |
| 93 | /** The `refs` group, mounted at `/api/v1`. */ |
| 94 | export class Refs extends HttpApiGroup.make("refs") |
| 95 | .add(ListRefs, GetRef, UpdateRef, RemoveRef) |
| 96 | .prefix("/api/v1") {} |