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 GitHubRoutingPermissions,
17 makeGitHubRoutingPermissions,
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* makeGitHubRoutingPermissions({
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: (registration, routes) =>
63 catalog
64 .update((document) => registerConnectionInCatalog(document, registration, routes))
65 .pipe(Effect.mapError((error) => targetPersistenceError("register-connection", error))),
66 setRoutes: (environmentId, routes) =>
67 catalog
68 .update((document) => setRoutesInCatalog(document, environmentId, routes))
69 .pipe(Effect.mapError((error) => targetPersistenceError("set-connection-routes", error))),
70 remove: (environmentId) =>
71 catalog
72 .update((document) => removeConnectionFromCatalog(document, environmentId))
73 .pipe(Effect.mapError((error) => targetPersistenceError("remove-connection", error))),
74 setEnabled: (environmentId, enabled) =>
75 catalog
76 .update((document) => setConnectionEnabledInCatalog(document, environmentId, enabled))
77 .pipe(
78 Effect.mapError((error) => targetPersistenceError("set-connection-enabled", error)),
79 ),
80 });
81 const profileStore = ProfileStore.make({
82 get: (connectionId) =>
83 catalog.read.pipe(
84 Effect.map((document) =>
85 Option.fromUndefinedOr(
86 document.profiles.find((candidate) => candidate.connectionId === connectionId),
87 ),
88 ),
89 ),
90 put: (profile) =>
91 catalog.update((document) => ({
92 ...document,
93 profiles: replaceCatalogValue(document.profiles, (value) => value.connectionId, profile),
94 })),
95 remove: (connectionId) =>
96 catalog.update((document) => ({
97 ...document,
98 profiles: removeCatalogValue(
99 document.profiles,
100 (value) => value.connectionId,
101 connectionId,
102 ),
103 })),
104 });
105 const credentialStore = CredentialStore.make({
106 get: (connectionId) =>
107 catalog.read.pipe(
108 Effect.map((document) =>
109 Option.fromUndefinedOr(
110 document.credentials.find((entry) => entry.connectionId === connectionId)?.credential,
111 ),
112 ),
113 ),
114 put: (connectionId, credential) =>
115 catalog.update((document) => ({
116 ...document,
117 credentials: replaceCatalogValue(document.credentials, (value) => value.connectionId, {
118 connectionId,
119 credential,
120 }),
121 })),
122 remove: (connectionId) =>
123 catalog.update((document) => ({
124 ...document,
125 credentials: removeCatalogValue(
126 document.credentials,
127 (value) => value.connectionId,
128 connectionId,
129 ),
130 })),
131 });
132 const remoteTokenStore = TokenStore.make({
133 get: (environmentId) =>
134 catalog.read.pipe(
135 Effect.map((document) =>
136 Option.fromUndefinedOr(
137 document.remoteDpopTokens.find((token) => token.environmentId === environmentId),
138 ),
139 ),
140 ),
141 put: (token) => catalog.update((document) => putRemoteDpopTokenInCatalog(document, token)),
142 remove: (environmentId) =>
143 catalog.update((document) => ({
144 ...document,
145 remoteDpopTokens: removeCatalogValue(
146 document.remoteDpopTokens,
147 (value) => value.environmentId,
148 environmentId,
149 ),
150 })),
151 });
152 return Context.make(Persistence.ConnectionTargetStore, targetStore).pipe(
153 Context.add(GitHubRoutingPermissions, githubRoutingPermissions),
154 Context.add(Persistence.ConnectionRegistrationStore, registrationStore),
155 Context.add(ProfileStore.ConnectionProfileStore, profileStore),
156 Context.add(CredentialStore.ConnectionCredentialStore, credentialStore),
157 Context.add(TokenStore.RemoteDpopAccessTokenStore, remoteTokenStore),
158 );
159 }),
160);