1import { SharedAccountError } from "@t3tools/contracts";
2import * as Context from "effect/Context";
3import * as Effect from "effect/Effect";
4import * as Layer from "effect/Layer";
5import * as Option from "effect/Option";
6import * as Redacted from "effect/Redacted";
7import * as Schema from "effect/Schema";
8import * as ServerSecretStore from "../auth/ServerSecretStore.ts";
9
10const Connection = Schema.Struct({ url: Schema.String, token: Schema.NonEmptyString });
11const decode = Schema.decodeEffect(Schema.fromJsonString(Connection));
12const encode = Schema.encodeEffect(Schema.fromJsonString(Connection));
13
14export class SharedServer extends Context.Service<
15 SharedServer,
16 {
17 readonly connection: Effect.Effect<Option.Option<typeof Connection.Type>, SharedAccountError>;
18 readonly save: (connection: typeof Connection.Type) => Effect.Effect<void, SharedAccountError>;
19 readonly disconnect: Effect.Effect<void, SharedAccountError>;
20 }
21>()("t3/environment/SharedServer") {}
22
23export const layer = Layer.effect(
24 SharedServer,
25 Effect.gen(function* () {
26 const secrets = yield* ServerSecretStore.ServerSecretStore;
27 const storageError = (cause: unknown) =>
28 new SharedAccountError({ reason: "storage", cause: Redacted.make(cause) });
29 return SharedServer.of({
30 connection: secrets.get("shared-server").pipe(
31 Effect.flatMap((bytes) =>
32 Option.isNone(bytes)
33 ? Effect.succeedNone
34 : decode(new TextDecoder().decode(bytes.value)).pipe(Effect.asSome),
35 ),
36 Effect.mapError(storageError),
37 ),
38 save: (value) =>
39 encode(value).pipe(
40 Effect.flatMap((json) => secrets.set("shared-server", new TextEncoder().encode(json))),
41 Effect.mapError(storageError),
42 ),
43 disconnect: secrets.remove("shared-server").pipe(Effect.mapError(storageError)),
44 });
45 }),
46);