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