| author | |
| committer | |
| log | 53d0107759b743cdd7a9b6e777d4628650ed2131 |
| tree | 819d1bdab1d3f9c903fa24e61ca6bab862dc4cfa |
| parent | 60b16d1f28104c95a3ab8e673cb439124a4a8668 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: codex <codex@users.noreply.github.com>36 files changed, 403 insertions(+), 385 deletions(-)
infra/relay/alchemy.run.ts+3-3| ... | ... | @@ -7,7 +7,7 @@ import * as Effect from "effect/Effect"; |
| 7 | 7 | import * as Layer from "effect/Layer"; |
| 8 | 8 | import * as Planetscale from "alchemy/Planetscale"; |
| 9 | 9 | |
| 10 | import { PlanetscaleDatabase, RelayHyperdrive } from "./src/db.ts"; | |
| 10 | import * as RelayDb from "./src/db.ts"; | |
| 11 | 11 | import { RelayObservability } from "./src/observability.ts"; |
| 12 | 12 | import { ManagedEndpointZone, RelayApiZone } from "./src/zone.ts"; |
| 13 | 13 | import Api from "./src/worker.ts"; |
| ... | ... | @@ -24,8 +24,8 @@ export default Alchemy.Stack( |
| 24 | 24 | state: Cloudflare.state(), |
| 25 | 25 | }, |
| 26 | 26 | Effect.gen(function* () { |
| 27 | const db = yield* PlanetscaleDatabase; | |
| 28 | const hyperdrive = yield* RelayHyperdrive; | |
| 27 | const db = yield* RelayDb.PlanetscaleDatabase; | |
| 28 | const hyperdrive = yield* RelayDb.RelayHyperdrive; | |
| 29 | 29 | const managedEndpointZone = yield* ManagedEndpointZone.pipe(Effect.orDie); |
| 30 | 30 | const relayApiZone = yield* RelayApiZone.pipe(Effect.orDie); |
| 31 | 31 | const observability = yield* RelayObservability; |
infra/relay/src/Config.ts+19-14| ... | ... | @@ -1,4 +1,5 @@ |
| 1 | 1 | import * as Context from "effect/Context"; |
| 2 | import * as Layer from "effect/Layer"; | |
| 2 | 3 | import * as Redacted from "effect/Redacted"; |
| 3 | 4 | import * as Schema from "effect/Schema"; |
| 4 | 5 | |
| ... | ... | @@ -13,20 +14,24 @@ export interface ApnsCredentials { |
| 13 | 14 | readonly environment: ApnsEnvironment; |
| 14 | 15 | } |
| 15 | 16 | |
| 16 | export interface RelayConfigurationShape { | |
| 17 | readonly relayIssuer: string; | |
| 18 | readonly apns: ApnsCredentials; | |
| 19 | readonly clerkSecretKey: Redacted.Redacted<string>; | |
| 20 | readonly clerkPublishableKey: string; | |
| 21 | readonly clerkJwtAudience: string; | |
| 22 | readonly apnsDeliveryJobSigningSecret: Redacted.Redacted<string>; | |
| 23 | readonly cloudMintPrivateKey: Redacted.Redacted<string>; | |
| 24 | readonly cloudMintPublicKey: string; | |
| 25 | readonly managedEndpointBaseDomain: string | undefined; | |
| 26 | readonly managedEndpointNamespace: string | undefined; | |
| 27 | } | |
| 28 | ||
| 29 | 17 | export class RelayConfiguration extends Context.Service< |
| 30 | 18 | RelayConfiguration, |
| 31 | RelayConfigurationShape | |
| 19 | { | |
| 20 | readonly relayIssuer: string; | |
| 21 | readonly apns: ApnsCredentials; | |
| 22 | readonly clerkSecretKey: Redacted.Redacted<string>; | |
| 23 | readonly clerkPublishableKey: string; | |
| 24 | readonly clerkJwtAudience: string; | |
| 25 | readonly apnsDeliveryJobSigningSecret: Redacted.Redacted<string>; | |
| 26 | readonly cloudMintPrivateKey: Redacted.Redacted<string>; | |
| 27 | readonly cloudMintPublicKey: string; | |
| 28 | readonly managedEndpointBaseDomain: string | undefined; | |
| 29 | readonly managedEndpointNamespace: string | undefined; | |
| 30 | } | |
| 32 | 31 | >()("t3code-relay/Config/RelayConfiguration") {} |
| 32 | ||
| 33 | export const make = (configuration: RelayConfiguration["Service"]) => | |
| 34 | RelayConfiguration.of(configuration); | |
| 35 | ||
| 36 | export const layer = (configuration: RelayConfiguration["Service"]) => | |
| 37 | Layer.succeed(RelayConfiguration, make(configuration)); |
infra/relay/src/agentActivity/AgentActivityPublisher.test.ts+2-2| ... | ... | @@ -66,8 +66,8 @@ function makeAgentActivityRows( |
| 66 | 66 | } |
| 67 | 67 | |
| 68 | 68 | function makeEnvironmentLinks( |
| 69 | overrides: Partial<EnvironmentLinks.EnvironmentLinksShape> = {}, | |
| 70 | ): EnvironmentLinks.EnvironmentLinksShape { | |
| 69 | overrides: Partial<EnvironmentLinks.EnvironmentLinks["Service"]> = {}, | |
| 70 | ): EnvironmentLinks.EnvironmentLinks["Service"] { | |
| 71 | 71 | return { |
| 72 | 72 | upsert: () => Effect.void, |
| 73 | 73 | listUsersForEnvironment: () => Effect.succeed(["dev:julius"]), |
infra/relay/src/agentActivity/AgentActivityRows.ts+2-2| ... | ... | @@ -9,7 +9,7 @@ import * as Option from "effect/Option"; |
| 9 | 9 | import * as Schema from "effect/Schema"; |
| 10 | 10 | import { and, desc, eq, isNull } from "drizzle-orm"; |
| 11 | 11 | |
| 12 | import { RelayDb } from "../db.ts"; | |
| 12 | import * as RelayDb from "../db.ts"; | |
| 13 | 13 | import { relayAgentActivityRows, relayEnvironmentLinks } from "../persistence/schema.ts"; |
| 14 | 14 | |
| 15 | 15 | export class AgentActivityRowUpsertPersistenceError extends Schema.TaggedErrorClass<AgentActivityRowUpsertPersistenceError>()( |
| ... | ... | @@ -70,7 +70,7 @@ const decodeRelayAgentActivityStateJson = Schema.decodeUnknownOption( |
| 70 | 70 | ); |
| 71 | 71 | |
| 72 | 72 | const make = Effect.gen(function* () { |
| 73 | const db = yield* RelayDb; | |
| 73 | const db = yield* RelayDb.RelayDb; | |
| 74 | 74 | |
| 75 | 75 | return AgentActivityRows.of({ |
| 76 | 76 | upsert: Effect.fn("relay.agent_activity_rows.upsert")( |
infra/relay/src/agentActivity/ApnsClient.ts+6-6| ... | ... | @@ -9,7 +9,7 @@ import * as Option from "effect/Option"; |
| 9 | 9 | import * as Redacted from "effect/Redacted"; |
| 10 | 10 | import * as Schema from "effect/Schema"; |
| 11 | 11 | import { Headers, HttpClient, HttpClientRequest } from "effect/unstable/http"; |
| 12 | import type { ApnsCredentials } from "../Config.ts"; | |
| 12 | import * as RelayConfiguration from "../Config.ts"; | |
| 13 | 13 | import type { ApnsNotificationPayload } from "./apnsDeliveryJobs.ts"; |
| 14 | 14 | |
| 15 | 15 | const LIVE_ACTIVITY_NAME = "AgentActivity"; |
| ... | ... | @@ -99,9 +99,9 @@ const encodeApnsJwtPayloadJson = Schema.encodeEffect( |
| 99 | 99 | ); |
| 100 | 100 | |
| 101 | 101 | const makeApnsJwt = Effect.fn("relay.apns.make_jwt")(function* (input: { |
| 102 | readonly teamId: ApnsCredentials["teamId"]; | |
| 103 | readonly keyId: ApnsCredentials["keyId"]; | |
| 104 | readonly privateKey: ApnsCredentials["privateKey"]; | |
| 102 | readonly teamId: RelayConfiguration.ApnsCredentials["teamId"]; | |
| 103 | readonly keyId: RelayConfiguration.ApnsCredentials["keyId"]; | |
| 104 | readonly privateKey: RelayConfiguration.ApnsCredentials["privateKey"]; | |
| 105 | 105 | readonly issuedAtUnixSeconds: number; |
| 106 | 106 | }) { |
| 107 | 107 | const headerJson = yield* encodeApnsJwtHeaderJson({ alg: "ES256", kid: input.keyId }).pipe( |
| ... | ... | @@ -235,12 +235,12 @@ export interface ApnsClientShape { |
| 235 | 235 | readonly makeLiveActivityRequest: typeof makeLiveActivityRequest; |
| 236 | 236 | readonly makePushNotificationRequest: typeof makePushNotificationRequest; |
| 237 | 237 | readonly sendLiveActivityRequest: (input: { |
| 238 | readonly credentials: ApnsCredentials; | |
| 238 | readonly credentials: RelayConfiguration.ApnsCredentials; | |
| 239 | 239 | readonly request: ApnsLiveActivityRequest; |
| 240 | 240 | readonly issuedAtUnixSeconds: number; |
| 241 | 241 | }) => Effect.Effect<ApnsDeliveryResult, ApnsError>; |
| 242 | 242 | readonly sendPushNotificationRequest: (input: { |
| 243 | readonly credentials: ApnsCredentials; | |
| 243 | readonly credentials: RelayConfiguration.ApnsCredentials; | |
| 244 | 244 | readonly request: ApnsPushNotificationRequest; |
| 245 | 245 | readonly issuedAtUnixSeconds: number; |
| 246 | 246 | }) => Effect.Effect<ApnsDeliveryResult, ApnsError>; |
infra/relay/src/agentActivity/ApnsDeliveries.test.ts+2-2| ... | ... | @@ -153,7 +153,7 @@ function makeLayer(input: { |
| 153 | 153 | Parameters<LiveActivities.LiveActivitiesShape["invalidateDeliveryToken"]>[0] |
| 154 | 154 | >; |
| 155 | 155 | readonly currentTargets?: ReadonlyArray<LiveActivities.TargetRow>; |
| 156 | readonly config?: RelayConfiguration.RelayConfigurationShape; | |
| 156 | readonly config?: RelayConfiguration.RelayConfiguration["Service"]; | |
| 157 | 157 | readonly execute?: ( |
| 158 | 158 | request: HttpClientRequest.HttpClientRequest, |
| 159 | 159 | ) => Effect.Effect<HttpClientResponse.HttpClientResponse>; |
| ... | ... | @@ -213,7 +213,7 @@ function makeLayer(input: { |
| 213 | 213 | input.invalidatedTokens?.push(invalidated); |
| 214 | 214 | }), |
| 215 | 215 | }), |
| 216 | Layer.succeed(RelayConfiguration.RelayConfiguration, input.config ?? config), | |
| 216 | RelayConfiguration.layer(input.config ?? config), | |
| 217 | 217 | input.execute |
| 218 | 218 | ? Layer.succeed(HttpClient.HttpClient, HttpClient.make(input.execute)) |
| 219 | 219 | : FetchHttpClient.layer, |
infra/relay/src/agentActivity/DeliveryAttempts.test.ts+13-13| ... | ... | @@ -3,7 +3,7 @@ import { describe, expect, it } from "@effect/vitest"; |
| 3 | 3 | import * as Effect from "effect/Effect"; |
| 4 | 4 | import * as Layer from "effect/Layer"; |
| 5 | 5 | |
| 6 | import { RelayDb, type RelayDatabase } from "../db.ts"; | |
| 6 | import * as RelayDb from "../db.ts"; | |
| 7 | 7 | import { relayDeliveryAttempts } from "../persistence/schema.ts"; |
| 8 | 8 | import * as DeliveryAttempts from "./DeliveryAttempts.ts"; |
| 9 | 9 | |
| ... | ... | @@ -20,7 +20,7 @@ describe("DeliveryAttempts", () => { |
| 20 | 20 | }, |
| 21 | 21 | }; |
| 22 | 22 | }, |
| 23 | } as unknown as RelayDatabase; | |
| 23 | } as unknown as RelayDb.RelayDb["Service"]; | |
| 24 | 24 | |
| 25 | 25 | return Effect.gen(function* () { |
| 26 | 26 | const attempts = yield* DeliveryAttempts.DeliveryAttempts; |
| ... | ... | @@ -52,7 +52,7 @@ describe("DeliveryAttempts", () => { |
| 52 | 52 | Effect.provide( |
| 53 | 53 | DeliveryAttempts.layer.pipe( |
| 54 | 54 | Layer.provide(NodeCryptoLayer.layer), |
| 55 | Layer.provide(Layer.succeed(RelayDb, fakeDb)), | |
| 55 | Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)), | |
| 56 | 56 | ), |
| 57 | 57 | ), |
| 58 | 58 | ); |
| ... | ... | @@ -78,7 +78,7 @@ describe("DeliveryAttempts", () => { |
| 78 | 78 | }, |
| 79 | 79 | }; |
| 80 | 80 | }, |
| 81 | } as unknown as RelayDatabase; | |
| 81 | } as unknown as RelayDb.RelayDb["Service"]; | |
| 82 | 82 | |
| 83 | 83 | return Effect.gen(function* () { |
| 84 | 84 | const attempts = yield* DeliveryAttempts.DeliveryAttempts; |
| ... | ... | @@ -104,7 +104,7 @@ describe("DeliveryAttempts", () => { |
| 104 | 104 | Effect.provide( |
| 105 | 105 | DeliveryAttempts.layer.pipe( |
| 106 | 106 | Layer.provide(NodeCryptoLayer.layer), |
| 107 | Layer.provide(Layer.succeed(RelayDb, fakeDb)), | |
| 107 | Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)), | |
| 108 | 108 | ), |
| 109 | 109 | ), |
| 110 | 110 | ); |
| ... | ... | @@ -135,7 +135,7 @@ describe("DeliveryAttempts", () => { |
| 135 | 135 | }), |
| 136 | 136 | }), |
| 137 | 137 | }), |
| 138 | } as unknown as RelayDatabase; | |
| 138 | } as unknown as RelayDb.RelayDb["Service"]; | |
| 139 | 139 | |
| 140 | 140 | return Effect.gen(function* () { |
| 141 | 141 | const attempts = yield* DeliveryAttempts.DeliveryAttempts; |
| ... | ... | @@ -154,7 +154,7 @@ describe("DeliveryAttempts", () => { |
| 154 | 154 | Effect.provide( |
| 155 | 155 | DeliveryAttempts.layer.pipe( |
| 156 | 156 | Layer.provide(NodeCryptoLayer.layer), |
| 157 | Layer.provide(Layer.succeed(RelayDb, fakeDb)), | |
| 157 | Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)), | |
| 158 | 158 | ), |
| 159 | 159 | ), |
| 160 | 160 | ); |
| ... | ... | @@ -185,7 +185,7 @@ describe("DeliveryAttempts", () => { |
| 185 | 185 | }), |
| 186 | 186 | }), |
| 187 | 187 | }), |
| 188 | } as unknown as RelayDatabase; | |
| 188 | } as unknown as RelayDb.RelayDb["Service"]; | |
| 189 | 189 | |
| 190 | 190 | return Effect.gen(function* () { |
| 191 | 191 | const attempts = yield* DeliveryAttempts.DeliveryAttempts; |
| ... | ... | @@ -204,7 +204,7 @@ describe("DeliveryAttempts", () => { |
| 204 | 204 | Effect.provide( |
| 205 | 205 | DeliveryAttempts.layer.pipe( |
| 206 | 206 | Layer.provide(NodeCryptoLayer.layer), |
| 207 | Layer.provide(Layer.succeed(RelayDb, fakeDb)), | |
| 207 | Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)), | |
| 208 | 208 | ), |
| 209 | 209 | ), |
| 210 | 210 | ); |
| ... | ... | @@ -246,7 +246,7 @@ describe("DeliveryAttempts", () => { |
| 246 | 246 | }; |
| 247 | 247 | }, |
| 248 | 248 | }), |
| 249 | } as unknown as RelayDatabase; | |
| 249 | } as unknown as RelayDb.RelayDb["Service"]; | |
| 250 | 250 | |
| 251 | 251 | return Effect.gen(function* () { |
| 252 | 252 | const attempts = yield* DeliveryAttempts.DeliveryAttempts; |
| ... | ... | @@ -266,7 +266,7 @@ describe("DeliveryAttempts", () => { |
| 266 | 266 | Effect.provide( |
| 267 | 267 | DeliveryAttempts.layer.pipe( |
| 268 | 268 | Layer.provide(NodeCryptoLayer.layer), |
| 269 | Layer.provide(Layer.succeed(RelayDb, fakeDb)), | |
| 269 | Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)), | |
| 270 | 270 | ), |
| 271 | 271 | ), |
| 272 | 272 | ); |
| ... | ... | @@ -290,7 +290,7 @@ describe("DeliveryAttempts", () => { |
| 290 | 290 | }, |
| 291 | 291 | }; |
| 292 | 292 | }, |
| 293 | } as unknown as RelayDatabase; | |
| 293 | } as unknown as RelayDb.RelayDb["Service"]; | |
| 294 | 294 | |
| 295 | 295 | return Effect.gen(function* () { |
| 296 | 296 | const attempts = yield* DeliveryAttempts.DeliveryAttempts; |
| ... | ... | @@ -315,7 +315,7 @@ describe("DeliveryAttempts", () => { |
| 315 | 315 | Effect.provide( |
| 316 | 316 | DeliveryAttempts.layer.pipe( |
| 317 | 317 | Layer.provide(NodeCryptoLayer.layer), |
| 318 | Layer.provide(Layer.succeed(RelayDb, fakeDb)), | |
| 318 | Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)), | |
| 319 | 319 | ), |
| 320 | 320 | ), |
| 321 | 321 | ); |
infra/relay/src/agentActivity/DeliveryAttempts.ts+2-2| ... | ... | @@ -7,7 +7,7 @@ import { and, eq, isNull } from "drizzle-orm"; |
| 7 | 7 | import * as Crypto from "effect/Crypto"; |
| 8 | 8 | import * as Schema from "effect/Schema"; |
| 9 | 9 | |
| 10 | import { RelayDb } from "../db.ts"; | |
| 10 | import * as RelayDb from "../db.ts"; | |
| 11 | 11 | import { relayDeliveryAttempts } from "../persistence/schema.ts"; |
| 12 | 12 | |
| 13 | 13 | export class DeliveryAttemptRecordPersistenceError extends Schema.TaggedErrorClass<DeliveryAttemptRecordPersistenceError>()( |
| ... | ... | @@ -84,7 +84,7 @@ function insertValues( |
| 84 | 84 | } |
| 85 | 85 | |
| 86 | 86 | const make = Effect.gen(function* () { |
| 87 | const db = yield* RelayDb; | |
| 87 | const db = yield* RelayDb.RelayDb; | |
| 88 | 88 | const crypto = yield* Crypto.Crypto; |
| 89 | 89 | |
| 90 | 90 | const isExpiredClaim = (claimedAt: string | null, now: DateTime.DateTime) => { |
infra/relay/src/agentActivity/Devices.test.ts+13-7| ... | ... | @@ -5,7 +5,7 @@ import { PgDialect } from "drizzle-orm/pg-core"; |
| 5 | 5 | import * as Effect from "effect/Effect"; |
| 6 | 6 | import * as Layer from "effect/Layer"; |
| 7 | 7 | |
| 8 | import { RelayDb, type RelayDatabase } from "../db.ts"; | |
| 8 | import * as RelayDb from "../db.ts"; | |
| 9 | 9 | import { relayLiveActivities, relayMobileDevices } from "../persistence/schema.ts"; |
| 10 | 10 | import * as Devices from "./Devices.ts"; |
| 11 | 11 | |
| ... | ... | @@ -71,7 +71,7 @@ describe("Devices", () => { |
| 71 | 71 | }, |
| 72 | 72 | }; |
| 73 | 73 | }, |
| 74 | } as unknown as RelayDatabase; | |
| 74 | } as unknown as RelayDb.RelayDb["Service"]; | |
| 75 | 75 | |
| 76 | 76 | return Effect.gen(function* () { |
| 77 | 77 | const devices = yield* Devices.Devices; |
| ... | ... | @@ -110,7 +110,9 @@ describe("Devices", () => { |
| 110 | 110 | pushToStartToken: "push-to-start-token", |
| 111 | 111 | }), |
| 112 | 112 | ]); |
| 113 | }).pipe(Effect.provide(Devices.layer.pipe(Layer.provide(Layer.succeed(RelayDb, fakeDb))))); | |
| 113 | }).pipe( | |
| 114 | Effect.provide(Devices.layer.pipe(Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)))), | |
| 115 | ); | |
| 114 | 116 | }); |
| 115 | 117 | |
| 116 | 118 | it.effect("unregisters APNs state only for the current user device", () => { |
| ... | ... | @@ -130,7 +132,7 @@ describe("Devices", () => { |
| 130 | 132 | }, |
| 131 | 133 | }; |
| 132 | 134 | }, |
| 133 | } as unknown as RelayDatabase; | |
| 135 | } as unknown as RelayDb.RelayDb["Service"]; | |
| 134 | 136 | |
| 135 | 137 | return Effect.gen(function* () { |
| 136 | 138 | const devices = yield* Devices.Devices; |
| ... | ... | @@ -156,7 +158,9 @@ describe("Devices", () => { |
| 156 | 158 | params: ["user-2", "device-1"], |
| 157 | 159 | }, |
| 158 | 160 | ]); |
| 159 | }).pipe(Effect.provide(Devices.layer.pipe(Layer.provide(Layer.succeed(RelayDb, fakeDb))))); | |
| 161 | }).pipe( | |
| 162 | Effect.provide(Devices.layer.pipe(Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)))), | |
| 163 | ); | |
| 160 | 164 | }); |
| 161 | 165 | |
| 162 | 166 | it.effect("lists safe notification state without exposing APNs tokens", () => { |
| ... | ... | @@ -184,7 +188,7 @@ describe("Devices", () => { |
| 184 | 188 | }; |
| 185 | 189 | }, |
| 186 | 190 | }), |
| 187 | } as unknown as RelayDatabase; | |
| 191 | } as unknown as RelayDb.RelayDb["Service"]; | |
| 188 | 192 | |
| 189 | 193 | return Effect.gen(function* () { |
| 190 | 194 | const devices = yield* Devices.Devices; |
| ... | ... | @@ -215,6 +219,8 @@ describe("Devices", () => { |
| 215 | 219 | updatedAt: "2026-06-01T00:00:00.000Z", |
| 216 | 220 | }, |
| 217 | 221 | ]); |
| 218 | }).pipe(Effect.provide(Devices.layer.pipe(Layer.provide(Layer.succeed(RelayDb, fakeDb))))); | |
| 222 | }).pipe( | |
| 223 | Effect.provide(Devices.layer.pipe(Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)))), | |
| 224 | ); | |
| 219 | 225 | }); |
| 220 | 226 | }); |
infra/relay/src/agentActivity/Devices.ts+2-2| ... | ... | @@ -10,7 +10,7 @@ import * as Schema from "effect/Schema"; |
| 10 | 10 | import { and, eq } from "drizzle-orm"; |
| 11 | 11 | import { sql } from "drizzle-orm"; |
| 12 | 12 | |
| 13 | import { RelayDb } from "../db.ts"; | |
| 13 | import * as RelayDb from "../db.ts"; | |
| 14 | 14 | import { relayLiveActivities, relayMobileDevices } from "../persistence/schema.ts"; |
| 15 | 15 | |
| 16 | 16 | export class DeviceRegistrationPersistenceError extends Schema.TaggedErrorClass<DeviceRegistrationPersistenceError>()( |
| ... | ... | @@ -59,7 +59,7 @@ export class Devices extends Context.Service<Devices, DevicesShape>()( |
| 59 | 59 | ) {} |
| 60 | 60 | |
| 61 | 61 | const make = Effect.gen(function* () { |
| 62 | const db = yield* RelayDb; | |
| 62 | const db = yield* RelayDb.RelayDb; | |
| 63 | 63 | |
| 64 | 64 | return Devices.of({ |
| 65 | 65 | register: Effect.fn("relay.devices.register")( |
infra/relay/src/agentActivity/LiveActivities.test.ts+9-5| ... | ... | @@ -8,7 +8,7 @@ import { PgDialect } from "drizzle-orm/pg-core"; |
| 8 | 8 | import * as Effect from "effect/Effect"; |
| 9 | 9 | import * as Layer from "effect/Layer"; |
| 10 | 10 | |
| 11 | import { RelayDb, type RelayDatabase } from "../db.ts"; | |
| 11 | import * as RelayDb from "../db.ts"; | |
| 12 | 12 | import { relayLiveActivities } from "../persistence/schema.ts"; |
| 13 | 13 | import * as LiveActivities from "./LiveActivities.ts"; |
| 14 | 14 | |
| ... | ... | @@ -88,7 +88,7 @@ describe("LiveActivities", () => { |
| 88 | 88 | }, |
| 89 | 89 | }; |
| 90 | 90 | }, |
| 91 | } as unknown as RelayDatabase; | |
| 91 | } as unknown as RelayDb.RelayDb["Service"]; | |
| 92 | 92 | |
| 93 | 93 | return Effect.gen(function* () { |
| 94 | 94 | const liveActivities = yield* LiveActivities.LiveActivities; |
| ... | ... | @@ -138,7 +138,9 @@ describe("LiveActivities", () => { |
| 138 | 138 | }), |
| 139 | 139 | ); |
| 140 | 140 | }).pipe( |
| 141 | Effect.provide(LiveActivities.layer.pipe(Layer.provide(Layer.succeed(RelayDb, fakeDb)))), | |
| 141 | Effect.provide( | |
| 142 | LiveActivities.layer.pipe(Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb))), | |
| 143 | ), | |
| 142 | 144 | ); |
| 143 | 145 | }, |
| 144 | 146 | ); |
| ... | ... | @@ -164,7 +166,7 @@ describe("LiveActivities", () => { |
| 164 | 166 | }, |
| 165 | 167 | }; |
| 166 | 168 | }, |
| 167 | } as unknown as RelayDatabase; | |
| 169 | } as unknown as RelayDb.RelayDb["Service"]; | |
| 168 | 170 | |
| 169 | 171 | return Effect.gen(function* () { |
| 170 | 172 | const liveActivities = yield* LiveActivities.LiveActivities; |
| ... | ... | @@ -190,7 +192,9 @@ describe("LiveActivities", () => { |
| 190 | 192 | }), |
| 191 | 193 | ); |
| 192 | 194 | }).pipe( |
| 193 | Effect.provide(LiveActivities.layer.pipe(Layer.provide(Layer.succeed(RelayDb, fakeDb)))), | |
| 195 | Effect.provide( | |
| 196 | LiveActivities.layer.pipe(Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb))), | |
| 197 | ), | |
| 194 | 198 | ); |
| 195 | 199 | }); |
| 196 | 200 | }); |
infra/relay/src/agentActivity/LiveActivities.ts+2-2| ... | ... | @@ -12,7 +12,7 @@ import * as Layer from "effect/Layer"; |
| 12 | 12 | import * as Schema from "effect/Schema"; |
| 13 | 13 | import { and, eq, sql } from "drizzle-orm"; |
| 14 | 14 | |
| 15 | import { RelayDb } from "../db.ts"; | |
| 15 | import * as RelayDb from "../db.ts"; | |
| 16 | 16 | import { relayLiveActivities, relayMobileDevices } from "../persistence/schema.ts"; |
| 17 | 17 | |
| 18 | 18 | export class LiveActivityRegistrationPersistenceError extends Schema.TaggedErrorClass<LiveActivityRegistrationPersistenceError>()( |
| ... | ... | @@ -108,7 +108,7 @@ const encodeRelayAgentActivityAggregateStateJson = Schema.encodeEffect( |
| 108 | 108 | ); |
| 109 | 109 | |
| 110 | 110 | const make = Effect.gen(function* () { |
| 111 | const db = yield* RelayDb; | |
| 111 | const db = yield* RelayDb.RelayDb; | |
| 112 | 112 | |
| 113 | 113 | return LiveActivities.of({ |
| 114 | 114 | register: Effect.fn("relay.live_activities.register")( |
infra/relay/src/agentActivity/MobileRegistrations.test.ts+3-3| ... | ... | @@ -86,8 +86,8 @@ function makeAgentActivityRows( |
| 86 | 86 | } |
| 87 | 87 | |
| 88 | 88 | function makeEnvironmentLinks( |
| 89 | overrides: Partial<EnvironmentLinks.EnvironmentLinksShape> = {}, | |
| 90 | ): EnvironmentLinks.EnvironmentLinksShape { | |
| 89 | overrides: Partial<EnvironmentLinks.EnvironmentLinks["Service"]> = {}, | |
| 90 | ): EnvironmentLinks.EnvironmentLinks["Service"] { | |
| 91 | 91 | return { |
| 92 | 92 | upsert: () => Effect.void, |
| 93 | 93 | listUsersForEnvironment: () => Effect.succeed(["dev:julius"]), |
| ... | ... | @@ -153,7 +153,7 @@ function makeRegistrationReplayLayer(input: { |
| 153 | 153 | Layer.succeed(EnvironmentLinks.EnvironmentLinks, makeEnvironmentLinks()), |
| 154 | 154 | Layer.succeed(LiveActivities.LiveActivities, input.liveActivities), |
| 155 | 155 | Layer.succeed(DeliveryAttempts.DeliveryAttempts, makeDeliveryAttempts()), |
| 156 | Layer.succeed(RelayConfiguration.RelayConfiguration, config), | |
| 156 | RelayConfiguration.layer(config), | |
| 157 | 157 | Layer.succeed(ApnsDeliveryQueue.ApnsDeliveryQueueSender, { |
| 158 | 158 | send: (body) => |
| 159 | 159 | Effect.sync(() => { |
infra/relay/src/auth/DpopProofs.test.ts+9-5| ... | ... | @@ -4,7 +4,7 @@ import * as Effect from "effect/Effect"; |
| 4 | 4 | import * as Layer from "effect/Layer"; |
| 5 | 5 | import * as Option from "effect/Option"; |
| 6 | 6 | |
| 7 | import { RelayDb, type RelayDatabase } from "../db.ts"; | |
| 7 | import * as RelayDb from "../db.ts"; | |
| 8 | 8 | import { relayDpopProofs } from "../persistence/schema.ts"; |
| 9 | 9 | import * as DpopProofs from "./DpopProofs.ts"; |
| 10 | 10 | |
| ... | ... | @@ -41,7 +41,7 @@ describe("DpopProofReplay", () => { |
| 41 | 41 | }, |
| 42 | 42 | }; |
| 43 | 43 | }, |
| 44 | } as unknown as RelayDatabase; | |
| 44 | } as unknown as RelayDb.RelayDb["Service"]; | |
| 45 | 45 | |
| 46 | 46 | return Effect.gen(function* () { |
| 47 | 47 | const replay = yield* DpopProofs.DpopProofReplay; |
| ... | ... | @@ -67,7 +67,9 @@ describe("DpopProofReplay", () => { |
| 67 | 67 | expiresAt: "2026-05-25T12:00:00.000Z", |
| 68 | 68 | }, |
| 69 | 69 | ]); |
| 70 | }).pipe(Effect.provide(DpopProofs.layer.pipe(Layer.provide(Layer.succeed(RelayDb, fakeDb))))); | |
| 70 | }).pipe( | |
| 71 | Effect.provide(DpopProofs.layer.pipe(Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)))), | |
| 72 | ); | |
| 71 | 73 | }); |
| 72 | 74 | |
| 73 | 75 | it.effect("prunes expired proof rows from the maintenance path", () => { |
| ... | ... | @@ -84,12 +86,14 @@ describe("DpopProofReplay", () => { |
| 84 | 86 | }, |
| 85 | 87 | }; |
| 86 | 88 | }, |
| 87 | } as unknown as RelayDatabase; | |
| 89 | } as unknown as RelayDb.RelayDb["Service"]; | |
| 88 | 90 | |
| 89 | 91 | return Effect.gen(function* () { |
| 90 | 92 | const replay = yield* DpopProofs.DpopProofReplay; |
| 91 | 93 | yield* replay.pruneExpired; |
| 92 | 94 | expect(calls).toEqual(["delete", "delete.where"]); |
| 93 | }).pipe(Effect.provide(DpopProofs.layer.pipe(Layer.provide(Layer.succeed(RelayDb, fakeDb))))); | |
| 95 | }).pipe( | |
| 96 | Effect.provide(DpopProofs.layer.pipe(Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)))), | |
| 97 | ); | |
| 94 | 98 | }); |
| 95 | 99 | }); |
infra/relay/src/auth/DpopProofs.ts+25-28| ... | ... | @@ -7,7 +7,7 @@ import * as HttpApiError from "effect/unstable/httpapi/HttpApiError"; |
| 7 | 7 | import { lt } from "drizzle-orm"; |
| 8 | 8 | |
| 9 | 9 | import { verifyDpopProof } from "@t3tools/shared/dpop"; |
| 10 | import { RelayDb } from "../db.ts"; | |
| 10 | import * as RelayDb from "../db.ts"; | |
| 11 | 11 | import { relayDpopProofs } from "../persistence/schema.ts"; |
| 12 | 12 | |
| 13 | 13 | export class DpopProofReplayPersistenceError extends Schema.TaggedErrorClass<DpopProofReplayPersistenceError>()( |
| ... | ... | @@ -21,34 +21,31 @@ export class DpopProofReplayPersistenceError extends Schema.TaggedErrorClass<Dpo |
| 21 | 21 | } |
| 22 | 22 | } |
| 23 | 23 | |
| 24 | export interface DpopProofReplayShape { | |
| 25 | readonly verifyAndConsume: (input: { | |
| 26 | readonly proof: string | undefined; | |
| 27 | readonly method: string; | |
| 28 | readonly url: string; | |
| 29 | readonly expectedThumbprint?: string; | |
| 30 | readonly expectedAccessToken?: string; | |
| 31 | readonly now: DateTime.DateTime; | |
| 32 | }) => Effect.Effect<string, HttpApiError.Unauthorized | DpopProofReplayPersistenceError>; | |
| 33 | ||
| 34 | readonly consume: (input: { | |
| 35 | readonly thumbprint: string; | |
| 36 | readonly jti: string; | |
| 37 | readonly iat: number; | |
| 38 | readonly expiresAt: DateTime.DateTime; | |
| 39 | }) => Effect.Effect<boolean, DpopProofReplayPersistenceError>; | |
| 40 | ||
| 41 | readonly pruneExpired: Effect.Effect<void, DpopProofReplayPersistenceError>; | |
| 42 | } | |
| 43 | ||
| 44 | export class DpopProofReplay extends Context.Service<DpopProofReplay, DpopProofReplayShape>()( | |
| 45 | "t3code-relay/auth/DpopProofs/DpopProofReplay", | |
| 46 | ) {} | |
| 24 | export class DpopProofReplay extends Context.Service< | |
| 25 | DpopProofReplay, | |
| 26 | { | |
| 27 | readonly verifyAndConsume: (input: { | |
| 28 | readonly proof: string | undefined; | |
| 29 | readonly method: string; | |
| 30 | readonly url: string; | |
| 31 | readonly expectedThumbprint?: string; | |
| 32 | readonly expectedAccessToken?: string; | |
| 33 | readonly now: DateTime.DateTime; | |
| 34 | }) => Effect.Effect<string, HttpApiError.Unauthorized | DpopProofReplayPersistenceError>; | |
| 35 | readonly consume: (input: { | |
| 36 | readonly thumbprint: string; | |
| 37 | readonly jti: string; | |
| 38 | readonly iat: number; | |
| 39 | readonly expiresAt: DateTime.DateTime; | |
| 40 | }) => Effect.Effect<boolean, DpopProofReplayPersistenceError>; | |
| 41 | readonly pruneExpired: Effect.Effect<void, DpopProofReplayPersistenceError>; | |
| 42 | } | |
| 43 | >()("t3code-relay/auth/DpopProofs/DpopProofReplay") {} | |
| 47 | 44 | |
| 48 | 45 | const make = Effect.gen(function* () { |
| 49 | const db = yield* RelayDb; | |
| 46 | const db = yield* RelayDb.RelayDb; | |
| 50 | 47 | |
| 51 | const consume: DpopProofReplayShape["consume"] = Effect.fn("relay.dpop_proofs.consume")( | |
| 48 | const consume: DpopProofReplay["Service"]["consume"] = Effect.fn("relay.dpop_proofs.consume")( | |
| 52 | 49 | function* (input) { |
| 53 | 50 | const createdAt = DateTime.formatIso(yield* DateTime.now); |
| 54 | 51 | const inserted = yield* db |
| ... | ... | @@ -67,7 +64,7 @@ const make = Effect.gen(function* () { |
| 67 | 64 | Effect.mapError((cause) => new DpopProofReplayPersistenceError({ cause })), |
| 68 | 65 | ); |
| 69 | 66 | |
| 70 | const verifyAndConsume: DpopProofReplayShape["verifyAndConsume"] = Effect.fn( | |
| 67 | const verifyAndConsume: DpopProofReplay["Service"]["verifyAndConsume"] = Effect.fn( | |
| 71 | 68 | "relay.dpop_proofs.verify_and_consume", |
| 72 | 69 | )(function* (input) { |
| 73 | 70 | yield* Effect.annotateCurrentSpan({ |
| ... | ... | @@ -114,7 +111,7 @@ const make = Effect.gen(function* () { |
| 114 | 111 | return result.thumbprint; |
| 115 | 112 | }); |
| 116 | 113 | |
| 117 | const pruneExpired: DpopProofReplayShape["pruneExpired"] = Effect.gen(function* () { | |
| 114 | const pruneExpired: DpopProofReplay["Service"]["pruneExpired"] = Effect.gen(function* () { | |
| 118 | 115 | const now = DateTime.formatIso(yield* DateTime.now); |
| 119 | 116 | yield* Effect.annotateCurrentSpan({ "relay.dpop_prune.before": now }); |
| 120 | 117 | yield* db.delete(relayDpopProofs).where(lt(relayDpopProofs.expiresAt, now)); |
infra/relay/src/auth/DpopProofs.verifyAndConsume.test.ts+3-3| ... | ... | @@ -10,7 +10,7 @@ import * as DateTime from "effect/DateTime"; |
| 10 | 10 | import * as Effect from "effect/Effect"; |
| 11 | 11 | import * as Layer from "effect/Layer"; |
| 12 | 12 | |
| 13 | import { RelayDb, type RelayDatabase } from "../db.ts"; | |
| 13 | import * as RelayDb from "../db.ts"; | |
| 14 | 14 | import { relayDpopProofs } from "../persistence/schema.ts"; |
| 15 | 15 | import * as DpopProofs from "./DpopProofs.ts"; |
| 16 | 16 | |
| ... | ... | @@ -78,8 +78,8 @@ function layer( |
| 78 | 78 | }), |
| 79 | 79 | }; |
| 80 | 80 | }, |
| 81 | } as unknown as RelayDatabase; | |
| 82 | return DpopProofs.layer.pipe(Layer.provide(Layer.succeed(RelayDb, fakeDb))); | |
| 81 | } as unknown as RelayDb.RelayDb["Service"]; | |
| 82 | return DpopProofs.layer.pipe(Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb))); | |
| 83 | 83 | } |
| 84 | 84 | |
| 85 | 85 | function consumeEachProofOnce() { |
infra/relay/src/auth/RelayTokens.test.ts+1-3| ... | ... | @@ -33,9 +33,7 @@ const config = RelayConfiguration.RelayConfiguration.of({ |
| 33 | 33 | managedEndpointNamespace: undefined, |
| 34 | 34 | }); |
| 35 | 35 | |
| 36 | const layer = RelayTokens.layer.pipe( | |
| 37 | Layer.provide(Layer.succeed(RelayConfiguration.RelayConfiguration, config)), | |
| 38 | ); | |
| 36 | const layer = RelayTokens.layer.pipe(Layer.provide(RelayConfiguration.layer(config))); | |
| 39 | 37 | |
| 40 | 38 | describe("RelayTokens", () => { |
| 41 | 39 | it.effect("issues a user-bound environment link challenge", () => |
infra/relay/src/auth/RelayTokens.ts+36-37| ... | ... | @@ -93,45 +93,44 @@ function resolveDpopAccessTokenScopes(input: { |
| 93 | 93 | }); |
| 94 | 94 | } |
| 95 | 95 | |
| 96 | export interface RelayTokensShape { | |
| 97 | readonly resolveDpopAccessTokenScopes: typeof resolveDpopAccessTokenScopes; | |
| 98 | readonly issueLinkChallenge: (input: { | |
| 99 | readonly userId: string; | |
| 100 | readonly request: RelayEnvironmentLinkChallengeRequest; | |
| 101 | readonly jti: string; | |
| 102 | readonly issuedAtEpochSeconds: number; | |
| 103 | readonly expiresAtEpochSeconds: number; | |
| 104 | }) => Effect.Effect<string, RelayJwtError>; | |
| 105 | readonly verifyLinkChallenge: (input: { | |
| 106 | readonly token: string; | |
| 107 | readonly userId: string; | |
| 108 | readonly request: RelayEnvironmentLinkChallengeRequest; | |
| 109 | readonly nowEpochSeconds: number; | |
| 110 | }) => Effect.Effect<LinkChallengeClaims | null>; | |
| 111 | readonly issueDpopAccessToken: (input: { | |
| 112 | readonly userId: string; | |
| 113 | readonly proofKeyThumbprint: string; | |
| 114 | readonly jti: string; | |
| 115 | readonly issuedAtEpochSeconds: number; | |
| 116 | readonly expiresAtEpochSeconds: number; | |
| 117 | readonly clientId: RelayPublicClientId; | |
| 118 | readonly scopes: ReadonlyArray<RelayDpopAccessTokenScope>; | |
| 119 | }) => Effect.Effect<string, RelayJwtError>; | |
| 120 | readonly verifyDpopAccessToken: (input: { | |
| 121 | readonly token: string; | |
| 122 | readonly nowEpochSeconds: number; | |
| 123 | }) => Effect.Effect<RelayDpopAccessTokenClaims | null>; | |
| 124 | } | |
| 125 | ||
| 126 | export class RelayTokens extends Context.Service<RelayTokens, RelayTokensShape>()( | |
| 127 | "t3code-relay/auth/RelayTokens", | |
| 128 | ) {} | |
| 96 | export class RelayTokens extends Context.Service< | |
| 97 | RelayTokens, | |
| 98 | { | |
| 99 | readonly resolveDpopAccessTokenScopes: typeof resolveDpopAccessTokenScopes; | |
| 100 | readonly issueLinkChallenge: (input: { | |
| 101 | readonly userId: string; | |
| 102 | readonly request: RelayEnvironmentLinkChallengeRequest; | |
| 103 | readonly jti: string; | |
| 104 | readonly issuedAtEpochSeconds: number; | |
| 105 | readonly expiresAtEpochSeconds: number; | |
| 106 | }) => Effect.Effect<string, RelayJwtError>; | |
| 107 | readonly verifyLinkChallenge: (input: { | |
| 108 | readonly token: string; | |
| 109 | readonly userId: string; | |
| 110 | readonly request: RelayEnvironmentLinkChallengeRequest; | |
| 111 | readonly nowEpochSeconds: number; | |
| 112 | }) => Effect.Effect<LinkChallengeClaims | null>; | |
| 113 | readonly issueDpopAccessToken: (input: { | |
| 114 | readonly userId: string; | |
| 115 | readonly proofKeyThumbprint: string; | |
| 116 | readonly jti: string; | |
| 117 | readonly issuedAtEpochSeconds: number; | |
| 118 | readonly expiresAtEpochSeconds: number; | |
| 119 | readonly clientId: RelayPublicClientId; | |
| 120 | readonly scopes: ReadonlyArray<RelayDpopAccessTokenScope>; | |
| 121 | }) => Effect.Effect<string, RelayJwtError>; | |
| 122 | readonly verifyDpopAccessToken: (input: { | |
| 123 | readonly token: string; | |
| 124 | readonly nowEpochSeconds: number; | |
| 125 | }) => Effect.Effect<RelayDpopAccessTokenClaims | null>; | |
| 126 | } | |
| 127 | >()("t3code-relay/auth/RelayTokens") {} | |
| 129 | 128 | |
| 130 | 129 | const make = Effect.gen(function* () { |
| 131 | 130 | const config = yield* RelayConfiguration.RelayConfiguration; |
| 132 | 131 | const issuer = normalizeRelayIssuer(config.relayIssuer); |
| 133 | 132 | |
| 134 | const issueLinkChallenge: RelayTokensShape["issueLinkChallenge"] = Effect.fn( | |
| 133 | const issueLinkChallenge: RelayTokens["Service"]["issueLinkChallenge"] = Effect.fn( | |
| 135 | 134 | "relay.tokens.issue_link_challenge", |
| 136 | 135 | )(function* (input) { |
| 137 | 136 | return yield* signRelayJwt({ |
| ... | ... | @@ -150,7 +149,7 @@ const make = Effect.gen(function* () { |
| 150 | 149 | }); |
| 151 | 150 | }); |
| 152 | 151 | |
| 153 | const verifyLinkChallenge: RelayTokensShape["verifyLinkChallenge"] = Effect.fn( | |
| 152 | const verifyLinkChallenge: RelayTokens["Service"]["verifyLinkChallenge"] = Effect.fn( | |
| 154 | 153 | "relay.tokens.verify_link_challenge", |
| 155 | 154 | )((input) => |
| 156 | 155 | verifyRelayJwt({ |
| ... | ... | @@ -177,7 +176,7 @@ const make = Effect.gen(function* () { |
| 177 | 176 | ), |
| 178 | 177 | ); |
| 179 | 178 | |
| 180 | const issueDpopAccessToken: RelayTokensShape["issueDpopAccessToken"] = Effect.fn( | |
| 179 | const issueDpopAccessToken: RelayTokens["Service"]["issueDpopAccessToken"] = Effect.fn( | |
| 181 | 180 | "relay.tokens.issue_dpop_access_token", |
| 182 | 181 | )(function* (input) { |
| 183 | 182 | return yield* signRelayJwt({ |
| ... | ... | @@ -197,7 +196,7 @@ const make = Effect.gen(function* () { |
| 197 | 196 | }); |
| 198 | 197 | }); |
| 199 | 198 | |
| 200 | const verifyDpopAccessToken: RelayTokensShape["verifyDpopAccessToken"] = Effect.fn( | |
| 199 | const verifyDpopAccessToken: RelayTokens["Service"]["verifyDpopAccessToken"] = Effect.fn( | |
| 201 | 200 | "relay.tokens.verify_dpop_access_token", |
| 202 | 201 | )((input) => |
| 203 | 202 | verifyRelayJwt({ |
infra/relay/src/db.ts+6-5| ... | ... | @@ -10,11 +10,12 @@ import * as Effect from "effect/Effect"; |
| 10 | 10 | |
| 11 | 11 | import { relayDatabaseMode } from "./dbConfig.ts"; |
| 12 | 12 | |
| 13 | export interface RelayDatabase extends EffectPgDatabase { | |
| 14 | readonly $client: PgClient; | |
| 15 | } | |
| 16 | ||
| 17 | export class RelayDb extends Context.Service<RelayDb, RelayDatabase>()("t3code-relay/db/RelayDb") {} | |
| 13 | export class RelayDb extends Context.Service< | |
| 14 | RelayDb, | |
| 15 | EffectPgDatabase & { | |
| 16 | readonly $client: PgClient; | |
| 17 | } | |
| 18 | >()("t3code-relay/db/RelayDb") {} | |
| 18 | 19 | |
| 19 | 20 | export const PlanetscaleDatabase = Effect.gen(function* () { |
| 20 | 21 | const { stage } = yield* Alchemy.Stack; |
infra/relay/src/environments/EnvironmentConnector.test.ts+5-5| ... | ... | @@ -161,8 +161,8 @@ function connectorTestLayer( |
| 161 | 161 | request: HttpClientRequest.HttpClientRequest, |
| 162 | 162 | ) => Effect.Effect<HttpClientResponse.HttpClientResponse>, |
| 163 | 163 | options?: { |
| 164 | readonly links?: EnvironmentLinks.EnvironmentLinksShape; | |
| 165 | readonly allocations?: ManagedEndpointAllocations.ManagedEndpointAllocationsShape; | |
| 164 | readonly links?: EnvironmentLinks.EnvironmentLinks["Service"]; | |
| 165 | readonly allocations?: ManagedEndpointAllocations.ManagedEndpointAllocations["Service"]; | |
| 166 | 166 | }, |
| 167 | 167 | ) { |
| 168 | 168 | return EnvironmentConnector.layer.pipe( |
| ... | ... | @@ -174,7 +174,7 @@ function connectorTestLayer( |
| 174 | 174 | options?.allocations ?? makeAllocations(), |
| 175 | 175 | ), |
| 176 | 176 | ), |
| 177 | Layer.provide(Layer.succeed(RelayConfiguration.RelayConfiguration, settings)), | |
| 177 | Layer.provide(RelayConfiguration.layer(settings)), | |
| 178 | 178 | Layer.provide(Layer.succeed(HttpClient.HttpClient, HttpClient.make(execute))), |
| 179 | 179 | ); |
| 180 | 180 | } |
| ... | ... | @@ -189,7 +189,7 @@ function makeAllocations( |
| 189 | 189 | dnsRecordId: "dns-record-id", |
| 190 | 190 | readyAt: "2026-05-25T00:00:00.000Z", |
| 191 | 191 | }, |
| 192 | ): ManagedEndpointAllocations.ManagedEndpointAllocationsShape { | |
| 192 | ): ManagedEndpointAllocations.ManagedEndpointAllocations["Service"] { | |
| 193 | 193 | return { |
| 194 | 194 | get: () => Effect.succeed(allocation), |
| 195 | 195 | reserve: () => Effect.die("unused"), |
| ... | ... | @@ -202,7 +202,7 @@ function makeAllocations( |
| 202 | 202 | |
| 203 | 203 | function makeLinks( |
| 204 | 204 | overrides: Partial<EnvironmentLinks.RelayLinkedEnvironmentRecord> = {}, |
| 205 | ): EnvironmentLinks.EnvironmentLinksShape { | |
| 205 | ): EnvironmentLinks.EnvironmentLinks["Service"] { | |
| 206 | 206 | return { |
| 207 | 207 | upsert: () => Effect.void, |
| 208 | 208 | listUsersForEnvironment: () => Effect.succeed([]), |
infra/relay/src/environments/EnvironmentConnector.ts+15-15| ... | ... | @@ -35,7 +35,9 @@ import * as Layer from "effect/Layer"; |
| 35 | 35 | import * as Option from "effect/Option"; |
| 36 | 36 | import * as Redacted from "effect/Redacted"; |
| 37 | 37 | import * as Schema from "effect/Schema"; |
| 38 | import { FetchHttpClient, HttpClient, HttpClientError } from "effect/unstable/http"; | |
| 38 | import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient"; | |
| 39 | import * as HttpClient from "effect/unstable/http/HttpClient"; | |
| 40 | import * as HttpClientError from "effect/unstable/http/HttpClientError"; | |
| 39 | 41 | |
| 40 | 42 | import * as EnvironmentLinks from "./EnvironmentLinks.ts"; |
| 41 | 43 | import * as ManagedEndpointAllocations from "./ManagedEndpointAllocations.ts"; |
| ... | ... | @@ -139,22 +141,20 @@ export type EnvironmentConnectorError = |
| 139 | 141 | export const ENVIRONMENT_MINT_REQUEST_TIMEOUT_MS = 10_000; |
| 140 | 142 | const ENVIRONMENT_HEALTH_CLOCK_SKEW_MILLIS = 60 * 1_000; |
| 141 | 143 | |
| 142 | export interface EnvironmentConnectorShape { | |
| 143 | readonly connect: (input: { | |
| 144 | readonly userId: string; | |
| 145 | readonly environmentId: string; | |
| 146 | readonly clientProofKeyThumbprint: string; | |
| 147 | readonly deviceId?: string; | |
| 148 | }) => Effect.Effect<RelayEnvironmentConnectResponse, EnvironmentConnectorError>; | |
| 149 | readonly status: (input: { | |
| 150 | readonly userId: string; | |
| 151 | readonly environmentId: string; | |
| 152 | }) => Effect.Effect<RelayEnvironmentStatusResponse, EnvironmentConnectorError>; | |
| 153 | } | |
| 154 | ||
| 155 | 144 | export class EnvironmentConnector extends Context.Service< |
| 156 | 145 | EnvironmentConnector, |
| 157 | EnvironmentConnectorShape | |
| 146 | { | |
| 147 | readonly connect: (input: { | |
| 148 | readonly userId: string; | |
| 149 | readonly environmentId: string; | |
| 150 | readonly clientProofKeyThumbprint: string; | |
| 151 | readonly deviceId?: string; | |
| 152 | }) => Effect.Effect<RelayEnvironmentConnectResponse, EnvironmentConnectorError>; | |
| 153 | readonly status: (input: { | |
| 154 | readonly userId: string; | |
| 155 | readonly environmentId: string; | |
| 156 | }) => Effect.Effect<RelayEnvironmentStatusResponse, EnvironmentConnectorError>; | |
| 157 | } | |
| 158 | 158 | >()("t3code-relay/environments/EnvironmentConnector") {} |
| 159 | 159 | |
| 160 | 160 | const decodeMintResponseProof = Schema.decodeUnknownEffect( |
infra/relay/src/environments/EnvironmentCredentials.test.ts+5-5| ... | ... | @@ -4,7 +4,7 @@ import { PgDialect, QueryBuilder } from "drizzle-orm/pg-core"; |
| 4 | 4 | import * as Effect from "effect/Effect"; |
| 5 | 5 | import * as Layer from "effect/Layer"; |
| 6 | 6 | |
| 7 | import { RelayDb, type RelayDatabase } from "../db.ts"; | |
| 7 | import * as RelayDb from "../db.ts"; | |
| 8 | 8 | import { relayEnvironmentCredentials } from "../persistence/schema.ts"; |
| 9 | 9 | import * as EnvironmentCredentials from "./EnvironmentCredentials.ts"; |
| 10 | 10 | |
| ... | ... | @@ -47,7 +47,7 @@ describe("EnvironmentCredentials", () => { |
| 47 | 47 | }), |
| 48 | 48 | }; |
| 49 | 49 | }, |
| 50 | } as unknown as RelayDatabase; | |
| 50 | } as unknown as RelayDb.RelayDb["Service"]; | |
| 51 | 51 | |
| 52 | 52 | return Effect.gen(function* () { |
| 53 | 53 | const credentials = yield* EnvironmentCredentials.EnvironmentCredentials; |
| ... | ... | @@ -87,7 +87,7 @@ describe("EnvironmentCredentials", () => { |
| 87 | 87 | Effect.provide( |
| 88 | 88 | EnvironmentCredentials.layer.pipe( |
| 89 | 89 | Layer.provide(NodeCryptoLayer.layer), |
| 90 | Layer.provide(Layer.succeed(RelayDb, fakeDb)), | |
| 90 | Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)), | |
| 91 | 91 | ), |
| 92 | 92 | ), |
| 93 | 93 | ); |
| ... | ... | @@ -118,7 +118,7 @@ describe("EnvironmentCredentials", () => { |
| 118 | 118 | }, |
| 119 | 119 | }; |
| 120 | 120 | }, |
| 121 | } as unknown as RelayDatabase; | |
| 121 | } as unknown as RelayDb.RelayDb["Service"]; | |
| 122 | 122 | |
| 123 | 123 | return Effect.gen(function* () { |
| 124 | 124 | const credentials = yield* EnvironmentCredentials.EnvironmentCredentials; |
| ... | ... | @@ -150,7 +150,7 @@ describe("EnvironmentCredentials", () => { |
| 150 | 150 | Effect.provide( |
| 151 | 151 | EnvironmentCredentials.layer.pipe( |
| 152 | 152 | Layer.provide(NodeCryptoLayer.layer), |
| 153 | Layer.provide(Layer.succeed(RelayDb, fakeDb)), | |
| 153 | Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)), | |
| 154 | 154 | ), |
| 155 | 155 | ), |
| 156 | 156 | ); |
infra/relay/src/environments/EnvironmentCredentials.ts+18-20| ... | ... | @@ -8,7 +8,7 @@ import * as Option from "effect/Option"; |
| 8 | 8 | import * as Schema from "effect/Schema"; |
| 9 | 9 | import { and, eq, isNull, ne, notExists } from "drizzle-orm"; |
| 10 | 10 | |
| 11 | import { RelayDb } from "../db.ts"; | |
| 11 | import * as RelayDb from "../db.ts"; | |
| 12 | 12 | import { relayEnvironmentCredentials, relayEnvironmentLinks } from "../persistence/schema.ts"; |
| 13 | 13 | |
| 14 | 14 | export class EnvironmentCredentialCreatePersistenceError extends Schema.TaggedErrorClass<EnvironmentCredentialCreatePersistenceError>()( |
| ... | ... | @@ -44,30 +44,28 @@ export interface EnvironmentCredentialPrincipal { |
| 44 | 44 | readonly environmentPublicKey: string; |
| 45 | 45 | } |
| 46 | 46 | |
| 47 | export interface EnvironmentCredentialsShape { | |
| 48 | readonly create: (input: { | |
| 49 | readonly environmentId: string; | |
| 50 | readonly environmentPublicKey: string; | |
| 51 | }) => Effect.Effect<string, EnvironmentCredentialCreatePersistenceError>; | |
| 52 | readonly authenticate: ( | |
| 53 | token: string, | |
| 54 | ) => Effect.Effect< | |
| 55 | Option.Option<EnvironmentCredentialPrincipal>, | |
| 56 | EnvironmentCredentialAuthenticatePersistenceError | |
| 57 | >; | |
| 58 | readonly revokeForEnvironmentPublicKey: (input: { | |
| 59 | readonly environmentId: string; | |
| 60 | readonly environmentPublicKey: string; | |
| 61 | }) => Effect.Effect<boolean, EnvironmentCredentialRevokePersistenceError>; | |
| 62 | } | |
| 63 | ||
| 64 | 47 | export class EnvironmentCredentials extends Context.Service< |
| 65 | 48 | EnvironmentCredentials, |
| 66 | EnvironmentCredentialsShape | |
| 49 | { | |
| 50 | readonly create: (input: { | |
| 51 | readonly environmentId: string; | |
| 52 | readonly environmentPublicKey: string; | |
| 53 | }) => Effect.Effect<string, EnvironmentCredentialCreatePersistenceError>; | |
| 54 | readonly authenticate: ( | |
| 55 | token: string, | |
| 56 | ) => Effect.Effect< | |
| 57 | Option.Option<EnvironmentCredentialPrincipal>, | |
| 58 | EnvironmentCredentialAuthenticatePersistenceError | |
| 59 | >; | |
| 60 | readonly revokeForEnvironmentPublicKey: (input: { | |
| 61 | readonly environmentId: string; | |
| 62 | readonly environmentPublicKey: string; | |
| 63 | }) => Effect.Effect<boolean, EnvironmentCredentialRevokePersistenceError>; | |
| 64 | } | |
| 67 | 65 | >()("t3code-relay/environments/EnvironmentCredentials") {} |
| 68 | 66 | |
| 69 | 67 | const make = Effect.gen(function* () { |
| 70 | const db = yield* RelayDb; | |
| 68 | const db = yield* RelayDb.RelayDb; | |
| 71 | 69 | const crypto = yield* Crypto.Crypto; |
| 72 | 70 | const hashToken = (token: string) => |
| 73 | 71 | crypto |
infra/relay/src/environments/EnvironmentLinker.test.ts+3-3| ... | ... | @@ -105,14 +105,14 @@ const makeRequest = Effect.gen(function* () { |
| 105 | 105 | }); |
| 106 | 106 | |
| 107 | 107 | function testLayer(input?: { |
| 108 | readonly upsert?: EnvironmentLinks.EnvironmentLinksShape["upsert"]; | |
| 109 | readonly consume?: DpopProofs.DpopProofReplayShape["consume"]; | |
| 108 | readonly upsert?: EnvironmentLinks.EnvironmentLinks["Service"]["upsert"]; | |
| 109 | readonly consume?: DpopProofs.DpopProofReplay["Service"]["consume"]; | |
| 110 | 110 | }) { |
| 111 | 111 | return EnvironmentLinker.layer.pipe( |
| 112 | 112 | Layer.provideMerge(RelayTokens.layer), |
| 113 | 113 | Layer.provide( |
| 114 | 114 | Layer.mergeAll( |
| 115 | Layer.succeed(RelayConfiguration.RelayConfiguration, config), | |
| 115 | RelayConfiguration.layer(config), | |
| 116 | 116 | Layer.succeed(DpopProofs.DpopProofReplay, { |
| 117 | 117 | verifyAndConsume: () => Effect.die("unexpected DPoP proof verification"), |
| 118 | 118 | consume: input?.consume ?? (() => Effect.succeed(true)), |
infra/relay/src/environments/EnvironmentLinker.ts+19-20| ... | ... | @@ -53,26 +53,25 @@ export type EnvironmentLinkError = |
| 53 | 53 | | EnvironmentCredentials.EnvironmentCredentialCreatePersistenceError |
| 54 | 54 | | ManagedEndpointProvider.ManagedEndpointProviderError; |
| 55 | 55 | |
| 56 | export interface EnvironmentLinkerShape { | |
| 57 | readonly link: (input: { | |
| 58 | readonly userId: string; | |
| 59 | readonly request: RelayEnvironmentLinkRequest; | |
| 60 | }) => Effect.Effect< | |
| 61 | { | |
| 62 | readonly environmentId: RelayEnvironmentLinkProofPayload["environmentId"]; | |
| 63 | readonly endpoint: RelayEnvironmentLinkProofPayload["endpoint"]; | |
| 64 | readonly endpointRuntime: | |
| 65 | | ManagedEndpointProvider.ManagedEndpointProvisioningResult["runtime"] | |
| 66 | | null; | |
| 67 | readonly environmentCredential: string; | |
| 68 | }, | |
| 69 | EnvironmentLinkError | |
| 70 | >; | |
| 71 | } | |
| 72 | ||
| 73 | export class EnvironmentLinker extends Context.Service<EnvironmentLinker, EnvironmentLinkerShape>()( | |
| 74 | "t3code-relay/environments/EnvironmentLinker", | |
| 75 | ) {} | |
| 56 | export class EnvironmentLinker extends Context.Service< | |
| 57 | EnvironmentLinker, | |
| 58 | { | |
| 59 | readonly link: (input: { | |
| 60 | readonly userId: string; | |
| 61 | readonly request: RelayEnvironmentLinkRequest; | |
| 62 | }) => Effect.Effect< | |
| 63 | { | |
| 64 | readonly environmentId: RelayEnvironmentLinkProofPayload["environmentId"]; | |
| 65 | readonly endpoint: RelayEnvironmentLinkProofPayload["endpoint"]; | |
| 66 | readonly endpointRuntime: | |
| 67 | | ManagedEndpointProvider.ManagedEndpointProvisioningResult["runtime"] | |
| 68 | | null; | |
| 69 | readonly environmentCredential: string; | |
| 70 | }, | |
| 71 | EnvironmentLinkError | |
| 72 | >; | |
| 73 | } | |
| 74 | >()("t3code-relay/environments/EnvironmentLinker") {} | |
| 76 | 75 | |
| 77 | 76 | const decodeProof = Schema.decodeUnknownEffect(RelayEnvironmentLinkProofPayload); |
| 78 | 77 |
infra/relay/src/environments/EnvironmentLinks.test.ts+16-8| ... | ... | @@ -3,9 +3,9 @@ import * as Effect from "effect/Effect"; |
| 3 | 3 | import * as Layer from "effect/Layer"; |
| 4 | 4 | import { PgDialect } from "drizzle-orm/pg-core"; |
| 5 | 5 | |
| 6 | import { RelayDb, type RelayDatabase } from "../db.ts"; | |
| 6 | import * as RelayDb from "../db.ts"; | |
| 7 | 7 | import { relayEnvironmentLinks } from "../persistence/schema.ts"; |
| 8 | import { EnvironmentLinks, layer } from "./EnvironmentLinks.ts"; | |
| 8 | import * as EnvironmentLinks from "./EnvironmentLinks.ts"; | |
| 9 | 9 | |
| 10 | 10 | describe("EnvironmentLinks", () => { |
| 11 | 11 | it.effect("selects users when either notifications or Live Activities are enabled", () => { |
| ... | ... | @@ -25,10 +25,10 @@ describe("EnvironmentLinks", () => { |
| 25 | 25 | }, |
| 26 | 26 | }; |
| 27 | 27 | }, |
| 28 | } as unknown as RelayDatabase; | |
| 28 | } as unknown as RelayDb.RelayDb["Service"]; | |
| 29 | 29 | |
| 30 | 30 | return Effect.gen(function* () { |
| 31 | const links = yield* EnvironmentLinks; | |
| 31 | const links = yield* EnvironmentLinks.EnvironmentLinks; | |
| 32 | 32 | expect(yield* links.listUsersForEnvironment({ environmentId: "env-1" })).toEqual([]); |
| 33 | 33 | expect(whereConditions).toHaveLength(1); |
| 34 | 34 | |
| ... | ... | @@ -39,7 +39,11 @@ describe("EnvironmentLinks", () => { |
| 39 | 39 | expect(query.sql).toContain('"relay_environment_links"."live_activities_enabled" = $3'); |
| 40 | 40 | expect(query.sql).toContain(" or "); |
| 41 | 41 | expect(query.params).toEqual(["env-1", true, true]); |
| 42 | }).pipe(Effect.provide(layer.pipe(Layer.provide(Layer.succeed(RelayDb, fakeDb))))); | |
| 42 | }).pipe( | |
| 43 | Effect.provide( | |
| 44 | EnvironmentLinks.layer.pipe(Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb))), | |
| 45 | ), | |
| 46 | ); | |
| 43 | 47 | }); |
| 44 | 48 | |
| 45 | 49 | it.effect("revokes only the active link owned by the requesting user", () => { |
| ... | ... | @@ -65,10 +69,10 @@ describe("EnvironmentLinks", () => { |
| 65 | 69 | }, |
| 66 | 70 | }; |
| 67 | 71 | }, |
| 68 | } as unknown as RelayDatabase; | |
| 72 | } as unknown as RelayDb.RelayDb["Service"]; | |
| 69 | 73 | |
| 70 | 74 | return Effect.gen(function* () { |
| 71 | const links = yield* EnvironmentLinks; | |
| 75 | const links = yield* EnvironmentLinks.EnvironmentLinks; | |
| 72 | 76 | const revoked = yield* links.revokeForUser({ |
| 73 | 77 | userId: "user-1", |
| 74 | 78 | environmentId: "env-1", |
| ... | ... | @@ -86,6 +90,10 @@ describe("EnvironmentLinks", () => { |
| 86 | 90 | expect(query.sql).toContain('"relay_environment_links"."environment_id" = $2'); |
| 87 | 91 | expect(query.sql).toContain('"relay_environment_links"."revoked_at" is null'); |
| 88 | 92 | expect(query.params).toEqual(["user-1", "env-1"]); |
| 89 | }).pipe(Effect.provide(layer.pipe(Layer.provide(Layer.succeed(RelayDb, fakeDb))))); | |
| 93 | }).pipe( | |
| 94 | Effect.provide( | |
| 95 | EnvironmentLinks.layer.pipe(Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb))), | |
| 96 | ), | |
| 97 | ); | |
| 90 | 98 | }); |
| 91 | 99 | }); |
infra/relay/src/environments/EnvironmentLinks.ts+40-41| ... | ... | @@ -11,7 +11,7 @@ import * as Layer from "effect/Layer"; |
| 11 | 11 | import * as Schema from "effect/Schema"; |
| 12 | 12 | import { and, eq, isNull, or } from "drizzle-orm"; |
| 13 | 13 | |
| 14 | import { RelayDb } from "../db.ts"; | |
| 14 | import * as RelayDb from "../db.ts"; | |
| 15 | 15 | import { relayEnvironmentLinks } from "../persistence/schema.ts"; |
| 16 | 16 | |
| 17 | 17 | export interface RelayLinkedEnvironmentRecord extends RelayClientEnvironmentRecord { |
| ... | ... | @@ -78,45 +78,44 @@ export class EnvironmentLinkRevokePersistenceError extends Schema.TaggedErrorCla |
| 78 | 78 | } |
| 79 | 79 | } |
| 80 | 80 | |
| 81 | export interface EnvironmentLinksShape { | |
| 82 | readonly upsert: (input: { | |
| 83 | readonly userId: string; | |
| 84 | readonly request: RelayEnvironmentLinkRequest; | |
| 85 | readonly proof: RelayEnvironmentLinkProofPayload; | |
| 86 | readonly endpoint: RelayManagedEndpoint; | |
| 87 | }) => Effect.Effect<void, EnvironmentLinkUpsertPersistenceError>; | |
| 88 | readonly listUsersForEnvironment: (input: { | |
| 89 | readonly environmentId: string; | |
| 90 | }) => Effect.Effect<ReadonlyArray<string>, EnvironmentLinkUserListPersistenceError>; | |
| 91 | readonly listDeliveryUsersForEnvironment: (input: { | |
| 92 | readonly environmentId: string; | |
| 93 | readonly environmentPublicKey: string; | |
| 94 | }) => Effect.Effect< | |
| 95 | ReadonlyArray<AgentAwarenessDeliveryUserRecord>, | |
| 96 | EnvironmentLinkUserListPersistenceError | |
| 97 | >; | |
| 98 | readonly listPublicKeysForEnvironment: (input: { | |
| 99 | readonly environmentId: string; | |
| 100 | }) => Effect.Effect<ReadonlyArray<string>, EnvironmentPublicKeyListPersistenceError>; | |
| 101 | readonly listForUser: (input: { | |
| 102 | readonly userId: string; | |
| 103 | }) => Effect.Effect< | |
| 104 | ReadonlyArray<RelayClientEnvironmentRecord>, | |
| 105 | EnvironmentLinkListPersistenceError | |
| 106 | >; | |
| 107 | readonly getForUser: (input: { | |
| 108 | readonly userId: string; | |
| 109 | readonly environmentId: string; | |
| 110 | }) => Effect.Effect<RelayLinkedEnvironmentRecord | null, EnvironmentLinkLookupPersistenceError>; | |
| 111 | readonly revokeForUser: (input: { | |
| 112 | readonly userId: string; | |
| 113 | readonly environmentId: string; | |
| 114 | }) => Effect.Effect<boolean, EnvironmentLinkRevokePersistenceError>; | |
| 115 | } | |
| 116 | ||
| 117 | export class EnvironmentLinks extends Context.Service<EnvironmentLinks, EnvironmentLinksShape>()( | |
| 118 | "t3code-relay/environments/EnvironmentLinks", | |
| 119 | ) {} | |
| 81 | export class EnvironmentLinks extends Context.Service< | |
| 82 | EnvironmentLinks, | |
| 83 | { | |
| 84 | readonly upsert: (input: { | |
| 85 | readonly userId: string; | |
| 86 | readonly request: RelayEnvironmentLinkRequest; | |
| 87 | readonly proof: RelayEnvironmentLinkProofPayload; | |
| 88 | readonly endpoint: RelayManagedEndpoint; | |
| 89 | }) => Effect.Effect<void, EnvironmentLinkUpsertPersistenceError>; | |
| 90 | readonly listUsersForEnvironment: (input: { | |
| 91 | readonly environmentId: string; | |
| 92 | }) => Effect.Effect<ReadonlyArray<string>, EnvironmentLinkUserListPersistenceError>; | |
| 93 | readonly listDeliveryUsersForEnvironment: (input: { | |
| 94 | readonly environmentId: string; | |
| 95 | readonly environmentPublicKey: string; | |
| 96 | }) => Effect.Effect< | |
| 97 | ReadonlyArray<AgentAwarenessDeliveryUserRecord>, | |
| 98 | EnvironmentLinkUserListPersistenceError | |
| 99 | >; | |
| 100 | readonly listPublicKeysForEnvironment: (input: { | |
| 101 | readonly environmentId: string; | |
| 102 | }) => Effect.Effect<ReadonlyArray<string>, EnvironmentPublicKeyListPersistenceError>; | |
| 103 | readonly listForUser: (input: { | |
| 104 | readonly userId: string; | |
| 105 | }) => Effect.Effect< | |
| 106 | ReadonlyArray<RelayClientEnvironmentRecord>, | |
| 107 | EnvironmentLinkListPersistenceError | |
| 108 | >; | |
| 109 | readonly getForUser: (input: { | |
| 110 | readonly userId: string; | |
| 111 | readonly environmentId: string; | |
| 112 | }) => Effect.Effect<RelayLinkedEnvironmentRecord | null, EnvironmentLinkLookupPersistenceError>; | |
| 113 | readonly revokeForUser: (input: { | |
| 114 | readonly userId: string; | |
| 115 | readonly environmentId: string; | |
| 116 | }) => Effect.Effect<boolean, EnvironmentLinkRevokePersistenceError>; | |
| 117 | } | |
| 118 | >()("t3code-relay/environments/EnvironmentLinks") {} | |
| 120 | 119 | |
| 121 | 120 | function agentAwarenessDeliveryUserCondition(environmentId: string) { |
| 122 | 121 | return and( |
| ... | ... | @@ -140,7 +139,7 @@ function agentAwarenessDeliveryUserKeyCondition(input: { |
| 140 | 139 | } |
| 141 | 140 | |
| 142 | 141 | const make = Effect.gen(function* () { |
| 143 | const db = yield* RelayDb; | |
| 142 | const db = yield* RelayDb.RelayDb; | |
| 144 | 143 | |
| 145 | 144 | return EnvironmentLinks.of({ |
| 146 | 145 | upsert: Effect.fn("relay.environment_links.upsert")( |
infra/relay/src/environments/EnvironmentPublishSignatures.test.ts+2-2| ... | ... | @@ -80,11 +80,11 @@ const freshRequest = Effect.gen(function* () { |
| 80 | 80 | } satisfies RelayAgentActivityPublishRequest; |
| 81 | 81 | }); |
| 82 | 82 | |
| 83 | function layer(replay?: Partial<DpopProofs.DpopProofReplayShape>) { | |
| 83 | function layer(replay?: Partial<DpopProofs.DpopProofReplay["Service"]>) { | |
| 84 | 84 | return EnvironmentPublishSignatures.layer.pipe( |
| 85 | 85 | Layer.provide( |
| 86 | 86 | Layer.merge( |
| 87 | Layer.succeed(RelayConfiguration.RelayConfiguration, config), | |
| 87 | RelayConfiguration.layer(config), | |
| 88 | 88 | Layer.succeed(DpopProofs.DpopProofReplay, { |
| 89 | 89 | verifyAndConsume: |
| 90 | 90 | replay?.verifyAndConsume ?? (() => Effect.die("unexpected DPoP proof verification")), |
infra/relay/src/environments/EnvironmentPublishSignatures.ts+8-10| ... | ... | @@ -59,18 +59,16 @@ export type EnvironmentPublishSignatureError = |
| 59 | 59 | | EnvironmentPublishPublicKeyMissing |
| 60 | 60 | | DpopProofs.DpopProofReplayPersistenceError; |
| 61 | 61 | |
| 62 | export interface EnvironmentPublishSignaturesShape { | |
| 63 | readonly verify: (input: { | |
| 64 | readonly environmentId: string; | |
| 65 | readonly environmentPublicKey: string; | |
| 66 | readonly threadId: string; | |
| 67 | readonly request: RelayAgentActivityPublishRequest; | |
| 68 | }) => Effect.Effect<void, EnvironmentPublishSignatureError>; | |
| 69 | } | |
| 70 | ||
| 71 | 62 | export class EnvironmentPublishSignatures extends Context.Service< |
| 72 | 63 | EnvironmentPublishSignatures, |
| 73 | EnvironmentPublishSignaturesShape | |
| 64 | { | |
| 65 | readonly verify: (input: { | |
| 66 | readonly environmentId: string; | |
| 67 | readonly environmentPublicKey: string; | |
| 68 | readonly threadId: string; | |
| 69 | readonly request: RelayAgentActivityPublishRequest; | |
| 70 | }) => Effect.Effect<void, EnvironmentPublishSignatureError>; | |
| 71 | } | |
| 74 | 72 | >()("t3code-relay/environments/EnvironmentPublishSignatures") {} |
| 75 | 73 | |
| 76 | 74 | const decodeProof = Schema.decodeUnknownEffect(RelayAgentActivityPublishProofPayload); |
infra/relay/src/environments/ManagedEndpointAllocations.ts+26-28| ... | ... | @@ -6,7 +6,7 @@ import * as Effect from "effect/Effect"; |
| 6 | 6 | import * as Layer from "effect/Layer"; |
| 7 | 7 | import * as Schema from "effect/Schema"; |
| 8 | 8 | |
| 9 | import { RelayDb } from "../db.ts"; | |
| 9 | import * as RelayDb from "../db.ts"; | |
| 10 | 10 | import { isManagedEndpointHostname, managedEndpointForHostname } from "../deploymentConfig.ts"; |
| 11 | 11 | import { relayManagedEndpointAllocations } from "../persistence/schema.ts"; |
| 12 | 12 | |
| ... | ... | @@ -66,26 +66,29 @@ interface RecordManagedEndpointDnsInput extends ManagedEndpointAllocationKey { |
| 66 | 66 | readonly dnsRecordId: string; |
| 67 | 67 | } |
| 68 | 68 | |
| 69 | export interface ManagedEndpointAllocationsShape { | |
| 70 | readonly get: ( | |
| 71 | input: ManagedEndpointAllocationKey, | |
| 72 | ) => Effect.Effect<ManagedEndpointAllocation | null, ManagedEndpointAllocationPersistenceError>; | |
| 73 | readonly reserve: ( | |
| 74 | input: ReserveManagedEndpointAllocationInput, | |
| 75 | ) => Effect.Effect<ManagedEndpointAllocation, ManagedEndpointAllocationPersistenceError>; | |
| 76 | readonly recordTunnel: ( | |
| 77 | input: RecordManagedEndpointTunnelInput, | |
| 78 | ) => Effect.Effect<void, ManagedEndpointAllocationPersistenceError>; | |
| 79 | readonly recordDns: ( | |
| 80 | input: RecordManagedEndpointDnsInput, | |
| 81 | ) => Effect.Effect<void, ManagedEndpointAllocationPersistenceError>; | |
| 82 | readonly markReady: ( | |
| 83 | input: ManagedEndpointAllocationKey, | |
| 84 | ) => Effect.Effect<void, ManagedEndpointAllocationPersistenceError>; | |
| 85 | readonly remove: ( | |
| 86 | input: ManagedEndpointAllocationKey, | |
| 87 | ) => Effect.Effect<void, ManagedEndpointAllocationPersistenceError>; | |
| 88 | } | |
| 69 | export class ManagedEndpointAllocations extends Context.Service< | |
| 70 | ManagedEndpointAllocations, | |
| 71 | { | |
| 72 | readonly get: ( | |
| 73 | input: ManagedEndpointAllocationKey, | |
| 74 | ) => Effect.Effect<ManagedEndpointAllocation | null, ManagedEndpointAllocationPersistenceError>; | |
| 75 | readonly reserve: ( | |
| 76 | input: ReserveManagedEndpointAllocationInput, | |
| 77 | ) => Effect.Effect<ManagedEndpointAllocation, ManagedEndpointAllocationPersistenceError>; | |
| 78 | readonly recordTunnel: ( | |
| 79 | input: RecordManagedEndpointTunnelInput, | |
| 80 | ) => Effect.Effect<void, ManagedEndpointAllocationPersistenceError>; | |
| 81 | readonly recordDns: ( | |
| 82 | input: RecordManagedEndpointDnsInput, | |
| 83 | ) => Effect.Effect<void, ManagedEndpointAllocationPersistenceError>; | |
| 84 | readonly markReady: ( | |
| 85 | input: ManagedEndpointAllocationKey, | |
| 86 | ) => Effect.Effect<void, ManagedEndpointAllocationPersistenceError>; | |
| 87 | readonly remove: ( | |
| 88 | input: ManagedEndpointAllocationKey, | |
| 89 | ) => Effect.Effect<void, ManagedEndpointAllocationPersistenceError>; | |
| 90 | } | |
| 91 | >()("t3code-relay/environments/ManagedEndpointAllocations") {} | |
| 89 | 92 | |
| 90 | 93 | const allocationSelection = { |
| 91 | 94 | userId: relayManagedEndpointAllocations.userId, |
| ... | ... | @@ -109,7 +112,7 @@ const persistenceError = (cause: unknown) => |
| 109 | 112 | : new ManagedEndpointAllocationPersistenceError({ cause }); |
| 110 | 113 | |
| 111 | 114 | const make = Effect.gen(function* () { |
| 112 | const db = yield* RelayDb; | |
| 115 | const db = yield* RelayDb.RelayDb; | |
| 113 | 116 | |
| 114 | 117 | return ManagedEndpointAllocations.of({ |
| 115 | 118 | get: Effect.fn("relay.managed_endpoint_allocations.get")(function* ( |
| ... | ... | @@ -198,9 +201,4 @@ const make = Effect.gen(function* () { |
| 198 | 201 | }); |
| 199 | 202 | }); |
| 200 | 203 | |
| 201 | export class ManagedEndpointAllocations extends Context.Service< | |
| 202 | ManagedEndpointAllocations, | |
| 203 | ManagedEndpointAllocationsShape | |
| 204 | >()("t3code-relay/environments/ManagedEndpointAllocations") { | |
| 205 | static readonly layer = Layer.effect(this, make); | |
| 206 | } | |
| 204 | export const layer = Layer.effect(ManagedEndpointAllocations, make); |
infra/relay/src/environments/ManagedEndpointProvider.test.ts+3-3| ... | ... | @@ -204,9 +204,9 @@ function providerLayer( |
| 204 | 204 | ) { |
| 205 | 205 | return ManagedEndpointProvider.layer.pipe( |
| 206 | 206 | Layer.provideMerge(NodeServices.layer), |
| 207 | Layer.provide(Layer.succeed(RelayConfiguration.RelayConfiguration, config)), | |
| 208 | Layer.provide(Layer.succeed(ManagedEndpointProvider.ManagedEndpointTunnelClient, tunnelClient)), | |
| 209 | Layer.provide(Layer.succeed(ManagedEndpointProvider.ManagedEndpointDnsClient, dnsClient)), | |
| 207 | Layer.provide(RelayConfiguration.layer(config)), | |
| 208 | Layer.provide(ManagedEndpointProvider.layerTunnelClient(tunnelClient)), | |
| 209 | Layer.provide(ManagedEndpointProvider.layerDnsClient(dnsClient)), | |
| 210 | 210 | Layer.provide( |
| 211 | 211 | Layer.succeed(ManagedEndpointAllocations.ManagedEndpointAllocations, allocations), |
| 212 | 212 | ), |
infra/relay/src/environments/ManagedEndpointProvider.ts+69-63| ... | ... | @@ -23,7 +23,7 @@ import { |
| 23 | 23 | managedEndpointHostname, |
| 24 | 24 | managedEndpointTunnelName, |
| 25 | 25 | } from "../deploymentConfig.ts"; |
| 26 | import { ManagedEndpointAllocations } from "./ManagedEndpointAllocations.ts"; | |
| 26 | import * as ManagedEndpointAllocations from "./ManagedEndpointAllocations.ts"; | |
| 27 | 27 | |
| 28 | 28 | export class ManagedEndpointProvisioningNotConfigured extends Schema.TaggedErrorClass<ManagedEndpointProvisioningNotConfigured>()( |
| 29 | 29 | "ManagedEndpointProvisioningNotConfigured", |
| ... | ... | @@ -74,21 +74,19 @@ export interface ManagedEndpointProvisioningResult { |
| 74 | 74 | readonly runtime: RelayManagedEndpointRuntimeConfig; |
| 75 | 75 | } |
| 76 | 76 | |
| 77 | export interface ManagedEndpointProviderShape { | |
| 78 | readonly provision: (input: { | |
| 79 | readonly userId: string; | |
| 80 | readonly environmentId: string; | |
| 81 | readonly origin: RelayManagedEndpointOrigin; | |
| 82 | }) => Effect.Effect<ManagedEndpointProvisioningResult, ManagedEndpointProviderError>; | |
| 83 | readonly deprovision: (input: { | |
| 84 | readonly userId: string; | |
| 85 | readonly environmentId: string; | |
| 86 | }) => Effect.Effect<void, ManagedEndpointDeprovisioningFailed>; | |
| 87 | } | |
| 88 | ||
| 89 | 77 | export class ManagedEndpointProvider extends Context.Service< |
| 90 | 78 | ManagedEndpointProvider, |
| 91 | ManagedEndpointProviderShape | |
| 79 | { | |
| 80 | readonly provision: (input: { | |
| 81 | readonly userId: string; | |
| 82 | readonly environmentId: string; | |
| 83 | readonly origin: RelayManagedEndpointOrigin; | |
| 84 | }) => Effect.Effect<ManagedEndpointProvisioningResult, ManagedEndpointProviderError>; | |
| 85 | readonly deprovision: (input: { | |
| 86 | readonly userId: string; | |
| 87 | readonly environmentId: string; | |
| 88 | }) => Effect.Effect<void, ManagedEndpointDeprovisioningFailed>; | |
| 89 | } | |
| 92 | 90 | >()("t3code-relay/environments/ManagedEndpointProvider") {} |
| 93 | 91 | |
| 94 | 92 | interface ManagedEndpointTunnel { |
| ... | ... | @@ -105,36 +103,42 @@ export class ManagedEndpointTunnelClientError extends Schema.TaggedErrorClass<Ma |
| 105 | 103 | } |
| 106 | 104 | } |
| 107 | 105 | |
| 108 | export interface ManagedEndpointTunnelClientShape { | |
| 109 | readonly list: (request: { | |
| 110 | readonly name: string; | |
| 111 | readonly isDeleted: false; | |
| 112 | }) => Effect.Effect< | |
| 113 | { readonly result: ReadonlyArray<ManagedEndpointTunnel> }, | |
| 114 | ManagedEndpointTunnelClientError | |
| 115 | >; | |
| 116 | readonly create: (request: { | |
| 117 | readonly name: string; | |
| 118 | readonly configSrc: "cloudflare"; | |
| 119 | }) => Effect.Effect<ManagedEndpointTunnel, ManagedEndpointTunnelClientError>; | |
| 120 | readonly putConfiguration: ( | |
| 121 | tunnelId: string, | |
| 122 | config: { | |
| 123 | readonly ingress: Array<{ | |
| 124 | readonly hostname?: string; | |
| 125 | readonly service: string; | |
| 126 | }>; | |
| 127 | }, | |
| 128 | ) => Effect.Effect<unknown, ManagedEndpointTunnelClientError>; | |
| 129 | readonly getToken: (tunnelId: string) => Effect.Effect<string, ManagedEndpointTunnelClientError>; | |
| 130 | readonly delete: (tunnelId: string) => Effect.Effect<unknown, ManagedEndpointTunnelClientError>; | |
| 131 | } | |
| 132 | ||
| 133 | 106 | export class ManagedEndpointTunnelClient extends Context.Service< |
| 134 | 107 | ManagedEndpointTunnelClient, |
| 135 | ManagedEndpointTunnelClientShape | |
| 108 | { | |
| 109 | readonly list: (request: { | |
| 110 | readonly name: string; | |
| 111 | readonly isDeleted: false; | |
| 112 | }) => Effect.Effect< | |
| 113 | { readonly result: ReadonlyArray<ManagedEndpointTunnel> }, | |
| 114 | ManagedEndpointTunnelClientError | |
| 115 | >; | |
| 116 | readonly create: (request: { | |
| 117 | readonly name: string; | |
| 118 | readonly configSrc: "cloudflare"; | |
| 119 | }) => Effect.Effect<ManagedEndpointTunnel, ManagedEndpointTunnelClientError>; | |
| 120 | readonly putConfiguration: ( | |
| 121 | tunnelId: string, | |
| 122 | config: { | |
| 123 | readonly ingress: Array<{ | |
| 124 | readonly hostname?: string; | |
| 125 | readonly service: string; | |
| 126 | }>; | |
| 127 | }, | |
| 128 | ) => Effect.Effect<unknown, ManagedEndpointTunnelClientError>; | |
| 129 | readonly getToken: ( | |
| 130 | tunnelId: string, | |
| 131 | ) => Effect.Effect<string, ManagedEndpointTunnelClientError>; | |
| 132 | readonly delete: (tunnelId: string) => Effect.Effect<unknown, ManagedEndpointTunnelClientError>; | |
| 133 | } | |
| 136 | 134 | >()("t3code-relay/environments/ManagedEndpointProvider/ManagedEndpointTunnelClient") {} |
| 137 | 135 | |
| 136 | export const makeTunnelClient = (client: ManagedEndpointTunnelClient["Service"]) => | |
| 137 | ManagedEndpointTunnelClient.of(client); | |
| 138 | ||
| 139 | export const layerTunnelClient = (client: ManagedEndpointTunnelClient["Service"]) => | |
| 140 | Layer.succeed(ManagedEndpointTunnelClient, makeTunnelClient(client)); | |
| 141 | ||
| 138 | 142 | interface ManagedEndpointCnameRecordInput { |
| 139 | 143 | readonly type: "CNAME"; |
| 140 | 144 | readonly name: string; |
| ... | ... | @@ -152,29 +156,33 @@ export class ManagedEndpointDnsClientError extends Schema.TaggedErrorClass<Manag |
| 152 | 156 | } |
| 153 | 157 | } |
| 154 | 158 | |
| 155 | export interface ManagedEndpointDnsClientShape { | |
| 156 | readonly listRecords: ( | |
| 157 | hostname: string, | |
| 158 | ) => Effect.Effect<ReadonlyArray<{ readonly id: string }>, ManagedEndpointDnsClientError>; | |
| 159 | readonly createRecord: ( | |
| 160 | request: ManagedEndpointCnameRecordInput, | |
| 161 | ) => Effect.Effect<{ readonly id: string }, ManagedEndpointDnsClientError>; | |
| 162 | readonly updateRecord: ( | |
| 163 | dnsRecordId: string, | |
| 164 | request: ManagedEndpointCnameRecordInput, | |
| 165 | ) => Effect.Effect<unknown, ManagedEndpointDnsClientError>; | |
| 166 | readonly deleteRecord: ( | |
| 167 | dnsRecordId: string, | |
| 168 | ) => Effect.Effect<unknown, ManagedEndpointDnsClientError>; | |
| 169 | } | |
| 170 | ||
| 171 | 159 | export class ManagedEndpointDnsClient extends Context.Service< |
| 172 | 160 | ManagedEndpointDnsClient, |
| 173 | ManagedEndpointDnsClientShape | |
| 161 | { | |
| 162 | readonly listRecords: ( | |
| 163 | hostname: string, | |
| 164 | ) => Effect.Effect<ReadonlyArray<{ readonly id: string }>, ManagedEndpointDnsClientError>; | |
| 165 | readonly createRecord: ( | |
| 166 | request: ManagedEndpointCnameRecordInput, | |
| 167 | ) => Effect.Effect<{ readonly id: string }, ManagedEndpointDnsClientError>; | |
| 168 | readonly updateRecord: ( | |
| 169 | dnsRecordId: string, | |
| 170 | request: ManagedEndpointCnameRecordInput, | |
| 171 | ) => Effect.Effect<unknown, ManagedEndpointDnsClientError>; | |
| 172 | readonly deleteRecord: ( | |
| 173 | dnsRecordId: string, | |
| 174 | ) => Effect.Effect<unknown, ManagedEndpointDnsClientError>; | |
| 175 | } | |
| 174 | 176 | >()("t3code-relay/environments/ManagedEndpointProvider/ManagedEndpointDnsClient") {} |
| 175 | 177 | |
| 178 | export const makeDnsClient = (client: ManagedEndpointDnsClient["Service"]) => | |
| 179 | ManagedEndpointDnsClient.of(client); | |
| 180 | ||
| 181 | export const layerDnsClient = (client: ManagedEndpointDnsClient["Service"]) => | |
| 182 | Layer.succeed(ManagedEndpointDnsClient, makeDnsClient(client)); | |
| 183 | ||
| 176 | 184 | const requireCloudflareSettings = Effect.fnUntraced(function* ( |
| 177 | settings: RelayConfiguration.RelayConfigurationShape, | |
| 185 | settings: RelayConfiguration.RelayConfiguration["Service"], | |
| 178 | 186 | ) { |
| 179 | 187 | if (!settings.managedEndpointBaseDomain || !settings.managedEndpointNamespace) { |
| 180 | 188 | return yield* new ManagedEndpointProvisioningNotConfigured(); |
| ... | ... | @@ -234,7 +242,7 @@ const make = Effect.gen(function* () { |
| 234 | 242 | const crypto = yield* Crypto.Crypto; |
| 235 | 243 | const tunnels = yield* ManagedEndpointTunnelClient; |
| 236 | 244 | const dns = yield* ManagedEndpointDnsClient; |
| 237 | const allocations = yield* ManagedEndpointAllocations; | |
| 245 | const allocations = yield* ManagedEndpointAllocations.ManagedEndpointAllocations; | |
| 238 | 246 | |
| 239 | 247 | const updateExistingDnsRecords = Effect.fnUntraced(function* ( |
| 240 | 248 | records: ReadonlyArray<{ readonly id: string }>, |
| ... | ... | @@ -452,8 +460,7 @@ export const layerCloudflareBindings = ( |
| 452 | 460 | layer.pipe( |
| 453 | 461 | Layer.provide( |
| 454 | 462 | Layer.mergeAll( |
| 455 | Layer.succeed( | |
| 456 | ManagedEndpointTunnelClient, | |
| 463 | layerTunnelClient( | |
| 457 | 464 | ManagedEndpointTunnelClient.of({ |
| 458 | 465 | list: (request) => |
| 459 | 466 | tunnelClient.list(request).pipe( |
| ... | ... | @@ -482,8 +489,7 @@ export const layerCloudflareBindings = ( |
| 482 | 489 | ), |
| 483 | 490 | }), |
| 484 | 491 | ), |
| 485 | Layer.succeed( | |
| 486 | ManagedEndpointDnsClient, | |
| 492 | layerDnsClient( | |
| 487 | 493 | ManagedEndpointDnsClient.of({ |
| 488 | 494 | listRecords: (hostname) => |
| 489 | 495 | dnsClient.listDnsRecords({ search: hostname }).pipe( |
infra/relay/src/http/Api.test.ts+2-2| ... | ... | @@ -30,7 +30,7 @@ vi.mock("@clerk/backend", () => ({ |
| 30 | 30 | verifyToken: vi.fn(), |
| 31 | 31 | })); |
| 32 | 32 | |
| 33 | const relaySettings: RelayConfiguration.RelayConfigurationShape = { | |
| 33 | const relaySettings: RelayConfiguration.RelayConfiguration["Service"] = { | |
| 34 | 34 | relayIssuer: "https://relay.example.test", |
| 35 | 35 | apns: { |
| 36 | 36 | teamId: "apns-team", |
| ... | ... | @@ -110,7 +110,7 @@ describe("relay environment authentication", () => { |
| 110 | 110 | const failure = new EnvironmentCredentials.EnvironmentCredentialAuthenticatePersistenceError({ |
| 111 | 111 | cause: "database unavailable", |
| 112 | 112 | }); |
| 113 | const credentials: EnvironmentCredentials.EnvironmentCredentialsShape = { | |
| 113 | const credentials: EnvironmentCredentials.EnvironmentCredentials["Service"] = { | |
| 114 | 114 | create: () => Effect.die("unused create"), |
| 115 | 115 | authenticate: () => Effect.fail(failure), |
| 116 | 116 | revokeForEnvironmentPublicKey: () => Effect.die("unused revoke"), |
infra/relay/src/http/Api.ts+8-5| ... | ... | @@ -66,7 +66,7 @@ import * as ManagedEndpointAllocations from "../environments/ManagedEndpointAllo |
| 66 | 66 | import * as EnvironmentPublishSignatures from "../environments/EnvironmentPublishSignatures.ts"; |
| 67 | 67 | import * as MobileRegistrations from "../agentActivity/MobileRegistrations.ts"; |
| 68 | 68 | import { withSpanAttributes } from "../observability.ts"; |
| 69 | import { RelayDb } from "../db.ts"; | |
| 69 | import * as RelayDb from "../db.ts"; | |
| 70 | 70 | |
| 71 | 71 | const relayCorsAllowedMethods = ["GET", "POST", "DELETE", "OPTIONS"] as const; |
| 72 | 72 | const relayCorsAllowedHeaders = [ |
| ... | ... | @@ -347,7 +347,7 @@ export const healthApi = HttpApiBuilder.group( |
| 347 | 347 | RelayApi, |
| 348 | 348 | "health", |
| 349 | 349 | Effect.fnUntraced(function* (handlers) { |
| 350 | const db = yield* RelayDb; | |
| 350 | const db = yield* RelayDb.RelayDb; | |
| 351 | 351 | return handlers.handle( |
| 352 | 352 | "health", |
| 353 | 353 | Effect.fn("relay.api.health")( |
| ... | ... | @@ -985,7 +985,10 @@ function hasExpectedClerkAudience(audience: unknown, expectedAudience: string): |
| 985 | 985 | audience.some((entry) => typeof entry === "string" && entry === expectedAudience); |
| 986 | 986 | } |
| 987 | 987 | |
| 988 | function verifyClerkBearerToken(config: RelayConfiguration.RelayConfigurationShape, token: string) { | |
| 988 | function verifyClerkBearerToken( | |
| 989 | config: RelayConfiguration.RelayConfiguration["Service"], | |
| 990 | token: string, | |
| 991 | ) { | |
| 989 | 992 | return Effect.tryPromise({ |
| 990 | 993 | try: () => |
| 991 | 994 | verifyToken(token, { |
| ... | ... | @@ -1001,7 +1004,7 @@ function verifyClerkBearerToken(config: RelayConfiguration.RelayConfigurationSha |
| 1001 | 1004 | } |
| 1002 | 1005 | |
| 1003 | 1006 | function verifyClerkOAuthBearerToken( |
| 1004 | config: RelayConfiguration.RelayConfigurationShape, | |
| 1007 | config: RelayConfiguration.RelayConfiguration["Service"], | |
| 1005 | 1008 | token: string, |
| 1006 | 1009 | ) { |
| 1007 | 1010 | return Effect.tryPromise({ |
| ... | ... | @@ -1027,7 +1030,7 @@ function verifyClerkOAuthBearerToken( |
| 1027 | 1030 | } |
| 1028 | 1031 | |
| 1029 | 1032 | export function verifyRelayClientBearerToken( |
| 1030 | config: RelayConfiguration.RelayConfigurationShape, | |
| 1033 | config: RelayConfiguration.RelayConfiguration["Service"], | |
| 1031 | 1034 | token: string, |
| 1032 | 1035 | ) { |
| 1033 | 1036 | return verifyClerkBearerToken(config, token).pipe( |
infra/relay/src/observability.test.ts+2-2| ... | ... | @@ -9,7 +9,7 @@ import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest"; |
| 9 | 9 | import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse"; |
| 10 | 10 | import type { OtlpTracer } from "effect/unstable/observability"; |
| 11 | 11 | |
| 12 | import { EnvironmentConnectNotAuthorized } from "./environments/EnvironmentConnector.ts"; | |
| 12 | import * as EnvironmentConnector from "./environments/EnvironmentConnector.ts"; | |
| 13 | 13 | import { makeRelayTraceLayer } from "./observability.ts"; |
| 14 | 14 | |
| 15 | 15 | interface ExportedRequest { |
| ... | ... | @@ -43,7 +43,7 @@ it.effect("exports schema error fields as span attributes", () => |
| 43 | 43 | ); |
| 44 | 44 | |
| 45 | 45 | yield* Effect.fail( |
| 46 | new EnvironmentConnectNotAuthorized({ | |
| 46 | new EnvironmentConnector.EnvironmentConnectNotAuthorized({ | |
| 47 | 47 | environmentId: "environment-1", |
| 48 | 48 | operation: "connect", |
| 49 | 49 | reason: "managed_endpoint_allocation_not_ready", |
infra/relay/src/worker.ts+4-9| ... | ... | @@ -42,7 +42,7 @@ import * as EnvironmentCredentials from "./environments/EnvironmentCredentials.t |
| 42 | 42 | import * as EnvironmentLinks from "./environments/EnvironmentLinks.ts"; |
| 43 | 43 | import * as ManagedEndpointAllocations from "./environments/ManagedEndpointAllocations.ts"; |
| 44 | 44 | import * as LiveActivities from "./agentActivity/LiveActivities.ts"; |
| 45 | import { RelayDb, RelayHyperdrive } from "./db.ts"; | |
| 45 | import * as RelayDb from "./db.ts"; | |
| 46 | 46 | import { RelayApnsDeliveryDeadLetterQueue, RelayApnsDeliveryQueue } from "./queues.ts"; |
| 47 | 47 | import * as RelayConfiguration from "./Config.ts"; |
| 48 | 48 | import * as AgentActivityPublisher from "./agentActivity/AgentActivityPublisher.ts"; |
| ... | ... | @@ -138,7 +138,7 @@ export default class Api extends Cloudflare.Worker<Api>()( |
| 138 | 138 | |
| 139 | 139 | const cloudMintPrivateKey = yield* cloudMintKeyPair.privateKey; |
| 140 | 140 | const cloudMintPublicKey = yield* cloudMintKeyPair.publicKey; |
| 141 | const hyperdrive = yield* Cloudflare.Hyperdrive.bind(yield* RelayHyperdrive); | |
| 141 | const hyperdrive = yield* Cloudflare.Hyperdrive.bind(yield* RelayDb.RelayHyperdrive); | |
| 142 | 142 | const db = yield* Drizzle.postgres(hyperdrive.connectionString); |
| 143 | 143 | |
| 144 | 144 | const managedEndpointTunnelBinding = yield* Cloudflare.TunnelReadWrite.bind(); |
| ... | ... | @@ -203,16 +203,11 @@ export default class Api extends Cloudflare.Worker<Api>()( |
| 203 | 203 | Layer.provideMerge(AgentActivityRows.layer), |
| 204 | 204 | Layer.provideMerge(Devices.layer), |
| 205 | 205 | Layer.provideMerge(EnvironmentCredentials.layer), |
| 206 | Layer.provideMerge( | |
| 207 | Layer.mergeAll( | |
| 208 | EnvironmentLinks.layer, | |
| 209 | ManagedEndpointAllocations.ManagedEndpointAllocations.layer, | |
| 210 | ), | |
| 211 | ), | |
| 206 | Layer.provideMerge(Layer.mergeAll(EnvironmentLinks.layer, ManagedEndpointAllocations.layer)), | |
| 212 | 207 | Layer.provideMerge(LiveActivities.layer), |
| 213 | 208 | Layer.provideMerge(DeliveryAttempts.layer), |
| 214 | 209 | Layer.provideMerge(RelayTokens.layer), |
| 215 | Layer.provideMerge(Layer.succeed(RelayDb, db)), | |
| 210 | Layer.provideMerge(Layer.succeed(RelayDb.RelayDb, db)), | |
| 216 | 211 | Layer.provideMerge(Layer.effect(RelayConfiguration.RelayConfiguration, loadSettings)), |
| 217 | 212 | Layer.provideMerge(webcryptoLayer), |
| 218 | 213 | ); |