authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-06-20 11:03:09-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-06-20 11:03:09-07:00
logf3b43a148b9e39192be5d43415289ac06eca1443
treefc64290a0de808a540049bc73aa28323870ecbb5
parent8331511b972242301bdaec328148a88b9b6cfa6c
signature Signed by PGP key B5690EEEBB952194

[codex] Structure missing client cloud config errors (#3346)

Co-authored-by: codex <codex@users.noreply.github.com>

4 files changed, 51 insertions(+), 4 deletions(-)

apps/mobile/src/features/cloud/publicConfig.test.ts+12-1
...@@ -1,6 +1,11 @@...@@ -1,6 +1,11 @@
1import { describe, expect, it, vi } from "vite-plus/test";1import { describe, expect, it, vi } from "vite-plus/test";
22
3import { hasTracingPublicConfig, resolveCloudPublicConfig } from "./publicConfig";3import {
4 CloudPublicConfigMissingError,
5 hasTracingPublicConfig,
6 resolveCloudPublicConfig,
7 resolveRelayClerkTokenOptions,
8} from "./publicConfig";
49
5vi.mock("expo-constants", () => ({10vi.mock("expo-constants", () => ({
6 default: {11 default: {
...@@ -11,6 +16,12 @@ vi.mock("expo-constants", () => ({...@@ -11,6 +16,12 @@ vi.mock("expo-constants", () => ({
11}));16}));
1217
13describe("resolveCloudPublicConfig", () => {18describe("resolveCloudPublicConfig", () => {
19 it("reports the missing Clerk JWT template as structured configuration", () => {
20 expect(() => resolveRelayClerkTokenOptions()).toThrowError(
21 new CloudPublicConfigMissingError({ key: "T3CODE_CLERK_JWT_TEMPLATE" }),
22 );
23 });
24
14 it("returns no cloud configuration for an unconfigured build", () => {25 it("returns no cloud configuration for an unconfigured build", () => {
15 expect(resolveCloudPublicConfig({})).toEqual({26 expect(resolveCloudPublicConfig({})).toEqual({
16 clerk: {27 clerk: {
apps/mobile/src/features/cloud/publicConfig.ts+13-1
...@@ -1,6 +1,18 @@...@@ -1,6 +1,18 @@
1import Constants from "expo-constants";1import Constants from "expo-constants";
2import { relayClerkTokenOptions } from "@t3tools/shared/relayAuth";2import { relayClerkTokenOptions } from "@t3tools/shared/relayAuth";
3import { normalizeSecureRelayUrl } from "@t3tools/shared/relayUrl";3import { normalizeSecureRelayUrl } from "@t3tools/shared/relayUrl";
4import * as Schema from "effect/Schema";
5
6export class CloudPublicConfigMissingError extends Schema.TaggedErrorClass<CloudPublicConfigMissingError>()(
7 "CloudPublicConfigMissingError",
8 {
9 key: Schema.Literal("T3CODE_CLERK_JWT_TEMPLATE"),
10 },
11) {
12 override get message(): string {
13 return `${this.key} is not configured.`;
14 }
15}
416
5export interface CloudPublicConfig {17export interface CloudPublicConfig {
6 readonly clerk: {18 readonly clerk: {
...@@ -87,7 +99,7 @@ export function hasTracingPublicConfig(...@@ -87,7 +99,7 @@ export function hasTracingPublicConfig(
87export function resolveRelayClerkTokenOptions() {99export function resolveRelayClerkTokenOptions() {
88 const { jwtTemplate } = resolveCloudPublicConfig().clerk;100 const { jwtTemplate } = resolveCloudPublicConfig().clerk;
89 if (!jwtTemplate) {101 if (!jwtTemplate) {
90 throw new Error("T3CODE_CLERK_JWT_TEMPLATE is not configured.");102 throw new CloudPublicConfigMissingError({ key: "T3CODE_CLERK_JWT_TEMPLATE" });
91 }103 }
92 return relayClerkTokenOptions(jwtTemplate);104 return relayClerkTokenOptions(jwtTemplate);
93}105}
apps/web/src/cloud/publicConfig.test.ts+13-1
...@@ -1,6 +1,10 @@...@@ -1,6 +1,10 @@
1import { afterEach, describe, expect, it, vi } from "vite-plus/test";1import { afterEach, describe, expect, it, vi } from "vite-plus/test";
22
3import { hasCloudPublicConfig } from "./publicConfig.ts";3import {
4 CloudPublicConfigMissingError,
5 hasCloudPublicConfig,
6 resolveRelayClerkTokenOptions,
7} from "./publicConfig.ts";
48
5afterEach(() => {9afterEach(() => {
6 vi.unstubAllEnvs();10 vi.unstubAllEnvs();
...@@ -30,4 +34,12 @@ describe("hasCloudPublicConfig", () => {...@@ -30,4 +34,12 @@ describe("hasCloudPublicConfig", () => {
3034
31 expect(hasCloudPublicConfig()).toBe(false);35 expect(hasCloudPublicConfig()).toBe(false);
32 });36 });
37
38 it("reports the missing Clerk JWT template as structured configuration", () => {
39 vi.stubEnv("VITE_CLERK_JWT_TEMPLATE", "");
40
41 expect(() => resolveRelayClerkTokenOptions()).toThrowError(
42 new CloudPublicConfigMissingError({ key: "T3CODE_CLERK_JWT_TEMPLATE" }),
43 );
44 });
33});45});
apps/web/src/cloud/publicConfig.ts+13-1
...@@ -1,5 +1,17 @@...@@ -1,5 +1,17 @@
1import { relayClerkTokenOptions } from "@t3tools/shared/relayAuth";1import { relayClerkTokenOptions } from "@t3tools/shared/relayAuth";
2import { normalizeSecureRelayUrl } from "@t3tools/shared/relayUrl";2import { normalizeSecureRelayUrl } from "@t3tools/shared/relayUrl";
3import * as Schema from "effect/Schema";
4
5export class CloudPublicConfigMissingError extends Schema.TaggedErrorClass<CloudPublicConfigMissingError>()(
6 "CloudPublicConfigMissingError",
7 {
8 key: Schema.Literal("T3CODE_CLERK_JWT_TEMPLATE"),
9 },
10) {
11 override get message(): string {
12 return `${this.key} is not configured.`;
13 }
14}
315
4export interface CloudPublicConfig {16export interface CloudPublicConfig {
5 readonly clerkPublishableKey: string | null;17 readonly clerkPublishableKey: string | null;
...@@ -65,7 +77,7 @@ export function hasCloudPublicConfig(): boolean {...@@ -65,7 +77,7 @@ export function hasCloudPublicConfig(): boolean {
65export function resolveRelayClerkTokenOptions() {77export function resolveRelayClerkTokenOptions() {
66 const { clerkJwtTemplate } = resolveCloudPublicConfig();78 const { clerkJwtTemplate } = resolveCloudPublicConfig();
67 if (!clerkJwtTemplate) {79 if (!clerkJwtTemplate) {
68 throw new Error("T3CODE_CLERK_JWT_TEMPLATE is not configured.");80 throw new CloudPublicConfigMissingError({ key: "T3CODE_CLERK_JWT_TEMPLATE" });
69 }81 }
70 return relayClerkTokenOptions(clerkJwtTemplate);82 return relayClerkTokenOptions(clerkJwtTemplate);
71}83}