| author | |
| committer | |
| log | a56496c7f20d53369ef87101681ee2a99b3a4337 |
| tree | ab5c0f6d14af5fcadeba0246578336b94c18855a |
| parent | b76f161d551235e1de89ed85c747f14041ac5d99 |
| signature | Signed by PGP key B5690EEEBB952194 |
20 files changed, 744 insertions(+), 232 deletions(-)
infra/relay/src/agentActivity/AgentActivityRows.ts+21-13| ... | @@ -1,7 +1,6 @@ | ... | @@ -1,7 +1,6 @@ |
| 1 | import type { RelayAgentActivityState } from "@t3tools/contracts/relay"; | 1 | import type { RelayAgentActivityState } from "@t3tools/contracts/relay"; |
| 2 | import { RelayAgentActivityState as RelayAgentActivityStateSchema } from "@t3tools/contracts/relay"; | 2 | import { RelayAgentActivityState as RelayAgentActivityStateSchema } from "@t3tools/contracts/relay"; |
| 3 | import * as Context from "effect/Context"; | 3 | import * as Context from "effect/Context"; |
| 4 | import * as Data from "effect/Data"; | ||
| 5 | import * as DateTime from "effect/DateTime"; | 4 | import * as DateTime from "effect/DateTime"; |
| 6 | import * as Effect from "effect/Effect"; | 5 | import * as Effect from "effect/Effect"; |
| 7 | import { cast } from "effect/Function"; | 6 | import { cast } from "effect/Function"; |
| ... | @@ -13,23 +12,32 @@ import { and, desc, eq, isNull } from "drizzle-orm"; | ... | @@ -13,23 +12,32 @@ import { and, desc, eq, isNull } from "drizzle-orm"; |
| 13 | import { RelayDb } from "../db.ts"; | 12 | import { RelayDb } from "../db.ts"; |
| 14 | import { relayAgentActivityRows, relayEnvironmentLinks } from "../persistence/schema.ts"; | 13 | import { relayAgentActivityRows, relayEnvironmentLinks } from "../persistence/schema.ts"; |
| 15 | 14 | ||
| 16 | export class AgentActivityRowUpsertPersistenceError extends Data.TaggedError( | 15 | export class AgentActivityRowUpsertPersistenceError extends Schema.TaggedErrorClass<AgentActivityRowUpsertPersistenceError>()( |
| 17 | "AgentActivityRowUpsertPersistenceError", | 16 | "AgentActivityRowUpsertPersistenceError", |
| 18 | )<{ | 17 | { cause: Schema.Defect() }, |
| 19 | readonly cause: unknown; | 18 | ) { |
| 20 | }> {} | 19 | override get message(): string { |
| 20 | return "Failed to persist agent activity state"; | ||
| 21 | } | ||
| 22 | } | ||
| 21 | 23 | ||
| 22 | export class AgentActivityRowDeletePersistenceError extends Data.TaggedError( | 24 | export class AgentActivityRowDeletePersistenceError extends Schema.TaggedErrorClass<AgentActivityRowDeletePersistenceError>()( |
| 23 | "AgentActivityRowDeletePersistenceError", | 25 | "AgentActivityRowDeletePersistenceError", |
| 24 | )<{ | 26 | { cause: Schema.Defect() }, |
| 25 | readonly cause: unknown; | 27 | ) { |
| 26 | }> {} | 28 | override get message(): string { |
| 29 | return "Failed to delete agent activity state"; | ||
| 30 | } | ||
| 31 | } | ||
| 27 | 32 | ||
| 28 | export class AgentActivityRowListPersistenceError extends Data.TaggedError( | 33 | export class AgentActivityRowListPersistenceError extends Schema.TaggedErrorClass<AgentActivityRowListPersistenceError>()( |
| 29 | "AgentActivityRowListPersistenceError", | 34 | "AgentActivityRowListPersistenceError", |
| 30 | )<{ | 35 | { cause: Schema.Defect() }, |
| 31 | readonly cause: unknown; | 36 | ) { |
| 32 | }> {} | 37 | override get message(): string { |
| 38 | return "Failed to list agent activity state"; | ||
| 39 | } | ||
| 40 | } | ||
| 33 | 41 | ||
| 34 | export interface AgentActivityRowsShape { | 42 | export interface AgentActivityRowsShape { |
| 35 | readonly upsert: (input: { | 43 | readonly upsert: (input: { |
infra/relay/src/agentActivity/ApnsClient.ts+32-18| ... | @@ -2,20 +2,13 @@ import * as NodeCrypto from "node:crypto"; | ... | @@ -2,20 +2,13 @@ import * as NodeCrypto from "node:crypto"; |
| 2 | 2 | ||
| 3 | import type { RelayAgentActivityAggregateState } from "@t3tools/contracts/relay"; | 3 | import type { RelayAgentActivityAggregateState } from "@t3tools/contracts/relay"; |
| 4 | import * as Context from "effect/Context"; | 4 | import * as Context from "effect/Context"; |
| 5 | import * as Data from "effect/Data"; | ||
| 6 | import * as Effect from "effect/Effect"; | 5 | import * as Effect from "effect/Effect"; |
| 7 | import * as Encoding from "effect/Encoding"; | 6 | import * as Encoding from "effect/Encoding"; |
| 8 | import * as Layer from "effect/Layer"; | 7 | import * as Layer from "effect/Layer"; |
| 9 | import * as Option from "effect/Option"; | 8 | import * as Option from "effect/Option"; |
| 10 | import * as Redacted from "effect/Redacted"; | 9 | import * as Redacted from "effect/Redacted"; |
| 11 | import * as Schema from "effect/Schema"; | 10 | import * as Schema from "effect/Schema"; |
| 12 | import { | 11 | import { Headers, HttpClient, HttpClientRequest } from "effect/unstable/http"; |
| 13 | Headers, | ||
| 14 | HttpClient, | ||
| 15 | HttpClientRequest, | ||
| 16 | type HttpBody, | ||
| 17 | type HttpClientError, | ||
| 18 | } from "effect/unstable/http"; | ||
| 19 | import type { ApnsCredentials } from "../Config.ts"; | 12 | import type { ApnsCredentials } from "../Config.ts"; |
| 20 | import type { ApnsNotificationPayload } from "./apnsDeliveryJobs.ts"; | 13 | import type { ApnsNotificationPayload } from "./apnsDeliveryJobs.ts"; |
| 21 | 14 | ||
| ... | @@ -45,18 +38,39 @@ export interface ApnsDeliveryResult { | ... | @@ -45,18 +38,39 @@ export interface ApnsDeliveryResult { |
| 45 | readonly apnsId: string | null; | 38 | readonly apnsId: string | null; |
| 46 | } | 39 | } |
| 47 | 40 | ||
| 48 | export class ApnsSigningError extends Data.TaggedError("ApnsSigningError")<{ | 41 | export class ApnsSigningError extends Schema.TaggedErrorClass<ApnsSigningError>()( |
| 49 | readonly phase: "encoding" | "signing"; | 42 | "ApnsSigningError", |
| 50 | readonly cause: unknown; | 43 | { |
| 51 | }> {} | 44 | phase: Schema.Literals(["encoding", "signing"]), |
| 45 | cause: Schema.Defect(), | ||
| 46 | }, | ||
| 47 | ) { | ||
| 48 | override get message(): string { | ||
| 49 | return `Failed during APNs JWT ${this.phase}`; | ||
| 50 | } | ||
| 51 | } | ||
| 52 | 52 | ||
| 53 | export class ApnsHttpRequestError extends Data.TaggedError("ApnsHttpRequestError")<{ | 53 | export class ApnsHttpRequestError extends Schema.TaggedErrorClass<ApnsHttpRequestError>()( |
| 54 | readonly cause: HttpClientError.HttpClientError | HttpBody.HttpBodyError; | 54 | "ApnsHttpRequestError", |
| 55 | }> {} | 55 | { |
| 56 | cause: Schema.Defect(), | ||
| 57 | }, | ||
| 58 | ) { | ||
| 59 | override get message(): string { | ||
| 60 | return "APNs HTTP request failed"; | ||
| 61 | } | ||
| 62 | } | ||
| 56 | 63 | ||
| 57 | export class ApnsInvalidResponseError extends Data.TaggedError("ApnsInvalidResponseError")<{ | 64 | export class ApnsInvalidResponseError extends Schema.TaggedErrorClass<ApnsInvalidResponseError>()( |
| 58 | readonly cause: unknown; | 65 | "ApnsInvalidResponseError", |
| 59 | }> {} | 66 | { |
| 67 | cause: Schema.Defect(), | ||
| 68 | }, | ||
| 69 | ) { | ||
| 70 | override get message(): string { | ||
| 71 | return "APNs returned an invalid response"; | ||
| 72 | } | ||
| 73 | } | ||
| 60 | 74 | ||
| 61 | export type ApnsError = ApnsSigningError | ApnsHttpRequestError | ApnsInvalidResponseError; | 75 | export type ApnsError = ApnsSigningError | ApnsHttpRequestError | ApnsInvalidResponseError; |
| 62 | 76 |
infra/relay/src/agentActivity/ApnsDeliveries.ts+10-4| ... | @@ -9,7 +9,6 @@ import { | ... | @@ -9,7 +9,6 @@ import { |
| 9 | RelayAgentAwarenessPreferences as RelayAgentAwarenessPreferencesSchema, | 9 | RelayAgentAwarenessPreferences as RelayAgentAwarenessPreferencesSchema, |
| 10 | } from "@t3tools/contracts/relay"; | 10 | } from "@t3tools/contracts/relay"; |
| 11 | import * as Context from "effect/Context"; | 11 | import * as Context from "effect/Context"; |
| 12 | import * as Data from "effect/Data"; | ||
| 13 | import * as DateTime from "effect/DateTime"; | 12 | import * as DateTime from "effect/DateTime"; |
| 14 | import * as Effect from "effect/Effect"; | 13 | import * as Effect from "effect/Effect"; |
| 15 | import * as Layer from "effect/Layer"; | 14 | import * as Layer from "effect/Layer"; |
| ... | @@ -74,9 +73,16 @@ export type ApnsDeliveryError = | ... | @@ -74,9 +73,16 @@ export type ApnsDeliveryError = |
| 74 | | LiveActivities.LiveActivityTargetListPersistenceError | 73 | | LiveActivities.LiveActivityTargetListPersistenceError |
| 75 | | LiveActivities.LiveActivityDeliveryMarkPersistenceError; | 74 | | LiveActivities.LiveActivityDeliveryMarkPersistenceError; |
| 76 | 75 | ||
| 77 | export class ApnsDeliveryJobClaimInFlight extends Data.TaggedError("ApnsDeliveryJobClaimInFlight")<{ | 76 | export class ApnsDeliveryJobClaimInFlight extends Schema.TaggedErrorClass<ApnsDeliveryJobClaimInFlight>()( |
| 78 | readonly sourceJobId: string; | 77 | "ApnsDeliveryJobClaimInFlight", |
| 79 | }> {} | 78 | { |
| 79 | sourceJobId: Schema.String, | ||
| 80 | }, | ||
| 81 | ) { | ||
| 82 | override get message(): string { | ||
| 83 | return `APNs delivery job '${this.sourceJobId}' is already in flight`; | ||
| 84 | } | ||
| 85 | } | ||
| 80 | 86 | ||
| 81 | const decodeRelayAgentActivityAggregateStateJson = Schema.decodeUnknownOption( | 87 | const decodeRelayAgentActivityAggregateStateJson = Schema.decodeUnknownOption( |
| 82 | Schema.fromJsonString(RelayAgentActivityAggregateStateSchema), | 88 | Schema.fromJsonString(RelayAgentActivityAggregateStateSchema), |
infra/relay/src/agentActivity/ApnsDeliveryQueue.ts+9-4| ... | @@ -2,10 +2,10 @@ import * as Alchemy from "alchemy"; | ... | @@ -2,10 +2,10 @@ import * as Alchemy from "alchemy"; |
| 2 | import * as Cloudflare from "alchemy/Cloudflare"; | 2 | import * as Cloudflare from "alchemy/Cloudflare"; |
| 3 | import * as Crypto from "effect/Crypto"; | 3 | import * as Crypto from "effect/Crypto"; |
| 4 | import * as Context from "effect/Context"; | 4 | import * as Context from "effect/Context"; |
| 5 | import * as Data from "effect/Data"; | ||
| 6 | import * as DateTime from "effect/DateTime"; | 5 | import * as DateTime from "effect/DateTime"; |
| 7 | import * as Effect from "effect/Effect"; | 6 | import * as Effect from "effect/Effect"; |
| 8 | import * as Layer from "effect/Layer"; | 7 | import * as Layer from "effect/Layer"; |
| 8 | import * as Schema from "effect/Schema"; | ||
| 9 | 9 | ||
| 10 | import type { RelayDeliveryResult } from "@t3tools/contracts/relay"; | 10 | import type { RelayDeliveryResult } from "@t3tools/contracts/relay"; |
| 11 | 11 | ||
| ... | @@ -22,9 +22,14 @@ import { | ... | @@ -22,9 +22,14 @@ import { |
| 22 | } from "./apnsDeliveryJobs.ts"; | 22 | } from "./apnsDeliveryJobs.ts"; |
| 23 | import * as RelayConfiguration from "../Config.ts"; | 23 | import * as RelayConfiguration from "../Config.ts"; |
| 24 | 24 | ||
| 25 | export class ApnsDeliveryQueueSendError extends Data.TaggedError("ApnsDeliveryQueueSendError")<{ | 25 | export class ApnsDeliveryQueueSendError extends Schema.TaggedErrorClass<ApnsDeliveryQueueSendError>()( |
| 26 | readonly cause: unknown; | 26 | "ApnsDeliveryQueueSendError", |
| 27 | }> {} | 27 | { cause: Schema.Defect() }, |
| 28 | ) { | ||
| 29 | override get message(): string { | ||
| 30 | return "Failed to enqueue APNs delivery"; | ||
| 31 | } | ||
| 32 | } | ||
| 28 | 33 | ||
| 29 | export type ApnsDeliveryQueueError = ApnsDeliveryQueueSendError; | 34 | export type ApnsDeliveryQueueError = ApnsDeliveryQueueSendError; |
| 30 | 35 |
infra/relay/src/agentActivity/DeliveryAttempts.ts+8-5| ... | @@ -1,20 +1,23 @@ | ... | @@ -1,20 +1,23 @@ |
| 1 | import * as Context from "effect/Context"; | 1 | import * as Context from "effect/Context"; |
| 2 | import * as Data from "effect/Data"; | ||
| 3 | import * as DateTime from "effect/DateTime"; | 2 | import * as DateTime from "effect/DateTime"; |
| 4 | import * as Effect from "effect/Effect"; | 3 | import * as Effect from "effect/Effect"; |
| 5 | import * as Layer from "effect/Layer"; | 4 | import * as Layer from "effect/Layer"; |
| 6 | import * as Option from "effect/Option"; | 5 | import * as Option from "effect/Option"; |
| 7 | import { and, eq, isNull } from "drizzle-orm"; | 6 | import { and, eq, isNull } from "drizzle-orm"; |
| 8 | import * as Crypto from "effect/Crypto"; | 7 | import * as Crypto from "effect/Crypto"; |
| 8 | import * as Schema from "effect/Schema"; | ||
| 9 | 9 | ||
| 10 | import { RelayDb } from "../db.ts"; | 10 | import { 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 Data.TaggedError( | 13 | export class DeliveryAttemptRecordPersistenceError extends Schema.TaggedErrorClass<DeliveryAttemptRecordPersistenceError>()( |
| 14 | "DeliveryAttemptRecordPersistenceError", | 14 | "DeliveryAttemptRecordPersistenceError", |
| 15 | )<{ | 15 | { cause: Schema.Defect() }, |
| 16 | readonly cause: unknown; | 16 | ) { |
| 17 | }> {} | 17 | override get message(): string { |
| 18 | return "Failed to persist APNs delivery attempt"; | ||
| 19 | } | ||
| 20 | } | ||
| 18 | 21 | ||
| 19 | export interface DeliveryAttemptInput { | 22 | export interface DeliveryAttemptInput { |
| 20 | readonly userId: string | null; | 23 | readonly userId: string | null; |
infra/relay/src/agentActivity/Devices.ts+23-12| ... | @@ -3,31 +3,42 @@ import type { | ... | @@ -3,31 +3,42 @@ import type { |
| 3 | RelayDeviceRegistrationRequest, | 3 | RelayDeviceRegistrationRequest, |
| 4 | } from "@t3tools/contracts/relay"; | 4 | } from "@t3tools/contracts/relay"; |
| 5 | import * as Context from "effect/Context"; | 5 | import * as Context from "effect/Context"; |
| 6 | import * as Data from "effect/Data"; | ||
| 7 | import * as DateTime from "effect/DateTime"; | 6 | import * as DateTime from "effect/DateTime"; |
| 8 | import * as Effect from "effect/Effect"; | 7 | import * as Effect from "effect/Effect"; |
| 9 | import * as Layer from "effect/Layer"; | 8 | import * as Layer from "effect/Layer"; |
| 9 | 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 { 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 Data.TaggedError( | 16 | export class DeviceRegistrationPersistenceError extends Schema.TaggedErrorClass<DeviceRegistrationPersistenceError>()( |
| 17 | "DeviceRegistrationPersistenceError", | 17 | "DeviceRegistrationPersistenceError", |
| 18 | )<{ | 18 | { cause: Schema.Defect() }, |
| 19 | readonly cause: unknown; | 19 | ) { |
| 20 | }> {} | 20 | override get message(): string { |
| 21 | return "Failed to persist mobile device registration"; | ||
| 22 | } | ||
| 23 | } | ||
| 21 | 24 | ||
| 22 | export class DeviceUnregistrationPersistenceError extends Data.TaggedError( | 25 | export class DeviceUnregistrationPersistenceError extends Schema.TaggedErrorClass<DeviceUnregistrationPersistenceError>()( |
| 23 | "DeviceUnregistrationPersistenceError", | 26 | "DeviceUnregistrationPersistenceError", |
| 24 | )<{ | 27 | { cause: Schema.Defect() }, |
| 25 | readonly cause: unknown; | 28 | ) { |
| 26 | }> {} | 29 | override get message(): string { |
| 30 | return "Failed to unregister mobile device"; | ||
| 31 | } | ||
| 32 | } | ||
| 27 | 33 | ||
| 28 | export class DeviceListPersistenceError extends Data.TaggedError("DeviceListPersistenceError")<{ | 34 | export class DeviceListPersistenceError extends Schema.TaggedErrorClass<DeviceListPersistenceError>()( |
| 29 | readonly cause: unknown; | 35 | "DeviceListPersistenceError", |
| 30 | }> {} | 36 | { cause: Schema.Defect() }, |
| 37 | ) { | ||
| 38 | override get message(): string { | ||
| 39 | return "Failed to list mobile devices"; | ||
| 40 | } | ||
| 41 | } | ||
| 31 | 42 | ||
| 32 | export interface DevicesShape { | 43 | export interface DevicesShape { |
| 33 | readonly register: (input: { | 44 | readonly register: (input: { |
infra/relay/src/agentActivity/LiveActivities.ts+21-13| ... | @@ -5,7 +5,6 @@ import type { | ... | @@ -5,7 +5,6 @@ import type { |
| 5 | } from "@t3tools/contracts/relay"; | 5 | } from "@t3tools/contracts/relay"; |
| 6 | import { RelayAgentActivityAggregateState as RelayAgentActivityAggregateStateSchema } from "@t3tools/contracts/relay"; | 6 | import { RelayAgentActivityAggregateState as RelayAgentActivityAggregateStateSchema } from "@t3tools/contracts/relay"; |
| 7 | import * as Context from "effect/Context"; | 7 | import * as Context from "effect/Context"; |
| 8 | import * as Data from "effect/Data"; | ||
| 9 | import * as DateTime from "effect/DateTime"; | 8 | import * as DateTime from "effect/DateTime"; |
| 10 | import * as Effect from "effect/Effect"; | 9 | import * as Effect from "effect/Effect"; |
| 11 | import { cast } from "effect/Function"; | 10 | import { cast } from "effect/Function"; |
| ... | @@ -16,23 +15,32 @@ import { and, eq, sql } from "drizzle-orm"; | ... | @@ -16,23 +15,32 @@ import { and, eq, sql } from "drizzle-orm"; |
| 16 | import { RelayDb } from "../db.ts"; | 15 | import { RelayDb } from "../db.ts"; |
| 17 | import { relayLiveActivities, relayMobileDevices } from "../persistence/schema.ts"; | 16 | import { relayLiveActivities, relayMobileDevices } from "../persistence/schema.ts"; |
| 18 | 17 | ||
| 19 | export class LiveActivityRegistrationPersistenceError extends Data.TaggedError( | 18 | export class LiveActivityRegistrationPersistenceError extends Schema.TaggedErrorClass<LiveActivityRegistrationPersistenceError>()( |
| 20 | "LiveActivityRegistrationPersistenceError", | 19 | "LiveActivityRegistrationPersistenceError", |
| 21 | )<{ | 20 | { cause: Schema.Defect() }, |
| 22 | readonly cause: unknown; | 21 | ) { |
| 23 | }> {} | 22 | override get message(): string { |
| 23 | return "Failed to persist Live Activity registration"; | ||
| 24 | } | ||
| 25 | } | ||
| 24 | 26 | ||
| 25 | export class LiveActivityTargetListPersistenceError extends Data.TaggedError( | 27 | export class LiveActivityTargetListPersistenceError extends Schema.TaggedErrorClass<LiveActivityTargetListPersistenceError>()( |
| 26 | "LiveActivityTargetListPersistenceError", | 28 | "LiveActivityTargetListPersistenceError", |
| 27 | )<{ | 29 | { cause: Schema.Defect() }, |
| 28 | readonly cause: unknown; | 30 | ) { |
| 29 | }> {} | 31 | override get message(): string { |
| 32 | return "Failed to list Live Activity delivery targets"; | ||
| 33 | } | ||
| 34 | } | ||
| 30 | 35 | ||
| 31 | export class LiveActivityDeliveryMarkPersistenceError extends Data.TaggedError( | 36 | export class LiveActivityDeliveryMarkPersistenceError extends Schema.TaggedErrorClass<LiveActivityDeliveryMarkPersistenceError>()( |
| 32 | "LiveActivityDeliveryMarkPersistenceError", | 37 | "LiveActivityDeliveryMarkPersistenceError", |
| 33 | )<{ | 38 | { cause: Schema.Defect() }, |
| 34 | readonly cause: unknown; | 39 | ) { |
| 35 | }> {} | 40 | override get message(): string { |
| 41 | return "Failed to persist Live Activity delivery state"; | ||
| 42 | } | ||
| 43 | } | ||
| 36 | 44 | ||
| 37 | export interface DeviceRow { | 45 | export interface DeviceRow { |
| 38 | readonly user_id: string; | 46 | readonly user_id: string; |
infra/relay/src/agentActivity/apnsDeliveryJobs.ts+17-8| ... | @@ -2,7 +2,6 @@ import * as NodeCrypto from "node:crypto"; | ... | @@ -2,7 +2,6 @@ import * as NodeCrypto from "node:crypto"; |
| 2 | 2 | ||
| 3 | import { RelayAgentActivityAggregateState, type RelayDeliveryKind } from "@t3tools/contracts/relay"; | 3 | import { RelayAgentActivityAggregateState, type RelayDeliveryKind } from "@t3tools/contracts/relay"; |
| 4 | import { stableStringify } from "@t3tools/shared/relaySigning"; | 4 | import { stableStringify } from "@t3tools/shared/relaySigning"; |
| 5 | import * as Data from "effect/Data"; | ||
| 6 | import * as DateTime from "effect/DateTime"; | 5 | import * as DateTime from "effect/DateTime"; |
| 7 | import * as Option from "effect/Option"; | 6 | import * as Option from "effect/Option"; |
| 8 | import * as Redacted from "effect/Redacted"; | 7 | import * as Redacted from "effect/Redacted"; |
| ... | @@ -50,13 +49,23 @@ export const SignedApnsDeliveryJob = Schema.Struct({ | ... | @@ -50,13 +49,23 @@ export const SignedApnsDeliveryJob = Schema.Struct({ |
| 50 | }); | 49 | }); |
| 51 | export type SignedApnsDeliveryJob = typeof SignedApnsDeliveryJob.Type; | 50 | export type SignedApnsDeliveryJob = typeof SignedApnsDeliveryJob.Type; |
| 52 | 51 | ||
| 53 | export class ApnsDeliveryJobInvalid extends Data.TaggedError("ApnsDeliveryJobInvalid")<{ | 52 | export class ApnsDeliveryJobInvalid extends Schema.TaggedErrorClass<ApnsDeliveryJobInvalid>()( |
| 54 | readonly message: string; | 53 | "ApnsDeliveryJobInvalid", |
| 55 | }> {} | 54 | { |
| 56 | 55 | message: Schema.String, | |
| 57 | export class ApnsDeliveryJobExpired extends Data.TaggedError("ApnsDeliveryJobExpired")<{ | 56 | }, |
| 58 | readonly expiresAt: string; | 57 | ) {} |
| 59 | }> {} | 58 | |
| 59 | export class ApnsDeliveryJobExpired extends Schema.TaggedErrorClass<ApnsDeliveryJobExpired>()( | ||
| 60 | "ApnsDeliveryJobExpired", | ||
| 61 | { | ||
| 62 | expiresAt: Schema.String, | ||
| 63 | }, | ||
| 64 | ) { | ||
| 65 | override get message(): string { | ||
| 66 | return `APNs delivery job expired at ${this.expiresAt}`; | ||
| 67 | } | ||
| 68 | } | ||
| 60 | 69 | ||
| 61 | export type ApnsDeliveryJobVerificationError = ApnsDeliveryJobInvalid | ApnsDeliveryJobExpired; | 70 | export type ApnsDeliveryJobVerificationError = ApnsDeliveryJobInvalid | ApnsDeliveryJobExpired; |
| 62 | 71 |
infra/relay/src/auth/DpopProofs.ts+10-5| ... | @@ -1,8 +1,8 @@ | ... | @@ -1,8 +1,8 @@ |
| 1 | import * as Context from "effect/Context"; | 1 | import * as Context from "effect/Context"; |
| 2 | import * as Data from "effect/Data"; | ||
| 3 | import * as DateTime from "effect/DateTime"; | 2 | import * as DateTime from "effect/DateTime"; |
| 4 | import * as Effect from "effect/Effect"; | 3 | import * as Effect from "effect/Effect"; |
| 5 | import * as Layer from "effect/Layer"; | 4 | import * as Layer from "effect/Layer"; |
| 5 | import * as Schema from "effect/Schema"; | ||
| 6 | import * as HttpApiError from "effect/unstable/httpapi/HttpApiError"; | 6 | import * as HttpApiError from "effect/unstable/httpapi/HttpApiError"; |
| 7 | import { lt } from "drizzle-orm"; | 7 | import { lt } from "drizzle-orm"; |
| 8 | 8 | ||
| ... | @@ -10,11 +10,16 @@ import { verifyDpopProof } from "@t3tools/shared/dpop"; | ... | @@ -10,11 +10,16 @@ import { verifyDpopProof } from "@t3tools/shared/dpop"; |
| 10 | import { RelayDb } from "../db.ts"; | 10 | import { 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 Data.TaggedError( | 13 | export class DpopProofReplayPersistenceError extends Schema.TaggedErrorClass<DpopProofReplayPersistenceError>()( |
| 14 | "DpopProofReplayPersistenceError", | 14 | "DpopProofReplayPersistenceError", |
| 15 | )<{ | 15 | { |
| 16 | readonly cause: unknown; | 16 | cause: Schema.Defect(), |
| 17 | }> {} | 17 | }, |
| 18 | ) { | ||
| 19 | override get message(): string { | ||
| 20 | return "Failed to persist DPoP proof replay state"; | ||
| 21 | } | ||
| 22 | } | ||
| 18 | 23 | ||
| 19 | export interface DpopProofReplayShape { | 24 | export interface DpopProofReplayShape { |
| 20 | readonly verifyAndConsume: (input: { | 25 | readonly verifyAndConsume: (input: { |
infra/relay/src/environments/EnvironmentConnector.ts+109-26| ... | @@ -28,7 +28,6 @@ import { | ... | @@ -28,7 +28,6 @@ import { |
| 28 | import { stableStringify } from "@t3tools/shared/relaySigning"; | 28 | import { stableStringify } from "@t3tools/shared/relaySigning"; |
| 29 | import * as Context from "effect/Context"; | 29 | import * as Context from "effect/Context"; |
| 30 | import * as Crypto from "effect/Crypto"; | 30 | import * as Crypto from "effect/Crypto"; |
| 31 | import * as Data from "effect/Data"; | ||
| 32 | import * as DateTime from "effect/DateTime"; | 31 | import * as DateTime from "effect/DateTime"; |
| 33 | import * as Duration from "effect/Duration"; | 32 | import * as Duration from "effect/Duration"; |
| 34 | import * as Effect from "effect/Effect"; | 33 | import * as Effect from "effect/Effect"; |
| ... | @@ -42,28 +41,53 @@ import * as EnvironmentLinks from "./EnvironmentLinks.ts"; | ... | @@ -42,28 +41,53 @@ import * as EnvironmentLinks from "./EnvironmentLinks.ts"; |
| 42 | import * as ManagedEndpointAllocations from "./ManagedEndpointAllocations.ts"; | 41 | import * as ManagedEndpointAllocations from "./ManagedEndpointAllocations.ts"; |
| 43 | import * as RelayConfiguration from "../Config.ts"; | 42 | import * as RelayConfiguration from "../Config.ts"; |
| 44 | 43 | ||
| 45 | export class EnvironmentConnectNotAuthorized extends Data.TaggedError( | 44 | export class EnvironmentConnectNotAuthorized extends Schema.TaggedErrorClass<EnvironmentConnectNotAuthorized>()( |
| 46 | "EnvironmentConnectNotAuthorized", | 45 | "EnvironmentConnectNotAuthorized", |
| 47 | )<{ | 46 | { |
| 48 | readonly environmentId: string; | 47 | environmentId: Schema.String, |
| 49 | }> {} | 48 | }, |
| 49 | ) { | ||
| 50 | override get message(): string { | ||
| 51 | return `Environment '${this.environmentId}' is not authorized to connect`; | ||
| 52 | } | ||
| 53 | } | ||
| 50 | 54 | ||
| 51 | export class EnvironmentMintRequestFailed extends Data.TaggedError("EnvironmentMintRequestFailed")<{ | 55 | export class EnvironmentMintRequestFailed extends Schema.TaggedErrorClass<EnvironmentMintRequestFailed>()( |
| 52 | readonly cause: unknown; | 56 | "EnvironmentMintRequestFailed", |
| 53 | }> {} | 57 | { |
| 58 | environmentId: Schema.String, | ||
| 59 | operation: Schema.Literals(["connect", "status"]), | ||
| 60 | cause: Schema.Defect(), | ||
| 61 | }, | ||
| 62 | ) { | ||
| 63 | override get message(): string { | ||
| 64 | return `Environment '${this.environmentId}' ${this.operation} request failed`; | ||
| 65 | } | ||
| 66 | } | ||
| 54 | 67 | ||
| 55 | export class EnvironmentMintRequestTimedOut extends Data.TaggedError( | 68 | export class EnvironmentMintRequestTimedOut extends Schema.TaggedErrorClass<EnvironmentMintRequestTimedOut>()( |
| 56 | "EnvironmentMintRequestTimedOut", | 69 | "EnvironmentMintRequestTimedOut", |
| 57 | )<{ | 70 | { |
| 58 | readonly environmentId: string; | 71 | environmentId: Schema.String, |
| 59 | readonly timeoutMs: number; | 72 | timeoutMs: Schema.Number, |
| 60 | }> {} | 73 | }, |
| 74 | ) { | ||
| 75 | override get message(): string { | ||
| 76 | return `Environment '${this.environmentId}' mint request timed out after ${this.timeoutMs}ms`; | ||
| 77 | } | ||
| 78 | } | ||
| 61 | 79 | ||
| 62 | export class EnvironmentMintResponseInvalid extends Data.TaggedError( | 80 | export class EnvironmentMintResponseInvalid extends Schema.TaggedErrorClass<EnvironmentMintResponseInvalid>()( |
| 63 | "EnvironmentMintResponseInvalid", | 81 | "EnvironmentMintResponseInvalid", |
| 64 | )<{ | 82 | { |
| 65 | readonly environmentId: string; | 83 | environmentId: Schema.String, |
| 66 | }> {} | 84 | operation: Schema.Literals(["connect", "status"]), |
| 85 | }, | ||
| 86 | ) { | ||
| 87 | override get message(): string { | ||
| 88 | return `Environment '${this.environmentId}' returned an invalid ${this.operation} response`; | ||
| 89 | } | ||
| 90 | } | ||
| 67 | 91 | ||
| 68 | export type EnvironmentConnectorError = | 92 | export type EnvironmentConnectorError = |
| 69 | | EnvironmentConnectNotAuthorized | 93 | | EnvironmentConnectNotAuthorized |
| ... | @@ -277,14 +301,28 @@ const make = Effect.gen(function* () { | ... | @@ -277,14 +301,28 @@ const make = Effect.gen(function* () { |
| 277 | const now = yield* DateTime.now; | 301 | const now = yield* DateTime.now; |
| 278 | const expiresAt = DateTime.add(now, { minutes: 2 }); | 302 | const expiresAt = DateTime.add(now, { minutes: 2 }); |
| 279 | const nonce = yield* crypto.randomUUIDv4.pipe( | 303 | const nonce = yield* crypto.randomUUIDv4.pipe( |
| 280 | Effect.mapError((cause) => new EnvironmentMintRequestFailed({ cause })), | 304 | Effect.mapError( |
| 305 | (cause) => | ||
| 306 | new EnvironmentMintRequestFailed({ | ||
| 307 | environmentId: input.environmentId, | ||
| 308 | operation: "status", | ||
| 309 | cause, | ||
| 310 | }), | ||
| 311 | ), | ||
| 281 | ); | 312 | ); |
| 282 | const payload = { | 313 | const payload = { |
| 283 | iss: relayIssuer, | 314 | iss: relayIssuer, |
| 284 | aud: `t3-env:${link.environmentId}`, | 315 | aud: `t3-env:${link.environmentId}`, |
| 285 | sub: input.userId, | 316 | sub: input.userId, |
| 286 | jti: yield* crypto.randomUUIDv4.pipe( | 317 | jti: yield* crypto.randomUUIDv4.pipe( |
| 287 | Effect.mapError((cause) => new EnvironmentMintRequestFailed({ cause })), | 318 | Effect.mapError( |
| 319 | (cause) => | ||
| 320 | new EnvironmentMintRequestFailed({ | ||
| 321 | environmentId: input.environmentId, | ||
| 322 | operation: "status", | ||
| 323 | cause, | ||
| 324 | }), | ||
| 325 | ), | ||
| 288 | ), | 326 | ), |
| 289 | iat: Math.floor(now.epochMilliseconds / 1_000), | 327 | iat: Math.floor(now.epochMilliseconds / 1_000), |
| 290 | exp: Math.floor(expiresAt.epochMilliseconds / 1_000), | 328 | exp: Math.floor(expiresAt.epochMilliseconds / 1_000), |
| ... | @@ -296,7 +334,16 @@ const make = Effect.gen(function* () { | ... | @@ -296,7 +334,16 @@ const make = Effect.gen(function* () { |
| 296 | privateKey: Redacted.value(settings.cloudMintPrivateKey), | 334 | privateKey: Redacted.value(settings.cloudMintPrivateKey), |
| 297 | typ: RELAY_HEALTH_REQUEST_TYP, | 335 | typ: RELAY_HEALTH_REQUEST_TYP, |
| 298 | payload, | 336 | payload, |
| 299 | }).pipe(Effect.mapError((cause) => new EnvironmentMintRequestFailed({ cause }))); | 337 | }).pipe( |
| 338 | Effect.mapError( | ||
| 339 | (cause) => | ||
| 340 | new EnvironmentMintRequestFailed({ | ||
| 341 | environmentId: input.environmentId, | ||
| 342 | operation: "status", | ||
| 343 | cause, | ||
| 344 | }), | ||
| 345 | ), | ||
| 346 | ); | ||
| 300 | const checkedAt = DateTime.formatIso(now); | 347 | const checkedAt = DateTime.formatIso(now); |
| 301 | const environmentClient = yield* makeEnvironmentClient(endpoint.httpBaseUrl); | 348 | const environmentClient = yield* makeEnvironmentClient(endpoint.httpBaseUrl); |
| 302 | const responseOption = yield* environmentClient.cloud.health({ payload: { proof } }).pipe( | 349 | const responseOption = yield* environmentClient.cloud.health({ payload: { proof } }).pipe( |
| ... | @@ -336,7 +383,10 @@ const make = Effect.gen(function* () { | ... | @@ -336,7 +383,10 @@ const make = Effect.gen(function* () { |
| 336 | now: yield* DateTime.now, | 383 | now: yield* DateTime.now, |
| 337 | }); | 384 | }); |
| 338 | if (!verified) { | 385 | if (!verified) { |
| 339 | return yield* new EnvironmentMintResponseInvalid({ environmentId: input.environmentId }); | 386 | return yield* new EnvironmentMintResponseInvalid({ |
| 387 | environmentId: input.environmentId, | ||
| 388 | operation: "status", | ||
| 389 | }); | ||
| 340 | } | 390 | } |
| 341 | return { | 391 | return { |
| 342 | environmentId: link.environmentId, | 392 | environmentId: link.environmentId, |
| ... | @@ -364,14 +414,28 @@ const make = Effect.gen(function* () { | ... | @@ -364,14 +414,28 @@ const make = Effect.gen(function* () { |
| 364 | const now = yield* DateTime.now; | 414 | const now = yield* DateTime.now; |
| 365 | const expiresAt = DateTime.add(now, { minutes: 2 }); | 415 | const expiresAt = DateTime.add(now, { minutes: 2 }); |
| 366 | const nonce = yield* crypto.randomUUIDv4.pipe( | 416 | const nonce = yield* crypto.randomUUIDv4.pipe( |
| 367 | Effect.mapError((cause) => new EnvironmentMintRequestFailed({ cause })), | 417 | Effect.mapError( |
| 418 | (cause) => | ||
| 419 | new EnvironmentMintRequestFailed({ | ||
| 420 | environmentId: input.environmentId, | ||
| 421 | operation: "connect", | ||
| 422 | cause, | ||
| 423 | }), | ||
| 424 | ), | ||
| 368 | ); | 425 | ); |
| 369 | const payload = { | 426 | const payload = { |
| 370 | iss: relayIssuer, | 427 | iss: relayIssuer, |
| 371 | aud: `t3-env:${link.environmentId}`, | 428 | aud: `t3-env:${link.environmentId}`, |
| 372 | sub: input.userId, | 429 | sub: input.userId, |
| 373 | jti: yield* crypto.randomUUIDv4.pipe( | 430 | jti: yield* crypto.randomUUIDv4.pipe( |
| 374 | Effect.mapError((cause) => new EnvironmentMintRequestFailed({ cause })), | 431 | Effect.mapError( |
| 432 | (cause) => | ||
| 433 | new EnvironmentMintRequestFailed({ | ||
| 434 | environmentId: input.environmentId, | ||
| 435 | operation: "connect", | ||
| 436 | cause, | ||
| 437 | }), | ||
| 438 | ), | ||
| 375 | ), | 439 | ), |
| 376 | iat: Math.floor(now.epochMilliseconds / 1_000), | 440 | iat: Math.floor(now.epochMilliseconds / 1_000), |
| 377 | exp: Math.floor(expiresAt.epochMilliseconds / 1_000), | 441 | exp: Math.floor(expiresAt.epochMilliseconds / 1_000), |
| ... | @@ -386,11 +450,27 @@ const make = Effect.gen(function* () { | ... | @@ -386,11 +450,27 @@ const make = Effect.gen(function* () { |
| 386 | privateKey: Redacted.value(settings.cloudMintPrivateKey), | 450 | privateKey: Redacted.value(settings.cloudMintPrivateKey), |
| 387 | typ: RELAY_MINT_REQUEST_TYP, | 451 | typ: RELAY_MINT_REQUEST_TYP, |
| 388 | payload, | 452 | payload, |
| 389 | }).pipe(Effect.mapError((cause) => new EnvironmentMintRequestFailed({ cause }))); | 453 | }).pipe( |
| 454 | Effect.mapError( | ||
| 455 | (cause) => | ||
| 456 | new EnvironmentMintRequestFailed({ | ||
| 457 | environmentId: input.environmentId, | ||
| 458 | operation: "connect", | ||
| 459 | cause, | ||
| 460 | }), | ||
| 461 | ), | ||
| 462 | ); | ||
| 390 | const environmentClient = yield* makeEnvironmentClient(endpoint.httpBaseUrl); | 463 | const environmentClient = yield* makeEnvironmentClient(endpoint.httpBaseUrl); |
| 391 | const decoded = yield* environmentClient.cloud.t3MintCredential({ payload: { proof } }).pipe( | 464 | const decoded = yield* environmentClient.cloud.t3MintCredential({ payload: { proof } }).pipe( |
| 392 | withoutRedirects, | 465 | withoutRedirects, |
| 393 | Effect.mapError((cause) => new EnvironmentMintRequestFailed({ cause })), | 466 | Effect.mapError( |
| 467 | (cause) => | ||
| 468 | new EnvironmentMintRequestFailed({ | ||
| 469 | environmentId: input.environmentId, | ||
| 470 | operation: "connect", | ||
| 471 | cause, | ||
| 472 | }), | ||
| 473 | ), | ||
| 394 | Effect.timeoutOption(Duration.millis(ENVIRONMENT_MINT_REQUEST_TIMEOUT_MS)), | 474 | Effect.timeoutOption(Duration.millis(ENVIRONMENT_MINT_REQUEST_TIMEOUT_MS)), |
| 395 | Effect.flatMap( | 475 | Effect.flatMap( |
| 396 | Option.match({ | 476 | Option.match({ |
| ... | @@ -415,7 +495,10 @@ const make = Effect.gen(function* () { | ... | @@ -415,7 +495,10 @@ const make = Effect.gen(function* () { |
| 415 | nowEpochSeconds: Math.floor(now.epochMilliseconds / 1_000), | 495 | nowEpochSeconds: Math.floor(now.epochMilliseconds / 1_000), |
| 416 | }); | 496 | }); |
| 417 | if (!verified) { | 497 | if (!verified) { |
| 418 | return yield* new EnvironmentMintResponseInvalid({ environmentId: input.environmentId }); | 498 | return yield* new EnvironmentMintResponseInvalid({ |
| 499 | environmentId: input.environmentId, | ||
| 500 | operation: "connect", | ||
| 501 | }); | ||
| 419 | } | 502 | } |
| 420 | return { | 503 | return { |
| 421 | environmentId: link.environmentId, | 504 | environmentId: link.environmentId, |
infra/relay/src/environments/EnvironmentCredentials.ts+22-13| ... | @@ -1,33 +1,42 @@ | ... | @@ -1,33 +1,42 @@ |
| 1 | import * as Context from "effect/Context"; | 1 | import * as Context from "effect/Context"; |
| 2 | import * as Crypto from "effect/Crypto"; | 2 | import * as Crypto from "effect/Crypto"; |
| 3 | import * as Data from "effect/Data"; | ||
| 4 | import * as DateTime from "effect/DateTime"; | 3 | import * as DateTime from "effect/DateTime"; |
| 5 | import * as Effect from "effect/Effect"; | 4 | import * as Effect from "effect/Effect"; |
| 6 | import * as Encoding from "effect/Encoding"; | 5 | import * as Encoding from "effect/Encoding"; |
| 7 | import * as Layer from "effect/Layer"; | 6 | import * as Layer from "effect/Layer"; |
| 8 | import * as Option from "effect/Option"; | 7 | import * as Option from "effect/Option"; |
| 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 { 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 Data.TaggedError( | 14 | export class EnvironmentCredentialCreatePersistenceError extends Schema.TaggedErrorClass<EnvironmentCredentialCreatePersistenceError>()( |
| 15 | "EnvironmentCredentialCreatePersistenceError", | 15 | "EnvironmentCredentialCreatePersistenceError", |
| 16 | )<{ | 16 | { cause: Schema.Defect() }, |
| 17 | readonly cause: unknown; | 17 | ) { |
| 18 | }> {} | 18 | override get message(): string { |
| 19 | return "Failed to persist environment credential"; | ||
| 20 | } | ||
| 21 | } | ||
| 19 | 22 | ||
| 20 | export class EnvironmentCredentialAuthenticatePersistenceError extends Data.TaggedError( | 23 | export class EnvironmentCredentialAuthenticatePersistenceError extends Schema.TaggedErrorClass<EnvironmentCredentialAuthenticatePersistenceError>()( |
| 21 | "EnvironmentCredentialAuthenticatePersistenceError", | 24 | "EnvironmentCredentialAuthenticatePersistenceError", |
| 22 | )<{ | 25 | { cause: Schema.Defect() }, |
| 23 | readonly cause: unknown; | 26 | ) { |
| 24 | }> {} | 27 | override get message(): string { |
| 28 | return "Failed to authenticate environment credential"; | ||
| 29 | } | ||
| 30 | } | ||
| 25 | 31 | ||
| 26 | export class EnvironmentCredentialRevokePersistenceError extends Data.TaggedError( | 32 | export class EnvironmentCredentialRevokePersistenceError extends Schema.TaggedErrorClass<EnvironmentCredentialRevokePersistenceError>()( |
| 27 | "EnvironmentCredentialRevokePersistenceError", | 33 | "EnvironmentCredentialRevokePersistenceError", |
| 28 | )<{ | 34 | { cause: Schema.Defect() }, |
| 29 | readonly cause: unknown; | 35 | ) { |
| 30 | }> {} | 36 | override get message(): string { |
| 37 | return "Failed to revoke environment credential"; | ||
| 38 | } | ||
| 39 | } | ||
| 31 | 40 | ||
| 32 | export interface EnvironmentCredentialPrincipal { | 41 | export interface EnvironmentCredentialPrincipal { |
| 33 | readonly credentialId: string; | 42 | readonly credentialId: string; |
infra/relay/src/environments/EnvironmentLinker.ts+22-9| ... | @@ -1,6 +1,6 @@ | ... | @@ -1,6 +1,6 @@ |
| 1 | import { | 1 | import { |
| 2 | RelayEnvironmentLinkProofPayload, | 2 | RelayEnvironmentLinkProofPayload, |
| 3 | type RelayEnvironmentLinkProofInvalidReason, | 3 | RelayEnvironmentLinkProofInvalidReason, |
| 4 | type RelayEnvironmentLinkRequest, | 4 | type RelayEnvironmentLinkRequest, |
| 5 | } from "@t3tools/contracts/relay"; | 5 | } from "@t3tools/contracts/relay"; |
| 6 | import { | 6 | import { |
| ... | @@ -10,7 +10,6 @@ import { | ... | @@ -10,7 +10,6 @@ import { |
| 10 | verifyRelayJwt, | 10 | verifyRelayJwt, |
| 11 | } from "@t3tools/shared/relayJwt"; | 11 | } from "@t3tools/shared/relayJwt"; |
| 12 | import * as Context from "effect/Context"; | 12 | import * as Context from "effect/Context"; |
| 13 | import * as Data from "effect/Data"; | ||
| 14 | import * as DateTime from "effect/DateTime"; | 13 | import * as DateTime from "effect/DateTime"; |
| 15 | import * as Effect from "effect/Effect"; | 14 | import * as Effect from "effect/Effect"; |
| 16 | import * as Layer from "effect/Layer"; | 15 | import * as Layer from "effect/Layer"; |
| ... | @@ -23,14 +22,28 @@ import * as EnvironmentLinks from "./EnvironmentLinks.ts"; | ... | @@ -23,14 +22,28 @@ import * as EnvironmentLinks from "./EnvironmentLinks.ts"; |
| 23 | import * as ManagedEndpointProvider from "./ManagedEndpointProvider.ts"; | 22 | import * as ManagedEndpointProvider from "./ManagedEndpointProvider.ts"; |
| 24 | import * as RelayConfiguration from "../Config.ts"; | 23 | import * as RelayConfiguration from "../Config.ts"; |
| 25 | 24 | ||
| 26 | export class EnvironmentLinkProofExpired extends Data.TaggedError("EnvironmentLinkProofExpired")<{ | 25 | export class EnvironmentLinkProofExpired extends Schema.TaggedErrorClass<EnvironmentLinkProofExpired>()( |
| 27 | readonly expiresAt: string; | 26 | "EnvironmentLinkProofExpired", |
| 28 | }> {} | 27 | { |
| 28 | expiresAt: Schema.String, | ||
| 29 | }, | ||
| 30 | ) { | ||
| 31 | override get message(): string { | ||
| 32 | return `Environment link proof expired at ${this.expiresAt}`; | ||
| 33 | } | ||
| 34 | } | ||
| 29 | 35 | ||
| 30 | export class EnvironmentLinkProofInvalid extends Data.TaggedError("EnvironmentLinkProofInvalid")<{ | 36 | export class EnvironmentLinkProofInvalid extends Schema.TaggedErrorClass<EnvironmentLinkProofInvalid>()( |
| 31 | readonly environmentId: string; | 37 | "EnvironmentLinkProofInvalid", |
| 32 | readonly reason: RelayEnvironmentLinkProofInvalidReason; | 38 | { |
| 33 | }> {} | 39 | environmentId: Schema.String, |
| 40 | reason: RelayEnvironmentLinkProofInvalidReason, | ||
| 41 | }, | ||
| 42 | ) { | ||
| 43 | override get message(): string { | ||
| 44 | return `Environment '${this.environmentId}' link proof is invalid: ${this.reason}`; | ||
| 45 | } | ||
| 46 | } | ||
| 34 | 47 | ||
| 35 | export type EnvironmentLinkError = | 48 | export type EnvironmentLinkError = |
| 36 | | EnvironmentLinkProofExpired | 49 | | EnvironmentLinkProofExpired |
infra/relay/src/environments/EnvironmentLinks.ts+43-25| ... | @@ -5,10 +5,10 @@ import type { | ... | @@ -5,10 +5,10 @@ import type { |
| 5 | RelayManagedEndpoint, | 5 | RelayManagedEndpoint, |
| 6 | } from "@t3tools/contracts/relay"; | 6 | } from "@t3tools/contracts/relay"; |
| 7 | import * as Context from "effect/Context"; | 7 | import * as Context from "effect/Context"; |
| 8 | import * as Data from "effect/Data"; | ||
| 9 | import * as DateTime from "effect/DateTime"; | 8 | import * as DateTime from "effect/DateTime"; |
| 10 | import * as Effect from "effect/Effect"; | 9 | import * as Effect from "effect/Effect"; |
| 11 | import * as Layer from "effect/Layer"; | 10 | import * as Layer from "effect/Layer"; |
| 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 { RelayDb } from "../db.ts"; |
| ... | @@ -24,41 +24,59 @@ export interface AgentAwarenessDeliveryUserRecord { | ... | @@ -24,41 +24,59 @@ export interface AgentAwarenessDeliveryUserRecord { |
| 24 | readonly liveActivitiesEnabled: boolean; | 24 | readonly liveActivitiesEnabled: boolean; |
| 25 | } | 25 | } |
| 26 | 26 | ||
| 27 | export class EnvironmentLinkUpsertPersistenceError extends Data.TaggedError( | 27 | export class EnvironmentLinkUpsertPersistenceError extends Schema.TaggedErrorClass<EnvironmentLinkUpsertPersistenceError>()( |
| 28 | "EnvironmentLinkUpsertPersistenceError", | 28 | "EnvironmentLinkUpsertPersistenceError", |
| 29 | )<{ | 29 | { cause: Schema.Defect() }, |
| 30 | readonly cause: unknown; | 30 | ) { |
| 31 | }> {} | 31 | override get message(): string { |
| 32 | return "Failed to persist environment link"; | ||
| 33 | } | ||
| 34 | } | ||
| 32 | 35 | ||
| 33 | export class EnvironmentLinkUserListPersistenceError extends Data.TaggedError( | 36 | export class EnvironmentLinkUserListPersistenceError extends Schema.TaggedErrorClass<EnvironmentLinkUserListPersistenceError>()( |
| 34 | "EnvironmentLinkUserListPersistenceError", | 37 | "EnvironmentLinkUserListPersistenceError", |
| 35 | )<{ | 38 | { cause: Schema.Defect() }, |
| 36 | readonly cause: unknown; | 39 | ) { |
| 37 | }> {} | 40 | override get message(): string { |
| 41 | return "Failed to list users linked to environment"; | ||
| 42 | } | ||
| 43 | } | ||
| 38 | 44 | ||
| 39 | export class EnvironmentPublicKeyListPersistenceError extends Data.TaggedError( | 45 | export class EnvironmentPublicKeyListPersistenceError extends Schema.TaggedErrorClass<EnvironmentPublicKeyListPersistenceError>()( |
| 40 | "EnvironmentPublicKeyListPersistenceError", | 46 | "EnvironmentPublicKeyListPersistenceError", |
| 41 | )<{ | 47 | { cause: Schema.Defect() }, |
| 42 | readonly cause: unknown; | 48 | ) { |
| 43 | }> {} | 49 | override get message(): string { |
| 50 | return "Failed to list environment public keys"; | ||
| 51 | } | ||
| 52 | } | ||
| 44 | 53 | ||
| 45 | export class EnvironmentLinkListPersistenceError extends Data.TaggedError( | 54 | export class EnvironmentLinkListPersistenceError extends Schema.TaggedErrorClass<EnvironmentLinkListPersistenceError>()( |
| 46 | "EnvironmentLinkListPersistenceError", | 55 | "EnvironmentLinkListPersistenceError", |
| 47 | )<{ | 56 | { cause: Schema.Defect() }, |
| 48 | readonly cause: unknown; | 57 | ) { |
| 49 | }> {} | 58 | override get message(): string { |
| 59 | return "Failed to list environment links"; | ||
| 60 | } | ||
| 61 | } | ||
| 50 | 62 | ||
| 51 | export class EnvironmentLinkLookupPersistenceError extends Data.TaggedError( | 63 | export class EnvironmentLinkLookupPersistenceError extends Schema.TaggedErrorClass<EnvironmentLinkLookupPersistenceError>()( |
| 52 | "EnvironmentLinkLookupPersistenceError", | 64 | "EnvironmentLinkLookupPersistenceError", |
| 53 | )<{ | 65 | { cause: Schema.Defect() }, |
| 54 | readonly cause: unknown; | 66 | ) { |
| 55 | }> {} | 67 | override get message(): string { |
| 68 | return "Failed to look up environment link"; | ||
| 69 | } | ||
| 70 | } | ||
| 56 | 71 | ||
| 57 | export class EnvironmentLinkRevokePersistenceError extends Data.TaggedError( | 72 | export class EnvironmentLinkRevokePersistenceError extends Schema.TaggedErrorClass<EnvironmentLinkRevokePersistenceError>()( |
| 58 | "EnvironmentLinkRevokePersistenceError", | 73 | "EnvironmentLinkRevokePersistenceError", |
| 59 | )<{ | 74 | { cause: Schema.Defect() }, |
| 60 | readonly cause: unknown; | 75 | ) { |
| 61 | }> {} | 76 | override get message(): string { |
| 77 | return "Failed to revoke environment link"; | ||
| 78 | } | ||
| 79 | } | ||
| 62 | 80 | ||
| 63 | export interface EnvironmentLinksShape { | 81 | export interface EnvironmentLinksShape { |
| 64 | readonly upsert: (input: { | 82 | readonly upsert: (input: { |
infra/relay/src/environments/EnvironmentPublishSignatures.ts+27-13| ... | @@ -11,7 +11,6 @@ import { | ... | @@ -11,7 +11,6 @@ import { |
| 11 | import { stableStringify } from "@t3tools/shared/relaySigning"; | 11 | import { stableStringify } from "@t3tools/shared/relaySigning"; |
| 12 | import * as Context from "effect/Context"; | 12 | import * as Context from "effect/Context"; |
| 13 | import * as Crypto from "effect/Crypto"; | 13 | import * as Crypto from "effect/Crypto"; |
| 14 | import * as Data from "effect/Data"; | ||
| 15 | import * as DateTime from "effect/DateTime"; | 14 | import * as DateTime from "effect/DateTime"; |
| 16 | import * as Effect from "effect/Effect"; | 15 | import * as Effect from "effect/Effect"; |
| 17 | import * as Encoding from "effect/Encoding"; | 16 | import * as Encoding from "effect/Encoding"; |
| ... | @@ -21,23 +20,38 @@ import * as Schema from "effect/Schema"; | ... | @@ -21,23 +20,38 @@ import * as Schema from "effect/Schema"; |
| 21 | import * as DpopProofs from "../auth/DpopProofs.ts"; | 20 | import * as DpopProofs from "../auth/DpopProofs.ts"; |
| 22 | import * as RelayConfiguration from "../Config.ts"; | 21 | import * as RelayConfiguration from "../Config.ts"; |
| 23 | 22 | ||
| 24 | export class EnvironmentPublishSignatureExpired extends Data.TaggedError( | 23 | export class EnvironmentPublishSignatureExpired extends Schema.TaggedErrorClass<EnvironmentPublishSignatureExpired>()( |
| 25 | "EnvironmentPublishSignatureExpired", | 24 | "EnvironmentPublishSignatureExpired", |
| 26 | )<{ | 25 | { |
| 27 | readonly expiresAt: string; | 26 | expiresAt: Schema.String, |
| 28 | }> {} | 27 | }, |
| 28 | ) { | ||
| 29 | override get message(): string { | ||
| 30 | return `Environment publish signature expired at ${this.expiresAt}`; | ||
| 31 | } | ||
| 32 | } | ||
| 29 | 33 | ||
| 30 | export class EnvironmentPublishSignatureInvalid extends Data.TaggedError( | 34 | export class EnvironmentPublishSignatureInvalid extends Schema.TaggedErrorClass<EnvironmentPublishSignatureInvalid>()( |
| 31 | "EnvironmentPublishSignatureInvalid", | 35 | "EnvironmentPublishSignatureInvalid", |
| 32 | )<{ | 36 | { |
| 33 | readonly environmentId: string; | 37 | environmentId: Schema.String, |
| 34 | }> {} | 38 | }, |
| 39 | ) { | ||
| 40 | override get message(): string { | ||
| 41 | return `Environment '${this.environmentId}' publish signature is invalid`; | ||
| 42 | } | ||
| 43 | } | ||
| 35 | 44 | ||
| 36 | export class EnvironmentPublishPublicKeyMissing extends Data.TaggedError( | 45 | export class EnvironmentPublishPublicKeyMissing extends Schema.TaggedErrorClass<EnvironmentPublishPublicKeyMissing>()( |
| 37 | "EnvironmentPublishPublicKeyMissing", | 46 | "EnvironmentPublishPublicKeyMissing", |
| 38 | )<{ | 47 | { |
| 39 | readonly environmentId: string; | 48 | environmentId: Schema.String, |
| 40 | }> {} | 49 | }, |
| 50 | ) { | ||
| 51 | override get message(): string { | ||
| 52 | return `Environment '${this.environmentId}' has no publish public key`; | ||
| 53 | } | ||
| 54 | } | ||
| 41 | 55 | ||
| 42 | export type EnvironmentPublishSignatureError = | 56 | export type EnvironmentPublishSignatureError = |
| 43 | | EnvironmentPublishSignatureExpired | 57 | | EnvironmentPublishSignatureExpired |
infra/relay/src/environments/ManagedEndpointAllocations.ts+12-6| ... | @@ -1,10 +1,10 @@ | ... | @@ -1,10 +1,10 @@ |
| 1 | import type { RelayManagedEndpoint } from "@t3tools/contracts/relay"; | 1 | import type { RelayManagedEndpoint } from "@t3tools/contracts/relay"; |
| 2 | import { and, eq } from "drizzle-orm"; | 2 | import { and, eq } from "drizzle-orm"; |
| 3 | import * as Context from "effect/Context"; | 3 | import * as Context from "effect/Context"; |
| 4 | import * as Data from "effect/Data"; | ||
| 5 | import * as DateTime from "effect/DateTime"; | 4 | import * as DateTime from "effect/DateTime"; |
| 6 | import * as Effect from "effect/Effect"; | 5 | import * as Effect from "effect/Effect"; |
| 7 | import * as Layer from "effect/Layer"; | 6 | import * as Layer from "effect/Layer"; |
| 7 | import * as Schema from "effect/Schema"; | ||
| 8 | 8 | ||
| 9 | import { RelayDb } from "../db.ts"; | 9 | import { RelayDb } from "../db.ts"; |
| 10 | import { isManagedEndpointHostname, managedEndpointForHostname } from "../deploymentConfig.ts"; | 10 | import { isManagedEndpointHostname, managedEndpointForHostname } from "../deploymentConfig.ts"; |
| ... | @@ -36,11 +36,17 @@ export function resolveReadyManagedEndpoint(input: { | ... | @@ -36,11 +36,17 @@ export function resolveReadyManagedEndpoint(input: { |
| 36 | return managedEndpointForHostname(input.allocation.hostname); | 36 | return managedEndpointForHostname(input.allocation.hostname); |
| 37 | } | 37 | } |
| 38 | 38 | ||
| 39 | export class ManagedEndpointAllocationPersistenceError extends Data.TaggedError( | 39 | export class ManagedEndpointAllocationPersistenceError extends Schema.TaggedErrorClass<ManagedEndpointAllocationPersistenceError>()( |
| 40 | "ManagedEndpointAllocationPersistenceError", | 40 | "ManagedEndpointAllocationPersistenceError", |
| 41 | )<{ | 41 | { cause: Schema.Defect() }, |
| 42 | readonly cause: unknown; | 42 | ) { |
| 43 | }> {} | 43 | override get message(): string { |
| 44 | return "Failed to persist managed endpoint allocation"; | ||
| 45 | } | ||
| 46 | } | ||
| 47 | const isManagedEndpointAllocationPersistenceError = Schema.is( | ||
| 48 | ManagedEndpointAllocationPersistenceError, | ||
| 49 | ); | ||
| 44 | 50 | ||
| 45 | interface ManagedEndpointAllocationKey { | 51 | interface ManagedEndpointAllocationKey { |
| 46 | readonly userId: string; | 52 | readonly userId: string; |
| ... | @@ -98,7 +104,7 @@ const whereAllocation = (input: ManagedEndpointAllocationKey) => | ... | @@ -98,7 +104,7 @@ const whereAllocation = (input: ManagedEndpointAllocationKey) => |
| 98 | ); | 104 | ); |
| 99 | 105 | ||
| 100 | const persistenceError = (cause: unknown) => | 106 | const persistenceError = (cause: unknown) => |
| 101 | cause instanceof ManagedEndpointAllocationPersistenceError | 107 | isManagedEndpointAllocationPersistenceError(cause) |
| 102 | ? cause | 108 | ? cause |
| 103 | : new ManagedEndpointAllocationPersistenceError({ cause }); | 109 | : new ManagedEndpointAllocationPersistenceError({ cause }); |
| 104 | 110 |
infra/relay/src/environments/ManagedEndpointProvider.ts+46-24| ... | @@ -3,12 +3,12 @@ import * as Cloudflare from "alchemy/Cloudflare"; | ... | @@ -3,12 +3,12 @@ import * as Cloudflare from "alchemy/Cloudflare"; |
| 3 | import * as Arr from "effect/Array"; | 3 | import * as Arr from "effect/Array"; |
| 4 | import * as Context from "effect/Context"; | 4 | import * as Context from "effect/Context"; |
| 5 | import * as Crypto from "effect/Crypto"; | 5 | import * as Crypto from "effect/Crypto"; |
| 6 | import * as Data from "effect/Data"; | ||
| 7 | import * as Effect from "effect/Effect"; | 6 | import * as Effect from "effect/Effect"; |
| 8 | import * as Encoding from "effect/Encoding"; | 7 | import * as Encoding from "effect/Encoding"; |
| 9 | import * as Layer from "effect/Layer"; | 8 | import * as Layer from "effect/Layer"; |
| 10 | import * as Option from "effect/Option"; | 9 | import * as Option from "effect/Option"; |
| 11 | import * as Result from "effect/Result"; | 10 | import * as Result from "effect/Result"; |
| 11 | import * as Schema from "effect/Schema"; | ||
| 12 | 12 | ||
| 13 | import type { | 13 | import type { |
| 14 | RelayManagedEndpoint, | 14 | RelayManagedEndpoint, |
| ... | @@ -25,28 +25,44 @@ import { | ... | @@ -25,28 +25,44 @@ import { |
| 25 | } from "../deploymentConfig.ts"; | 25 | } from "../deploymentConfig.ts"; |
| 26 | import { ManagedEndpointAllocations } from "./ManagedEndpointAllocations.ts"; | 26 | import { ManagedEndpointAllocations } from "./ManagedEndpointAllocations.ts"; |
| 27 | 27 | ||
| 28 | export class ManagedEndpointProvisioningNotConfigured extends Data.TaggedError( | 28 | export class ManagedEndpointProvisioningNotConfigured extends Schema.TaggedErrorClass<ManagedEndpointProvisioningNotConfigured>()( |
| 29 | "ManagedEndpointProvisioningNotConfigured", | 29 | "ManagedEndpointProvisioningNotConfigured", |
| 30 | )<{}> {} | 30 | {}, |
| 31 | ) { | ||
| 32 | override get message(): string { | ||
| 33 | return "Managed endpoint provisioning is not configured"; | ||
| 34 | } | ||
| 35 | } | ||
| 31 | 36 | ||
| 32 | export class ManagedEndpointProvisioningFailed extends Data.TaggedError( | 37 | export class ManagedEndpointProvisioningFailed extends Schema.TaggedErrorClass<ManagedEndpointProvisioningFailed>()( |
| 33 | "ManagedEndpointProvisioningFailed", | 38 | "ManagedEndpointProvisioningFailed", |
| 34 | )<{ | 39 | { cause: Schema.Defect() }, |
| 35 | readonly cause: unknown; | 40 | ) { |
| 36 | }> {} | 41 | override get message(): string { |
| 42 | return "Managed endpoint provisioning failed"; | ||
| 43 | } | ||
| 44 | } | ||
| 37 | 45 | ||
| 38 | export class ManagedEndpointDeprovisioningFailed extends Data.TaggedError( | 46 | export class ManagedEndpointDeprovisioningFailed extends Schema.TaggedErrorClass<ManagedEndpointDeprovisioningFailed>()( |
| 39 | "ManagedEndpointDeprovisioningFailed", | 47 | "ManagedEndpointDeprovisioningFailed", |
| 40 | )<{ | 48 | { cause: Schema.Defect() }, |
| 41 | readonly cause: unknown; | 49 | ) { |
| 42 | }> {} | 50 | override get message(): string { |
| 51 | return "Managed endpoint deprovisioning failed"; | ||
| 52 | } | ||
| 53 | } | ||
| 43 | 54 | ||
| 44 | export class ManagedEndpointOriginNotAllowed extends Data.TaggedError( | 55 | export class ManagedEndpointOriginNotAllowed extends Schema.TaggedErrorClass<ManagedEndpointOriginNotAllowed>()( |
| 45 | "ManagedEndpointOriginNotAllowed", | 56 | "ManagedEndpointOriginNotAllowed", |
| 46 | )<{ | 57 | { |
| 47 | readonly host: string; | 58 | host: Schema.String, |
| 48 | readonly port: number; | 59 | port: Schema.Number, |
| 49 | }> {} | 60 | }, |
| 61 | ) { | ||
| 62 | override get message(): string { | ||
| 63 | return `Managed endpoint origin '${this.host}:${this.port}' is not allowed`; | ||
| 64 | } | ||
| 65 | } | ||
| 50 | 66 | ||
| 51 | export type ManagedEndpointProviderError = | 67 | export type ManagedEndpointProviderError = |
| 52 | | ManagedEndpointProvisioningNotConfigured | 68 | | ManagedEndpointProvisioningNotConfigured |
| ... | @@ -80,11 +96,14 @@ interface ManagedEndpointTunnel { | ... | @@ -80,11 +96,14 @@ interface ManagedEndpointTunnel { |
| 80 | readonly name?: string | null; | 96 | readonly name?: string | null; |
| 81 | } | 97 | } |
| 82 | 98 | ||
| 83 | export class ManagedEndpointTunnelClientError extends Data.TaggedError( | 99 | export class ManagedEndpointTunnelClientError extends Schema.TaggedErrorClass<ManagedEndpointTunnelClientError>()( |
| 84 | "ManagedEndpointTunnelClientError", | 100 | "ManagedEndpointTunnelClientError", |
| 85 | )<{ | 101 | { cause: Schema.Defect() }, |
| 86 | readonly cause: unknown; | 102 | ) { |
| 87 | }> {} | 103 | override get message(): string { |
| 104 | return "Managed endpoint tunnel provider request failed"; | ||
| 105 | } | ||
| 106 | } | ||
| 88 | 107 | ||
| 89 | export interface ManagedEndpointTunnelClientShape { | 108 | export interface ManagedEndpointTunnelClientShape { |
| 90 | readonly list: (request: { | 109 | readonly list: (request: { |
| ... | @@ -124,11 +143,14 @@ interface ManagedEndpointCnameRecordInput { | ... | @@ -124,11 +143,14 @@ interface ManagedEndpointCnameRecordInput { |
| 124 | readonly proxied: true; | 143 | readonly proxied: true; |
| 125 | } | 144 | } |
| 126 | 145 | ||
| 127 | export class ManagedEndpointDnsClientError extends Data.TaggedError( | 146 | export class ManagedEndpointDnsClientError extends Schema.TaggedErrorClass<ManagedEndpointDnsClientError>()( |
| 128 | "ManagedEndpointDnsClientError", | 147 | "ManagedEndpointDnsClientError", |
| 129 | )<{ | 148 | { cause: Schema.Defect() }, |
| 130 | readonly cause: unknown; | 149 | ) { |
| 131 | }> {} | 150 | override get message(): string { |
| 151 | return "Managed endpoint DNS provider request failed"; | ||
| 152 | } | ||
| 153 | } | ||
| 132 | 154 | ||
| 133 | export interface ManagedEndpointDnsClientShape { | 155 | export interface ManagedEndpointDnsClientShape { |
| 134 | readonly listRecords: ( | 156 | readonly listRecords: ( |
infra/relay/src/http/Api.ts+11-8| ... | @@ -1,6 +1,5 @@ | ... | @@ -1,6 +1,5 @@ |
| 1 | import { createClerkClient, verifyToken } from "@clerk/backend"; | 1 | import { createClerkClient, verifyToken } from "@clerk/backend"; |
| 2 | import { sql as drizzleSql } from "drizzle-orm"; | 2 | import { sql as drizzleSql } from "drizzle-orm"; |
| 3 | import * as Data from "effect/Data"; | ||
| 4 | import * as Crypto from "effect/Crypto"; | 3 | import * as Crypto from "effect/Crypto"; |
| 5 | import * as Context from "effect/Context"; | 4 | import * as Context from "effect/Context"; |
| 6 | import * as DateTime from "effect/DateTime"; | 5 | import * as DateTime from "effect/DateTime"; |
| ... | @@ -813,9 +812,16 @@ export const serverApi = HttpApiBuilder.group( | ... | @@ -813,9 +812,16 @@ export const serverApi = HttpApiBuilder.group( |
| 813 | }), | 812 | }), |
| 814 | ); | 813 | ); |
| 815 | 814 | ||
| 816 | class ClerkTokenVerificationFailed extends Data.TaggedError("ClerkTokenVerificationFailed")<{ | 815 | class ClerkTokenVerificationFailed extends Schema.TaggedErrorClass<ClerkTokenVerificationFailed>()( |
| 817 | readonly cause: unknown; | 816 | "ClerkTokenVerificationFailed", |
| 818 | }> {} | 817 | { |
| 818 | cause: Schema.Defect(), | ||
| 819 | }, | ||
| 820 | ) { | ||
| 821 | override get message(): string { | ||
| 822 | return "Clerk token verification failed"; | ||
| 823 | } | ||
| 824 | } | ||
| 819 | 825 | ||
| 820 | const isHttpUnauthorized = Schema.is(HttpApiError.Unauthorized); | 826 | const isHttpUnauthorized = Schema.is(HttpApiError.Unauthorized); |
| 821 | 827 | ||
| ... | @@ -846,16 +852,13 @@ const COMMON_AUTH_INVALID_REASONS = [ | ... | @@ -846,16 +852,13 @@ const COMMON_AUTH_INVALID_REASONS = [ |
| 846 | DeliveryAttempts.DeliveryAttemptRecordPersistenceError, | 852 | DeliveryAttempts.DeliveryAttemptRecordPersistenceError, |
| 847 | ] as const; | 853 | ] as const; |
| 848 | type RelayCommonPersistenceError = InstanceType<(typeof COMMON_AUTH_INVALID_REASONS)[number]>; | 854 | type RelayCommonPersistenceError = InstanceType<(typeof COMMON_AUTH_INVALID_REASONS)[number]>; |
| 855 | const isRelayCommonPersistenceError = Schema.is(Schema.Union(COMMON_AUTH_INVALID_REASONS)); | ||
| 849 | 856 | ||
| 850 | type MapRelayCommonApiError<E> = | 857 | type MapRelayCommonApiError<E> = |
| 851 | | Exclude<E, HttpApiError.Unauthorized | RelayCommonPersistenceError> | 858 | | Exclude<E, HttpApiError.Unauthorized | RelayCommonPersistenceError> |
| 852 | | (Extract<E, HttpApiError.Unauthorized> extends never ? never : RelayAuthInvalidError) | 859 | | (Extract<E, HttpApiError.Unauthorized> extends never ? never : RelayAuthInvalidError) |
| 853 | | (Extract<E, RelayCommonPersistenceError> extends never ? never : RelayInternalError); | 860 | | (Extract<E, RelayCommonPersistenceError> extends never ? never : RelayInternalError); |
| 854 | 861 | ||
| 855 | function isRelayCommonPersistenceError(error: unknown): error is RelayCommonPersistenceError { | ||
| 856 | return COMMON_AUTH_INVALID_REASONS.some((ErrorType) => error instanceof ErrorType); | ||
| 857 | } | ||
| 858 | |||
| 859 | function relayInternalErrorResponse(reason: RelayInternalError["reason"]) { | 862 | function relayInternalErrorResponse(reason: RelayInternalError["reason"]) { |
| 860 | return currentTraceId.pipe( | 863 | return currentTraceId.pipe( |
| 861 | Effect.flatMap((traceId) => | 864 | Effect.flatMap((traceId) => |
infra/relay/src/observability.test.ts created+86| ... | @@ -0,0 +1,86 @@ | ||
| 1 | import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer"; | ||
| 2 | import { expect, it } from "@effect/vitest"; | ||
| 3 | import * as Deferred from "effect/Deferred"; | ||
| 4 | import * as Effect from "effect/Effect"; | ||
| 5 | import * as Redacted from "effect/Redacted"; | ||
| 6 | import * as Schema from "effect/Schema"; | ||
| 7 | import * as HttpServer from "effect/unstable/http/HttpServer"; | ||
| 8 | import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest"; | ||
| 9 | import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse"; | ||
| 10 | import type { OtlpTracer } from "effect/unstable/observability"; | ||
| 11 | |||
| 12 | import { EnvironmentMintRequestFailed } from "./environments/EnvironmentConnector.ts"; | ||
| 13 | import { makeRelayTraceLayer } from "./observability.ts"; | ||
| 14 | |||
| 15 | interface ExportedRequest { | ||
| 16 | readonly authorization: string | undefined; | ||
| 17 | readonly body: string; | ||
| 18 | readonly dataset: string | undefined; | ||
| 19 | } | ||
| 20 | |||
| 21 | const otlpAttributeValue = (value: { | ||
| 22 | readonly stringValue?: string | null; | ||
| 23 | readonly boolValue?: boolean | null; | ||
| 24 | readonly intValue?: number | null; | ||
| 25 | readonly doubleValue?: number | null; | ||
| 26 | }) => value.stringValue ?? value.boolValue ?? value.intValue ?? value.doubleValue; | ||
| 27 | |||
| 28 | const decodeJson = Schema.decodeUnknownEffect(Schema.UnknownFromJsonString); | ||
| 29 | |||
| 30 | it.effect("exports schema error fields as span attributes", () => | ||
| 31 | Effect.gen(function* () { | ||
| 32 | const exportedRequest = yield* Deferred.make<ExportedRequest>(); | ||
| 33 | yield* HttpServer.serveEffect( | ||
| 34 | Effect.gen(function* () { | ||
| 35 | const request = yield* HttpServerRequest.HttpServerRequest; | ||
| 36 | yield* Deferred.succeed(exportedRequest, { | ||
| 37 | authorization: request.headers.authorization, | ||
| 38 | body: yield* request.text, | ||
| 39 | dataset: request.headers["x-axiom-dataset"], | ||
| 40 | }); | ||
| 41 | return HttpServerResponse.empty({ status: 204 }); | ||
| 42 | }), | ||
| 43 | ); | ||
| 44 | |||
| 45 | yield* Effect.fail( | ||
| 46 | new EnvironmentMintRequestFailed({ | ||
| 47 | environmentId: "environment-1", | ||
| 48 | operation: "connect", | ||
| 49 | cause: new Error("upstream unavailable"), | ||
| 50 | }), | ||
| 51 | ).pipe( | ||
| 52 | Effect.withSpan("relay.test.schema_error"), | ||
| 53 | Effect.exit, | ||
| 54 | Effect.provide( | ||
| 55 | makeRelayTraceLayer({ | ||
| 56 | tracesEndpoint: "/v1/traces", | ||
| 57 | tracesDatasetName: "relay-test-traces", | ||
| 58 | ingestToken: Redacted.make("test-token"), | ||
| 59 | }), | ||
| 60 | ), | ||
| 61 | ); | ||
| 62 | |||
| 63 | const request = yield* Deferred.await(exportedRequest).pipe(Effect.timeout("1 second")); | ||
| 64 | const payload = (yield* decodeJson(request.body)) as OtlpTracer.TraceData; | ||
| 65 | const span = payload.resourceSpans | ||
| 66 | .flatMap((resourceSpan) => resourceSpan.scopeSpans) | ||
| 67 | .flatMap((scopeSpan) => scopeSpan.spans) | ||
| 68 | .find((candidate) => candidate.name === "relay.test.schema_error"); | ||
| 69 | const attributes = Object.fromEntries( | ||
| 70 | (span?.attributes ?? []).map((attribute) => [ | ||
| 71 | attribute.key, | ||
| 72 | otlpAttributeValue(attribute.value), | ||
| 73 | ]), | ||
| 74 | ); | ||
| 75 | |||
| 76 | expect(request.authorization).toBe("Bearer test-token"); | ||
| 77 | expect(request.dataset).toBe("relay-test-traces"); | ||
| 78 | expect(attributes).toMatchObject({ | ||
| 79 | "error.type": "EnvironmentMintRequestFailed", | ||
| 80 | "error.environmentId": "environment-1", | ||
| 81 | "error.operation": "connect", | ||
| 82 | "error.cause.name": "Error", | ||
| 83 | "error.cause.message": "upstream unavailable", | ||
| 84 | }); | ||
| 85 | }).pipe(Effect.provide(NodeHttpServer.layerTest), Effect.scoped), | ||
| 86 | ); | ||
infra/relay/src/observability.ts+160-15| ... | @@ -1,9 +1,14 @@ | ... | @@ -1,9 +1,14 @@ |
| 1 | import * as Alchemy from "alchemy"; | 1 | import * as Alchemy from "alchemy"; |
| 2 | import * as Axiom from "alchemy/Axiom"; | 2 | import * as Axiom from "alchemy/Axiom"; |
| 3 | import * as Output from "alchemy/Output"; | 3 | import * as Output from "alchemy/Output"; |
| 4 | import * as Layer from "effect/Layer"; | 4 | import * as Cause from "effect/Cause"; |
| 5 | import * as Effect from "effect/Effect"; | 5 | import * as Effect from "effect/Effect"; |
| 6 | import * as Exit from "effect/Exit"; | ||
| 7 | import * as Layer from "effect/Layer"; | ||
| 8 | import * as Option from "effect/Option"; | ||
| 6 | import * as Redacted from "effect/Redacted"; | 9 | import * as Redacted from "effect/Redacted"; |
| 10 | import * as Schema from "effect/Schema"; | ||
| 11 | import * as Tracer from "effect/Tracer"; | ||
| 7 | import { OtlpSerialization, OtlpTracer } from "effect/unstable/observability"; | 12 | import { OtlpSerialization, OtlpTracer } from "effect/unstable/observability"; |
| 8 | 13 | ||
| 9 | import { relayResourceNameForStage } from "./deploymentConfig.ts"; | 14 | import { relayResourceNameForStage } from "./deploymentConfig.ts"; |
| ... | @@ -54,23 +59,163 @@ export const withSpanAttributes = | ... | @@ -54,23 +59,163 @@ export const withSpanAttributes = |
| 54 | Effect.andThen(effect.pipe(Effect.annotateSpans(attributes))), | 59 | Effect.andThen(effect.pipe(Effect.annotateSpans(attributes))), |
| 55 | ); | 60 | ); |
| 56 | 61 | ||
| 62 | const appendEncodedAttributes = ( | ||
| 63 | attributes: Record<string, unknown>, | ||
| 64 | prefix: string, | ||
| 65 | value: unknown, | ||
| 66 | ): void => { | ||
| 67 | if ( | ||
| 68 | value === null || | ||
| 69 | typeof value === "string" || | ||
| 70 | typeof value === "number" || | ||
| 71 | typeof value === "boolean" || | ||
| 72 | typeof value === "bigint" || | ||
| 73 | Array.isArray(value) | ||
| 74 | ) { | ||
| 75 | attributes[prefix] = value; | ||
| 76 | return; | ||
| 77 | } | ||
| 78 | if (typeof value !== "object") { | ||
| 79 | return; | ||
| 80 | } | ||
| 81 | for (const [key, child] of Object.entries(value)) { | ||
| 82 | appendEncodedAttributes(attributes, `${prefix}.${key}`, child); | ||
| 83 | } | ||
| 84 | }; | ||
| 85 | |||
| 86 | const schemaErrorAttributes = (error: unknown): Record<string, unknown> | undefined => { | ||
| 87 | if (typeof error !== "object" || error === null) { | ||
| 88 | return undefined; | ||
| 89 | } | ||
| 90 | const constructor = error.constructor; | ||
| 91 | if (!Schema.isSchema(constructor)) { | ||
| 92 | return undefined; | ||
| 93 | } | ||
| 94 | const encoded = Schema.encodeUnknownOption(constructor as unknown as Schema.Encoder<unknown>)( | ||
| 95 | error, | ||
| 96 | ); | ||
| 97 | if (Option.isNone(encoded) || typeof encoded.value !== "object" || encoded.value === null) { | ||
| 98 | return undefined; | ||
| 99 | } | ||
| 100 | const tag = Reflect.get(encoded.value, "_tag"); | ||
| 101 | if (typeof tag !== "string") { | ||
| 102 | return undefined; | ||
| 103 | } | ||
| 104 | |||
| 105 | const attributes: Record<string, unknown> = { | ||
| 106 | "error.type": tag, | ||
| 107 | }; | ||
| 108 | for (const [key, value] of Object.entries(encoded.value)) { | ||
| 109 | if (key !== "_tag") { | ||
| 110 | appendEncodedAttributes(attributes, `error.${key}`, value); | ||
| 111 | } | ||
| 112 | } | ||
| 113 | return attributes; | ||
| 114 | }; | ||
| 115 | |||
| 116 | const annotateSchemaError = (span: Tracer.Span, exit: Exit.Exit<unknown, unknown>): void => { | ||
| 117 | if (Exit.isSuccess(exit)) { | ||
| 118 | return; | ||
| 119 | } | ||
| 120 | for (const reason of exit.cause.reasons) { | ||
| 121 | const error = Cause.isFailReason(reason) | ||
| 122 | ? reason.error | ||
| 123 | : Cause.isDieReason(reason) | ||
| 124 | ? reason.defect | ||
| 125 | : undefined; | ||
| 126 | const attributes = schemaErrorAttributes(error); | ||
| 127 | if (attributes) { | ||
| 128 | for (const [key, value] of Object.entries(attributes)) { | ||
| 129 | span.attribute(key, value); | ||
| 130 | } | ||
| 131 | return; | ||
| 132 | } | ||
| 133 | } | ||
| 134 | }; | ||
| 135 | |||
| 136 | class RelayTraceSpan implements Tracer.Span { | ||
| 137 | readonly _tag = "Span"; | ||
| 138 | private readonly delegate: Tracer.Span; | ||
| 139 | |||
| 140 | constructor(delegate: Tracer.Span) { | ||
| 141 | this.delegate = delegate; | ||
| 142 | } | ||
| 143 | |||
| 144 | get name() { | ||
| 145 | return this.delegate.name; | ||
| 146 | } | ||
| 147 | get spanId() { | ||
| 148 | return this.delegate.spanId; | ||
| 149 | } | ||
| 150 | get traceId() { | ||
| 151 | return this.delegate.traceId; | ||
| 152 | } | ||
| 153 | get parent() { | ||
| 154 | return this.delegate.parent; | ||
| 155 | } | ||
| 156 | get annotations() { | ||
| 157 | return this.delegate.annotations; | ||
| 158 | } | ||
| 159 | get status() { | ||
| 160 | return this.delegate.status; | ||
| 161 | } | ||
| 162 | get attributes() { | ||
| 163 | return this.delegate.attributes; | ||
| 164 | } | ||
| 165 | get links() { | ||
| 166 | return this.delegate.links; | ||
| 167 | } | ||
| 168 | get sampled() { | ||
| 169 | return this.delegate.sampled; | ||
| 170 | } | ||
| 171 | get kind() { | ||
| 172 | return this.delegate.kind; | ||
| 173 | } | ||
| 174 | |||
| 175 | end(endTime: bigint, exit: Exit.Exit<unknown, unknown>): void { | ||
| 176 | annotateSchemaError(this.delegate, exit); | ||
| 177 | this.delegate.end(endTime, exit); | ||
| 178 | } | ||
| 179 | |||
| 180 | attribute(key: string, value: unknown): void { | ||
| 181 | this.delegate.attribute(key, value); | ||
| 182 | } | ||
| 183 | |||
| 184 | event(name: string, startTime: bigint, attributes?: Record<string, unknown>): void { | ||
| 185 | this.delegate.event(name, startTime, attributes); | ||
| 186 | } | ||
| 187 | |||
| 188 | addLinks(links: ReadonlyArray<Tracer.SpanLink>): void { | ||
| 189 | this.delegate.addLinks(links); | ||
| 190 | } | ||
| 191 | } | ||
| 192 | |||
| 193 | const withSchemaErrorAttributes = (delegate: Tracer.Tracer): Tracer.Tracer => | ||
| 194 | Tracer.make({ | ||
| 195 | span: (options) => new RelayTraceSpan(delegate.span(options)), | ||
| 196 | ...(delegate.context ? { context: delegate.context } : {}), | ||
| 197 | }); | ||
| 198 | |||
| 57 | export const makeRelayTraceLayer = (input: { | 199 | export const makeRelayTraceLayer = (input: { |
| 58 | readonly tracesEndpoint: string; | 200 | readonly tracesEndpoint: string; |
| 59 | readonly tracesDatasetName: string; | 201 | readonly tracesDatasetName: string; |
| 60 | readonly ingestToken: Redacted.Redacted<string>; | 202 | readonly ingestToken: Redacted.Redacted<string>; |
| 61 | }) => | 203 | }) => |
| 62 | OtlpTracer.layer({ | 204 | Layer.effect( |
| 63 | url: input.tracesEndpoint, | 205 | Tracer.Tracer, |
| 64 | resource: { | 206 | OtlpTracer.make({ |
| 65 | serviceName: "t3-code-relay-worker", | 207 | url: input.tracesEndpoint, |
| 66 | attributes: { | 208 | resource: { |
| 67 | "service.runtime": "cloudflare-worker", | 209 | serviceName: "t3-code-relay-worker", |
| 68 | "service.component": "relay", | 210 | attributes: { |
| 211 | "service.runtime": "cloudflare-worker", | ||
| 212 | "service.component": "relay", | ||
| 213 | }, | ||
| 214 | }, | ||
| 215 | headers: { | ||
| 216 | Authorization: `Bearer ${Redacted.value(input.ingestToken)}`, | ||
| 217 | "X-Axiom-Dataset": input.tracesDatasetName, | ||
| 69 | }, | 218 | }, |
| 70 | }, | 219 | exportInterval: "1 second", |
| 71 | headers: { | 220 | }).pipe(Effect.map(withSchemaErrorAttributes)), |
| 72 | Authorization: `Bearer ${Redacted.value(input.ingestToken)}`, | 221 | ).pipe(Layer.provide(OtlpSerialization.layerJson)); |
| 73 | "X-Axiom-Dataset": input.tracesDatasetName, | ||
| 74 | }, | ||
| 75 | exportInterval: "1 second", | ||
| 76 | }).pipe(Layer.provide(OtlpSerialization.layerJson)); |
packages/contracts/src/relay.ts+55-11| ... | @@ -329,7 +329,11 @@ export class RelayAuthInvalidError extends Schema.TaggedErrorClass<RelayAuthInva | ... | @@ -329,7 +329,11 @@ export class RelayAuthInvalidError extends Schema.TaggedErrorClass<RelayAuthInva |
| 329 | traceId: TrimmedNonEmptyString, | 329 | traceId: TrimmedNonEmptyString, |
| 330 | }, | 330 | }, |
| 331 | { httpApiStatus: 401 }, | 331 | { httpApiStatus: 401 }, |
| 332 | ) {} | 332 | ) { |
| 333 | override get message(): string { | ||
| 334 | return `Relay authentication failed: ${this.reason}`; | ||
| 335 | } | ||
| 336 | } | ||
| 333 | 337 | ||
| 334 | export class RelayEnvironmentLinkProofExpiredError extends Schema.TaggedErrorClass<RelayEnvironmentLinkProofExpiredError>()( | 338 | export class RelayEnvironmentLinkProofExpiredError extends Schema.TaggedErrorClass<RelayEnvironmentLinkProofExpiredError>()( |
| 335 | "RelayEnvironmentLinkProofExpiredError", | 339 | "RelayEnvironmentLinkProofExpiredError", |
| ... | @@ -338,7 +342,11 @@ export class RelayEnvironmentLinkProofExpiredError extends Schema.TaggedErrorCla | ... | @@ -338,7 +342,11 @@ export class RelayEnvironmentLinkProofExpiredError extends Schema.TaggedErrorCla |
| 338 | traceId: TrimmedNonEmptyString, | 342 | traceId: TrimmedNonEmptyString, |
| 339 | }, | 343 | }, |
| 340 | { httpApiStatus: 401 }, | 344 | { httpApiStatus: 401 }, |
| 341 | ) {} | 345 | ) { |
| 346 | override get message(): string { | ||
| 347 | return "Relay environment link proof expired"; | ||
| 348 | } | ||
| 349 | } | ||
| 342 | 350 | ||
| 343 | export class RelayEnvironmentLinkProofInvalidError extends Schema.TaggedErrorClass<RelayEnvironmentLinkProofInvalidError>()( | 351 | export class RelayEnvironmentLinkProofInvalidError extends Schema.TaggedErrorClass<RelayEnvironmentLinkProofInvalidError>()( |
| 344 | "RelayEnvironmentLinkProofInvalidError", | 352 | "RelayEnvironmentLinkProofInvalidError", |
| ... | @@ -348,7 +356,11 @@ export class RelayEnvironmentLinkProofInvalidError extends Schema.TaggedErrorCla | ... | @@ -348,7 +356,11 @@ export class RelayEnvironmentLinkProofInvalidError extends Schema.TaggedErrorCla |
| 348 | traceId: TrimmedNonEmptyString, | 356 | traceId: TrimmedNonEmptyString, |
| 349 | }, | 357 | }, |
| 350 | { httpApiStatus: 400 }, | 358 | { httpApiStatus: 400 }, |
| 351 | ) {} | 359 | ) { |
| 360 | override get message(): string { | ||
| 361 | return `Relay environment link proof is invalid: ${this.reason}`; | ||
| 362 | } | ||
| 363 | } | ||
| 352 | 364 | ||
| 353 | export class RelayEnvironmentConnectNotAuthorizedError extends Schema.TaggedErrorClass<RelayEnvironmentConnectNotAuthorizedError>()( | 365 | export class RelayEnvironmentConnectNotAuthorizedError extends Schema.TaggedErrorClass<RelayEnvironmentConnectNotAuthorizedError>()( |
| 354 | "RelayEnvironmentConnectNotAuthorizedError", | 366 | "RelayEnvironmentConnectNotAuthorizedError", |
| ... | @@ -357,7 +369,11 @@ export class RelayEnvironmentConnectNotAuthorizedError extends Schema.TaggedErro | ... | @@ -357,7 +369,11 @@ export class RelayEnvironmentConnectNotAuthorizedError extends Schema.TaggedErro |
| 357 | traceId: TrimmedNonEmptyString, | 369 | traceId: TrimmedNonEmptyString, |
| 358 | }, | 370 | }, |
| 359 | { httpApiStatus: 403 }, | 371 | { httpApiStatus: 403 }, |
| 360 | ) {} | 372 | ) { |
| 373 | override get message(): string { | ||
| 374 | return "Relay environment connection is not authorized"; | ||
| 375 | } | ||
| 376 | } | ||
| 361 | 377 | ||
| 362 | export class RelayEnvironmentEndpointUnavailableError extends Schema.TaggedErrorClass<RelayEnvironmentEndpointUnavailableError>()( | 378 | export class RelayEnvironmentEndpointUnavailableError extends Schema.TaggedErrorClass<RelayEnvironmentEndpointUnavailableError>()( |
| 363 | "RelayEnvironmentEndpointUnavailableError", | 379 | "RelayEnvironmentEndpointUnavailableError", |
| ... | @@ -367,7 +383,11 @@ export class RelayEnvironmentEndpointUnavailableError extends Schema.TaggedError | ... | @@ -367,7 +383,11 @@ export class RelayEnvironmentEndpointUnavailableError extends Schema.TaggedError |
| 367 | traceId: TrimmedNonEmptyString, | 383 | traceId: TrimmedNonEmptyString, |
| 368 | }, | 384 | }, |
| 369 | { httpApiStatus: 502 }, | 385 | { httpApiStatus: 502 }, |
| 370 | ) {} | 386 | ) { |
| 387 | override get message(): string { | ||
| 388 | return `Relay environment endpoint is unavailable: ${this.reason}`; | ||
| 389 | } | ||
| 390 | } | ||
| 371 | 391 | ||
| 372 | export class RelayEnvironmentEndpointTimedOutError extends Schema.TaggedErrorClass<RelayEnvironmentEndpointTimedOutError>()( | 392 | export class RelayEnvironmentEndpointTimedOutError extends Schema.TaggedErrorClass<RelayEnvironmentEndpointTimedOutError>()( |
| 373 | "RelayEnvironmentEndpointTimedOutError", | 393 | "RelayEnvironmentEndpointTimedOutError", |
| ... | @@ -376,7 +396,11 @@ export class RelayEnvironmentEndpointTimedOutError extends Schema.TaggedErrorCla | ... | @@ -376,7 +396,11 @@ export class RelayEnvironmentEndpointTimedOutError extends Schema.TaggedErrorCla |
| 376 | traceId: TrimmedNonEmptyString, | 396 | traceId: TrimmedNonEmptyString, |
| 377 | }, | 397 | }, |
| 378 | { httpApiStatus: 504 }, | 398 | { httpApiStatus: 504 }, |
| 379 | ) {} | 399 | ) { |
| 400 | override get message(): string { | ||
| 401 | return "Relay environment endpoint request timed out"; | ||
| 402 | } | ||
| 403 | } | ||
| 380 | 404 | ||
| 381 | export class RelayEnvironmentLinkFailedError extends Schema.TaggedErrorClass<RelayEnvironmentLinkFailedError>()( | 405 | export class RelayEnvironmentLinkFailedError extends Schema.TaggedErrorClass<RelayEnvironmentLinkFailedError>()( |
| 382 | "RelayEnvironmentLinkFailedError", | 406 | "RelayEnvironmentLinkFailedError", |
| ... | @@ -386,7 +410,11 @@ export class RelayEnvironmentLinkFailedError extends Schema.TaggedErrorClass<Rel | ... | @@ -386,7 +410,11 @@ export class RelayEnvironmentLinkFailedError extends Schema.TaggedErrorClass<Rel |
| 386 | traceId: TrimmedNonEmptyString, | 410 | traceId: TrimmedNonEmptyString, |
| 387 | }, | 411 | }, |
| 388 | { httpApiStatus: 500 }, | 412 | { httpApiStatus: 500 }, |
| 389 | ) {} | 413 | ) { |
| 414 | override get message(): string { | ||
| 415 | return `Relay environment link failed: ${this.reason}`; | ||
| 416 | } | ||
| 417 | } | ||
| 390 | 418 | ||
| 391 | export class RelayEnvironmentLinkUnavailableError extends Schema.TaggedErrorClass<RelayEnvironmentLinkUnavailableError>()( | 419 | export class RelayEnvironmentLinkUnavailableError extends Schema.TaggedErrorClass<RelayEnvironmentLinkUnavailableError>()( |
| 392 | "RelayEnvironmentLinkUnavailableError", | 420 | "RelayEnvironmentLinkUnavailableError", |
| ... | @@ -396,7 +424,11 @@ export class RelayEnvironmentLinkUnavailableError extends Schema.TaggedErrorClas | ... | @@ -396,7 +424,11 @@ export class RelayEnvironmentLinkUnavailableError extends Schema.TaggedErrorClas |
| 396 | traceId: TrimmedNonEmptyString, | 424 | traceId: TrimmedNonEmptyString, |
| 397 | }, | 425 | }, |
| 398 | { httpApiStatus: 503 }, | 426 | { httpApiStatus: 503 }, |
| 399 | ) {} | 427 | ) { |
| 428 | override get message(): string { | ||
| 429 | return `Relay environment link is unavailable: ${this.reason}`; | ||
| 430 | } | ||
| 431 | } | ||
| 400 | 432 | ||
| 401 | export class RelayAgentActivityPublishProofExpiredError extends Schema.TaggedErrorClass<RelayAgentActivityPublishProofExpiredError>()( | 433 | export class RelayAgentActivityPublishProofExpiredError extends Schema.TaggedErrorClass<RelayAgentActivityPublishProofExpiredError>()( |
| 402 | "RelayAgentActivityPublishProofExpiredError", | 434 | "RelayAgentActivityPublishProofExpiredError", |
| ... | @@ -405,7 +437,11 @@ export class RelayAgentActivityPublishProofExpiredError extends Schema.TaggedErr | ... | @@ -405,7 +437,11 @@ export class RelayAgentActivityPublishProofExpiredError extends Schema.TaggedErr |
| 405 | traceId: TrimmedNonEmptyString, | 437 | traceId: TrimmedNonEmptyString, |
| 406 | }, | 438 | }, |
| 407 | { httpApiStatus: 401 }, | 439 | { httpApiStatus: 401 }, |
| 408 | ) {} | 440 | ) { |
| 441 | override get message(): string { | ||
| 442 | return "Relay agent activity publish proof expired"; | ||
| 443 | } | ||
| 444 | } | ||
| 409 | 445 | ||
| 410 | export class RelayAgentActivityPublishProofInvalidError extends Schema.TaggedErrorClass<RelayAgentActivityPublishProofInvalidError>()( | 446 | export class RelayAgentActivityPublishProofInvalidError extends Schema.TaggedErrorClass<RelayAgentActivityPublishProofInvalidError>()( |
| 411 | "RelayAgentActivityPublishProofInvalidError", | 447 | "RelayAgentActivityPublishProofInvalidError", |
| ... | @@ -415,7 +451,11 @@ export class RelayAgentActivityPublishProofInvalidError extends Schema.TaggedErr | ... | @@ -415,7 +451,11 @@ export class RelayAgentActivityPublishProofInvalidError extends Schema.TaggedErr |
| 415 | traceId: TrimmedNonEmptyString, | 451 | traceId: TrimmedNonEmptyString, |
| 416 | }, | 452 | }, |
| 417 | { httpApiStatus: 401 }, | 453 | { httpApiStatus: 401 }, |
| 418 | ) {} | 454 | ) { |
| 455 | override get message(): string { | ||
| 456 | return `Relay agent activity publish proof is invalid: ${this.reason}`; | ||
| 457 | } | ||
| 458 | } | ||
| 419 | 459 | ||
| 420 | export class RelayInternalError extends Schema.TaggedErrorClass<RelayInternalError>()( | 460 | export class RelayInternalError extends Schema.TaggedErrorClass<RelayInternalError>()( |
| 421 | "RelayInternalError", | 461 | "RelayInternalError", |
| ... | @@ -425,7 +465,11 @@ export class RelayInternalError extends Schema.TaggedErrorClass<RelayInternalErr | ... | @@ -425,7 +465,11 @@ export class RelayInternalError extends Schema.TaggedErrorClass<RelayInternalErr |
| 425 | traceId: TrimmedNonEmptyString, | 465 | traceId: TrimmedNonEmptyString, |
| 426 | }, | 466 | }, |
| 427 | { httpApiStatus: 500 }, | 467 | { httpApiStatus: 500 }, |
| 428 | ) {} | 468 | ) { |
| 469 | override get message(): string { | ||
| 470 | return `Relay internal error: ${this.reason}`; | ||
| 471 | } | ||
| 472 | } | ||
| 429 | 473 | ||
| 430 | export const RelayProtectedError = Schema.Union([ | 474 | export const RelayProtectedError = Schema.Union([ |
| 431 | RelayAuthInvalidError, | 475 | RelayAuthInvalidError, |