authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-06-19 21:21:15-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-06-19 21:21:15-07:00
log4ee719a094d4c4f30fab88c1f6969483890e223e
tree4b9dfc31b8333e631e544b39eeee7f0c8b22a3b2
parent01492ebd3b624e544e6375c12540b741c65256b9
signature Signed by PGP key B5690EEEBB952194

[codex] refactor server cloud Effect services (#3183)

Co-authored-by: codex <codex@users.noreply.github.com>

5 files changed, 180 insertions(+), 140 deletions(-)

apps/server/src/cloud/CliTokenManager.ts+71-31
......@@ -6,7 +6,6 @@ import * as Clock from "effect/Clock";
66import * as Console from "effect/Console";
77import * as Context from "effect/Context";
88import * as Crypto from "effect/Crypto";
9import * as Data from "effect/Data";
109import * as Deferred from "effect/Deferred";
1110import * as Duration from "effect/Duration";
1211import * as Effect from "effect/Effect";
......@@ -15,10 +14,12 @@ import * as Layer from "effect/Layer";
1514import * as Option from "effect/Option";
1615import * as Schema from "effect/Schema";
1716import * as Semaphore from "effect/Semaphore";
17import * as HttpClient from "effect/unstable/http/HttpClient";
18import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest";
19import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse";
1820import * as HttpRouter from "effect/unstable/http/HttpRouter";
1921import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest";
2022import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";
21import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";
2223
2324import * as ServerSecretStore from "../auth/ServerSecretStore.ts";
2425import { cloudCliOAuthConfig, type CloudCliOAuthConfig } from "./publicConfig.ts";
......@@ -45,35 +46,74 @@ const OAuthTokenResponse = Schema.Struct({
4546 token_type: Schema.String,
4647});
4748
48export class CloudCliTokenManagerError extends Data.TaggedError("CloudCliTokenManagerError")<{
49 readonly message: string;
50 readonly cause?: unknown;
51}> {}
49export class CloudCliCredentialRemovalError extends Schema.TaggedErrorClass<CloudCliCredentialRemovalError>()(
50 "CloudCliCredentialRemovalError",
51 { cause: Schema.Defect() },
52) {
53 override get message(): string {
54 return "Could not remove the stored T3 Connect CLI credential.";
55 }
56}
57
58export class CloudCliCredentialRefreshError extends Schema.TaggedErrorClass<CloudCliCredentialRefreshError>()(
59 "CloudCliCredentialRefreshError",
60 { cause: Schema.Defect() },
61) {
62 override get message(): string {
63 return "Could not refresh the T3 Connect CLI credential.";
64 }
65}
66
67export class CloudCliCredentialReadError extends Schema.TaggedErrorClass<CloudCliCredentialReadError>()(
68 "CloudCliCredentialReadError",
69 { cause: Schema.Defect() },
70) {
71 override get message(): string {
72 return "Could not read the stored T3 Connect CLI credential.";
73 }
74}
75
76export class CloudCliAuthorizationError extends Schema.TaggedErrorClass<CloudCliAuthorizationError>()(
77 "CloudCliAuthorizationError",
78 { cause: Schema.Defect() },
79) {
80 override get message(): string {
81 return "Could not authorize the T3 Connect CLI.";
82 }
83}
5284
53export interface CloudCliTokenManagerShape {
54 readonly get: Effect.Effect<PersistedToken, CloudCliTokenManagerError>;
55 readonly getExisting: Effect.Effect<Option.Option<PersistedToken>, CloudCliTokenManagerError>;
56 readonly hasCredential: Effect.Effect<boolean, CloudCliTokenManagerError>;
57 readonly clear: Effect.Effect<void, CloudCliTokenManagerError>;
85export class CloudCliAuthorizationTimeoutError extends Schema.TaggedErrorClass<CloudCliAuthorizationTimeoutError>()(
86 "CloudCliAuthorizationTimeoutError",
87 { cause: Schema.Defect() },
88) {
89 override get message(): string {
90 return "Timed out waiting for T3 Connect authorization.";
91 }
5892}
5993
94export const CloudCliTokenManagerError = Schema.Union([
95 CloudCliCredentialRemovalError,
96 CloudCliCredentialRefreshError,
97 CloudCliCredentialReadError,
98 CloudCliAuthorizationError,
99 CloudCliAuthorizationTimeoutError,
100]);
101export type CloudCliTokenManagerError = typeof CloudCliTokenManagerError.Type;
102
60103export class CloudCliTokenManager extends Context.Service<
61104 CloudCliTokenManager,
62 CloudCliTokenManagerShape
105 {
106 readonly get: Effect.Effect<PersistedToken, CloudCliTokenManagerError>;
107 readonly getExisting: Effect.Effect<Option.Option<PersistedToken>, CloudCliTokenManagerError>;
108 readonly hasCredential: Effect.Effect<boolean, CloudCliTokenManagerError>;
109 readonly clear: Effect.Effect<void, CloudCliTokenManagerError>;
110 }
63111>()("t3/cloud/CliTokenManager/CloudCliTokenManager") {}
64112
65113const wrapError =
66 (message: string) =>
67 <A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, CloudCliTokenManagerError, R> =>
68 effect.pipe(
69 Effect.mapError(
70 (cause) =>
71 new CloudCliTokenManagerError({
72 message,
73 cause,
74 }),
75 ),
76 );
114 <WrappedError extends CloudCliTokenManagerError>(makeError: (cause: unknown) => WrappedError) =>
115 <A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, WrappedError, R> =>
116 effect.pipe(Effect.mapError(makeError));
77117
78118function stringToBytes(value: string): Uint8Array {
79119 return new TextEncoder().encode(value);
......@@ -83,7 +123,7 @@ function bytesToString(value: Uint8Array): string {
83123 return new TextDecoder().decode(value);
84124}
85125
86const make = Effect.gen(function* () {
126export const make = Effect.gen(function* () {
87127 const crypto = yield* Crypto.Crypto;
88128 const httpClient = (yield* HttpClient.HttpClient).pipe(HttpClient.filterStatusOk);
89129 const secrets = yield* ServerSecretStore.ServerSecretStore;
......@@ -96,7 +136,7 @@ const make = Effect.gen(function* () {
96136
97137 const clear = secrets
98138 .remove(CLOUD_CLI_OAUTH_TOKEN_SECRET)
99 .pipe(wrapError("Could not remove the stored T3 Connect CLI credential."));
139 .pipe(wrapError((cause) => new CloudCliCredentialRemovalError({ cause })));
100140
101141 const read = Effect.fn("cloud.cli_token.read")(function* () {
102142 const encoded = yield* secrets.get(CLOUD_CLI_OAUTH_TOKEN_SECRET);
......@@ -185,10 +225,10 @@ const make = Effect.gen(function* () {
185225 yield* Console.log(`Open this URL to authorize T3 Connect:\n${authorizationUrl.toString()}\n`);
186226 const code = yield* Deferred.await(callback).pipe(
187227 Effect.timeout(CLOUD_CLI_OAUTH_CALLBACK_TIMEOUT),
188 Effect.catchTag("TimeoutError", () =>
228 Effect.catchTag("TimeoutError", (cause) =>
189229 Effect.fail(
190 new CloudCliTokenManagerError({
191 message: "Timed out waiting for T3 Connect authorization.",
230 new CloudCliAuthorizationTimeoutError({
231 cause,
192232 }),
193233 ),
194234 ),
......@@ -213,12 +253,12 @@ const make = Effect.gen(function* () {
213253 });
214254
215255 const getExisting = semaphore.withPermits(1)(
216 getExistingNoLock().pipe(wrapError("Could not refresh the T3 Connect CLI credential.")),
256 getExistingNoLock().pipe(wrapError((cause) => new CloudCliCredentialRefreshError({ cause }))),
217257 );
218258 const hasCredential = semaphore.withPermits(1)(
219259 read().pipe(
220260 Effect.map(Option.isSome),
221 wrapError("Could not read the stored T3 Connect CLI credential."),
261 wrapError((cause) => new CloudCliCredentialReadError({ cause })),
222262 ),
223263 );
224264 const get = semaphore.withPermits(1)(
......@@ -227,7 +267,7 @@ const make = Effect.gen(function* () {
227267 return Option.isSome(token)
228268 ? token.value
229269 : yield* Effect.scoped(login()).pipe(Effect.flatMap(persist));
230 }).pipe(wrapError("Could not authorize the T3 Connect CLI.")),
270 }).pipe(wrapError((cause) => new CloudCliAuthorizationError({ cause }))),
231271 );
232272
233273 return CloudCliTokenManager.of({ get, getExisting, hasCredential, clear });
apps/server/src/cloud/ManagedEndpointRuntime.test.ts+53-45
......@@ -4,16 +4,15 @@ import * as Deferred from "effect/Deferred";
44import * as Effect from "effect/Effect";
55import * as Fiber from "effect/Fiber";
66import * as Layer from "effect/Layer";
7import * as Option from "effect/Option";
78import * as PlatformError from "effect/PlatformError";
89import * as Sink from "effect/Sink";
910import * as Stream from "effect/Stream";
1011import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
1112import * as RelayClient from "@t3tools/shared/relayClient";
1213
13import {
14 classifyRelayClientOutput,
15 makeCloudManagedEndpointRuntime,
16} from "./ManagedEndpointRuntime.ts";
14import * as ServerSecretStore from "../auth/ServerSecretStore.ts";
15import * as ManagedEndpointRuntime from "./ManagedEndpointRuntime.ts";
1716
1817const relayClientAvailableLayer = Layer.succeed(
1918 RelayClient.RelayClient,
......@@ -29,12 +28,33 @@ const relayClientAvailableLayer = Layer.succeed(
2928 }),
3029);
3130
32const runtimeDependencies = (spawner: ReturnType<typeof ChildProcessSpawner.make>) =>
31const runtimeDependencies = (
32 spawner: ReturnType<typeof ChildProcessSpawner.make>,
33 relayClientLayer = relayClientAvailableLayer,
34) =>
3335 Layer.mergeAll(
3436 Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner),
35 relayClientAvailableLayer,
37 relayClientLayer,
38 Layer.mock(ServerSecretStore.ServerSecretStore)({
39 get: () => Effect.succeed(Option.none()),
40 }),
3641 );
3742
43const buildCloudManagedEndpointRuntime = (
44 spawner: ReturnType<typeof ChildProcessSpawner.make>,
45 relayClientLayer = relayClientAvailableLayer,
46) =>
47 Effect.gen(function* () {
48 const context = yield* Layer.build(
49 ManagedEndpointRuntime.layer.pipe(
50 Layer.provide(runtimeDependencies(spawner, relayClientLayer)),
51 ),
52 );
53 return yield* Effect.service(ManagedEndpointRuntime.CloudManagedEndpointRuntime).pipe(
54 Effect.provide(context),
55 );
56 });
57
3858function makeHandle(input: {
3959 readonly pid: number;
4060 readonly onKill: () => void;
......@@ -62,16 +82,20 @@ function makeHandle(input: {
6282describe("CloudManagedEndpointRuntime", () => {
6383 it("classifies Cloudflare connection and warning output", () => {
6484 expect(
65 classifyRelayClientOutput(
85 ManagedEndpointRuntime.classifyRelayClientOutput(
6686 "2026-06-17T02:00:00Z INF Registered tunnel connection connIndex=0",
6787 ),
6888 ).toBe("connected");
6989 expect(
70 classifyRelayClientOutput("2026-06-17T02:00:00Z ERR Failed to serve tunnel connection"),
90 ManagedEndpointRuntime.classifyRelayClientOutput(
91 "2026-06-17T02:00:00Z ERR Failed to serve tunnel connection",
92 ),
7193 ).toBe("warning");
72 expect(classifyRelayClientOutput("2026-06-17T02:00:00Z INF Starting metrics server")).toBe(
73 "debug",
74 );
94 expect(
95 ManagedEndpointRuntime.classifyRelayClientOutput(
96 "2026-06-17T02:00:00Z INF Starting metrics server",
97 ),
98 ).toBe("debug");
7599 });
76100
77101 it.effect("starts, deduplicates, rotates, and stops the Cloudflare connector", () =>
......@@ -97,9 +121,7 @@ describe("CloudManagedEndpointRuntime", () => {
97121 return handle;
98122 }),
99123 );
100 const runtime = yield* makeCloudManagedEndpointRuntime.pipe(
101 Effect.provide(runtimeDependencies(spawner)),
102 );
124 const runtime = yield* buildCloudManagedEndpointRuntime(spawner);
103125
104126 yield* runtime.applyConfig({
105127 providerKind: "cloudflare_tunnel",
......@@ -154,9 +176,7 @@ describe("CloudManagedEndpointRuntime", () => {
154176 return handle;
155177 }),
156178 );
157 const runtime = yield* makeCloudManagedEndpointRuntime.pipe(
158 Effect.provide(runtimeDependencies(spawner)),
159 );
179 const runtime = yield* buildCloudManagedEndpointRuntime(spawner);
160180
161181 const started = yield* runtime.applyConfig({
162182 providerKind: "cloudflare_tunnel",
......@@ -193,9 +213,7 @@ describe("CloudManagedEndpointRuntime", () => {
193213 return handle;
194214 }),
195215 );
196 const runtime = yield* makeCloudManagedEndpointRuntime.pipe(
197 Effect.provide(runtimeDependencies(spawner)),
198 );
216 const runtime = yield* buildCloudManagedEndpointRuntime(spawner);
199217 const config = {
200218 providerKind: "cloudflare_tunnel" as const,
201219 connectorToken: "token",
......@@ -240,9 +258,7 @@ describe("CloudManagedEndpointRuntime", () => {
240258 return handle;
241259 }),
242260 );
243 const runtime = yield* makeCloudManagedEndpointRuntime.pipe(
244 Effect.provide(runtimeDependencies(spawner)),
245 );
261 const runtime = yield* buildCloudManagedEndpointRuntime(spawner);
246262
247263 const started = yield* runtime.applyConfig({
248264 providerKind: "cloudflare_tunnel",
......@@ -282,9 +298,7 @@ describe("CloudManagedEndpointRuntime", () => {
282298 return handle;
283299 }),
284300 );
285 const runtime = yield* makeCloudManagedEndpointRuntime.pipe(
286 Effect.provide(runtimeDependencies(spawner)),
287 );
301 const runtime = yield* buildCloudManagedEndpointRuntime(spawner);
288302
289303 const first = yield* runtime
290304 .applyConfig({
......@@ -322,9 +336,7 @@ describe("CloudManagedEndpointRuntime", () => {
322336 }),
323337 ),
324338 );
325 const runtime = yield* makeCloudManagedEndpointRuntime.pipe(
326 Effect.provide(runtimeDependencies(spawner)),
327 );
339 const runtime = yield* buildCloudManagedEndpointRuntime(spawner);
328340
329341 const status = yield* runtime.applyConfig({
330342 providerKind: "cloudflare_tunnel",
......@@ -344,22 +356,18 @@ describe("CloudManagedEndpointRuntime", () => {
344356 Effect.gen(function* () {
345357 const spawn = vi.fn();
346358 const spawner = ChildProcessSpawner.make(spawn);
347 const runtime = yield* makeCloudManagedEndpointRuntime.pipe(
348 Effect.provide(
349 Layer.mergeAll(
350 Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner),
351 Layer.succeed(
352 RelayClient.RelayClient,
353 RelayClient.RelayClient.of({
354 resolve: Effect.succeed({
355 status: "missing",
356 version: RelayClient.CLOUDFLARED_VERSION,
357 }),
358 install: Effect.die("unused"),
359 installWithProgress: () => Effect.die("unused"),
360 }),
361 ),
362 ),
359 const runtime = yield* buildCloudManagedEndpointRuntime(
360 spawner,
361 Layer.succeed(
362 RelayClient.RelayClient,
363 RelayClient.RelayClient.of({
364 resolve: Effect.succeed({
365 status: "missing",
366 version: RelayClient.CLOUDFLARED_VERSION,
367 }),
368 install: Effect.die("unused"),
369 installWithProgress: () => Effect.die("unused"),
370 }),
363371 ),
364372 );
365373
apps/server/src/cloud/ManagedEndpointRuntime.ts+26-31
......@@ -10,7 +10,8 @@ import * as Result from "effect/Result";
1010import * as Semaphore from "effect/Semaphore";
1111import * as Scope from "effect/Scope";
1212import * as Stream from "effect/Stream";
13import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
13import * as ChildProcess from "effect/unstable/process/ChildProcess";
14import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner";
1415
1516import * as ServerSecretStore from "../auth/ServerSecretStore.ts";
1617import { CLOUD_ENDPOINT_RUNTIME_CONFIG, decodeRuntimeConfig } from "./config.ts";
......@@ -28,17 +29,6 @@ const readRuntimeConfig = Effect.gen(function* () {
2829 return Option.getOrNull(decodeRuntimeConfig(bytesToString(bytes.value)));
2930});
3031
31export interface CloudManagedEndpointRuntimeShape {
32 readonly applyConfig: (
33 config: RelayManagedEndpointRuntimeConfig | null,
34 ) => Effect.Effect<CloudManagedEndpointRuntimeStatus>;
35}
36
37export class CloudManagedEndpointRuntime extends Context.Service<
38 CloudManagedEndpointRuntime,
39 CloudManagedEndpointRuntimeShape
40>()("t3/cloud/ManagedEndpointRuntime/CloudManagedEndpointRuntime") {}
41
4232export type CloudManagedEndpointRuntimeStatus =
4333 | {
4434 readonly status: "disabled";
......@@ -62,6 +52,15 @@ export type CloudManagedEndpointRuntimeStatus =
6252 readonly providerKind: RelayManagedEndpointRuntimeConfig["providerKind"];
6353 };
6454
55export class CloudManagedEndpointRuntime extends Context.Service<
56 CloudManagedEndpointRuntime,
57 {
58 readonly applyConfig: (
59 config: RelayManagedEndpointRuntimeConfig | null,
60 ) => Effect.Effect<CloudManagedEndpointRuntimeStatus>;
61 }
62>()("t3/cloud/ManagedEndpointRuntime/CloudManagedEndpointRuntime") {}
63
6564interface ActiveConnector {
6665 readonly child: ChildProcessSpawner.ChildProcessHandle;
6766 readonly scope: Scope.Closeable;
......@@ -97,13 +96,13 @@ const stopConnector = (connector: ActiveConnector | null) =>
9796 )
9897 : Effect.void;
9998
100export const makeCloudManagedEndpointRuntime = Effect.gen(function* () {
99export const make = Effect.gen(function* () {
101100 const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
102101 const relayClient = yield* RelayClient.RelayClient;
103102 const activeRef = yield* Ref.make<ActiveConnector | null>(null);
104103 const desiredConfigRef = yield* Ref.make<RelayManagedEndpointRuntimeConfig | null>(null);
105104 const reconcileSemaphore = yield* Semaphore.make(1);
106 let reconcileConfig: CloudManagedEndpointRuntimeShape["applyConfig"];
105 let reconcileConfig: CloudManagedEndpointRuntime["Service"]["applyConfig"];
107106
108107 const stopActive = Effect.gen(function* () {
109108 const active = yield* Ref.getAndSet(activeRef, null);
......@@ -301,24 +300,20 @@ export const makeCloudManagedEndpointRuntime = Effect.gen(function* () {
301300 ),
302301 );
303302
304 return CloudManagedEndpointRuntime.of({
303 const runtime = CloudManagedEndpointRuntime.of({
305304 applyConfig,
306305 });
307});
308306
309export const layer = Layer.effect(
310 CloudManagedEndpointRuntime,
311 Effect.gen(function* () {
312 const runtime = yield* makeCloudManagedEndpointRuntime;
313 const initialConfig = yield* readRuntimeConfig.pipe(
314 Effect.catch((cause) =>
315 Effect.logWarning("Failed to read managed endpoint runtime config", { cause }).pipe(
316 Effect.as(null),
317 ),
307 const initialConfig = yield* readRuntimeConfig.pipe(
308 Effect.catch((cause) =>
309 Effect.logWarning("Failed to read managed endpoint runtime config", { cause }).pipe(
310 Effect.as(null),
318311 ),
319 );
320 yield* runtime.applyConfig(initialConfig);
321 yield* Effect.addFinalizer(() => runtime.applyConfig(null));
322 return runtime;
323 }),
324);
312 ),
313 );
314 yield* runtime.applyConfig(initialConfig);
315 yield* Effect.addFinalizer(() => runtime.applyConfig(null));
316 return runtime;
317});
318
319export const layer = Layer.effect(CloudManagedEndpointRuntime, make);
apps/server/src/cloud/http.test.ts+10-13
......@@ -9,13 +9,10 @@ import { HttpClient, HttpServerRequest } from "effect/unstable/http";
99import { RelayClientTracer } from "@t3tools/shared/relayTracing";
1010import * as EnvironmentAuth from "../auth/EnvironmentAuth.ts";
1111import * as ServerSecretStore from "../auth/ServerSecretStore.ts";
12import { ServerEnvironment } from "../environment/Services/ServerEnvironment.ts";
12import * as ServerEnvironment from "../environment/Services/ServerEnvironment.ts";
1313import * as CliTokenManager from "./CliTokenManager.ts";
1414import { consumeCloudReplayGuards, reconcileDesiredCloudLink } from "./http.ts";
15import {
16 CloudManagedEndpointRuntime,
17 type CloudManagedEndpointRuntimeShape,
18} from "./ManagedEndpointRuntime.ts";
15import * as ManagedEndpointRuntime from "./ManagedEndpointRuntime.ts";
1916import { traceAuthenticatedRelayRequest, traceRelayRequest } from "./traceRelayRequest.ts";
2017
2118const storeFailure = (tag: "AlreadyExists" | "PermissionDenied") =>
......@@ -32,8 +29,8 @@ const storeFailure = (tag: "AlreadyExists" | "PermissionDenied") =>
3229const unusedSecretStoreOperation = () => Effect.die("unused secret-store operation");
3330
3431function makeSecretStore(
35 create: ServerSecretStore.ServerSecretStoreShape["create"],
36): ServerSecretStore.ServerSecretStoreShape {
32 create: ServerSecretStore.ServerSecretStore["Service"]["create"],
33): ServerSecretStore.ServerSecretStore["Service"] {
3734 return {
3835 get: unusedSecretStoreOperation,
3936 set: unusedSecretStoreOperation,
......@@ -151,21 +148,21 @@ describe("reconcileDesiredCloudLink", () => {
151148 makeSecretStore(unusedSecretStoreOperation),
152149 ),
153150 Effect.provideService(
154 ServerEnvironment,
155 ServerEnvironment.of({
151 ServerEnvironment.ServerEnvironment,
152 ServerEnvironment.ServerEnvironment.of({
156153 getEnvironmentId: unusedSecretStoreOperation(),
157154 getDescriptor: unusedSecretStoreOperation(),
158155 }),
159156 ),
160157 Effect.provideService(
161 CloudManagedEndpointRuntime,
162 CloudManagedEndpointRuntime.of({
158 ManagedEndpointRuntime.CloudManagedEndpointRuntime,
159 ManagedEndpointRuntime.CloudManagedEndpointRuntime.of({
163160 applyConfig: unusedSecretStoreOperation,
164 } satisfies CloudManagedEndpointRuntimeShape),
161 } satisfies ManagedEndpointRuntime.CloudManagedEndpointRuntime["Service"]),
165162 ),
166163 Effect.provideService(
167164 EnvironmentAuth.EnvironmentAuth,
168 EnvironmentAuth.EnvironmentAuth.of({} as EnvironmentAuth.EnvironmentAuthShape),
165 EnvironmentAuth.EnvironmentAuth.of({} as EnvironmentAuth.EnvironmentAuth["Service"]),
169166 ),
170167 Effect.provideService(
171168 CliTokenManager.CloudCliTokenManager,
apps/server/src/cloud/http.ts+20-20
......@@ -55,14 +55,8 @@ import * as HttpApiBuilder from "effect/unstable/httpapi/HttpApiBuilder";
5555import * as EnvironmentAuth from "../auth/EnvironmentAuth.ts";
5656import * as ServerSecretStore from "../auth/ServerSecretStore.ts";
5757import { requireEnvironmentScope } from "../auth/http.ts";
58import {
59 ServerEnvironment,
60 type ServerEnvironmentShape,
61} from "../environment/Services/ServerEnvironment.ts";
62import {
63 CloudManagedEndpointRuntime,
64 type CloudManagedEndpointRuntimeShape,
65} from "./ManagedEndpointRuntime.ts";
58import * as ServerEnvironment from "../environment/Services/ServerEnvironment.ts";
59import * as ManagedEndpointRuntime from "./ManagedEndpointRuntime.ts";
6660import {
6761 CLOUD_ENDPOINT_RUNTIME_CONFIG,
6862 CLOUD_LINKED_USER_ID,
......@@ -103,6 +97,9 @@ const failEnvironmentCloudInternalError =
10397 Effect.flatMap(() => Effect.fail(new EnvironmentHttpInternalServerError({ message }))),
10498 );
10599
100const failCloudCliTokenManagerError = (error: CliTokenManager.CloudCliTokenManagerError) =>
101 failEnvironmentCloudInternalError(error.message)(error.cause);
102
106103const requireRelayUrl = relayUrlConfig.pipe(
107104 Effect.mapError(
108105 () =>
......@@ -121,7 +118,7 @@ function stringToBytes(value: string): Uint8Array {
121118}
122119
123120export function consumeCloudReplayGuards(input: {
124 readonly secrets: ServerSecretStore.ServerSecretStoreShape;
121 readonly secrets: ServerSecretStore.ServerSecretStore["Service"];
125122 readonly names: ReadonlyArray<string>;
126123 readonly value: Uint8Array;
127124}) {
......@@ -208,7 +205,7 @@ function validateRelayConfigPayload(
208205}
209206
210207function validateLinkedCloudUser(input: {
211 readonly secrets: ServerSecretStore.ServerSecretStoreShape;
208 readonly secrets: ServerSecretStore.ServerSecretStore["Service"];
212209 readonly cloudUserId: string;
213210}): Effect.Effect<void, EnvironmentAuth.ServerAuthInternalError | EnvironmentHttpConflictError> {
214211 return input.secrets.get(CLOUD_LINKED_USER_ID).pipe(
......@@ -237,7 +234,7 @@ function validateLinkedCloudUser(input: {
237234}
238235
239236function readInstalledCloudUserId(
240 secrets: ServerSecretStore.ServerSecretStoreShape,
237 secrets: ServerSecretStore.ServerSecretStore["Service"],
241238): Effect.Effect<string, EnvironmentAuth.ServerAuthInternalError> {
242239 return secrets.get(CLOUD_LINKED_USER_ID).pipe(
243240 Effect.mapError(
......@@ -335,19 +332,19 @@ const decodeCloudHealthProof = Schema.decodeUnknownEffect(RelayCloudEnvironmentH
335332const decodeCloudMintProof = Schema.decodeUnknownEffect(RelayCloudMintCredentialProofPayload);
336333
337334interface CloudHttpDependencies {
338 readonly secrets: ServerSecretStore.ServerSecretStoreShape;
339 readonly environment: ServerEnvironmentShape;
340 readonly endpointRuntime: CloudManagedEndpointRuntimeShape;
341 readonly environmentAuth: EnvironmentAuth.EnvironmentAuthShape;
342 readonly cliTokenManager: CliTokenManager.CloudCliTokenManagerShape;
335 readonly secrets: ServerSecretStore.ServerSecretStore["Service"];
336 readonly environment: ServerEnvironment.ServerEnvironment["Service"];
337 readonly endpointRuntime: ManagedEndpointRuntime.CloudManagedEndpointRuntime["Service"];
338 readonly environmentAuth: EnvironmentAuth.EnvironmentAuth["Service"];
339 readonly cliTokenManager: CliTokenManager.CloudCliTokenManager["Service"];
343340 readonly httpClient: HttpClient.HttpClient;
344341}
345342
346343const cloudHttpDependencies = Effect.gen(function* () {
347344 return {
348345 secrets: yield* ServerSecretStore.ServerSecretStore,
349 environment: yield* ServerEnvironment,
350 endpointRuntime: yield* CloudManagedEndpointRuntime,
346 environment: yield* ServerEnvironment.ServerEnvironment,
347 endpointRuntime: yield* ManagedEndpointRuntime.CloudManagedEndpointRuntime,
351348 environmentAuth: yield* EnvironmentAuth.EnvironmentAuth,
352349 cliTokenManager: yield* CliTokenManager.CloudCliTokenManager,
353350 httpClient: yield* HttpClient.HttpClient,
......@@ -595,8 +592,11 @@ const reconcileDesiredCloudLinkWith = Effect.fn("environment.cloud.reconcileDesi
595592 });
596593 },
597594 Effect.catchTags({
598 CloudCliTokenManagerError: (error) =>
599 failEnvironmentCloudInternalError(error.message)(error.cause),
595 CloudCliCredentialRemovalError: failCloudCliTokenManagerError,
596 CloudCliCredentialRefreshError: failCloudCliTokenManagerError,
597 CloudCliCredentialReadError: failCloudCliTokenManagerError,
598 CloudCliAuthorizationError: failCloudCliTokenManagerError,
599 CloudCliAuthorizationTimeoutError: failCloudCliTokenManagerError,
600600 SecretStoreError: failEnvironmentCloudInternalError(
601601 "Could not persist desired T3 Connect link state.",
602602 ),