Skip to content
Source
Sign inSign up

pull-requests.ts

96 lines4.1 KB

1import {
2 createPullRequestEnvironmentAtoms,
3 createPullRequestStackAtomFamily,
4 patchPullRequestListEntries,
5} from "@t3tools/client-runtime/state/pull-requests";
6import type { EnvironmentId, PullRequestListInput } from "@t3tools/contracts";
7import * as Effect from "effect/Effect";
8import * as Option from "effect/Option";
9import { AsyncResult, Atom } from "effect/reactivity";
10import { connectionAtomRuntime } from "../connection/runtime";
11import {
12 EMPTY_PULL_REQUEST_STATS_CACHE,
13 pullRequestListHead,
14 withAnsweredPullRequestStats,
15 withListedPullRequests,
16 type PullRequestListPages,
17 type PullRequestStatsCache,
18} from "../features/pull-requests/pullRequestList";
19​
20export const pullRequestListPagesKey = (target: {
21 readonly environmentId: EnvironmentId;
22 readonly input: PullRequestListInput;
23}) => JSON.stringify([target.environmentId, target.input]);
24​
25// Which listing each pages atom holds, so an action's reply can reach the pages past its head.
26const pullRequestListPagesTargets = new WeakMap<
27 object,
28 readonly [EnvironmentId, PullRequestListInput]
29>();
30/** The pages loaded past each listing's head, keyed by {@link pullRequestListPagesKey}. */
31export const pullRequestListPagesFamily = Atom.family((key: string) => {
32 const atom = Atom.make<PullRequestListPages | null>(null).pipe(Atom.setIdleTTL(5 * 60_000));
33 pullRequestListPagesTargets.set(atom, JSON.parse(key));
34 return atom;
35});
36​
37/** Diff stats each environment has answered so far, so rows keep them when they scroll away. */
38export const pullRequestListStatsFamily = Atom.family((_environmentId: string) =>
39 Atom.make<PullRequestStatsCache>(EMPTY_PULL_REQUEST_STATS_CACHE).pipe(
40 Atom.setIdleTTL(5 * 60_000),
41 ),
42);
43​
44export const composerPullRequests = createPullRequestEnvironmentAtoms(connectionAtomRuntime, {
45 // Rows on pages past each listing's head; the head itself is patched where it is cached.
46 actionReply: (target, reply, registry) =>
47 Effect.sync(() => {
48 for (const atom of registry.getNodes().keys()) {
49 const listing =
50 typeof atom === "string" ? undefined : pullRequestListPagesTargets.get(atom);
51 if (listing === undefined || listing[0] !== target.environmentId) continue;
52 registry.update(atom as Atom.Writable<PullRequestListPages | null>, (pages) => {
53 if (pages === null) return pages;
54 const entries = patchPullRequestListEntries(
55 listing[1],
56 pages.entries,
57 target.input,
58 reply,
59 );
60 return entries === "refresh" || entries === pages.entries ? pages : { ...pages, entries };
61 });
62 }
63 }),
64 // A new head can change which files GitHub still counts as viewed, so the viewed-files read is
65 // refreshed where the new head lands. The detail atom still holds the previous answer here.
66 detail: (target, value, registry) =>
67 Effect.sync(() => {
68 const previous = AsyncResult.value(registry.get(composerPullRequests.detail(target)));
69 if (Option.isSome(previous) && previous.value.headSha !== value.headSha)
70 registry.refresh(composerPullRequests.filesViewed(target));
71 }),
72 list: (target, value, registry) =>
73 Effect.sync(() => {
74 const statsAtom = pullRequestListStatsFamily(target.environmentId);
75 const { cache, refresh } = withListedPullRequests(registry.get(statsAtom), value.entries);
76 registry.set(statsAtom, cache);
77 for (const input of refresh)
78 registry.refresh(
79 composerPullRequests.listStats({ environmentId: target.environmentId, input }),
80 );
81 if (target.input.cursors === undefined)
82 registry.update(pullRequestListPagesFamily(pullRequestListPagesKey(target)), (previous) =>
83 pullRequestListHead(previous, value),
84 );
85 }),
86 listStats: (target, value, registry) =>
87 Effect.sync(() =>
88 registry.update(pullRequestListStatsFamily(target.environmentId), (previous) =>
89 withAnsweredPullRequestStats(previous, target.input, value.stats),
90 ),
91 ),
92});
93export const pullRequestStackAtom = createPullRequestStackAtomFamily(
94 connectionAtomRuntime,
95 composerPullRequests.refreshes,
96);