| author | |
| committer | |
| log | 4ee719a094d4c4f30fab88c1f6969483890e223e |
| tree | 4b9dfc31b8333e631e544b39eeee7f0c8b22a3b2 |
| parent | 01492ebd3b624e544e6375c12540b741c65256b9 |
| signature | Signed by PGP key B5690EEEBB952194 |
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"; | ... | @@ -6,7 +6,6 @@ import * as Clock from "effect/Clock"; |
| 6 | import * as Console from "effect/Console"; | 6 | import * as Console from "effect/Console"; |
| 7 | import * as Context from "effect/Context"; | 7 | import * as Context from "effect/Context"; |
| 8 | import * as Crypto from "effect/Crypto"; | 8 | import * as Crypto from "effect/Crypto"; |
| 9 | import * as Data from "effect/Data"; | ||
| 10 | import * as Deferred from "effect/Deferred"; | 9 | import * as Deferred from "effect/Deferred"; |
| 11 | import * as Duration from "effect/Duration"; | 10 | import * as Duration from "effect/Duration"; |
| 12 | import * as Effect from "effect/Effect"; | 11 | import * as Effect from "effect/Effect"; |
| ... | @@ -15,10 +14,12 @@ import * as Layer from "effect/Layer"; | ... | @@ -15,10 +14,12 @@ import * as Layer from "effect/Layer"; |
| 15 | import * as Option from "effect/Option"; | 14 | import * as Option from "effect/Option"; |
| 16 | import * as Schema from "effect/Schema"; | 15 | import * as Schema from "effect/Schema"; |
| 17 | import * as Semaphore from "effect/Semaphore"; | 16 | import * as Semaphore from "effect/Semaphore"; |
| 17 | import * as HttpClient from "effect/unstable/http/HttpClient"; | ||
| 18 | import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest"; | ||
| 19 | import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse"; | ||
| 18 | import * as HttpRouter from "effect/unstable/http/HttpRouter"; | 20 | import * as HttpRouter from "effect/unstable/http/HttpRouter"; |
| 19 | import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest"; | 21 | import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest"; |
| 20 | import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse"; | 22 | import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse"; |
| 21 | import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"; | ||
| 22 | 23 | ||
| 23 | import * as ServerSecretStore from "../auth/ServerSecretStore.ts"; | 24 | import * as ServerSecretStore from "../auth/ServerSecretStore.ts"; |
| 24 | import { cloudCliOAuthConfig, type CloudCliOAuthConfig } from "./publicConfig.ts"; | 25 | import { cloudCliOAuthConfig, type CloudCliOAuthConfig } from "./publicConfig.ts"; |
| ... | @@ -45,35 +46,74 @@ const OAuthTokenResponse = Schema.Struct({ | ... | @@ -45,35 +46,74 @@ const OAuthTokenResponse = Schema.Struct({ |
| 45 | token_type: Schema.String, | 46 | token_type: Schema.String, |
| 46 | }); | 47 | }); |
| 47 | 48 | ||
| 48 | export class CloudCliTokenManagerError extends Data.TaggedError("CloudCliTokenManagerError")<{ | 49 | export class CloudCliCredentialRemovalError extends Schema.TaggedErrorClass<CloudCliCredentialRemovalError>()( |
| 49 | readonly message: string; | 50 | "CloudCliCredentialRemovalError", |
| 50 | readonly cause?: unknown; | 51 | { cause: Schema.Defect() }, |
| 51 | }> {} | 52 | ) { |
| 53 | override get message(): string { | ||
| 54 | return "Could not remove the stored T3 Connect CLI credential."; | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | export 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 | |||
| 67 | export 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 | |||
| 76 | export 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 | } | ||
| 52 | 84 | ||
| 53 | export interface CloudCliTokenManagerShape { | 85 | export class CloudCliAuthorizationTimeoutError extends Schema.TaggedErrorClass<CloudCliAuthorizationTimeoutError>()( |
| 54 | readonly get: Effect.Effect<PersistedToken, CloudCliTokenManagerError>; | 86 | "CloudCliAuthorizationTimeoutError", |
| 55 | readonly getExisting: Effect.Effect<Option.Option<PersistedToken>, CloudCliTokenManagerError>; | 87 | { cause: Schema.Defect() }, |
| 56 | readonly hasCredential: Effect.Effect<boolean, CloudCliTokenManagerError>; | 88 | ) { |
| 57 | readonly clear: Effect.Effect<void, CloudCliTokenManagerError>; | 89 | override get message(): string { |
| 90 | return "Timed out waiting for T3 Connect authorization."; | ||
| 91 | } | ||
| 58 | } | 92 | } |
| 59 | 93 | ||
| 94 | export const CloudCliTokenManagerError = Schema.Union([ | ||
| 95 | CloudCliCredentialRemovalError, | ||
| 96 | CloudCliCredentialRefreshError, | ||
| 97 | CloudCliCredentialReadError, | ||
| 98 | CloudCliAuthorizationError, | ||
| 99 | CloudCliAuthorizationTimeoutError, | ||
| 100 | ]); | ||
| 101 | export type CloudCliTokenManagerError = typeof CloudCliTokenManagerError.Type; | ||
| 102 | |||
| 60 | export class CloudCliTokenManager extends Context.Service< | 103 | export class CloudCliTokenManager extends Context.Service< |
| 61 | CloudCliTokenManager, | 104 | 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 | } | ||
| 63 | >()("t3/cloud/CliTokenManager/CloudCliTokenManager") {} | 111 | >()("t3/cloud/CliTokenManager/CloudCliTokenManager") {} |
| 64 | 112 | ||
| 65 | const wrapError = | 113 | const wrapError = |
| 66 | (message: string) => | 114 | <WrappedError extends CloudCliTokenManagerError>(makeError: (cause: unknown) => WrappedError) => |
| 67 | <A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, CloudCliTokenManagerError, R> => | 115 | <A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, WrappedError, R> => |
| 68 | effect.pipe( | 116 | effect.pipe(Effect.mapError(makeError)); |
| 69 | Effect.mapError( | ||
| 70 | (cause) => | ||
| 71 | new CloudCliTokenManagerError({ | ||
| 72 | message, | ||
| 73 | cause, | ||
| 74 | }), | ||
| 75 | ), | ||
| 76 | ); | ||
| 77 | 117 | ||
| 78 | function stringToBytes(value: string): Uint8Array { | 118 | function stringToBytes(value: string): Uint8Array { |
| 79 | return new TextEncoder().encode(value); | 119 | return new TextEncoder().encode(value); |
| ... | @@ -83,7 +123,7 @@ function bytesToString(value: Uint8Array): string { | ... | @@ -83,7 +123,7 @@ function bytesToString(value: Uint8Array): string { |
| 83 | return new TextDecoder().decode(value); | 123 | return new TextDecoder().decode(value); |
| 84 | } | 124 | } |
| 85 | 125 | ||
| 86 | const make = Effect.gen(function* () { | 126 | export const make = Effect.gen(function* () { |
| 87 | const crypto = yield* Crypto.Crypto; | 127 | const crypto = yield* Crypto.Crypto; |
| 88 | const httpClient = (yield* HttpClient.HttpClient).pipe(HttpClient.filterStatusOk); | 128 | const httpClient = (yield* HttpClient.HttpClient).pipe(HttpClient.filterStatusOk); |
| 89 | const secrets = yield* ServerSecretStore.ServerSecretStore; | 129 | const secrets = yield* ServerSecretStore.ServerSecretStore; |
| ... | @@ -96,7 +136,7 @@ const make = Effect.gen(function* () { | ... | @@ -96,7 +136,7 @@ const make = Effect.gen(function* () { |
| 96 | 136 | ||
| 97 | const clear = secrets | 137 | const clear = secrets |
| 98 | .remove(CLOUD_CLI_OAUTH_TOKEN_SECRET) | 138 | .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 }))); |
| 100 | 140 | ||
| 101 | const read = Effect.fn("cloud.cli_token.read")(function* () { | 141 | const read = Effect.fn("cloud.cli_token.read")(function* () { |
| 102 | const encoded = yield* secrets.get(CLOUD_CLI_OAUTH_TOKEN_SECRET); | 142 | const encoded = yield* secrets.get(CLOUD_CLI_OAUTH_TOKEN_SECRET); |
| ... | @@ -185,10 +225,10 @@ const make = Effect.gen(function* () { | ... | @@ -185,10 +225,10 @@ const make = Effect.gen(function* () { |
| 185 | yield* Console.log(`Open this URL to authorize T3 Connect:\n${authorizationUrl.toString()}\n`); | 225 | yield* Console.log(`Open this URL to authorize T3 Connect:\n${authorizationUrl.toString()}\n`); |
| 186 | const code = yield* Deferred.await(callback).pipe( | 226 | const code = yield* Deferred.await(callback).pipe( |
| 187 | Effect.timeout(CLOUD_CLI_OAUTH_CALLBACK_TIMEOUT), | 227 | Effect.timeout(CLOUD_CLI_OAUTH_CALLBACK_TIMEOUT), |
| 188 | Effect.catchTag("TimeoutError", () => | 228 | Effect.catchTag("TimeoutError", (cause) => |
| 189 | Effect.fail( | 229 | Effect.fail( |
| 190 | new CloudCliTokenManagerError({ | 230 | new CloudCliAuthorizationTimeoutError({ |
| 191 | message: "Timed out waiting for T3 Connect authorization.", | 231 | cause, |
| 192 | }), | 232 | }), |
| 193 | ), | 233 | ), |
| 194 | ), | 234 | ), |
| ... | @@ -213,12 +253,12 @@ const make = Effect.gen(function* () { | ... | @@ -213,12 +253,12 @@ const make = Effect.gen(function* () { |
| 213 | }); | 253 | }); |
| 214 | 254 | ||
| 215 | const getExisting = semaphore.withPermits(1)( | 255 | 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 }))), |
| 217 | ); | 257 | ); |
| 218 | const hasCredential = semaphore.withPermits(1)( | 258 | const hasCredential = semaphore.withPermits(1)( |
| 219 | read().pipe( | 259 | read().pipe( |
| 220 | Effect.map(Option.isSome), | 260 | Effect.map(Option.isSome), |
| 221 | wrapError("Could not read the stored T3 Connect CLI credential."), | 261 | wrapError((cause) => new CloudCliCredentialReadError({ cause })), |
| 222 | ), | 262 | ), |
| 223 | ); | 263 | ); |
| 224 | const get = semaphore.withPermits(1)( | 264 | const get = semaphore.withPermits(1)( |
| ... | @@ -227,7 +267,7 @@ const make = Effect.gen(function* () { | ... | @@ -227,7 +267,7 @@ const make = Effect.gen(function* () { |
| 227 | return Option.isSome(token) | 267 | return Option.isSome(token) |
| 228 | ? token.value | 268 | ? token.value |
| 229 | : yield* Effect.scoped(login()).pipe(Effect.flatMap(persist)); | 269 | : 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 }))), |
| 231 | ); | 271 | ); |
| 232 | 272 | ||
| 233 | return CloudCliTokenManager.of({ get, getExisting, hasCredential, clear }); | 273 | 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"; | ... | @@ -4,16 +4,15 @@ import * as Deferred from "effect/Deferred"; |
| 4 | import * as Effect from "effect/Effect"; | 4 | import * as Effect from "effect/Effect"; |
| 5 | import * as Fiber from "effect/Fiber"; | 5 | import * as Fiber from "effect/Fiber"; |
| 6 | import * as Layer from "effect/Layer"; | 6 | import * as Layer from "effect/Layer"; |
| 7 | import * as Option from "effect/Option"; | ||
| 7 | import * as PlatformError from "effect/PlatformError"; | 8 | import * as PlatformError from "effect/PlatformError"; |
| 8 | import * as Sink from "effect/Sink"; | 9 | import * as Sink from "effect/Sink"; |
| 9 | import * as Stream from "effect/Stream"; | 10 | import * as Stream from "effect/Stream"; |
| 10 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; | 11 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; |
| 11 | import * as RelayClient from "@t3tools/shared/relayClient"; | 12 | import * as RelayClient from "@t3tools/shared/relayClient"; |
| 12 | 13 | ||
| 13 | import { | 14 | import * as ServerSecretStore from "../auth/ServerSecretStore.ts"; |
| 14 | classifyRelayClientOutput, | 15 | import * as ManagedEndpointRuntime from "./ManagedEndpointRuntime.ts"; |
| 15 | makeCloudManagedEndpointRuntime, | ||
| 16 | } from "./ManagedEndpointRuntime.ts"; | ||
| 17 | 16 | ||
| 18 | const relayClientAvailableLayer = Layer.succeed( | 17 | const relayClientAvailableLayer = Layer.succeed( |
| 19 | RelayClient.RelayClient, | 18 | RelayClient.RelayClient, |
| ... | @@ -29,12 +28,33 @@ const relayClientAvailableLayer = Layer.succeed( | ... | @@ -29,12 +28,33 @@ const relayClientAvailableLayer = Layer.succeed( |
| 29 | }), | 28 | }), |
| 30 | ); | 29 | ); |
| 31 | 30 | ||
| 32 | const runtimeDependencies = (spawner: ReturnType<typeof ChildProcessSpawner.make>) => | 31 | const runtimeDependencies = ( |
| 32 | spawner: ReturnType<typeof ChildProcessSpawner.make>, | ||
| 33 | relayClientLayer = relayClientAvailableLayer, | ||
| 34 | ) => | ||
| 33 | Layer.mergeAll( | 35 | Layer.mergeAll( |
| 34 | Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner), | 36 | Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner), |
| 35 | relayClientAvailableLayer, | 37 | relayClientLayer, |
| 38 | Layer.mock(ServerSecretStore.ServerSecretStore)({ | ||
| 39 | get: () => Effect.succeed(Option.none()), | ||
| 40 | }), | ||
| 36 | ); | 41 | ); |
| 37 | 42 | ||
| 43 | const 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 | |||
| 38 | function makeHandle(input: { | 58 | function makeHandle(input: { |
| 39 | readonly pid: number; | 59 | readonly pid: number; |
| 40 | readonly onKill: () => void; | 60 | readonly onKill: () => void; |
| ... | @@ -62,16 +82,20 @@ function makeHandle(input: { | ... | @@ -62,16 +82,20 @@ function makeHandle(input: { |
| 62 | describe("CloudManagedEndpointRuntime", () => { | 82 | describe("CloudManagedEndpointRuntime", () => { |
| 63 | it("classifies Cloudflare connection and warning output", () => { | 83 | it("classifies Cloudflare connection and warning output", () => { |
| 64 | expect( | 84 | expect( |
| 65 | classifyRelayClientOutput( | 85 | ManagedEndpointRuntime.classifyRelayClientOutput( |
| 66 | "2026-06-17T02:00:00Z INF Registered tunnel connection connIndex=0", | 86 | "2026-06-17T02:00:00Z INF Registered tunnel connection connIndex=0", |
| 67 | ), | 87 | ), |
| 68 | ).toBe("connected"); | 88 | ).toBe("connected"); |
| 69 | expect( | 89 | 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 | ), | ||
| 71 | ).toBe("warning"); | 93 | ).toBe("warning"); |
| 72 | expect(classifyRelayClientOutput("2026-06-17T02:00:00Z INF Starting metrics server")).toBe( | 94 | expect( |
| 73 | "debug", | 95 | ManagedEndpointRuntime.classifyRelayClientOutput( |
| 74 | ); | 96 | "2026-06-17T02:00:00Z INF Starting metrics server", |
| 97 | ), | ||
| 98 | ).toBe("debug"); | ||
| 75 | }); | 99 | }); |
| 76 | 100 | ||
| 77 | it.effect("starts, deduplicates, rotates, and stops the Cloudflare connector", () => | 101 | it.effect("starts, deduplicates, rotates, and stops the Cloudflare connector", () => |
| ... | @@ -97,9 +121,7 @@ describe("CloudManagedEndpointRuntime", () => { | ... | @@ -97,9 +121,7 @@ describe("CloudManagedEndpointRuntime", () => { |
| 97 | return handle; | 121 | return handle; |
| 98 | }), | 122 | }), |
| 99 | ); | 123 | ); |
| 100 | const runtime = yield* makeCloudManagedEndpointRuntime.pipe( | 124 | const runtime = yield* buildCloudManagedEndpointRuntime(spawner); |
| 101 | Effect.provide(runtimeDependencies(spawner)), | ||
| 102 | ); | ||
| 103 | 125 | ||
| 104 | yield* runtime.applyConfig({ | 126 | yield* runtime.applyConfig({ |
| 105 | providerKind: "cloudflare_tunnel", | 127 | providerKind: "cloudflare_tunnel", |
| ... | @@ -154,9 +176,7 @@ describe("CloudManagedEndpointRuntime", () => { | ... | @@ -154,9 +176,7 @@ describe("CloudManagedEndpointRuntime", () => { |
| 154 | return handle; | 176 | return handle; |
| 155 | }), | 177 | }), |
| 156 | ); | 178 | ); |
| 157 | const runtime = yield* makeCloudManagedEndpointRuntime.pipe( | 179 | const runtime = yield* buildCloudManagedEndpointRuntime(spawner); |
| 158 | Effect.provide(runtimeDependencies(spawner)), | ||
| 159 | ); | ||
| 160 | 180 | ||
| 161 | const started = yield* runtime.applyConfig({ | 181 | const started = yield* runtime.applyConfig({ |
| 162 | providerKind: "cloudflare_tunnel", | 182 | providerKind: "cloudflare_tunnel", |
| ... | @@ -193,9 +213,7 @@ describe("CloudManagedEndpointRuntime", () => { | ... | @@ -193,9 +213,7 @@ describe("CloudManagedEndpointRuntime", () => { |
| 193 | return handle; | 213 | return handle; |
| 194 | }), | 214 | }), |
| 195 | ); | 215 | ); |
| 196 | const runtime = yield* makeCloudManagedEndpointRuntime.pipe( | 216 | const runtime = yield* buildCloudManagedEndpointRuntime(spawner); |
| 197 | Effect.provide(runtimeDependencies(spawner)), | ||
| 198 | ); | ||
| 199 | const config = { | 217 | const config = { |
| 200 | providerKind: "cloudflare_tunnel" as const, | 218 | providerKind: "cloudflare_tunnel" as const, |
| 201 | connectorToken: "token", | 219 | connectorToken: "token", |
| ... | @@ -240,9 +258,7 @@ describe("CloudManagedEndpointRuntime", () => { | ... | @@ -240,9 +258,7 @@ describe("CloudManagedEndpointRuntime", () => { |
| 240 | return handle; | 258 | return handle; |
| 241 | }), | 259 | }), |
| 242 | ); | 260 | ); |
| 243 | const runtime = yield* makeCloudManagedEndpointRuntime.pipe( | 261 | const runtime = yield* buildCloudManagedEndpointRuntime(spawner); |
| 244 | Effect.provide(runtimeDependencies(spawner)), | ||
| 245 | ); | ||
| 246 | 262 | ||
| 247 | const started = yield* runtime.applyConfig({ | 263 | const started = yield* runtime.applyConfig({ |
| 248 | providerKind: "cloudflare_tunnel", | 264 | providerKind: "cloudflare_tunnel", |
| ... | @@ -282,9 +298,7 @@ describe("CloudManagedEndpointRuntime", () => { | ... | @@ -282,9 +298,7 @@ describe("CloudManagedEndpointRuntime", () => { |
| 282 | return handle; | 298 | return handle; |
| 283 | }), | 299 | }), |
| 284 | ); | 300 | ); |
| 285 | const runtime = yield* makeCloudManagedEndpointRuntime.pipe( | 301 | const runtime = yield* buildCloudManagedEndpointRuntime(spawner); |
| 286 | Effect.provide(runtimeDependencies(spawner)), | ||
| 287 | ); | ||
| 288 | 302 | ||
| 289 | const first = yield* runtime | 303 | const first = yield* runtime |
| 290 | .applyConfig({ | 304 | .applyConfig({ |
| ... | @@ -322,9 +336,7 @@ describe("CloudManagedEndpointRuntime", () => { | ... | @@ -322,9 +336,7 @@ describe("CloudManagedEndpointRuntime", () => { |
| 322 | }), | 336 | }), |
| 323 | ), | 337 | ), |
| 324 | ); | 338 | ); |
| 325 | const runtime = yield* makeCloudManagedEndpointRuntime.pipe( | 339 | const runtime = yield* buildCloudManagedEndpointRuntime(spawner); |
| 326 | Effect.provide(runtimeDependencies(spawner)), | ||
| 327 | ); | ||
| 328 | 340 | ||
| 329 | const status = yield* runtime.applyConfig({ | 341 | const status = yield* runtime.applyConfig({ |
| 330 | providerKind: "cloudflare_tunnel", | 342 | providerKind: "cloudflare_tunnel", |
| ... | @@ -344,22 +356,18 @@ describe("CloudManagedEndpointRuntime", () => { | ... | @@ -344,22 +356,18 @@ describe("CloudManagedEndpointRuntime", () => { |
| 344 | Effect.gen(function* () { | 356 | Effect.gen(function* () { |
| 345 | const spawn = vi.fn(); | 357 | const spawn = vi.fn(); |
| 346 | const spawner = ChildProcessSpawner.make(spawn); | 358 | const spawner = ChildProcessSpawner.make(spawn); |
| 347 | const runtime = yield* makeCloudManagedEndpointRuntime.pipe( | 359 | const runtime = yield* buildCloudManagedEndpointRuntime( |
| 348 | Effect.provide( | 360 | spawner, |
| 349 | Layer.mergeAll( | 361 | Layer.succeed( |
| 350 | Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner), | 362 | RelayClient.RelayClient, |
| 351 | Layer.succeed( | 363 | RelayClient.RelayClient.of({ |
| 352 | RelayClient.RelayClient, | 364 | resolve: Effect.succeed({ |
| 353 | RelayClient.RelayClient.of({ | 365 | status: "missing", |
| 354 | resolve: Effect.succeed({ | 366 | version: RelayClient.CLOUDFLARED_VERSION, |
| 355 | status: "missing", | 367 | }), |
| 356 | version: RelayClient.CLOUDFLARED_VERSION, | 368 | install: Effect.die("unused"), |
| 357 | }), | 369 | installWithProgress: () => Effect.die("unused"), |
| 358 | install: Effect.die("unused"), | 370 | }), |
| 359 | installWithProgress: () => Effect.die("unused"), | ||
| 360 | }), | ||
| 361 | ), | ||
| 362 | ), | ||
| 363 | ), | 371 | ), |
| 364 | ); | 372 | ); |
| 365 | 373 |
apps/server/src/cloud/ManagedEndpointRuntime.ts+26-31| ... | @@ -10,7 +10,8 @@ import * as Result from "effect/Result"; | ... | @@ -10,7 +10,8 @@ import * as Result from "effect/Result"; |
| 10 | import * as Semaphore from "effect/Semaphore"; | 10 | import * as Semaphore from "effect/Semaphore"; |
| 11 | import * as Scope from "effect/Scope"; | 11 | import * as Scope from "effect/Scope"; |
| 12 | import * as Stream from "effect/Stream"; | 12 | import * as Stream from "effect/Stream"; |
| 13 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; | 13 | import * as ChildProcess from "effect/unstable/process/ChildProcess"; |
| 14 | import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner"; | ||
| 14 | 15 | ||
| 15 | import * as ServerSecretStore from "../auth/ServerSecretStore.ts"; | 16 | import * as ServerSecretStore from "../auth/ServerSecretStore.ts"; |
| 16 | import { CLOUD_ENDPOINT_RUNTIME_CONFIG, decodeRuntimeConfig } from "./config.ts"; | 17 | import { CLOUD_ENDPOINT_RUNTIME_CONFIG, decodeRuntimeConfig } from "./config.ts"; |
| ... | @@ -28,17 +29,6 @@ const readRuntimeConfig = Effect.gen(function* () { | ... | @@ -28,17 +29,6 @@ const readRuntimeConfig = Effect.gen(function* () { |
| 28 | return Option.getOrNull(decodeRuntimeConfig(bytesToString(bytes.value))); | 29 | return Option.getOrNull(decodeRuntimeConfig(bytesToString(bytes.value))); |
| 29 | }); | 30 | }); |
| 30 | 31 | ||
| 31 | export interface CloudManagedEndpointRuntimeShape { | ||
| 32 | readonly applyConfig: ( | ||
| 33 | config: RelayManagedEndpointRuntimeConfig | null, | ||
| 34 | ) => Effect.Effect<CloudManagedEndpointRuntimeStatus>; | ||
| 35 | } | ||
| 36 | |||
| 37 | export class CloudManagedEndpointRuntime extends Context.Service< | ||
| 38 | CloudManagedEndpointRuntime, | ||
| 39 | CloudManagedEndpointRuntimeShape | ||
| 40 | >()("t3/cloud/ManagedEndpointRuntime/CloudManagedEndpointRuntime") {} | ||
| 41 | |||
| 42 | export type CloudManagedEndpointRuntimeStatus = | 32 | export type CloudManagedEndpointRuntimeStatus = |
| 43 | | { | 33 | | { |
| 44 | readonly status: "disabled"; | 34 | readonly status: "disabled"; |
| ... | @@ -62,6 +52,15 @@ export type CloudManagedEndpointRuntimeStatus = | ... | @@ -62,6 +52,15 @@ export type CloudManagedEndpointRuntimeStatus = |
| 62 | readonly providerKind: RelayManagedEndpointRuntimeConfig["providerKind"]; | 52 | readonly providerKind: RelayManagedEndpointRuntimeConfig["providerKind"]; |
| 63 | }; | 53 | }; |
| 64 | 54 | ||
| 55 | export 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 | |||
| 65 | interface ActiveConnector { | 64 | interface ActiveConnector { |
| 66 | readonly child: ChildProcessSpawner.ChildProcessHandle; | 65 | readonly child: ChildProcessSpawner.ChildProcessHandle; |
| 67 | readonly scope: Scope.Closeable; | 66 | readonly scope: Scope.Closeable; |
| ... | @@ -97,13 +96,13 @@ const stopConnector = (connector: ActiveConnector | null) => | ... | @@ -97,13 +96,13 @@ const stopConnector = (connector: ActiveConnector | null) => |
| 97 | ) | 96 | ) |
| 98 | : Effect.void; | 97 | : Effect.void; |
| 99 | 98 | ||
| 100 | export const makeCloudManagedEndpointRuntime = Effect.gen(function* () { | 99 | export const make = Effect.gen(function* () { |
| 101 | const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; | 100 | const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; |
| 102 | const relayClient = yield* RelayClient.RelayClient; | 101 | const relayClient = yield* RelayClient.RelayClient; |
| 103 | const activeRef = yield* Ref.make<ActiveConnector | null>(null); | 102 | const activeRef = yield* Ref.make<ActiveConnector | null>(null); |
| 104 | const desiredConfigRef = yield* Ref.make<RelayManagedEndpointRuntimeConfig | null>(null); | 103 | const desiredConfigRef = yield* Ref.make<RelayManagedEndpointRuntimeConfig | null>(null); |
| 105 | const reconcileSemaphore = yield* Semaphore.make(1); | 104 | const reconcileSemaphore = yield* Semaphore.make(1); |
| 106 | let reconcileConfig: CloudManagedEndpointRuntimeShape["applyConfig"]; | 105 | let reconcileConfig: CloudManagedEndpointRuntime["Service"]["applyConfig"]; |
| 107 | 106 | ||
| 108 | const stopActive = Effect.gen(function* () { | 107 | const stopActive = Effect.gen(function* () { |
| 109 | const active = yield* Ref.getAndSet(activeRef, null); | 108 | const active = yield* Ref.getAndSet(activeRef, null); |
| ... | @@ -301,24 +300,20 @@ export const makeCloudManagedEndpointRuntime = Effect.gen(function* () { | ... | @@ -301,24 +300,20 @@ export const makeCloudManagedEndpointRuntime = Effect.gen(function* () { |
| 301 | ), | 300 | ), |
| 302 | ); | 301 | ); |
| 303 | 302 | ||
| 304 | return CloudManagedEndpointRuntime.of({ | 303 | const runtime = CloudManagedEndpointRuntime.of({ |
| 305 | applyConfig, | 304 | applyConfig, |
| 306 | }); | 305 | }); |
| 307 | }); | ||
| 308 | 306 | ||
| 309 | export const layer = Layer.effect( | 307 | const initialConfig = yield* readRuntimeConfig.pipe( |
| 310 | CloudManagedEndpointRuntime, | 308 | Effect.catch((cause) => |
| 311 | Effect.gen(function* () { | 309 | Effect.logWarning("Failed to read managed endpoint runtime config", { cause }).pipe( |
| 312 | const runtime = yield* makeCloudManagedEndpointRuntime; | 310 | Effect.as(null), |
| 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 | ), | ||
| 318 | ), | 311 | ), |
| 319 | ); | 312 | ), |
| 320 | yield* runtime.applyConfig(initialConfig); | 313 | ); |
| 321 | yield* Effect.addFinalizer(() => runtime.applyConfig(null)); | 314 | yield* runtime.applyConfig(initialConfig); |
| 322 | return runtime; | 315 | yield* Effect.addFinalizer(() => runtime.applyConfig(null)); |
| 323 | }), | 316 | return runtime; |
| 324 | ); | 317 | }); |
| 318 | |||
| 319 | export 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"; | ... | @@ -9,13 +9,10 @@ import { HttpClient, HttpServerRequest } from "effect/unstable/http"; |
| 9 | import { RelayClientTracer } from "@t3tools/shared/relayTracing"; | 9 | import { RelayClientTracer } from "@t3tools/shared/relayTracing"; |
| 10 | import * as EnvironmentAuth from "../auth/EnvironmentAuth.ts"; | 10 | import * as EnvironmentAuth from "../auth/EnvironmentAuth.ts"; |
| 11 | import * as ServerSecretStore from "../auth/ServerSecretStore.ts"; | 11 | import * as ServerSecretStore from "../auth/ServerSecretStore.ts"; |
| 12 | import { ServerEnvironment } from "../environment/Services/ServerEnvironment.ts"; | 12 | import * as ServerEnvironment from "../environment/Services/ServerEnvironment.ts"; |
| 13 | import * as CliTokenManager from "./CliTokenManager.ts"; | 13 | import * as CliTokenManager from "./CliTokenManager.ts"; |
| 14 | import { consumeCloudReplayGuards, reconcileDesiredCloudLink } from "./http.ts"; | 14 | import { consumeCloudReplayGuards, reconcileDesiredCloudLink } from "./http.ts"; |
| 15 | import { | 15 | import * as ManagedEndpointRuntime from "./ManagedEndpointRuntime.ts"; |
| 16 | CloudManagedEndpointRuntime, | ||
| 17 | type CloudManagedEndpointRuntimeShape, | ||
| 18 | } from "./ManagedEndpointRuntime.ts"; | ||
| 19 | import { traceAuthenticatedRelayRequest, traceRelayRequest } from "./traceRelayRequest.ts"; | 16 | import { traceAuthenticatedRelayRequest, traceRelayRequest } from "./traceRelayRequest.ts"; |
| 20 | 17 | ||
| 21 | const storeFailure = (tag: "AlreadyExists" | "PermissionDenied") => | 18 | const storeFailure = (tag: "AlreadyExists" | "PermissionDenied") => |
| ... | @@ -32,8 +29,8 @@ const storeFailure = (tag: "AlreadyExists" | "PermissionDenied") => | ... | @@ -32,8 +29,8 @@ const storeFailure = (tag: "AlreadyExists" | "PermissionDenied") => |
| 32 | const unusedSecretStoreOperation = () => Effect.die("unused secret-store operation"); | 29 | const unusedSecretStoreOperation = () => Effect.die("unused secret-store operation"); |
| 33 | 30 | ||
| 34 | function makeSecretStore( | 31 | function makeSecretStore( |
| 35 | create: ServerSecretStore.ServerSecretStoreShape["create"], | 32 | create: ServerSecretStore.ServerSecretStore["Service"]["create"], |
| 36 | ): ServerSecretStore.ServerSecretStoreShape { | 33 | ): ServerSecretStore.ServerSecretStore["Service"] { |
| 37 | return { | 34 | return { |
| 38 | get: unusedSecretStoreOperation, | 35 | get: unusedSecretStoreOperation, |
| 39 | set: unusedSecretStoreOperation, | 36 | set: unusedSecretStoreOperation, |
| ... | @@ -151,21 +148,21 @@ describe("reconcileDesiredCloudLink", () => { | ... | @@ -151,21 +148,21 @@ describe("reconcileDesiredCloudLink", () => { |
| 151 | makeSecretStore(unusedSecretStoreOperation), | 148 | makeSecretStore(unusedSecretStoreOperation), |
| 152 | ), | 149 | ), |
| 153 | Effect.provideService( | 150 | Effect.provideService( |
| 154 | ServerEnvironment, | 151 | ServerEnvironment.ServerEnvironment, |
| 155 | ServerEnvironment.of({ | 152 | ServerEnvironment.ServerEnvironment.of({ |
| 156 | getEnvironmentId: unusedSecretStoreOperation(), | 153 | getEnvironmentId: unusedSecretStoreOperation(), |
| 157 | getDescriptor: unusedSecretStoreOperation(), | 154 | getDescriptor: unusedSecretStoreOperation(), |
| 158 | }), | 155 | }), |
| 159 | ), | 156 | ), |
| 160 | Effect.provideService( | 157 | Effect.provideService( |
| 161 | CloudManagedEndpointRuntime, | 158 | ManagedEndpointRuntime.CloudManagedEndpointRuntime, |
| 162 | CloudManagedEndpointRuntime.of({ | 159 | ManagedEndpointRuntime.CloudManagedEndpointRuntime.of({ |
| 163 | applyConfig: unusedSecretStoreOperation, | 160 | applyConfig: unusedSecretStoreOperation, |
| 164 | } satisfies CloudManagedEndpointRuntimeShape), | 161 | } satisfies ManagedEndpointRuntime.CloudManagedEndpointRuntime["Service"]), |
| 165 | ), | 162 | ), |
| 166 | Effect.provideService( | 163 | Effect.provideService( |
| 167 | EnvironmentAuth.EnvironmentAuth, | 164 | EnvironmentAuth.EnvironmentAuth, |
| 168 | EnvironmentAuth.EnvironmentAuth.of({} as EnvironmentAuth.EnvironmentAuthShape), | 165 | EnvironmentAuth.EnvironmentAuth.of({} as EnvironmentAuth.EnvironmentAuth["Service"]), |
| 169 | ), | 166 | ), |
| 170 | Effect.provideService( | 167 | Effect.provideService( |
| 171 | CliTokenManager.CloudCliTokenManager, | 168 | CliTokenManager.CloudCliTokenManager, |
apps/server/src/cloud/http.ts+20-20| ... | @@ -55,14 +55,8 @@ import * as HttpApiBuilder from "effect/unstable/httpapi/HttpApiBuilder"; | ... | @@ -55,14 +55,8 @@ import * as HttpApiBuilder from "effect/unstable/httpapi/HttpApiBuilder"; |
| 55 | import * as EnvironmentAuth from "../auth/EnvironmentAuth.ts"; | 55 | import * as EnvironmentAuth from "../auth/EnvironmentAuth.ts"; |
| 56 | import * as ServerSecretStore from "../auth/ServerSecretStore.ts"; | 56 | import * as ServerSecretStore from "../auth/ServerSecretStore.ts"; |
| 57 | import { requireEnvironmentScope } from "../auth/http.ts"; | 57 | import { requireEnvironmentScope } from "../auth/http.ts"; |
| 58 | import { | 58 | import * as ServerEnvironment from "../environment/Services/ServerEnvironment.ts"; |
| 59 | ServerEnvironment, | 59 | import * as ManagedEndpointRuntime from "./ManagedEndpointRuntime.ts"; |
| 60 | type ServerEnvironmentShape, | ||
| 61 | } from "../environment/Services/ServerEnvironment.ts"; | ||
| 62 | import { | ||
| 63 | CloudManagedEndpointRuntime, | ||
| 64 | type CloudManagedEndpointRuntimeShape, | ||
| 65 | } from "./ManagedEndpointRuntime.ts"; | ||
| 66 | import { | 60 | import { |
| 67 | CLOUD_ENDPOINT_RUNTIME_CONFIG, | 61 | CLOUD_ENDPOINT_RUNTIME_CONFIG, |
| 68 | CLOUD_LINKED_USER_ID, | 62 | CLOUD_LINKED_USER_ID, |
| ... | @@ -103,6 +97,9 @@ const failEnvironmentCloudInternalError = | ... | @@ -103,6 +97,9 @@ const failEnvironmentCloudInternalError = |
| 103 | Effect.flatMap(() => Effect.fail(new EnvironmentHttpInternalServerError({ message }))), | 97 | Effect.flatMap(() => Effect.fail(new EnvironmentHttpInternalServerError({ message }))), |
| 104 | ); | 98 | ); |
| 105 | 99 | ||
| 100 | const failCloudCliTokenManagerError = (error: CliTokenManager.CloudCliTokenManagerError) => | ||
| 101 | failEnvironmentCloudInternalError(error.message)(error.cause); | ||
| 102 | |||
| 106 | const requireRelayUrl = relayUrlConfig.pipe( | 103 | const requireRelayUrl = relayUrlConfig.pipe( |
| 107 | Effect.mapError( | 104 | Effect.mapError( |
| 108 | () => | 105 | () => |
| ... | @@ -121,7 +118,7 @@ function stringToBytes(value: string): Uint8Array { | ... | @@ -121,7 +118,7 @@ function stringToBytes(value: string): Uint8Array { |
| 121 | } | 118 | } |
| 122 | 119 | ||
| 123 | export function consumeCloudReplayGuards(input: { | 120 | export function consumeCloudReplayGuards(input: { |
| 124 | readonly secrets: ServerSecretStore.ServerSecretStoreShape; | 121 | readonly secrets: ServerSecretStore.ServerSecretStore["Service"]; |
| 125 | readonly names: ReadonlyArray<string>; | 122 | readonly names: ReadonlyArray<string>; |
| 126 | readonly value: Uint8Array; | 123 | readonly value: Uint8Array; |
| 127 | }) { | 124 | }) { |
| ... | @@ -208,7 +205,7 @@ function validateRelayConfigPayload( | ... | @@ -208,7 +205,7 @@ function validateRelayConfigPayload( |
| 208 | } | 205 | } |
| 209 | 206 | ||
| 210 | function validateLinkedCloudUser(input: { | 207 | function validateLinkedCloudUser(input: { |
| 211 | readonly secrets: ServerSecretStore.ServerSecretStoreShape; | 208 | readonly secrets: ServerSecretStore.ServerSecretStore["Service"]; |
| 212 | readonly cloudUserId: string; | 209 | readonly cloudUserId: string; |
| 213 | }): Effect.Effect<void, EnvironmentAuth.ServerAuthInternalError | EnvironmentHttpConflictError> { | 210 | }): Effect.Effect<void, EnvironmentAuth.ServerAuthInternalError | EnvironmentHttpConflictError> { |
| 214 | return input.secrets.get(CLOUD_LINKED_USER_ID).pipe( | 211 | return input.secrets.get(CLOUD_LINKED_USER_ID).pipe( |
| ... | @@ -237,7 +234,7 @@ function validateLinkedCloudUser(input: { | ... | @@ -237,7 +234,7 @@ function validateLinkedCloudUser(input: { |
| 237 | } | 234 | } |
| 238 | 235 | ||
| 239 | function readInstalledCloudUserId( | 236 | function readInstalledCloudUserId( |
| 240 | secrets: ServerSecretStore.ServerSecretStoreShape, | 237 | secrets: ServerSecretStore.ServerSecretStore["Service"], |
| 241 | ): Effect.Effect<string, EnvironmentAuth.ServerAuthInternalError> { | 238 | ): Effect.Effect<string, EnvironmentAuth.ServerAuthInternalError> { |
| 242 | return secrets.get(CLOUD_LINKED_USER_ID).pipe( | 239 | return secrets.get(CLOUD_LINKED_USER_ID).pipe( |
| 243 | Effect.mapError( | 240 | Effect.mapError( |
| ... | @@ -335,19 +332,19 @@ const decodeCloudHealthProof = Schema.decodeUnknownEffect(RelayCloudEnvironmentH | ... | @@ -335,19 +332,19 @@ const decodeCloudHealthProof = Schema.decodeUnknownEffect(RelayCloudEnvironmentH |
| 335 | const decodeCloudMintProof = Schema.decodeUnknownEffect(RelayCloudMintCredentialProofPayload); | 332 | const decodeCloudMintProof = Schema.decodeUnknownEffect(RelayCloudMintCredentialProofPayload); |
| 336 | 333 | ||
| 337 | interface CloudHttpDependencies { | 334 | interface CloudHttpDependencies { |
| 338 | readonly secrets: ServerSecretStore.ServerSecretStoreShape; | 335 | readonly secrets: ServerSecretStore.ServerSecretStore["Service"]; |
| 339 | readonly environment: ServerEnvironmentShape; | 336 | readonly environment: ServerEnvironment.ServerEnvironment["Service"]; |
| 340 | readonly endpointRuntime: CloudManagedEndpointRuntimeShape; | 337 | readonly endpointRuntime: ManagedEndpointRuntime.CloudManagedEndpointRuntime["Service"]; |
| 341 | readonly environmentAuth: EnvironmentAuth.EnvironmentAuthShape; | 338 | readonly environmentAuth: EnvironmentAuth.EnvironmentAuth["Service"]; |
| 342 | readonly cliTokenManager: CliTokenManager.CloudCliTokenManagerShape; | 339 | readonly cliTokenManager: CliTokenManager.CloudCliTokenManager["Service"]; |
| 343 | readonly httpClient: HttpClient.HttpClient; | 340 | readonly httpClient: HttpClient.HttpClient; |
| 344 | } | 341 | } |
| 345 | 342 | ||
| 346 | const cloudHttpDependencies = Effect.gen(function* () { | 343 | const cloudHttpDependencies = Effect.gen(function* () { |
| 347 | return { | 344 | return { |
| 348 | secrets: yield* ServerSecretStore.ServerSecretStore, | 345 | secrets: yield* ServerSecretStore.ServerSecretStore, |
| 349 | environment: yield* ServerEnvironment, | 346 | environment: yield* ServerEnvironment.ServerEnvironment, |
| 350 | endpointRuntime: yield* CloudManagedEndpointRuntime, | 347 | endpointRuntime: yield* ManagedEndpointRuntime.CloudManagedEndpointRuntime, |
| 351 | environmentAuth: yield* EnvironmentAuth.EnvironmentAuth, | 348 | environmentAuth: yield* EnvironmentAuth.EnvironmentAuth, |
| 352 | cliTokenManager: yield* CliTokenManager.CloudCliTokenManager, | 349 | cliTokenManager: yield* CliTokenManager.CloudCliTokenManager, |
| 353 | httpClient: yield* HttpClient.HttpClient, | 350 | httpClient: yield* HttpClient.HttpClient, |
| ... | @@ -595,8 +592,11 @@ const reconcileDesiredCloudLinkWith = Effect.fn("environment.cloud.reconcileDesi | ... | @@ -595,8 +592,11 @@ const reconcileDesiredCloudLinkWith = Effect.fn("environment.cloud.reconcileDesi |
| 595 | }); | 592 | }); |
| 596 | }, | 593 | }, |
| 597 | Effect.catchTags({ | 594 | Effect.catchTags({ |
| 598 | CloudCliTokenManagerError: (error) => | 595 | CloudCliCredentialRemovalError: failCloudCliTokenManagerError, |
| 599 | failEnvironmentCloudInternalError(error.message)(error.cause), | 596 | CloudCliCredentialRefreshError: failCloudCliTokenManagerError, |
| 597 | CloudCliCredentialReadError: failCloudCliTokenManagerError, | ||
| 598 | CloudCliAuthorizationError: failCloudCliTokenManagerError, | ||
| 599 | CloudCliAuthorizationTimeoutError: failCloudCliTokenManagerError, | ||
| 600 | SecretStoreError: failEnvironmentCloudInternalError( | 600 | SecretStoreError: failEnvironmentCloudInternalError( |
| 601 | "Could not persist desired T3 Connect link state.", | 601 | "Could not persist desired T3 Connect link state.", |
| 602 | ), | 602 | ), |