From d41b3c44738aff2a7424c98a31918b11a9426877 Mon Sep 17 00:00:00 2001 From: chenhang Date: Wed, 17 Jun 2026 12:05:09 +0800 Subject: [PATCH] refactor(schemas): extract nullable-default helpers to shared file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The three defensive Zod helpers (stringOrEmpty, numberOrZero, booleanOrFalse) used by user.ts are general-purpose Zod utilities that any schema may need — they're not user-specific. The original inline declaration in user.ts had two downsides: 1. Any new schema (chat, auth, metrics, etc.) that needs the same 'null | undefined → default scalar' pattern would have to duplicate the helper. 2. The long defensive-parse comment block (11 lines) lived inside user.ts and didn't explain the helpers' general purpose. Move them to src/data/schemas/nullable-defaults.ts (top-level sibling of auth/, chat/, metrics/, user/ subdirs) so any schema can import them. Keep the original explanation verbatim at the top of the new file. Bundled in this commit: - user.ts imports the helpers from the new file (drops 11 lines of comment + 3 const declarations) - 3 auto-generated barrel index files regenerated by barrelsby to pick up the new exports (chat/, subscription/, stores/user/) --- src/app/chat/components/index.ts | 1 - src/app/subscription/components/index.ts | 2 ++ src/data/schemas/nullable-defaults.ts | 33 ++++++++++++++++++++++++ src/data/schemas/user/user.ts | 18 ++++--------- src/stores/user/index.ts | 2 ++ 5 files changed, 42 insertions(+), 14 deletions(-) create mode 100644 src/data/schemas/nullable-defaults.ts diff --git a/src/app/chat/components/index.ts b/src/app/chat/components/index.ts index 5a830fd3..e11b2589 100644 --- a/src/app/chat/components/index.ts +++ b/src/app/chat/components/index.ts @@ -10,7 +10,6 @@ export * from "./chat-input-bar"; export * from "./chat-input-text-field"; export * from "./chat-quota-exhausted-banner"; export * from "./chat-screen"; - export * from "./chat-send-button"; export * from "./date-header"; export * from "./fullscreen-image-viewer"; diff --git a/src/app/subscription/components/index.ts b/src/app/subscription/components/index.ts index ac8be8c5..cc0d758f 100644 --- a/src/app/subscription/components/index.ts +++ b/src/app/subscription/components/index.ts @@ -5,7 +5,9 @@ export * from "./subscription-back-link"; export * from "./subscription-banner"; export * from "./subscription-benefits-card"; +export * from "./subscription-checkout-button"; export * from "./subscription-cta-button"; +export * from "./subscription-manage-button"; export * from "./subscription-plan-card"; export * from "./subscription-screen"; export * from "./subscription-user-row"; diff --git a/src/data/schemas/nullable-defaults.ts b/src/data/schemas/nullable-defaults.ts new file mode 100644 index 00000000..4d9df94b --- /dev/null +++ b/src/data/schemas/nullable-defaults.ts @@ -0,0 +1,33 @@ +/** + * Zod 默认值辅助器(处理 `null | undefined` → 标量默认) + * + * 防御性解析:后端对 Facebook / Apple 等 OAuth 用户可能返回 `null`(如 `email: null`), + * Zod 默认的 `.default("")` 只接受 `undefined`,遇到 `null` 会抛 ZodError → 登录静默失败。 + * 用 `z.string().nullable().transform(v => v ?? "").default("")` 模式: + * - input 接受 `string | null | undefined`(保持 optional) + * - output 永远是 `string`(User 类的 `email: string` 声明不会破) + * + * 这是前端防御性解析(不要求后端修改)—— 和后端对齐更稳。 + */ +import { z } from "zod"; + +/** `string | null | undefined` → `string`(默认空串) */ +export const stringOrEmpty = z + .string() + .nullable() + .transform((v) => v ?? "") + .default(""); + +/** `number | null | undefined` → `number`(默认 0) */ +export const numberOrZero = z + .number() + .nullable() + .transform((v) => v ?? 0) + .default(0); + +/** `boolean | null | undefined` → `boolean`(默认 false) */ +export const booleanOrFalse = z + .boolean() + .nullable() + .transform((v) => v ?? false) + .default(false); \ No newline at end of file diff --git a/src/data/schemas/user/user.ts b/src/data/schemas/user/user.ts index b2c9f9d8..d1bae781 100644 --- a/src/data/schemas/user/user.ts +++ b/src/data/schemas/user/user.ts @@ -4,19 +4,11 @@ */ import { z } from "zod"; import { PersonalityTraitsSchema, PERSONALITY_TRAITS_DEFAULTS } from "./personality_traits"; - -/** - * 防御性解析:后端对 Facebook / Apple 等 OAuth 用户可能返回 `null`(如 `email: null`), - * Zod 默认的 `.default("")` 只接受 `undefined`,遇到 `null` 会抛 ZodError → 登录静默失败。 - * 用 `z.string().nullable().transform(v => v ?? "").default("")` 模式: - * - input 接受 `string | null | undefined`(保持 optional) - * - output 永远是 `string`(User 类的 `email: string` 声明不会破) - * - * 这是前端防御性解析(不要求后端修改)—— 和后端对齐更稳。 - */ -const stringOrEmpty = z.string().nullable().transform((v) => v ?? "").default(""); -const numberOrZero = z.number().nullable().transform((v) => v ?? 0).default(0); -const booleanOrFalse = z.boolean().nullable().transform((v) => v ?? false).default(false); +import { + booleanOrFalse, + numberOrZero, + stringOrEmpty, +} from "../nullable-defaults"; export const UserSchema = z.object({ id: stringOrEmpty, // 兜底:理论 id 不会 null,但保持防御 diff --git a/src/stores/user/index.ts b/src/stores/user/index.ts index be8f44b5..b6c636b5 100644 --- a/src/stores/user/index.ts +++ b/src/stores/user/index.ts @@ -4,5 +4,7 @@ export * from "./user-context"; export * from "./user-events"; +export * from "./user-machine.actors"; +export * from "./user-machine.helpers"; export * from "./user-machine"; export * from "./user-state";