1import {
2 EnvironmentAuthInvalidError,
3 type PullRequestDiffInput,
4 type PullRequestDiffResult,
5} from "@t3tools/contracts";
6import * as Context from "effect/Context";
7import * as Effect from "effect/Effect";
8import * as Layer from "effect/Layer";
9import * as Option from "effect/Option";
10import * as Schema from "effect/Schema";
11import { HttpClient } from "effect/http";
12
13import * as RemoteEnvironmentAuthorization from "../authorization/service.ts";
14import type { PreparedConnection } from "../connection/model.ts";
15import * as ManagedRelay from "../relay/managedRelay.ts";
16import { type RemoteEnvironmentRequestError } from "../rpc/http.ts";
17import { executeAuthenticatedEnvironmentHttpRequest } from "./environmentHttpAuth.ts";
18
19const DEFAULT_PULL_REQUEST_DIFF_TIMEOUT_MS = 60_000;
20
21export class PullRequestDiffCredentialRejectedError extends Schema.TaggedError<PullRequestDiffCredentialRejectedError>()(
22 "PullRequestDiffCredentialRejectedError",
23 {
24 repository: Schema.String,
25 number: Schema.Number,
26 traceId: Schema.String,
27 cause: EnvironmentAuthInvalidError,
28 },
29) {
30 override get message(): string {
31 return "This environment session is no longer valid (invalid_credential). Refresh the page or quit and reopen The Knife.";
32 }
33}
34
35export type PullRequestDiffLoadError =
36 | RemoteEnvironmentRequestError
37 | PullRequestDiffCredentialRejectedError;
38
39export const fetchEnvironmentPullRequestDiff = Effect.fn(
40 "clientRuntime.state.fetchEnvironmentPullRequestDiff",
41)(function* (input: {
42 readonly prepared: PreparedConnection;
43 readonly diff: PullRequestDiffInput;
44 readonly signer: Option.Option<ManagedRelay.ManagedRelayDpopSigner["Service"]>;
45 readonly remoteAuthorization?: Option.Option<
46 RemoteEnvironmentAuthorization.RemoteEnvironmentAuthorization["Service"]
47 >;
48 readonly timeoutMs?: number;
49}) {
50 return yield* executeAuthenticatedEnvironmentHttpRequest({
51 ...input,
52 group: "pullRequests",
53 method: "POST",
54 url: (urls) => urls.diff(),
55 timeoutMs: input.timeoutMs ?? DEFAULT_PULL_REQUEST_DIFF_TIMEOUT_MS,
56 request: ({ client, headers }) => client.diff({ payload: input.diff, headers }),
57 }).pipe(
58 Effect.mapError((error) =>
59 error._tag === "EnvironmentAuthInvalidError" && error.reason === "invalid_credential"
60 ? new PullRequestDiffCredentialRejectedError({
61 repository: input.diff.repository,
62 number: input.diff.number,
63 traceId: error.traceId,
64 cause: error,
65 })
66 : error,
67 ),
68 );
69});
70
71export class PullRequestDiffLoader extends Context.Service<
72 PullRequestDiffLoader,
73 {
74 readonly load: (
75 prepared: PreparedConnection,
76 input: PullRequestDiffInput,
77 ) => Effect.Effect<PullRequestDiffResult, PullRequestDiffLoadError>;
78 }
79>()("@t3tools/client-runtime/state/pullRequestDiffHttp/PullRequestDiffLoader") {}
80
81export const layer: Layer.Layer<PullRequestDiffLoader, never, HttpClient.HttpClient> = Layer.effect(
82 PullRequestDiffLoader,
83 Effect.gen(function* () {
84 const httpClient = yield* HttpClient.HttpClient;
85 const signer = yield* Effect.serviceOption(ManagedRelay.ManagedRelayDpopSigner);
86 const remoteAuthorization = yield* Effect.serviceOption(
87 RemoteEnvironmentAuthorization.RemoteEnvironmentAuthorization,
88 );
89 return PullRequestDiffLoader.of({
90 load: (prepared, input) =>
91 fetchEnvironmentPullRequestDiff({
92 prepared,
93 diff: input,
94 signer,
95 remoteAuthorization,
96 }).pipe(Effect.provideService(HttpClient.HttpClient, httpClient)),
97 });
98 }),
99);