Skip to content

Add Zod schemas to common; infer types from schemas; compose schemas in app#80

Merged
rk234 merged 4 commits into
mainfrom
zod-schema-refactor
May 10, 2026
Merged

Add Zod schemas to common; infer types from schemas; compose schemas in app#80
rk234 merged 4 commits into
mainfrom
zod-schema-refactor

Conversation

@rk234
Copy link
Copy Markdown
Member

@rk234 rk234 commented May 8, 2026

Closes #79

Summary by CodeRabbit

  • Refactor

    • Standardized and consolidated validation across profiles, families, children, gifts, claims, and related flows for more consistent runtime checks and safer data handling.
    • Server flows now use more robust presence checks, reducing runtime errors and improving update validations.
  • Chores

    • Added runtime validation dependency to shared utilities.
    • Tightened TypeScript lint rules to prevent unsafe assertions.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 8, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d132c2a2-86a9-4ee8-b4ff-d0889b0a5bc7

📥 Commits

Reviewing files that changed from the base of the PR and between abf8540 and 9a798d6.

📒 Files selected for processing (1)
  • app/src/server/functions/child.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/server/functions/child.ts

📝 Walkthrough

Walkthrough

This PR converts many common TypeScript types to Zod schemas (adding zod dependency), updates app/server validation to derive from those schemas, tightens lint rules, and makes Firestore reads, Map accumulation, and UI non-null usage null-safe.

Changes

Zod Schema Migration

