authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-06-19 19:56:36-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-06-19 19:56:36-07:00
log53d0107759b743cdd7a9b6e777d4628650ed2131
tree819d1bdab1d3f9c903fa24e61ca6bab862dc4cfa
parent60b16d1f28104c95a3ab8e673cb439124a4a8668
signature Signed by PGP key B5690EEEBB952194

[codex] align relay foundation Effect services (#3182)

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";
77import * as Layer from "effect/Layer";
88import * as Planetscale from "alchemy/Planetscale";
99
10import { PlanetscaleDatabase, RelayHyperdrive } from "./src/db.ts";
10import * as RelayDb from "./src/db.ts";
1111import { RelayObservability } from "./src/observability.ts";
1212import { ManagedEndpointZone, RelayApiZone } from "./src/zone.ts";
1313import Api from "./src/worker.ts";
......@@ -24,8 +24,8 @@ export default Alchemy.Stack(
2424 state: Cloudflare.state(),
2525 },
2626 Effect.gen(function* () {
27 const db = yield* PlanetscaleDatabase;
28 const hyperdrive = yield* RelayHyperdrive;
27 const db = yield* RelayDb.PlanetscaleDatabase;
28 const hyperdrive = yield* RelayDb.RelayHyperdrive;
2929 const managedEndpointZone = yield* ManagedEndpointZone.pipe(Effect.orDie);
3030 const relayApiZone = yield* RelayApiZone.pipe(Effect.orDie);
3131 const observability = yield* RelayObservability;
infra/relay/src/Config.ts+19-14
......@@ -1,4 +1,5 @@
11import * as Context from "effect/Context";
2import * as Layer from "effect/Layer";
23import * as Redacted from "effect/Redacted";
34import * as Schema from "effect/Schema";
45
......@@ -13,20 +14,24 @@ export interface ApnsCredentials {
1314 readonly environment: ApnsEnvironment;
1415}
1516
16export 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
2917export class RelayConfiguration extends Context.Service<
3018 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 }
3231>()("t3code-relay/Config/RelayConfiguration") {}
32
33export const make = (configuration: RelayConfiguration["Service"]) =>
34 RelayConfiguration.of(configuration);
35
36export 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(
6666}
6767
6868function makeEnvironmentLinks(
69 overrides: Partial<EnvironmentLinks.EnvironmentLinksShape> = {},
70): EnvironmentLinks.EnvironmentLinksShape {
69 overrides: Partial<EnvironmentLinks.EnvironmentLinks["Service"]> = {},
70): EnvironmentLinks.EnvironmentLinks["Service"] {
7171 return {
7272 upsert: () => Effect.void,
7373 listUsersForEnvironment: () => Effect.succeed(["dev:julius"]),
infra/relay/src/agentActivity/AgentActivityRows.ts+2-2
......@@ -9,7 +9,7 @@ import * as Option from "effect/Option";
99import * as Schema from "effect/Schema";
1010import { and, desc, eq, isNull } from "drizzle-orm";
1111
12import { RelayDb } from "../db.ts";
12import * as RelayDb from "../db.ts";
1313import { relayAgentActivityRows, relayEnvironmentLinks } from "../persistence/schema.ts";
1414
1515export class AgentActivityRowUpsertPersistenceError extends Schema.TaggedErrorClass<AgentActivityRowUpsertPersistenceError>()(
......@@ -70,7 +70,7 @@ const decodeRelayAgentActivityStateJson = Schema.decodeUnknownOption(
7070);
7171
7272const make = Effect.gen(function* () {
73 const db = yield* RelayDb;
73 const db = yield* RelayDb.RelayDb;
7474
7575 return AgentActivityRows.of({
7676 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";
99import * as Redacted from "effect/Redacted";
1010import * as Schema from "effect/Schema";
1111import { Headers, HttpClient, HttpClientRequest } from "effect/unstable/http";
12import type { ApnsCredentials } from "../Config.ts";
12import * as RelayConfiguration from "../Config.ts";
1313import type { ApnsNotificationPayload } from "./apnsDeliveryJobs.ts";
1414
1515const LIVE_ACTIVITY_NAME = "AgentActivity";
......@@ -99,9 +99,9 @@ const encodeApnsJwtPayloadJson = Schema.encodeEffect(
9999);
100100
101101const makeApnsJwt = Effect.fn("relay.apns.make_jwt")(function* (input: {
102 readonly teamId: ApnsCredentials["teamId"];
103 readonly keyId: ApnsCredentials["keyId"];
104 readonly privateKey: ApnsCredentials["privateKey"];
102 readonly teamId: RelayConfiguration.ApnsCredentials["teamId"];
103 readonly keyId: RelayConfiguration.ApnsCredentials["keyId"];
104 readonly privateKey: RelayConfiguration.ApnsCredentials["privateKey"];
105105 readonly issuedAtUnixSeconds: number;
106106}) {
107107 const headerJson = yield* encodeApnsJwtHeaderJson({ alg: "ES256", kid: input.keyId }).pipe(
......@@ -235,12 +235,12 @@ export interface ApnsClientShape {
235235 readonly makeLiveActivityRequest: typeof makeLiveActivityRequest;
236236 readonly makePushNotificationRequest: typeof makePushNotificationRequest;
237237 readonly sendLiveActivityRequest: (input: {
238 readonly credentials: ApnsCredentials;
238 readonly credentials: RelayConfiguration.ApnsCredentials;
239239 readonly request: ApnsLiveActivityRequest;
240240 readonly issuedAtUnixSeconds: number;
241241 }) => Effect.Effect<ApnsDeliveryResult, ApnsError>;
242242 readonly sendPushNotificationRequest: (input: {
243 readonly credentials: ApnsCredentials;
243 readonly credentials: RelayConfiguration.ApnsCredentials;
244244 readonly request: ApnsPushNotificationRequest;
245245 readonly issuedAtUnixSeconds: number;
246246 }) => Effect.Effect<ApnsDeliveryResult, ApnsError>;
infra/relay/src/agentActivity/ApnsDeliveries.test.ts+2-2
......@@ -153,7 +153,7 @@ function makeLayer(input: {
153153 Parameters<LiveActivities.LiveActivitiesShape["invalidateDeliveryToken"]>[0]
154154 >;
155155 readonly currentTargets?: ReadonlyArray<LiveActivities.TargetRow>;
156 readonly config?: RelayConfiguration.RelayConfigurationShape;
156 readonly config?: RelayConfiguration.RelayConfiguration["Service"];
157157 readonly execute?: (
158158 request: HttpClientRequest.HttpClientRequest,
159159 ) => Effect.Effect<HttpClientResponse.HttpClientResponse>;
......@@ -213,7 +213,7 @@ function makeLayer(input: {
213213 input.invalidatedTokens?.push(invalidated);
214214 }),
215215 }),
216 Layer.succeed(RelayConfiguration.RelayConfiguration, input.config ?? config),
216 RelayConfiguration.layer(input.config ?? config),
217217 input.execute
218218 ? Layer.succeed(HttpClient.HttpClient, HttpClient.make(input.execute))
219219 : FetchHttpClient.layer,
infra/relay/src/agentActivity/DeliveryAttempts.test.ts+13-13
......@@ -3,7 +3,7 @@ import { describe, expect, it } from "@effect/vitest";
33import * as Effect from "effect/Effect";
44import * as Layer from "effect/Layer";
55
6import { RelayDb, type RelayDatabase } from "../db.ts";
6import * as RelayDb from "../db.ts";
77import { relayDeliveryAttempts } from "../persistence/schema.ts";
88import * as DeliveryAttempts from "./DeliveryAttempts.ts";
99
......@@ -20,7 +20,7 @@ describe("DeliveryAttempts", () => {
2020 },
2121 };
2222 },
23 } as unknown as RelayDatabase;
23 } as unknown as RelayDb.RelayDb["Service"];
2424
2525 return Effect.gen(function* () {
2626 const attempts = yield* DeliveryAttempts.DeliveryAttempts;
......@@ -52,7 +52,7 @@ describe("DeliveryAttempts", () => {
5252 Effect.provide(
5353 DeliveryAttempts.layer.pipe(
5454 Layer.provide(NodeCryptoLayer.layer),
55 Layer.provide(Layer.succeed(RelayDb, fakeDb)),
55 Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)),
5656 ),
5757 ),
5858 );
......@@ -78,7 +78,7 @@ describe("DeliveryAttempts", () => {
7878 },
7979 };
8080 },
81 } as unknown as RelayDatabase;
81 } as unknown as RelayDb.RelayDb["Service"];
8282
8383 return Effect.gen(function* () {
8484 const attempts = yield* DeliveryAttempts.DeliveryAttempts;
......@@ -104,7 +104,7 @@ describe("DeliveryAttempts", () => {
104104 Effect.provide(
105105 DeliveryAttempts.layer.pipe(
106106 Layer.provide(NodeCryptoLayer.layer),
107 Layer.provide(Layer.succeed(RelayDb, fakeDb)),
107 Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)),
108108 ),
109109 ),
110110 );
......@@ -135,7 +135,7 @@ describe("DeliveryAttempts", () => {
135135 }),
136136 }),
137137 }),
138 } as unknown as RelayDatabase;
138 } as unknown as RelayDb.RelayDb["Service"];
139139
140140 return Effect.gen(function* () {
141141 const attempts = yield* DeliveryAttempts.DeliveryAttempts;
......@@ -154,7 +154,7 @@ describe("DeliveryAttempts", () => {
154154 Effect.provide(
155155 DeliveryAttempts.layer.pipe(
156156 Layer.provide(NodeCryptoLayer.layer),
157 Layer.provide(Layer.succeed(RelayDb, fakeDb)),
157 Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)),
158158 ),
159159 ),
160160 );
......@@ -185,7 +185,7 @@ describe("DeliveryAttempts", () => {
185185 }),
186186 }),
187187 }),
188 } as unknown as RelayDatabase;
188 } as unknown as RelayDb.RelayDb["Service"];
189189
190190 return Effect.gen(function* () {
191191 const attempts = yield* DeliveryAttempts.DeliveryAttempts;
......@@ -204,7 +204,7 @@ describe("DeliveryAttempts", () => {
204204 Effect.provide(
205205 DeliveryAttempts.layer.pipe(
206206 Layer.provide(NodeCryptoLayer.layer),
207 Layer.provide(Layer.succeed(RelayDb, fakeDb)),
207 Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)),
208208 ),
209209 ),
210210 );
......@@ -246,7 +246,7 @@ describe("DeliveryAttempts", () => {
246246 };
247247 },
248248 }),
249 } as unknown as RelayDatabase;
249 } as unknown as RelayDb.RelayDb["Service"];
250250
251251 return Effect.gen(function* () {
252252 const attempts = yield* DeliveryAttempts.DeliveryAttempts;
......@@ -266,7 +266,7 @@ describe("DeliveryAttempts", () => {
266266 Effect.provide(
267267 DeliveryAttempts.layer.pipe(
268268 Layer.provide(NodeCryptoLayer.layer),
269 Layer.provide(Layer.succeed(RelayDb, fakeDb)),
269 Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)),
270270 ),
271271 ),
272272 );
......@@ -290,7 +290,7 @@ describe("DeliveryAttempts", () => {
290290 },
291291 };
292292 },
293 } as unknown as RelayDatabase;
293 } as unknown as RelayDb.RelayDb["Service"];
294294
295295 return Effect.gen(function* () {
296296 const attempts = yield* DeliveryAttempts.DeliveryAttempts;
......@@ -315,7 +315,7 @@ describe("DeliveryAttempts", () => {
315315 Effect.provide(
316316 DeliveryAttempts.layer.pipe(
317317 Layer.provide(NodeCryptoLayer.layer),
318 Layer.provide(Layer.succeed(RelayDb, fakeDb)),
318 Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)),
319319 ),
320320 ),
321321 );
infra/relay/src/agentActivity/DeliveryAttempts.ts+2-2
......@@ -7,7 +7,7 @@ import { and, eq, isNull } from "drizzle-orm";
77import * as Crypto from "effect/Crypto";
88import * as Schema from "effect/Schema";
99
10import { RelayDb } from "../db.ts";
10import * as RelayDb from "../db.ts";
1111import { relayDeliveryAttempts } from "../persistence/schema.ts";
1212
1313export class DeliveryAttemptRecordPersistenceError extends Schema.TaggedErrorClass<DeliveryAttemptRecordPersistenceError>()(
......@@ -84,7 +84,7 @@ function insertValues(
8484}
8585
8686const make = Effect.gen(function* () {
87 const db = yield* RelayDb;
87 const db = yield* RelayDb.RelayDb;
8888 const crypto = yield* Crypto.Crypto;
8989
9090 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";
55import * as Effect from "effect/Effect";
66import * as Layer from "effect/Layer";
77
8import { RelayDb, type RelayDatabase } from "../db.ts";
8import * as RelayDb from "../db.ts";
99import { relayLiveActivities, relayMobileDevices } from "../persistence/schema.ts";
1010import * as Devices from "./Devices.ts";
1111
......@@ -71,7 +71,7 @@ describe("Devices", () => {
7171 },
7272 };
7373 },
74 } as unknown as RelayDatabase;
74 } as unknown as RelayDb.RelayDb["Service"];
7575
7676 return Effect.gen(function* () {
7777 const devices = yield* Devices.Devices;
......@@ -110,7 +110,9 @@ describe("Devices", () => {
110110 pushToStartToken: "push-to-start-token",
111111 }),
112112 ]);
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 );
114116 });
115117
116118 it.effect("unregisters APNs state only for the current user device", () => {
......@@ -130,7 +132,7 @@ describe("Devices", () => {
130132 },
131133 };
132134 },
133 } as unknown as RelayDatabase;
135 } as unknown as RelayDb.RelayDb["Service"];
134136
135137 return Effect.gen(function* () {
136138 const devices = yield* Devices.Devices;
......@@ -156,7 +158,9 @@ describe("Devices", () => {
156158 params: ["user-2", "device-1"],
157159 },
158160 ]);
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 );
160164 });
161165
162166 it.effect("lists safe notification state without exposing APNs tokens", () => {
......@@ -184,7 +188,7 @@ describe("Devices", () => {
184188 };
185189 },
186190 }),
187 } as unknown as RelayDatabase;
191 } as unknown as RelayDb.RelayDb["Service"];
188192
189193 return Effect.gen(function* () {
190194 const devices = yield* Devices.Devices;
......@@ -215,6 +219,8 @@ describe("Devices", () => {
215219 updatedAt: "2026-06-01T00:00:00.000Z",
216220 },
217221 ]);
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 );
219225 });
220226});
infra/relay/src/agentActivity/Devices.ts+2-2
......@@ -10,7 +10,7 @@ import * as Schema from "effect/Schema";
1010import { and, eq } from "drizzle-orm";
1111import { sql } from "drizzle-orm";
1212
13import { RelayDb } from "../db.ts";
13import * as RelayDb from "../db.ts";
1414import { relayLiveActivities, relayMobileDevices } from "../persistence/schema.ts";
1515
1616export class DeviceRegistrationPersistenceError extends Schema.TaggedErrorClass<DeviceRegistrationPersistenceError>()(
......@@ -59,7 +59,7 @@ export class Devices extends Context.Service<Devices, DevicesShape>()(
5959) {}
6060
6161const make = Effect.gen(function* () {
62 const db = yield* RelayDb;
62 const db = yield* RelayDb.RelayDb;
6363
6464 return Devices.of({
6565 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";
88import * as Effect from "effect/Effect";
99import * as Layer from "effect/Layer";
1010
11import { RelayDb, type RelayDatabase } from "../db.ts";
11import * as RelayDb from "../db.ts";
1212import { relayLiveActivities } from "../persistence/schema.ts";
1313import * as LiveActivities from "./LiveActivities.ts";
1414
......@@ -88,7 +88,7 @@ describe("LiveActivities", () => {
8888 },
8989 };
9090 },
91 } as unknown as RelayDatabase;
91 } as unknown as RelayDb.RelayDb["Service"];
9292
9393 return Effect.gen(function* () {
9494 const liveActivities = yield* LiveActivities.LiveActivities;
......@@ -138,7 +138,9 @@ describe("LiveActivities", () => {
138138 }),
139139 );
140140 }).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 ),
142144 );
143145 },
144146 );
......@@ -164,7 +166,7 @@ describe("LiveActivities", () => {
164166 },
165167 };
166168 },
167 } as unknown as RelayDatabase;
169 } as unknown as RelayDb.RelayDb["Service"];
168170
169171 return Effect.gen(function* () {
170172 const liveActivities = yield* LiveActivities.LiveActivities;
......@@ -190,7 +192,9 @@ describe("LiveActivities", () => {
190192 }),
191193 );
192194 }).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 ),
194198 );
195199 });
196200});
infra/relay/src/agentActivity/LiveActivities.ts+2-2
......@@ -12,7 +12,7 @@ import * as Layer from "effect/Layer";
1212import * as Schema from "effect/Schema";
1313import { and, eq, sql } from "drizzle-orm";
1414
15import { RelayDb } from "../db.ts";
15import * as RelayDb from "../db.ts";
1616import { relayLiveActivities, relayMobileDevices } from "../persistence/schema.ts";
1717
1818export class LiveActivityRegistrationPersistenceError extends Schema.TaggedErrorClass<LiveActivityRegistrationPersistenceError>()(
......@@ -108,7 +108,7 @@ const encodeRelayAgentActivityAggregateStateJson = Schema.encodeEffect(
108108);
109109
110110const make = Effect.gen(function* () {
111 const db = yield* RelayDb;
111 const db = yield* RelayDb.RelayDb;
112112
113113 return LiveActivities.of({
114114 register: Effect.fn("relay.live_activities.register")(
infra/relay/src/agentActivity/MobileRegistrations.test.ts+3-3
......@@ -86,8 +86,8 @@ function makeAgentActivityRows(
8686}
8787
8888function makeEnvironmentLinks(
89 overrides: Partial<EnvironmentLinks.EnvironmentLinksShape> = {},
90): EnvironmentLinks.EnvironmentLinksShape {
89 overrides: Partial<EnvironmentLinks.EnvironmentLinks["Service"]> = {},
90): EnvironmentLinks.EnvironmentLinks["Service"] {
9191 return {
9292 upsert: () => Effect.void,
9393 listUsersForEnvironment: () => Effect.succeed(["dev:julius"]),
......@@ -153,7 +153,7 @@ function makeRegistrationReplayLayer(input: {
153153 Layer.succeed(EnvironmentLinks.EnvironmentLinks, makeEnvironmentLinks()),
154154 Layer.succeed(LiveActivities.LiveActivities, input.liveActivities),
155155 Layer.succeed(DeliveryAttempts.DeliveryAttempts, makeDeliveryAttempts()),
156 Layer.succeed(RelayConfiguration.RelayConfiguration, config),
156 RelayConfiguration.layer(config),
157157 Layer.succeed(ApnsDeliveryQueue.ApnsDeliveryQueueSender, {
158158 send: (body) =>
159159 Effect.sync(() => {
infra/relay/src/auth/DpopProofs.test.ts+9-5
......@@ -4,7 +4,7 @@ import * as Effect from "effect/Effect";
44import * as Layer from "effect/Layer";
55import * as Option from "effect/Option";
66
7import { RelayDb, type RelayDatabase } from "../db.ts";
7import * as RelayDb from "../db.ts";
88import { relayDpopProofs } from "../persistence/schema.ts";
99import * as DpopProofs from "./DpopProofs.ts";
1010
......@@ -41,7 +41,7 @@ describe("DpopProofReplay", () => {
4141 },
4242 };
4343 },
44 } as unknown as RelayDatabase;
44 } as unknown as RelayDb.RelayDb["Service"];
4545
4646 return Effect.gen(function* () {
4747 const replay = yield* DpopProofs.DpopProofReplay;
......@@ -67,7 +67,9 @@ describe("DpopProofReplay", () => {
6767 expiresAt: "2026-05-25T12:00:00.000Z",
6868 },
6969 ]);
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 );
7173 });
7274
7375 it.effect("prunes expired proof rows from the maintenance path", () => {
......@@ -84,12 +86,14 @@ describe("DpopProofReplay", () => {
8486 },
8587 };
8688 },
87 } as unknown as RelayDatabase;
89 } as unknown as RelayDb.RelayDb["Service"];
8890
8991 return Effect.gen(function* () {
9092 const replay = yield* DpopProofs.DpopProofReplay;
9193 yield* replay.pruneExpired;
9294 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 );
9498 });
9599});
infra/relay/src/auth/DpopProofs.ts+25-28
......@@ -7,7 +7,7 @@ import * as HttpApiError from "effect/unstable/httpapi/HttpApiError";
77import { lt } from "drizzle-orm";
88
99import { verifyDpopProof } from "@t3tools/shared/dpop";
10import { RelayDb } from "../db.ts";
10import * as RelayDb from "../db.ts";
1111import { relayDpopProofs } from "../persistence/schema.ts";
1212
1313export class DpopProofReplayPersistenceError extends Schema.TaggedErrorClass<DpopProofReplayPersistenceError>()(
......@@ -21,34 +21,31 @@ export class DpopProofReplayPersistenceError extends Schema.TaggedErrorClass<Dpo
2121 }
2222}
2323
24export interface DpopProofReplayShape {
25 readonly verifyAndConsume: (input: {
26 readonly proof: string | undefined;
27 readonly method: string;
28 readonly url: string;
29 readonly expectedThumbprint?: string;
30 readonly expectedAccessToken?: string;
31 readonly now: DateTime.DateTime;
32 }) => Effect.Effect<string, HttpApiError.Unauthorized | DpopProofReplayPersistenceError>;
33
34 readonly consume: (input: {
35 readonly thumbprint: string;
36 readonly jti: string;
37 readonly iat: number;
38 readonly expiresAt: DateTime.DateTime;
39 }) => Effect.Effect<boolean, DpopProofReplayPersistenceError>;
40
41 readonly pruneExpired: Effect.Effect<void, DpopProofReplayPersistenceError>;
42}
43
44export class DpopProofReplay extends Context.Service<DpopProofReplay, DpopProofReplayShape>()(
45 "t3code-relay/auth/DpopProofs/DpopProofReplay",
46) {}
24export class DpopProofReplay extends Context.Service<
25 DpopProofReplay,
26 {
27 readonly verifyAndConsume: (input: {
28 readonly proof: string | undefined;
29 readonly method: string;
30 readonly url: string;
31 readonly expectedThumbprint?: string;
32 readonly expectedAccessToken?: string;
33 readonly now: DateTime.DateTime;
34 }) => Effect.Effect<string, HttpApiError.Unauthorized | DpopProofReplayPersistenceError>;
35 readonly consume: (input: {
36 readonly thumbprint: string;
37 readonly jti: string;
38 readonly iat: number;
39 readonly expiresAt: DateTime.DateTime;
40 }) => Effect.Effect<boolean, DpopProofReplayPersistenceError>;
41 readonly pruneExpired: Effect.Effect<void, DpopProofReplayPersistenceError>;
42 }
43>()("t3code-relay/auth/DpopProofs/DpopProofReplay") {}
4744
4845const make = Effect.gen(function* () {
49 const db = yield* RelayDb;
46 const db = yield* RelayDb.RelayDb;
5047
51 const consume: DpopProofReplayShape["consume"] = Effect.fn("relay.dpop_proofs.consume")(
48 const consume: DpopProofReplay["Service"]["consume"] = Effect.fn("relay.dpop_proofs.consume")(
5249 function* (input) {
5350 const createdAt = DateTime.formatIso(yield* DateTime.now);
5451 const inserted = yield* db
......@@ -67,7 +64,7 @@ const make = Effect.gen(function* () {
6764 Effect.mapError((cause) => new DpopProofReplayPersistenceError({ cause })),
6865 );
6966
70 const verifyAndConsume: DpopProofReplayShape["verifyAndConsume"] = Effect.fn(
67 const verifyAndConsume: DpopProofReplay["Service"]["verifyAndConsume"] = Effect.fn(
7168 "relay.dpop_proofs.verify_and_consume",
7269 )(function* (input) {
7370 yield* Effect.annotateCurrentSpan({
......@@ -114,7 +111,7 @@ const make = Effect.gen(function* () {
114111 return result.thumbprint;
115112 });
116113
117 const pruneExpired: DpopProofReplayShape["pruneExpired"] = Effect.gen(function* () {
114 const pruneExpired: DpopProofReplay["Service"]["pruneExpired"] = Effect.gen(function* () {
118115 const now = DateTime.formatIso(yield* DateTime.now);
119116 yield* Effect.annotateCurrentSpan({ "relay.dpop_prune.before": now });
120117 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";
1010import * as Effect from "effect/Effect";
1111import * as Layer from "effect/Layer";
1212
13import { RelayDb, type RelayDatabase } from "../db.ts";
13import * as RelayDb from "../db.ts";
1414import { relayDpopProofs } from "../persistence/schema.ts";
1515import * as DpopProofs from "./DpopProofs.ts";
1616
......@@ -78,8 +78,8 @@ function layer(
7878 }),
7979 };
8080 },
81 } as unknown as RelayDatabase;
82 return DpopProofs.layer.pipe(Layer.provide(Layer.succeed(RelayDb, fakeDb)));
81 } as unknown as RelayDb.RelayDb["Service"];
82 return DpopProofs.layer.pipe(Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)));
8383}
8484
8585function consumeEachProofOnce() {
infra/relay/src/auth/RelayTokens.test.ts+1-3
......@@ -33,9 +33,7 @@ const config = RelayConfiguration.RelayConfiguration.of({
3333 managedEndpointNamespace: undefined,
3434});
3535
36const layer = RelayTokens.layer.pipe(
37 Layer.provide(Layer.succeed(RelayConfiguration.RelayConfiguration, config)),
38);
36const layer = RelayTokens.layer.pipe(Layer.provide(RelayConfiguration.layer(config)));
3937
4038describe("RelayTokens", () => {
4139 it.effect("issues a user-bound environment link challenge", () =>
infra/relay/src/auth/RelayTokens.ts+36-37
......@@ -93,45 +93,44 @@ function resolveDpopAccessTokenScopes(input: {
9393 });
9494}
9595
96export interface RelayTokensShape {
97 readonly resolveDpopAccessTokenScopes: typeof resolveDpopAccessTokenScopes;
98 readonly issueLinkChallenge: (input: {
99 readonly userId: string;
100 readonly request: RelayEnvironmentLinkChallengeRequest;
101 readonly jti: string;
102 readonly issuedAtEpochSeconds: number;
103 readonly expiresAtEpochSeconds: number;
104 }) => Effect.Effect<string, RelayJwtError>;
105 readonly verifyLinkChallenge: (input: {
106 readonly token: string;
107 readonly userId: string;
108 readonly request: RelayEnvironmentLinkChallengeRequest;
109 readonly nowEpochSeconds: number;
110 }) => Effect.Effect<LinkChallengeClaims | null>;
111 readonly issueDpopAccessToken: (input: {
112 readonly userId: string;
113 readonly proofKeyThumbprint: string;
114 readonly jti: string;
115 readonly issuedAtEpochSeconds: number;
116 readonly expiresAtEpochSeconds: number;
117 readonly clientId: RelayPublicClientId;
118 readonly scopes: ReadonlyArray<RelayDpopAccessTokenScope>;
119 }) => Effect.Effect<string, RelayJwtError>;
120 readonly verifyDpopAccessToken: (input: {
121 readonly token: string;
122 readonly nowEpochSeconds: number;
123 }) => Effect.Effect<RelayDpopAccessTokenClaims | null>;
124}
125
126export class RelayTokens extends Context.Service<RelayTokens, RelayTokensShape>()(
127 "t3code-relay/auth/RelayTokens",
128) {}
96export class RelayTokens extends Context.Service<
97 RelayTokens,
98 {
99 readonly resolveDpopAccessTokenScopes: typeof resolveDpopAccessTokenScopes;
100 readonly issueLinkChallenge: (input: {
101 readonly userId: string;
102 readonly request: RelayEnvironmentLinkChallengeRequest;
103 readonly jti: string;
104 readonly issuedAtEpochSeconds: number;
105 readonly expiresAtEpochSeconds: number;
106 }) => Effect.Effect<string, RelayJwtError>;
107 readonly verifyLinkChallenge: (input: {
108 readonly token: string;
109 readonly userId: string;
110 readonly request: RelayEnvironmentLinkChallengeRequest;
111 readonly nowEpochSeconds: number;
112 }) => Effect.Effect<LinkChallengeClaims | null>;
113 readonly issueDpopAccessToken: (input: {
114 readonly userId: string;
115 readonly proofKeyThumbprint: string;
116 readonly jti: string;
117 readonly issuedAtEpochSeconds: number;
118 readonly expiresAtEpochSeconds: number;
119 readonly clientId: RelayPublicClientId;
120 readonly scopes: ReadonlyArray<RelayDpopAccessTokenScope>;
121 }) => Effect.Effect<string, RelayJwtError>;
122 readonly verifyDpopAccessToken: (input: {
123 readonly token: string;
124 readonly nowEpochSeconds: number;
125 }) => Effect.Effect<RelayDpopAccessTokenClaims | null>;
126 }
127>()("t3code-relay/auth/RelayTokens") {}
129128
130129const make = Effect.gen(function* () {
131130 const config = yield* RelayConfiguration.RelayConfiguration;
132131 const issuer = normalizeRelayIssuer(config.relayIssuer);
133132
134 const issueLinkChallenge: RelayTokensShape["issueLinkChallenge"] = Effect.fn(
133 const issueLinkChallenge: RelayTokens["Service"]["issueLinkChallenge"] = Effect.fn(
135134 "relay.tokens.issue_link_challenge",
136135 )(function* (input) {
137136 return yield* signRelayJwt({
......@@ -150,7 +149,7 @@ const make = Effect.gen(function* () {
150149 });
151150 });
152151
153 const verifyLinkChallenge: RelayTokensShape["verifyLinkChallenge"] = Effect.fn(
152 const verifyLinkChallenge: RelayTokens["Service"]["verifyLinkChallenge"] = Effect.fn(
154153 "relay.tokens.verify_link_challenge",
155154 )((input) =>
156155 verifyRelayJwt({
......@@ -177,7 +176,7 @@ const make = Effect.gen(function* () {
177176 ),
178177 );
179178
180 const issueDpopAccessToken: RelayTokensShape["issueDpopAccessToken"] = Effect.fn(
179 const issueDpopAccessToken: RelayTokens["Service"]["issueDpopAccessToken"] = Effect.fn(
181180 "relay.tokens.issue_dpop_access_token",
182181 )(function* (input) {
183182 return yield* signRelayJwt({
......@@ -197,7 +196,7 @@ const make = Effect.gen(function* () {
197196 });
198197 });
199198
200 const verifyDpopAccessToken: RelayTokensShape["verifyDpopAccessToken"] = Effect.fn(
199 const verifyDpopAccessToken: RelayTokens["Service"]["verifyDpopAccessToken"] = Effect.fn(
201200 "relay.tokens.verify_dpop_access_token",
202201 )((input) =>
203202 verifyRelayJwt({
infra/relay/src/db.ts+6-5
......@@ -10,11 +10,12 @@ import * as Effect from "effect/Effect";
1010
1111import { relayDatabaseMode } from "./dbConfig.ts";
1212
13export interface RelayDatabase extends EffectPgDatabase {
14 readonly $client: PgClient;
15}
16
17export class RelayDb extends Context.Service<RelayDb, RelayDatabase>()("t3code-relay/db/RelayDb") {}
13export class RelayDb extends Context.Service<
14 RelayDb,
15 EffectPgDatabase & {
16 readonly $client: PgClient;
17 }
18>()("t3code-relay/db/RelayDb") {}
1819
1920export const PlanetscaleDatabase = Effect.gen(function* () {
2021 const { stage } = yield* Alchemy.Stack;
infra/relay/src/environments/EnvironmentConnector.test.ts+5-5
......@@ -161,8 +161,8 @@ function connectorTestLayer(
161161 request: HttpClientRequest.HttpClientRequest,
162162 ) => Effect.Effect<HttpClientResponse.HttpClientResponse>,
163163 options?: {
164 readonly links?: EnvironmentLinks.EnvironmentLinksShape;
165 readonly allocations?: ManagedEndpointAllocations.ManagedEndpointAllocationsShape;
164 readonly links?: EnvironmentLinks.EnvironmentLinks["Service"];
165 readonly allocations?: ManagedEndpointAllocations.ManagedEndpointAllocations["Service"];
166166 },
167167) {
168168 return EnvironmentConnector.layer.pipe(
......@@ -174,7 +174,7 @@ function connectorTestLayer(
174174 options?.allocations ?? makeAllocations(),
175175 ),
176176 ),
177 Layer.provide(Layer.succeed(RelayConfiguration.RelayConfiguration, settings)),
177 Layer.provide(RelayConfiguration.layer(settings)),
178178 Layer.provide(Layer.succeed(HttpClient.HttpClient, HttpClient.make(execute))),
179179 );
180180}
......@@ -189,7 +189,7 @@ function makeAllocations(
189189 dnsRecordId: "dns-record-id",
190190 readyAt: "2026-05-25T00:00:00.000Z",
191191 },
192): ManagedEndpointAllocations.ManagedEndpointAllocationsShape {
192): ManagedEndpointAllocations.ManagedEndpointAllocations["Service"] {
193193 return {
194194 get: () => Effect.succeed(allocation),
195195 reserve: () => Effect.die("unused"),
......@@ -202,7 +202,7 @@ function makeAllocations(
202202
203203function makeLinks(
204204 overrides: Partial<EnvironmentLinks.RelayLinkedEnvironmentRecord> = {},
205): EnvironmentLinks.EnvironmentLinksShape {
205): EnvironmentLinks.EnvironmentLinks["Service"] {
206206 return {
207207 upsert: () => Effect.void,
208208 listUsersForEnvironment: () => Effect.succeed([]),
infra/relay/src/environments/EnvironmentConnector.ts+15-15
......@@ -35,7 +35,9 @@ import * as Layer from "effect/Layer";
3535import * as Option from "effect/Option";
3636import * as Redacted from "effect/Redacted";
3737import * as Schema from "effect/Schema";
38import { FetchHttpClient, HttpClient, HttpClientError } from "effect/unstable/http";
38import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
39import * as HttpClient from "effect/unstable/http/HttpClient";
40import * as HttpClientError from "effect/unstable/http/HttpClientError";
3941
4042import * as EnvironmentLinks from "./EnvironmentLinks.ts";
4143import * as ManagedEndpointAllocations from "./ManagedEndpointAllocations.ts";
......@@ -139,22 +141,20 @@ export type EnvironmentConnectorError =
139141export const ENVIRONMENT_MINT_REQUEST_TIMEOUT_MS = 10_000;
140142const ENVIRONMENT_HEALTH_CLOCK_SKEW_MILLIS = 60 * 1_000;
141143
142export 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
155144export class EnvironmentConnector extends Context.Service<
156145 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 }
158158>()("t3code-relay/environments/EnvironmentConnector") {}
159159
160160const decodeMintResponseProof = Schema.decodeUnknownEffect(
infra/relay/src/environments/EnvironmentCredentials.test.ts+5-5
......@@ -4,7 +4,7 @@ import { PgDialect, QueryBuilder } from "drizzle-orm/pg-core";
44import * as Effect from "effect/Effect";
55import * as Layer from "effect/Layer";
66
7import { RelayDb, type RelayDatabase } from "../db.ts";
7import * as RelayDb from "../db.ts";
88import { relayEnvironmentCredentials } from "../persistence/schema.ts";
99import * as EnvironmentCredentials from "./EnvironmentCredentials.ts";
1010
......@@ -47,7 +47,7 @@ describe("EnvironmentCredentials", () => {
4747 }),
4848 };
4949 },
50 } as unknown as RelayDatabase;
50 } as unknown as RelayDb.RelayDb["Service"];
5151
5252 return Effect.gen(function* () {
5353 const credentials = yield* EnvironmentCredentials.EnvironmentCredentials;
......@@ -87,7 +87,7 @@ describe("EnvironmentCredentials", () => {
8787 Effect.provide(
8888 EnvironmentCredentials.layer.pipe(
8989 Layer.provide(NodeCryptoLayer.layer),
90 Layer.provide(Layer.succeed(RelayDb, fakeDb)),
90 Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)),
9191 ),
9292 ),
9393 );
......@@ -118,7 +118,7 @@ describe("EnvironmentCredentials", () => {
118118 },
119119 };
120120 },
121 } as unknown as RelayDatabase;
121 } as unknown as RelayDb.RelayDb["Service"];
122122
123123 return Effect.gen(function* () {
124124 const credentials = yield* EnvironmentCredentials.EnvironmentCredentials;
......@@ -150,7 +150,7 @@ describe("EnvironmentCredentials", () => {
150150 Effect.provide(
151151 EnvironmentCredentials.layer.pipe(
152152 Layer.provide(NodeCryptoLayer.layer),
153 Layer.provide(Layer.succeed(RelayDb, fakeDb)),
153 Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)),
154154 ),
155155 ),
156156 );
infra/relay/src/environments/EnvironmentCredentials.ts+18-20
......@@ -8,7 +8,7 @@ import * as Option from "effect/Option";
88import * as Schema from "effect/Schema";
99import { and, eq, isNull, ne, notExists } from "drizzle-orm";
1010
11import { RelayDb } from "../db.ts";
11import * as RelayDb from "../db.ts";
1212import { relayEnvironmentCredentials, relayEnvironmentLinks } from "../persistence/schema.ts";
1313
1414export class EnvironmentCredentialCreatePersistenceError extends Schema.TaggedErrorClass<EnvironmentCredentialCreatePersistenceError>()(
......@@ -44,30 +44,28 @@ export interface EnvironmentCredentialPrincipal {
4444 readonly environmentPublicKey: string;
4545}
4646
47export 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
6447export class EnvironmentCredentials extends Context.Service<
6548 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 }
6765>()("t3code-relay/environments/EnvironmentCredentials") {}
6866
6967const make = Effect.gen(function* () {
70 const db = yield* RelayDb;
68 const db = yield* RelayDb.RelayDb;
7169 const crypto = yield* Crypto.Crypto;
7270 const hashToken = (token: string) =>
7371 crypto
infra/relay/src/environments/EnvironmentLinker.test.ts+3-3
......@@ -105,14 +105,14 @@ const makeRequest = Effect.gen(function* () {
105105});
106106
107107function testLayer(input?: {
108 readonly upsert?: EnvironmentLinks.EnvironmentLinksShape["upsert"];
109 readonly consume?: DpopProofs.DpopProofReplayShape["consume"];
108 readonly upsert?: EnvironmentLinks.EnvironmentLinks["Service"]["upsert"];
109 readonly consume?: DpopProofs.DpopProofReplay["Service"]["consume"];
110110}) {
111111 return EnvironmentLinker.layer.pipe(
112112 Layer.provideMerge(RelayTokens.layer),
113113 Layer.provide(
114114 Layer.mergeAll(
115 Layer.succeed(RelayConfiguration.RelayConfiguration, config),
115 RelayConfiguration.layer(config),
116116 Layer.succeed(DpopProofs.DpopProofReplay, {
117117 verifyAndConsume: () => Effect.die("unexpected DPoP proof verification"),
118118 consume: input?.consume ?? (() => Effect.succeed(true)),
infra/relay/src/environments/EnvironmentLinker.ts+19-20
......@@ -53,26 +53,25 @@ export type EnvironmentLinkError =
5353 | EnvironmentCredentials.EnvironmentCredentialCreatePersistenceError
5454 | ManagedEndpointProvider.ManagedEndpointProviderError;
5555
56export interface EnvironmentLinkerShape {
57 readonly link: (input: {
58 readonly userId: string;
59 readonly request: RelayEnvironmentLinkRequest;
60 }) => Effect.Effect<
61 {
62 readonly environmentId: RelayEnvironmentLinkProofPayload["environmentId"];
63 readonly endpoint: RelayEnvironmentLinkProofPayload["endpoint"];
64 readonly endpointRuntime:
65 | ManagedEndpointProvider.ManagedEndpointProvisioningResult["runtime"]
66 | null;
67 readonly environmentCredential: string;
68 },
69 EnvironmentLinkError
70 >;
71}
72
73export class EnvironmentLinker extends Context.Service<EnvironmentLinker, EnvironmentLinkerShape>()(
74 "t3code-relay/environments/EnvironmentLinker",
75) {}
56export class EnvironmentLinker extends Context.Service<
57 EnvironmentLinker,
58 {
59 readonly link: (input: {
60 readonly userId: string;
61 readonly request: RelayEnvironmentLinkRequest;
62 }) => Effect.Effect<
63 {
64 readonly environmentId: RelayEnvironmentLinkProofPayload["environmentId"];
65 readonly endpoint: RelayEnvironmentLinkProofPayload["endpoint"];
66 readonly endpointRuntime:
67 | ManagedEndpointProvider.ManagedEndpointProvisioningResult["runtime"]
68 | null;
69 readonly environmentCredential: string;
70 },
71 EnvironmentLinkError
72 >;
73 }
74>()("t3code-relay/environments/EnvironmentLinker") {}
7675
7776const decodeProof = Schema.decodeUnknownEffect(RelayEnvironmentLinkProofPayload);
7877
infra/relay/src/environments/EnvironmentLinks.test.ts+16-8
......@@ -3,9 +3,9 @@ import * as Effect from "effect/Effect";
33import * as Layer from "effect/Layer";
44import { PgDialect } from "drizzle-orm/pg-core";
55
6import { RelayDb, type RelayDatabase } from "../db.ts";
6import * as RelayDb from "../db.ts";
77import { relayEnvironmentLinks } from "../persistence/schema.ts";
8import { EnvironmentLinks, layer } from "./EnvironmentLinks.ts";
8import * as EnvironmentLinks from "./EnvironmentLinks.ts";
99
1010describe("EnvironmentLinks", () => {
1111 it.effect("selects users when either notifications or Live Activities are enabled", () => {
......@@ -25,10 +25,10 @@ describe("EnvironmentLinks", () => {
2525 },
2626 };
2727 },
28 } as unknown as RelayDatabase;
28 } as unknown as RelayDb.RelayDb["Service"];
2929
3030 return Effect.gen(function* () {
31 const links = yield* EnvironmentLinks;
31 const links = yield* EnvironmentLinks.EnvironmentLinks;
3232 expect(yield* links.listUsersForEnvironment({ environmentId: "env-1" })).toEqual([]);
3333 expect(whereConditions).toHaveLength(1);
3434
......@@ -39,7 +39,11 @@ describe("EnvironmentLinks", () => {
3939 expect(query.sql).toContain('"relay_environment_links"."live_activities_enabled" = $3');
4040 expect(query.sql).toContain(" or ");
4141 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 );
4347 });
4448
4549 it.effect("revokes only the active link owned by the requesting user", () => {
......@@ -65,10 +69,10 @@ describe("EnvironmentLinks", () => {
6569 },
6670 };
6771 },
68 } as unknown as RelayDatabase;
72 } as unknown as RelayDb.RelayDb["Service"];
6973
7074 return Effect.gen(function* () {
71 const links = yield* EnvironmentLinks;
75 const links = yield* EnvironmentLinks.EnvironmentLinks;
7276 const revoked = yield* links.revokeForUser({
7377 userId: "user-1",
7478 environmentId: "env-1",
......@@ -86,6 +90,10 @@ describe("EnvironmentLinks", () => {
8690 expect(query.sql).toContain('"relay_environment_links"."environment_id" = $2');
8791 expect(query.sql).toContain('"relay_environment_links"."revoked_at" is null');
8892 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 );
9098 });
9199});
infra/relay/src/environments/EnvironmentLinks.ts+40-41
......@@ -11,7 +11,7 @@ import * as Layer from "effect/Layer";
1111import * as Schema from "effect/Schema";
1212import { and, eq, isNull, or } from "drizzle-orm";
1313
14import { RelayDb } from "../db.ts";
14import * as RelayDb from "../db.ts";
1515import { relayEnvironmentLinks } from "../persistence/schema.ts";
1616
1717export interface RelayLinkedEnvironmentRecord extends RelayClientEnvironmentRecord {
......@@ -78,45 +78,44 @@ export class EnvironmentLinkRevokePersistenceError extends Schema.TaggedErrorCla
7878 }
7979}
8080
81export interface EnvironmentLinksShape {
82 readonly upsert: (input: {
83 readonly userId: string;
84 readonly request: RelayEnvironmentLinkRequest;
85 readonly proof: RelayEnvironmentLinkProofPayload;
86 readonly endpoint: RelayManagedEndpoint;
87 }) => Effect.Effect<void, EnvironmentLinkUpsertPersistenceError>;
88 readonly listUsersForEnvironment: (input: {
89 readonly environmentId: string;
90 }) => Effect.Effect<ReadonlyArray<string>, EnvironmentLinkUserListPersistenceError>;
91 readonly listDeliveryUsersForEnvironment: (input: {
92 readonly environmentId: string;
93 readonly environmentPublicKey: string;
94 }) => Effect.Effect<
95 ReadonlyArray<AgentAwarenessDeliveryUserRecord>,
96 EnvironmentLinkUserListPersistenceError
97 >;
98 readonly listPublicKeysForEnvironment: (input: {
99 readonly environmentId: string;
100 }) => Effect.Effect<ReadonlyArray<string>, EnvironmentPublicKeyListPersistenceError>;
101 readonly listForUser: (input: {
102 readonly userId: string;
103 }) => Effect.Effect<
104 ReadonlyArray<RelayClientEnvironmentRecord>,
105 EnvironmentLinkListPersistenceError
106 >;
107 readonly getForUser: (input: {
108 readonly userId: string;
109 readonly environmentId: string;
110 }) => Effect.Effect<RelayLinkedEnvironmentRecord | null, EnvironmentLinkLookupPersistenceError>;
111 readonly revokeForUser: (input: {
112 readonly userId: string;
113 readonly environmentId: string;
114 }) => Effect.Effect<boolean, EnvironmentLinkRevokePersistenceError>;
115}
116
117export class EnvironmentLinks extends Context.Service<EnvironmentLinks, EnvironmentLinksShape>()(
118 "t3code-relay/environments/EnvironmentLinks",
119) {}
81export class EnvironmentLinks extends Context.Service<
82 EnvironmentLinks,
83 {
84 readonly upsert: (input: {
85 readonly userId: string;
86 readonly request: RelayEnvironmentLinkRequest;
87 readonly proof: RelayEnvironmentLinkProofPayload;
88 readonly endpoint: RelayManagedEndpoint;
89 }) => Effect.Effect<void, EnvironmentLinkUpsertPersistenceError>;
90 readonly listUsersForEnvironment: (input: {
91 readonly environmentId: string;
92 }) => Effect.Effect<ReadonlyArray<string>, EnvironmentLinkUserListPersistenceError>;
93 readonly listDeliveryUsersForEnvironment: (input: {
94 readonly environmentId: string;
95 readonly environmentPublicKey: string;
96 }) => Effect.Effect<
97 ReadonlyArray<AgentAwarenessDeliveryUserRecord>,
98 EnvironmentLinkUserListPersistenceError
99 >;
100 readonly listPublicKeysForEnvironment: (input: {
101 readonly environmentId: string;
102 }) => Effect.Effect<ReadonlyArray<string>, EnvironmentPublicKeyListPersistenceError>;
103 readonly listForUser: (input: {
104 readonly userId: string;
105 }) => Effect.Effect<
106 ReadonlyArray<RelayClientEnvironmentRecord>,
107 EnvironmentLinkListPersistenceError
108 >;
109 readonly getForUser: (input: {
110 readonly userId: string;
111 readonly environmentId: string;
112 }) => Effect.Effect<RelayLinkedEnvironmentRecord | null, EnvironmentLinkLookupPersistenceError>;
113 readonly revokeForUser: (input: {
114 readonly userId: string;
115 readonly environmentId: string;
116 }) => Effect.Effect<boolean, EnvironmentLinkRevokePersistenceError>;
117 }
118>()("t3code-relay/environments/EnvironmentLinks") {}
120119
121120function agentAwarenessDeliveryUserCondition(environmentId: string) {
122121 return and(
......@@ -140,7 +139,7 @@ function agentAwarenessDeliveryUserKeyCondition(input: {
140139}
141140
142141const make = Effect.gen(function* () {
143 const db = yield* RelayDb;
142 const db = yield* RelayDb.RelayDb;
144143
145144 return EnvironmentLinks.of({
146145 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* () {
8080 } satisfies RelayAgentActivityPublishRequest;
8181});
8282
83function layer(replay?: Partial<DpopProofs.DpopProofReplayShape>) {
83function layer(replay?: Partial<DpopProofs.DpopProofReplay["Service"]>) {
8484 return EnvironmentPublishSignatures.layer.pipe(
8585 Layer.provide(
8686 Layer.merge(
87 Layer.succeed(RelayConfiguration.RelayConfiguration, config),
87 RelayConfiguration.layer(config),
8888 Layer.succeed(DpopProofs.DpopProofReplay, {
8989 verifyAndConsume:
9090 replay?.verifyAndConsume ?? (() => Effect.die("unexpected DPoP proof verification")),
infra/relay/src/environments/EnvironmentPublishSignatures.ts+8-10
......@@ -59,18 +59,16 @@ export type EnvironmentPublishSignatureError =
5959 | EnvironmentPublishPublicKeyMissing
6060 | DpopProofs.DpopProofReplayPersistenceError;
6161
62export 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
7162export class EnvironmentPublishSignatures extends Context.Service<
7263 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 }
7472>()("t3code-relay/environments/EnvironmentPublishSignatures") {}
7573
7674const decodeProof = Schema.decodeUnknownEffect(RelayAgentActivityPublishProofPayload);
infra/relay/src/environments/ManagedEndpointAllocations.ts+26-28
......@@ -6,7 +6,7 @@ import * as Effect from "effect/Effect";
66import * as Layer from "effect/Layer";
77import * as Schema from "effect/Schema";
88
9import { RelayDb } from "../db.ts";
9import * as RelayDb from "../db.ts";
1010import { isManagedEndpointHostname, managedEndpointForHostname } from "../deploymentConfig.ts";
1111import { relayManagedEndpointAllocations } from "../persistence/schema.ts";
1212
......@@ -66,26 +66,29 @@ interface RecordManagedEndpointDnsInput extends ManagedEndpointAllocationKey {
6666 readonly dnsRecordId: string;
6767}
6868
69export interface ManagedEndpointAllocationsShape {
70 readonly get: (
71 input: ManagedEndpointAllocationKey,
72 ) => Effect.Effect<ManagedEndpointAllocation | null, ManagedEndpointAllocationPersistenceError>;
73 readonly reserve: (
74 input: ReserveManagedEndpointAllocationInput,
75 ) => Effect.Effect<ManagedEndpointAllocation, ManagedEndpointAllocationPersistenceError>;
76 readonly recordTunnel: (
77 input: RecordManagedEndpointTunnelInput,
78 ) => Effect.Effect<void, ManagedEndpointAllocationPersistenceError>;
79 readonly recordDns: (
80 input: RecordManagedEndpointDnsInput,
81 ) => Effect.Effect<void, ManagedEndpointAllocationPersistenceError>;
82 readonly markReady: (
83 input: ManagedEndpointAllocationKey,
84 ) => Effect.Effect<void, ManagedEndpointAllocationPersistenceError>;
85 readonly remove: (
86 input: ManagedEndpointAllocationKey,
87 ) => Effect.Effect<void, ManagedEndpointAllocationPersistenceError>;
88}
69export class ManagedEndpointAllocations extends Context.Service<
70 ManagedEndpointAllocations,
71 {
72 readonly get: (
73 input: ManagedEndpointAllocationKey,
74 ) => Effect.Effect<ManagedEndpointAllocation | null, ManagedEndpointAllocationPersistenceError>;
75 readonly reserve: (
76 input: ReserveManagedEndpointAllocationInput,
77 ) => Effect.Effect<ManagedEndpointAllocation, ManagedEndpointAllocationPersistenceError>;
78 readonly recordTunnel: (
79 input: RecordManagedEndpointTunnelInput,
80 ) => Effect.Effect<void, ManagedEndpointAllocationPersistenceError>;
81 readonly recordDns: (
82 input: RecordManagedEndpointDnsInput,
83 ) => Effect.Effect<void, ManagedEndpointAllocationPersistenceError>;
84 readonly markReady: (
85 input: ManagedEndpointAllocationKey,
86 ) => Effect.Effect<void, ManagedEndpointAllocationPersistenceError>;
87 readonly remove: (
88 input: ManagedEndpointAllocationKey,
89 ) => Effect.Effect<void, ManagedEndpointAllocationPersistenceError>;
90 }
91>()("t3code-relay/environments/ManagedEndpointAllocations") {}
8992
9093const allocationSelection = {
9194 userId: relayManagedEndpointAllocations.userId,
......@@ -109,7 +112,7 @@ const persistenceError = (cause: unknown) =>
109112 : new ManagedEndpointAllocationPersistenceError({ cause });
110113
111114const make = Effect.gen(function* () {
112 const db = yield* RelayDb;
115 const db = yield* RelayDb.RelayDb;
113116
114117 return ManagedEndpointAllocations.of({
115118 get: Effect.fn("relay.managed_endpoint_allocations.get")(function* (
......@@ -198,9 +201,4 @@ const make = Effect.gen(function* () {
198201 });
199202});
200203
201export class ManagedEndpointAllocations extends Context.Service<
202 ManagedEndpointAllocations,
203 ManagedEndpointAllocationsShape
204>()("t3code-relay/environments/ManagedEndpointAllocations") {
205 static readonly layer = Layer.effect(this, make);
206}
204export const layer = Layer.effect(ManagedEndpointAllocations, make);
infra/relay/src/environments/ManagedEndpointProvider.test.ts+3-3
......@@ -204,9 +204,9 @@ function providerLayer(
204204) {
205205 return ManagedEndpointProvider.layer.pipe(
206206 Layer.provideMerge(NodeServices.layer),
207 Layer.provide(Layer.succeed(RelayConfiguration.RelayConfiguration, config)),
208 Layer.provide(Layer.succeed(ManagedEndpointProvider.ManagedEndpointTunnelClient, tunnelClient)),
209 Layer.provide(Layer.succeed(ManagedEndpointProvider.ManagedEndpointDnsClient, dnsClient)),
207 Layer.provide(RelayConfiguration.layer(config)),
208 Layer.provide(ManagedEndpointProvider.layerTunnelClient(tunnelClient)),
209 Layer.provide(ManagedEndpointProvider.layerDnsClient(dnsClient)),
210210 Layer.provide(
211211 Layer.succeed(ManagedEndpointAllocations.ManagedEndpointAllocations, allocations),
212212 ),
infra/relay/src/environments/ManagedEndpointProvider.ts+69-63
......@@ -23,7 +23,7 @@ import {
2323 managedEndpointHostname,
2424 managedEndpointTunnelName,
2525} from "../deploymentConfig.ts";
26import { ManagedEndpointAllocations } from "./ManagedEndpointAllocations.ts";
26import * as ManagedEndpointAllocations from "./ManagedEndpointAllocations.ts";
2727
2828export class ManagedEndpointProvisioningNotConfigured extends Schema.TaggedErrorClass<ManagedEndpointProvisioningNotConfigured>()(
2929 "ManagedEndpointProvisioningNotConfigured",
......@@ -74,21 +74,19 @@ export interface ManagedEndpointProvisioningResult {
7474 readonly runtime: RelayManagedEndpointRuntimeConfig;
7575}
7676
77export 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
8977export class ManagedEndpointProvider extends Context.Service<
9078 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 }
9290>()("t3code-relay/environments/ManagedEndpointProvider") {}
9391
9492interface ManagedEndpointTunnel {
......@@ -105,36 +103,42 @@ export class ManagedEndpointTunnelClientError extends Schema.TaggedErrorClass<Ma
105103 }
106104}
107105
108export 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
133106export class ManagedEndpointTunnelClient extends Context.Service<
134107 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 }
136134>()("t3code-relay/environments/ManagedEndpointProvider/ManagedEndpointTunnelClient") {}
137135
136export const makeTunnelClient = (client: ManagedEndpointTunnelClient["Service"]) =>
137 ManagedEndpointTunnelClient.of(client);
138
139export const layerTunnelClient = (client: ManagedEndpointTunnelClient["Service"]) =>
140 Layer.succeed(ManagedEndpointTunnelClient, makeTunnelClient(client));
141
138142interface ManagedEndpointCnameRecordInput {
139143 readonly type: "CNAME";
140144 readonly name: string;
......@@ -152,29 +156,33 @@ export class ManagedEndpointDnsClientError extends Schema.TaggedErrorClass<Manag
152156 }
153157}
154158
155export 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
171159export class ManagedEndpointDnsClient extends Context.Service<
172160 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 }
174176>()("t3code-relay/environments/ManagedEndpointProvider/ManagedEndpointDnsClient") {}
175177
178export const makeDnsClient = (client: ManagedEndpointDnsClient["Service"]) =>
179 ManagedEndpointDnsClient.of(client);
180
181export const layerDnsClient = (client: ManagedEndpointDnsClient["Service"]) =>
182 Layer.succeed(ManagedEndpointDnsClient, makeDnsClient(client));
183
176184const requireCloudflareSettings = Effect.fnUntraced(function* (
177 settings: RelayConfiguration.RelayConfigurationShape,
185 settings: RelayConfiguration.RelayConfiguration["Service"],
178186) {
179187 if (!settings.managedEndpointBaseDomain || !settings.managedEndpointNamespace) {
180188 return yield* new ManagedEndpointProvisioningNotConfigured();
......@@ -234,7 +242,7 @@ const make = Effect.gen(function* () {
234242 const crypto = yield* Crypto.Crypto;
235243 const tunnels = yield* ManagedEndpointTunnelClient;
236244 const dns = yield* ManagedEndpointDnsClient;
237 const allocations = yield* ManagedEndpointAllocations;
245 const allocations = yield* ManagedEndpointAllocations.ManagedEndpointAllocations;
238246
239247 const updateExistingDnsRecords = Effect.fnUntraced(function* (
240248 records: ReadonlyArray<{ readonly id: string }>,
......@@ -452,8 +460,7 @@ export const layerCloudflareBindings = (
452460 layer.pipe(
453461 Layer.provide(
454462 Layer.mergeAll(
455 Layer.succeed(
456 ManagedEndpointTunnelClient,
463 layerTunnelClient(
457464 ManagedEndpointTunnelClient.of({
458465 list: (request) =>
459466 tunnelClient.list(request).pipe(
......@@ -482,8 +489,7 @@ export const layerCloudflareBindings = (
482489 ),
483490 }),
484491 ),
485 Layer.succeed(
486 ManagedEndpointDnsClient,
492 layerDnsClient(
487493 ManagedEndpointDnsClient.of({
488494 listRecords: (hostname) =>
489495 dnsClient.listDnsRecords({ search: hostname }).pipe(
infra/relay/src/http/Api.test.ts+2-2
......@@ -30,7 +30,7 @@ vi.mock("@clerk/backend", () => ({
3030 verifyToken: vi.fn(),
3131}));
3232
33const relaySettings: RelayConfiguration.RelayConfigurationShape = {
33const relaySettings: RelayConfiguration.RelayConfiguration["Service"] = {
3434 relayIssuer: "https://relay.example.test",
3535 apns: {
3636 teamId: "apns-team",
......@@ -110,7 +110,7 @@ describe("relay environment authentication", () => {
110110 const failure = new EnvironmentCredentials.EnvironmentCredentialAuthenticatePersistenceError({
111111 cause: "database unavailable",
112112 });
113 const credentials: EnvironmentCredentials.EnvironmentCredentialsShape = {
113 const credentials: EnvironmentCredentials.EnvironmentCredentials["Service"] = {
114114 create: () => Effect.die("unused create"),
115115 authenticate: () => Effect.fail(failure),
116116 revokeForEnvironmentPublicKey: () => Effect.die("unused revoke"),
infra/relay/src/http/Api.ts+8-5
......@@ -66,7 +66,7 @@ import * as ManagedEndpointAllocations from "../environments/ManagedEndpointAllo
6666import * as EnvironmentPublishSignatures from "../environments/EnvironmentPublishSignatures.ts";
6767import * as MobileRegistrations from "../agentActivity/MobileRegistrations.ts";
6868import { withSpanAttributes } from "../observability.ts";
69import { RelayDb } from "../db.ts";
69import * as RelayDb from "../db.ts";
7070
7171const relayCorsAllowedMethods = ["GET", "POST", "DELETE", "OPTIONS"] as const;
7272const relayCorsAllowedHeaders = [
......@@ -347,7 +347,7 @@ export const healthApi = HttpApiBuilder.group(
347347 RelayApi,
348348 "health",
349349 Effect.fnUntraced(function* (handlers) {
350 const db = yield* RelayDb;
350 const db = yield* RelayDb.RelayDb;
351351 return handlers.handle(
352352 "health",
353353 Effect.fn("relay.api.health")(
......@@ -985,7 +985,10 @@ function hasExpectedClerkAudience(audience: unknown, expectedAudience: string):
985985 audience.some((entry) => typeof entry === "string" && entry === expectedAudience);
986986}
987987
988function verifyClerkBearerToken(config: RelayConfiguration.RelayConfigurationShape, token: string) {
988function verifyClerkBearerToken(
989 config: RelayConfiguration.RelayConfiguration["Service"],
990 token: string,
991) {
989992 return Effect.tryPromise({
990993 try: () =>
991994 verifyToken(token, {
......@@ -1001,7 +1004,7 @@ function verifyClerkBearerToken(config: RelayConfiguration.RelayConfigurationSha
10011004}
10021005
10031006function verifyClerkOAuthBearerToken(
1004 config: RelayConfiguration.RelayConfigurationShape,
1007 config: RelayConfiguration.RelayConfiguration["Service"],
10051008 token: string,
10061009) {
10071010 return Effect.tryPromise({
......@@ -1027,7 +1030,7 @@ function verifyClerkOAuthBearerToken(
10271030}
10281031
10291032export function verifyRelayClientBearerToken(
1030 config: RelayConfiguration.RelayConfigurationShape,
1033 config: RelayConfiguration.RelayConfiguration["Service"],
10311034 token: string,
10321035) {
10331036 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";
99import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";
1010import type { OtlpTracer } from "effect/unstable/observability";
1111
12import { EnvironmentConnectNotAuthorized } from "./environments/EnvironmentConnector.ts";
12import * as EnvironmentConnector from "./environments/EnvironmentConnector.ts";
1313import { makeRelayTraceLayer } from "./observability.ts";
1414
1515interface ExportedRequest {
......@@ -43,7 +43,7 @@ it.effect("exports schema error fields as span attributes", () =>
4343 );
4444
4545 yield* Effect.fail(
46 new EnvironmentConnectNotAuthorized({
46 new EnvironmentConnector.EnvironmentConnectNotAuthorized({
4747 environmentId: "environment-1",
4848 operation: "connect",
4949 reason: "managed_endpoint_allocation_not_ready",
infra/relay/src/worker.ts+4-9
......@@ -42,7 +42,7 @@ import * as EnvironmentCredentials from "./environments/EnvironmentCredentials.t
4242import * as EnvironmentLinks from "./environments/EnvironmentLinks.ts";
4343import * as ManagedEndpointAllocations from "./environments/ManagedEndpointAllocations.ts";
4444import * as LiveActivities from "./agentActivity/LiveActivities.ts";
45import { RelayDb, RelayHyperdrive } from "./db.ts";
45import * as RelayDb from "./db.ts";
4646import { RelayApnsDeliveryDeadLetterQueue, RelayApnsDeliveryQueue } from "./queues.ts";
4747import * as RelayConfiguration from "./Config.ts";
4848import * as AgentActivityPublisher from "./agentActivity/AgentActivityPublisher.ts";
......@@ -138,7 +138,7 @@ export default class Api extends Cloudflare.Worker<Api>()(
138138
139139 const cloudMintPrivateKey = yield* cloudMintKeyPair.privateKey;
140140 const cloudMintPublicKey = yield* cloudMintKeyPair.publicKey;
141 const hyperdrive = yield* Cloudflare.Hyperdrive.bind(yield* RelayHyperdrive);
141 const hyperdrive = yield* Cloudflare.Hyperdrive.bind(yield* RelayDb.RelayHyperdrive);
142142 const db = yield* Drizzle.postgres(hyperdrive.connectionString);
143143
144144 const managedEndpointTunnelBinding = yield* Cloudflare.TunnelReadWrite.bind();
......@@ -203,16 +203,11 @@ export default class Api extends Cloudflare.Worker<Api>()(
203203 Layer.provideMerge(AgentActivityRows.layer),
204204 Layer.provideMerge(Devices.layer),
205205 Layer.provideMerge(EnvironmentCredentials.layer),
206 Layer.provideMerge(
207 Layer.mergeAll(
208 EnvironmentLinks.layer,
209 ManagedEndpointAllocations.ManagedEndpointAllocations.layer,
210 ),
211 ),
206 Layer.provideMerge(Layer.mergeAll(EnvironmentLinks.layer, ManagedEndpointAllocations.layer)),
212207 Layer.provideMerge(LiveActivities.layer),
213208 Layer.provideMerge(DeliveryAttempts.layer),
214209 Layer.provideMerge(RelayTokens.layer),
215 Layer.provideMerge(Layer.succeed(RelayDb, db)),
210 Layer.provideMerge(Layer.succeed(RelayDb.RelayDb, db)),
216211 Layer.provideMerge(Layer.effect(RelayConfiguration.RelayConfiguration, loadSettings)),
217212 Layer.provideMerge(webcryptoLayer),
218213 );