1import {
2 putRemoteDpopTokenInCatalog,
3 registerConnectionInCatalog,
4 removeConnectionFromCatalog,
5 setConnectionEnabledInCatalog,
6 setRoutesInCatalog,
7 removeCatalogValue,
8 replaceCatalogValue,
9 Persistence,
10} from "@t3tools/client-runtime/platform";
11import { TokenStore } from "@t3tools/client-runtime/authorization";
12import {
13 ConnectionTransientError,
14 CredentialStore,
15 ProfileStore,
16 PullRequestRoutingPermissions,
17 makePullRequestRoutingPermissions,
18} from "@t3tools/client-runtime/connection";
19import * as Context from "effect/Context";
20import * as Effect from "effect/Effect";
21import * as Layer from "effect/Layer";
22import * as Option from "effect/Option";
23
24import * as CatalogStore from "./catalog-store";
25
26function targetPersistenceError(
27 operation:
28 | "list-targets"
29 | "list-disabled-targets"
30 | "register-connection"
31 | "set-connection-routes"
32 | "remove-connection"
33 | "set-connection-enabled",
34 error: ConnectionTransientError,
35) {
36 return new Persistence.ConnectionPersistenceError({
37 operation,
38 message: error.message,
39 });
40}
41
42export const layer = Layer.effectContext(
43 Effect.gen(function* () {
44 const catalog = yield* CatalogStore.make();
45 const githubRoutingPermissions = yield* makePullRequestRoutingPermissions({
46 read: catalog.read.pipe(Effect.map((document) => document.githubRoutingPermissions ?? [])),
47 write: (githubRoutingPermissions) =>
48 catalog.update((document) => ({ ...document, githubRoutingPermissions })),
49 });
50
51 const targetStore = Persistence.ConnectionTargetStore.of({
52 list: catalog.read.pipe(
53 Effect.map((document) => document.targets),
54 Effect.mapError((error) => targetPersistenceError("list-targets", error)),
55 ),
56 listDisabled: catalog.read.pipe(
57 Effect.map((document) => document.disabledEnvironmentIds),
58 Effect.mapError((error) => targetPersistenceError("list-disabled-targets", error)),
59 ),
60 });
61 const registrationStore = Persistence.ConnectionRegistrationStore.of({
62 register: Effect.fn("Persistence.ConnectionRegistrationStore.register")(
63 (registration, routes) =>
64 catalog
65 .update((document) => registerConnectionInCatalog(document, registration, routes))
66 .pipe(Effect.mapError((error) => targetPersistenceError("register-connection", error))),
67 ),
68 setRoutes: Effect.fn("Persistence.ConnectionRegistrationStore.setRoutes")(
69 (environmentId, routes) =>
70 catalog
71 .update((document) => setRoutesInCatalog(document, environmentId, routes))
72 .pipe(
73 Effect.mapError((error) => targetPersistenceError("set-connection-routes", error)),
74 ),
75 ),
76 remove: Effect.fn("Persistence.ConnectionRegistrationStore.remove")((environmentId) =>
77 catalog
78 .update((document) => removeConnectionFromCatalog(document, environmentId))
79 .pipe(Effect.mapError((error) => targetPersistenceError("remove-connection", error))),
80 ),
81 setEnabled: Effect.fn("Persistence.ConnectionRegistrationStore.setEnabled")(
82 (environmentId, enabled) =>
83 catalog
84 .update((document) => setConnectionEnabledInCatalog(document, environmentId, enabled))
85 .pipe(
86 Effect.mapError((error) => targetPersistenceError("set-connection-enabled", error)),
87 ),
88 ),
89 });
90 const profileStore = ProfileStore.make({
91 get: (connectionId) =>
92 catalog.read.pipe(
93 Effect.map((document) =>
94 Option.fromUndefinedOr(
95 document.profiles.find((candidate) => candidate.connectionId === connectionId),
96 ),
97 ),
98 ),
99 put: (profile) =>
100 catalog.update((document) => ({
101 ...document,
102 profiles: replaceCatalogValue(document.profiles, (value) => value.connectionId, profile),
103 })),
104 remove: (connectionId) =>
105 catalog.update((document) => ({
106 ...document,
107 profiles: removeCatalogValue(
108 document.profiles,
109 (value) => value.connectionId,
110 connectionId,
111 ),
112 })),
113 });
114 const credentialStore = CredentialStore.make({
115 get: (connectionId) =>
116 catalog.read.pipe(
117 Effect.map((document) =>
118 Option.fromUndefinedOr(
119 document.credentials.find((entry) => entry.connectionId === connectionId)?.credential,
120 ),
121 ),
122 ),
123 put: (connectionId, credential) =>
124 catalog.update((document) => ({
125 ...document,
126 credentials: replaceCatalogValue(document.credentials, (value) => value.connectionId, {
127 connectionId,
128 credential,
129 }),
130 })),
131 remove: (connectionId) =>
132 catalog.update((document) => ({
133 ...document,
134 credentials: removeCatalogValue(
135 document.credentials,
136 (value) => value.connectionId,
137 connectionId,
138 ),
139 })),
140 });
141 const remoteTokenStore = TokenStore.make({
142 get: (environmentId) =>
143 catalog.read.pipe(
144 Effect.map((document) =>
145 Option.fromUndefinedOr(
146 document.remoteDpopTokens.find((token) => token.environmentId === environmentId),
147 ),
148 ),
149 ),
150 put: (token) => catalog.update((document) => putRemoteDpopTokenInCatalog(document, token)),
151 remove: (environmentId) =>
152 catalog.update((document) => ({
153 ...document,
154 remoteDpopTokens: removeCatalogValue(
155 document.remoteDpopTokens,
156 (value) => value.environmentId,
157 environmentId,
158 ),
159 })),
160 });
161 return Context.make(Persistence.ConnectionTargetStore, targetStore).pipe(
162 Context.add(PullRequestRoutingPermissions, githubRoutingPermissions),
163 Context.add(Persistence.ConnectionRegistrationStore, registrationStore),
164 Context.add(ProfileStore.ConnectionProfileStore, profileStore),
165 Context.add(CredentialStore.ConnectionCredentialStore, credentialStore),
166 Context.add(TokenStore.RemoteDpopAccessTokenStore, remoteTokenStore),
167 );
168 }),
169);