Layer / File(s) Summary
Package Dependency
common/package.json
Adds zod (^4.3.6) as a runtime dependency.
Common Schemas & Enums
common/src/types/*
Introduces Zod schemas and z.infer types for Child, Gift, GiftDrive, Family, UserProfile, StaffInvite, FamilyLink, Claims, and related enums.
Profile Update Schemas
common/src/types/profile-update.ts
Composes ProfileUpdate schemas from Gift/Child schemas via pick/omit/partial.
Lint Configs
app/.oxlintrc.json, common/.oxlintrc.json, functions/.oxlintrc.json
Adds stricter @typescript-eslint rules banning non-null/unsafe assertions.
Form & familyForm
app/src/lib/formSchemas.ts, app/src/server/functions/familyForm.ts
CHILD_STATUS_VALUES now derives from ChildStatusSchema.options; address/status fields use common schemas; gift mapping uses type-narrowing filters.
Server Imports
app/src/server/functions/*
Server functions import schemas from common to enable derived validations.
Server Validation Refactors
app/src/server/functions/child.ts, family.ts, profile.ts
updateChild/updateGift/updateFamily/updateUserProfile validations are derived from common schemas with omit/pick/partial and keep "at least one field" refinements.
Firestore Read Handling
app/src/server/functions/*, app/src/server/services/*
Replace doc.exists + doc.data()! patterns with const data = doc.data(); if (!data) ... and validate updatedDoc.data() before returning.
Map Accumulation
app/src/server/functions/*, app/src/server/functions/storefront.ts
Replace Map.has(...)/get()! patterns with get(...) ?? [] + push + set.
UI non-null assertion removals
app/src/components/*
Remove ! assertions in components and reauth callsites; use optional chaining/runtime guards.
Queries & Routes
app/src/queries/publishedGifts.ts, app/src/routes/_authenticated/staff/*
tableRowsByDrive requires driveId: string; staff route passes activeDriveId ?? ""; SidebarTrigger icons swapped.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • ArnavGupta23

Poem

🐰 I hopped through types and stitched them neat,

Zod now guards each field and keeps types sweet.
doc.data() wakes the safety bell,
Maps collect with calm, no ! to yell.
Hooray — one schema, tidy and complete!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main change: adding Zod schemas to common, inferring types from them, and composing schemas in the app.
Linked Issues check ✅ Passed The PR meets all objectives from issue #79: Zod schemas are added to common package types, types are inferred from schemas, and schemas are composed/extended throughout the application.
Out of Scope Changes check ✅ Passed All changes directly support the stated objectives: schema definitions in common, type inference, schema composition in app, and complementary linting rule updates to prevent non-null assertions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch zod-schema-refactor

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@rk234 rk234 changed the title Add Zod schemas to common; infer types from schemas; compose schmas in app Add Zod schemas to common; infer types from schemas; compose schemas in app May 8, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
common/src/types/invite.ts (1)

8-8: ⚡ Quick win

Validate invite email format in the shared schema.

z.string() accepts malformed emails; using an email schema catches bad data earlier in the common contract.

Proposed patch
-  email: z.string(),
+  email: z.email(),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@common/src/types/invite.ts` at line 8, The invite schema currently defines
the email property as a plain string (email: z.string()), which allows invalid
emails; update the invite Zod schema's email field (the 'email' property in the
invite schema object, e.g., InviteSchema or similar) to use Zod's email
validator (z.string().email(...)) so malformed addresses are rejected and
include an optional helpful message for failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/server/functions/family.ts`:
- Around line 42-45: The current refine predicate only ensures the top-level
update object isn't empty but allows updates like { address: {} } to pass;
update the refine on the schema that extends AddressSchema.partial() so it also
checks that if data.address is present it has at least one key (e.g., change the
predicate to require Object.keys(data).length > 0 && (!data.address ||
Object.keys(data.address).length > 0)), keeping the existing error message or
adding a specific one for empty address; reference AddressSchema and the refine
call on the update schema to locate where to apply this change.

In `@app/src/server/functions/profile.ts`:
- Around line 76-80: The current updates schema uses UserProfileSchema.pick({
name: true, phone: true }).partial().refine(...) which silently strips unknown
keys; change it to enforce rejecting unknown fields by adding .strict() on the
picked/partial schema (e.g., call .strict() on the result of
UserProfileSchema.pick(...).partial()) so unknown update fields trigger
validation errors while preserving the existing .partial() and .refine()
behavior.

In `@common/src/types/profile-update.ts`:
- Around line 16-18: ChildProfileUpdateDataSchema currently uses
ChildSchema.omit({ id: true }).partial(), which still allows system and
relationship fields; replace it with a schema that explicitly picks only
editable profile fields from ChildSchema (e.g., name, nickname, birthDate,
gender, avatarUrl, medicalNotes — whatever set of editable attributes your
domain allows) and then apply .partial() for patch updates, ensuring you
explicitly exclude system/relationship fields such as id, createdAt, updatedAt,
parentId, familyId and any relation objects; update the declaration of
ChildProfileUpdateDataSchema to use ChildSchema.pick({...}).partial() (or an
equivalent approach) so only safe editable fields are accepted.

---

Nitpick comments:
In `@common/src/types/invite.ts`:
- Line 8: The invite schema currently defines the email property as a plain
string (email: z.string()), which allows invalid emails; update the invite Zod
schema's email field (the 'email' property in the invite schema object, e.g.,
InviteSchema or similar) to use Zod's email validator (z.string().email(...)) so
malformed addresses are rejected and include an optional helpful message for
failures.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 96b22cbf-d7e3-48f3-995f-50fd32a38623

📥 Commits

Reviewing files that changed from the base of the PR and between e82aeb9 and da452f3.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (15)
  • app/src/lib/formSchemas.ts
  • app/src/server/functions/child.ts
  • app/src/server/functions/family.ts
  • app/src/server/functions/familyForm.ts
  • app/src/server/functions/profile.ts
  • common/package.json
  • common/src/types/child.ts
  • common/src/types/claim.ts
  • common/src/types/family-link.ts
  • common/src/types/family.ts
  • common/src/types/gift-drive.ts
  • common/src/types/gift.ts
  • common/src/types/invite.ts
  • common/src/types/profile-update.ts
  • common/src/types/user.ts

Comment thread app/src/server/functions/family.ts Outdated
Comment thread app/src/server/functions/profile.ts Outdated
Comment on lines +16 to +18
export const ChildProfileUpdateDataSchema = ChildSchema.omit({
id: true,
}).partial();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Narrow ChildProfileUpdateDataSchema to editable fields only.

Current shape allows system and relationship fields in profile update payloads, which can lead to unsafe state changes if applied without extra filtering.

Suggested fix
 export const ChildProfileUpdateDataSchema = ChildSchema.omit({
   id: true,
+  familyId: true,
+  giftDrive: true,
+  createdAt: true,
+  published: true,
+  category: true,
+  status: true,
+  livesAtHome: true,
 }).partial();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@common/src/types/profile-update.ts` around lines 16 - 18,
ChildProfileUpdateDataSchema currently uses ChildSchema.omit({ id: true
}).partial(), which still allows system and relationship fields; replace it with
a schema that explicitly picks only editable profile fields from ChildSchema
(e.g., name, nickname, birthDate, gender, avatarUrl, medicalNotes — whatever set
of editable attributes your domain allows) and then apply .partial() for patch
updates, ensuring you explicitly exclude system/relationship fields such as id,
createdAt, updatedAt, parentId, familyId and any relation objects; update the
declaration of ChildProfileUpdateDataSchema to use
ChildSchema.pick({...}).partial() (or an equivalent approach) so only safe
editable fields are accepted.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
app/src/routes/_authenticated/staff/gifts.tsx (1)

14-16: ⚡ Quick win

Consider conditional query construction instead of placeholder value.

Passing activeDriveId ?? "" creates a query object with an invalid empty-string driveId, which is only prevented from running by the enabled guard. While functionally safe due to the guard, this pattern is fragile—if the enabled logic is later modified or removed, the query would execute with an invalid parameter.

♻️ Recommended: construct the query object conditionally
  const { activeDriveId } = useDrive();
- const tableRowsByDriveQuery = publishedGiftsQueries.tableRowsByDrive(
-   activeDriveId ?? "",
- );
+ const tableRowsByDriveQuery = activeDriveId
+   ? publishedGiftsQueries.tableRowsByDrive(activeDriveId)
+   : { queryKey: ["publishedGifts", "tableRowsByDrive", null], queryFn: () => Promise.resolve([]) };
  const { data: tableRows = [], isLoading } = useQuery({
    ...tableRowsByDriveQuery,
    enabled: !!activeDriveId,
  });

This makes it explicit that no valid query exists when activeDriveId is falsy, and eliminates the placeholder anti-pattern.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/routes/_authenticated/staff/gifts.tsx` around lines 14 - 16, The
query object is built with a placeholder driveId (activeDriveId ?? ""), which
risks creating an invalid query if the enabled guard changes; instead only
construct the query object when activeDriveId is truthy: call
publishedGiftsQueries.tableRowsByDrive(activeDriveId) conditionally (e.g. set
tableRowsByDrive to undefined/null when no activeDriveId) and keep the existing
enabled guard that checks activeDriveId to control execution. Update references
that expect the query object to handle the possibly undefined value.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@app/src/routes/_authenticated/staff/gifts.tsx`:
- Around line 14-16: The query object is built with a placeholder driveId
(activeDriveId ?? ""), which risks creating an invalid query if the enabled
guard changes; instead only construct the query object when activeDriveId is
truthy: call publishedGiftsQueries.tableRowsByDrive(activeDriveId) conditionally
(e.g. set tableRowsByDrive to undefined/null when no activeDriveId) and keep the
existing enabled guard that checks activeDriveId to control execution. Update
references that expect the query object to handle the possibly undefined value.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 01703d77-8ddb-4b85-9511-b4778b72d409

📥 Commits

Reviewing files that changed from the base of the PR and between da452f3 and abf8540.

📒 Files selected for processing (22)
  • app/.oxlintrc.json
  • app/src/components/auth/ReauthAlertDialog.tsx
  • app/src/components/donor/home/ChildBlock.tsx
  • app/src/components/donor/home/ChildDetailSection.tsx
  • app/src/queries/publishedGifts.ts
  • app/src/routes/_authenticated/staff/gifts.tsx
  • app/src/routes/_authenticated/staff/route.tsx
  • app/src/server/functions/cart.ts
  • app/src/server/functions/child.ts
  • app/src/server/functions/family.ts
  • app/src/server/functions/familyForm.ts
  • app/src/server/functions/profile.ts
  • app/src/server/functions/storefront.ts
  • app/src/server/services/giftDriveService.server.ts
  • common/.oxlintrc.json
  • common/src/types/child.ts
  • common/src/types/family.ts
  • common/src/types/gift-drive.ts
  • common/src/types/gift.ts
  • common/src/types/invite.ts
  • common/src/types/user.ts
  • functions/.oxlintrc.json
✅ Files skipped from review due to trivial changes (2)
  • functions/.oxlintrc.json
  • common/.oxlintrc.json
🚧 Files skipped from review as they are similar to previous changes (8)
  • common/src/types/invite.ts
  • common/src/types/gift-drive.ts
  • common/src/types/user.ts
  • common/src/types/gift.ts
  • common/src/types/family.ts
  • common/src/types/child.ts
  • app/src/server/functions/profile.ts
  • app/src/server/functions/family.ts

@rk234 rk234 merged commit aa20561 into main May 10, 2026
4 checks passed
@rk234 rk234 deleted the zod-schema-refactor branch May 10, 2026 04:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate types in common package to Zod schemas

1 participant