npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ufh-schema

v0.1.2

Published

Shared Zod domain schemas for United for Humanity (frontend + backend).

Readme

ufh-schema

Shared Zod domain schemas for United for Humanity (UFH) platform. Use this package in both frontend and backend to ensure type-safe API contracts and form validation.

npm version TypeScript Zod


Table of Contents


Installation

# npm
npm install ufh-schema zod

# pnpm
pnpm add ufh-schema zod

# yarn
yarn add ufh-schema zod

Note: zod is a peer dependency. This package supports Zod v3.25+ and v4.0+.


Quick Start

import { Auth, User, Survey, Donation, Admin, Shared } from 'ufh-schema';

// Import pre-exported types directly (RECOMMENDED)
import type { LoginInput, UserPublic, SurveyTemplate } from 'ufh-schema';

// Validate login input
const loginResult = Auth.LoginInputSchema.safeParse({
  email: '[email protected]',
  password: 'securePassword123',
});

if (loginResult.success) {
  const data: LoginInput = loginResult.data; // Type is pre-exported!
  console.log('Valid login data:', data);
} else {
  console.error('Validation errors:', loginResult.error.issues);
}

Architecture

ufh-schema
├── Auth       # Authentication & session management
├── User       # User profiles, settings, dashboards
├── Survey     # Survey templates, questions, responses
├── Donation   # Payment processing, history
├── Admin      # Admin-only management schemas
├── Shared     # Common utilities (pagination, enums, errors)
└── System     # Health checks, error responses

Design Principles

  1. Single Source of Truth: Define schemas once, use everywhere
  2. Pre-exported Types: Every schema exports its TypeScript type (e.g., LoginInput alongside LoginInputSchema)
  3. Namespace Exports: Import as Auth.LoginInputSchema or Auth.LoginInput for clarity
  4. Input/Output Separation: *InputSchema for requests, *Schema for responses
  5. Direct Type Imports: import type { LoginInput } from 'ufh-schema'

Module Reference

Auth Module

Authentication, registration, and session management.

import { Auth } from 'ufh-schema';

| Schema | Purpose | Example Usage | |--------|---------|---------------| | LoginInputSchema | User login | { phone, password } | | RegisterBasicInputSchema | Initial registration | { firstName, lastName, phone, joinReason } | | RegisterWithGoogleInputSchema | Google OAuth | { idToken, phone, joinReason } | | ForgotPasswordInputSchema | Request password reset | { phone } | | ResetPasswordInputSchema | Set new password | { token, newPassword } | | VerifyEmailInputSchema | Verify email OTP | { email, otp } | | SetEmailInputSchema | Set user email | { email } | | SetPasswordInputSchema | Set initial password | { password } | | RefreshTokenInputSchema | Refresh JWT | { refreshToken } | | TokenPairSchema | Token response | { accessToken, refreshToken } | | SessionSchema | Session data | { id, userId, jti, expiresAt, ... } |

Frontend Example: Login Form

import { Auth } from 'ufh-schema';
import type { LoginInput } from 'ufh-schema'; // Pre-exported type!
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';

function LoginForm() {
  const { register, handleSubmit, formState: { errors } } = useForm<LoginInput>({
    resolver: zodResolver(Auth.LoginInputSchema),
  });

  const onSubmit = async (data: LoginInput) => {
    const response = await fetch('/api/auth/login', {
      method: 'POST',
      body: JSON.stringify(data),
    });
    // Handle response...
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('phone')} placeholder="+91 Phone Number" />
      {errors.phone && <span>{errors.phone.message}</span>}
      
      <input {...register('password')} type="password" placeholder="Password" />
      {errors.password && <span>{errors.password.message}</span>}
      
      <button type="submit">Login</button>
    </form>
  );
}

Backend Example: Login Endpoint (Express/Hono)

import { Auth } from 'ufh-schema';
import { Hono } from 'hono';

const app = new Hono();

app.post('/auth/login', async (c) => {
  const body = await c.req.json();
  
  // Validate request body
  const result = Auth.LoginInputSchema.safeParse(body);
  
  if (!result.success) {
    return c.json({ 
      success: false, 
      error: result.error.flatten() 
    }, 400);
  }

  const { phone, password } = result.data;
  
  // Your authentication logic here...
  const user = await authenticateUser(phone, password);
  const tokens = await generateTokens(user);

  return c.json({
    success: true,
    data: { user, tokens }
  });
});

User Module

User profiles, settings, and role-based dashboards.

import { User } from 'ufh-schema';

| Schema | Purpose | |--------|---------| | UserPublicSchema | Public user profile | | UserUpdateInputSchema | Self-service profile update | | UserRoleUpdateInputSchema | Admin role change | | UserSearchQuerySchema | User search/filter | | UserSettingsSchema | User preferences | | UserSettingsUpdateInputSchema | Update preferences | | SupporterDashboardStatsSchema | Supporter dashboard | | VolunteerDashboardStatsSchema | Volunteer dashboard | | SurveyorDashboardStatsSchema | Surveyor dashboard | | MentorDashboardStatsSchema | Mentor dashboard | | ManagerDashboardStatsSchema | Manager dashboard |

Role Hierarchy

supporter → volunteer → surveyor → mentor → manager → superadmin

Each higher role inherits all schemas/stats of lower roles.

Dashboard Stats by Role

import { User, Shared } from 'ufh-schema';

// Get dashboard based on user role
function getDashboardSchema(role: Shared.Role) {
  switch (role) {
    case 'supporter':
      return User.SupporterDashboardStatsSchema;
    case 'volunteer':
      return User.VolunteerDashboardStatsSchema;
    case 'surveyor':
      return User.SurveyorDashboardStatsSchema;
    case 'mentor':
      return User.MentorDashboardStatsSchema;
    case 'manager':
    case 'superadmin':
      return User.ManagerDashboardStatsSchema;
  }
}

Survey Module

Multi-step survey forms with dynamic question types.

import { Survey } from 'ufh-schema';

Template & Question Management

| Schema | Purpose | |--------|---------| | SurveyTemplateSchema | Survey template definition | | SurveyTemplateCreateInputSchema | Create new template | | SurveyTemplateUpdateInputSchema | Update template | | SurveyTemplatePublishInputSchema | Publish draft template | | SurveyQuestionSchema | Question definition | | SurveyQuestionCreateInputSchema | Add question | | SurveyQuestionUpdateInputSchema | Edit question | | SurveyQuestionOptionSchema | Choice options | | SurveyValidationRulesSchema | Field validation rules | | SurveyStepSchema | Multi-step grouping | | SurveyStepCreateInputSchema | Create step |

Survey Response Flow

| Schema | Purpose | |--------|---------| | SurveyInstanceSchema | Active survey session | | SurveyInstanceCreateInputSchema | Start new survey | | SurveyResponseStepInputSchema | Submit step answers | | SurveyResponseSubmitInputSchema | Final submission | | AvailableSurveyListItemSchema | Surveyor's survey list | | FileUploadAnswerSchema | File/image uploads |

Custom Question Types (Admin)

| Schema | Purpose | |--------|---------| | CustomQuestionTypeSchema | Define new question type | | CustomQuestionTypeCreateInputSchema | Create custom type | | QuestionTypeConfigSchema | Type configuration |

Question Types Available

type QuestionType = 
  | 'single_choice'    // Radio buttons
  | 'multi_choice'     // Checkboxes
  | 'text'             // Text input
  | 'number'           // Numeric input
  | 'rating'           // Star/scale rating
  | 'date'             // Date picker
  | 'file_upload'      // Document upload
  | 'image_upload';    // Image upload

Frontend: Dynamic Survey Form

import { Survey, Shared } from 'ufh-schema';
import type { z } from 'zod';

type Question = z.infer<typeof Survey.SurveyQuestionSchema>;

function renderQuestion(question: Question) {
  switch (question.type) {
    case 'single_choice':
      return (
        <RadioGroup name={question.id}>
          {question.options?.map(opt => (
            <Radio key={opt.id} value={opt.id}>{opt.label}</Radio>
          ))}
        </RadioGroup>
      );
    
    case 'multi_choice':
      return (
        <CheckboxGroup name={question.id}>
          {question.options?.map(opt => (
            <Checkbox key={opt.id} value={opt.id}>{opt.label}</Checkbox>
          ))}
        </CheckboxGroup>
      );
    
    case 'text':
      return <TextInput name={question.id} required={question.validation?.required} />;
    
    case 'image_upload':
      return <ImageUploader name={question.id} />;
    
    // ... other types
  }
}

Backend: Create Survey Template

import { Survey } from 'ufh-schema';

async function createSurveyTemplate(req: Request) {
  const result = Survey.SurveyTemplateCreateInputSchema.safeParse(req.body);
  
  if (!result.success) {
    throw new ValidationError(result.error);
  }

  const template = await db.surveyTemplates.create({
    ...result.data,
    status: 'draft',  // Always start as draft
    version: 1,
    createdBy: req.user.id,
  });

  return template;
}

Donation Module

Payment processing with UPI intent support.

import { Donation } from 'ufh-schema';

| Schema | Purpose | |--------|---------| | DonationCreateInputSchema | Initiate donation | | DonationPublicSchema | Donation record | | DonationConfirmInputSchema | Confirm payment | | DonationHistoryQuerySchema | Filter donations | | DonationListPayloadSchema | Paginated list |

Donation Methods

type DonationMethod = 
  | 'upi_intent'     // UPI deeplink (current)
  | 'upi_collect'    // UPI collect request
  | 'netbanking'     // Net banking
  | 'card'           // Credit/Debit card
  | 'wallet'         // Digital wallets
  | 'cash'           // Offline cash
  | 'bank_transfer'; // NEFT/RTGS

Donation Purpose

type DonationPurpose = 
  | 'membership_fee'     // Annual membership
  | 'community_support'  // Community fund
  | 'general'            // General donation
  | 'campaign'           // Specific campaign
  | 'other';

Frontend: Donation Form

import { Donation } from 'ufh-schema';

const DonationForm = () => {
  const handleDonate = async (amount: number) => {
    const result = Donation.DonationCreateInputSchema.safeParse({
      amount,
      method: 'upi_intent',
      purpose: 'membership_fee',
      isAnonymous: false,
    });

    if (!result.success) {
      toast.error('Invalid donation data');
      return;
    }

    const response = await api.post('/donations', result.data);
    
    // Open UPI app via deeplink
    window.location.href = response.data.upiDeeplink;
  };

  return (
    <div>
      <h2>Support Our Mission</h2>
      <Button onClick={() => handleDonate(100)}>₹100</Button>
      <Button onClick={() => handleDonate(500)}>₹500</Button>
      <Button onClick={() => handleDonate(1000)}>₹1000</Button>
    </div>
  );
};

Admin Module

Management schemas for superadmin role.

import { Admin } from 'ufh-schema';

| Schema | Purpose | |--------|---------| | AdminDashboardStatsSchema | System-wide statistics | | AdminStatsTimeSeriesSchema | Chart data | | AdminDashboardQuerySchema | Date range filter | | AdminUserSearchQuerySchema | Advanced user search | | AdminBulkUserActionSchema | Bulk user operations | | AdminSurveySearchQuerySchema | Survey search | | SurveyCloneInputSchema | Clone template | | BulkQuestionOrderUpdateSchema | Reorder questions | | AdminDonationSearchQuerySchema | Donation search | | DonationRefundInputSchema | Process refund | | DonationReportQuerySchema | Generate reports | | DonationPresetSchema | Suggested amounts |

Admin User Search (15+ Filters)

import { Admin } from 'ufh-schema';

// Example: Find volunteers who haven't donated, created this month
const query = Admin.AdminUserSearchQuerySchema.parse({
  roles: ['volunteer', 'surveyor'],
  hasCompletedDonation: false,
  createdAfter: '2024-01-01',
  sortBy: 'createdAt',
  sortOrder: 'desc',
  page: 1,
  limit: 20,
});

const users = await api.get('/admin/users', { params: query });

Bulk User Actions

import { Admin } from 'ufh-schema';

// Change role for multiple users
const action = Admin.AdminBulkUserActionSchema.parse({
  userIds: ['user1', 'user2', 'user3'],
  action: 'changeRole',
  payload: { newRole: 'surveyor' },
});

await api.post('/admin/users/bulk', action);

Shared Module

Common utilities used across all modules.

import { Shared } from 'ufh-schema';

Enums & Primitives

| Schema | Values | |--------|--------| | RoleSchema | supporter, volunteer, surveyor, mentor, manager, superadmin | | UserStatusSchema | pending, active, suspended, deleted | | SurveyStatusSchema | draft, published, archived | | DonationStatusSchema | initiated, pending, success, failed, cancelled, refunded | | DonationMethodSchema | upi_intent, upi_collect, netbanking, card, wallet, cash, bank_transfer | | DonationPurposeSchema | membership_fee, community_support, general, campaign, other | | QuestionTypeSchema | single_choice, multi_choice, text, number, rating, date, file_upload, image_upload |

Validation Schemas

| Schema | Purpose | |--------|---------| | ObjectIdSchema | MongoDB ObjectId (24 hex chars) | | EmailSchema | Email validation | | PhoneSchema | Indian phone (+91) | | IsoDateTimeStringSchema | ISO 8601 datetime |

Pagination

import { Shared } from 'ufh-schema';

// Standard pagination (page-based)
const query = Shared.PaginationQuerySchema.parse({
  page: 1,
  limit: 20,
  search: 'query',
  sortBy: 'createdAt',
  sortOrder: 'desc',
});

// Cursor pagination (for infinite scroll)
const cursorQuery = Shared.CursorPaginationQuerySchema.parse({
  cursor: 'abc123',
  limit: 20,
  direction: 'forward',
});

API Response Helpers

import { Shared } from 'ufh-schema';

// Create standard success response schema
const UserResponseSchema = Shared.createApiSuccessResponseSchema(
  z.object({ user: User.UserPublicSchema })
);

// Create response with error handling
const UserApiResponseSchema = Shared.createApiResponseSchema(
  z.object({ user: User.UserPublicSchema })
);

Frontend Usage

With React Hook Form + Zod Resolver

npm install react-hook-form @hookform/resolvers
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { Auth } from 'ufh-schema';
import type { z } from 'zod';

type RegisterInput = z.infer<typeof Auth.RegisterBasicInputSchema>;

function RegisterForm() {
  const form = useForm<RegisterInput>({
    resolver: zodResolver(Auth.RegisterBasicInputSchema),
    defaultValues: {
      firstName: '',
      lastName: '',
      phone: '',
      joinReason: '',
    },
  });

  return (
    <form onSubmit={form.handleSubmit(onSubmit)}>
      {/* Form fields */}
    </form>
  );
}

With TanStack Query

import { useMutation } from '@tanstack/react-query';
import { Auth } from 'ufh-schema';

function useLogin() {
  return useMutation({
    mutationFn: async (input: z.infer<typeof Auth.LoginInputSchema>) => {
      // Validate before sending
      const validated = Auth.LoginInputSchema.parse(input);
      
      const response = await fetch('/api/auth/login', {
        method: 'POST',
        body: JSON.stringify(validated),
      });
      
      return response.json();
    },
  });
}

Backend Usage

With Express.js

import express from 'express';
import { Auth, Shared } from 'ufh-schema';

const app = express();
app.use(express.json());

// Validation middleware factory
function validate<T>(schema: z.ZodSchema<T>) {
  return (req: Request, res: Response, next: NextFunction) => {
    const result = schema.safeParse(req.body);
    
    if (!result.success) {
      return res.status(400).json({
        success: false,
        error: {
          code: 'VALIDATION_ERROR',
          issues: result.error.issues,
        },
      });
    }
    
    req.validatedBody = result.data;
    next();
  };
}

// Use middleware
app.post('/auth/register', 
  validate(Auth.RegisterBasicInputSchema),
  async (req, res) => {
    const data = req.validatedBody; // Fully typed!
    // Handle registration...
  }
);

With Hono.js

import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { Auth } from 'ufh-schema';

const app = new Hono();

app.post('/auth/login',
  zValidator('json', Auth.LoginInputSchema),
  async (c) => {
    const data = c.req.valid('json'); // Fully typed!
    // Handle login...
  }
);

With NestJS

import { createZodDto } from 'nestjs-zod';
import { Auth } from 'ufh-schema';

// Create DTO from Zod schema
class LoginDto extends createZodDto(Auth.LoginInputSchema) {}

@Controller('auth')
export class AuthController {
  @Post('login')
  async login(@Body() dto: LoginDto) {
    // dto is validated and typed
  }
}

TypeScript Types

All TypeScript types are pre-exported alongside their Zod schemas. You can import them directly:

import { 
  Auth, 
  User, 
  Survey, 
  Donation, 
  Shared 
} from 'ufh-schema';

// Types are exported from each schema file
// Auth types
import type { LoginInput } from 'ufh-schema';  // Already exported!
import type { Session } from 'ufh-schema';

// Or access via namespace
type UserPublic = User.UserPublic;          // Pre-exported type
type SurveyTemplate = Survey.SurveyTemplate;  // Pre-exported type

// You can also use z.infer if you prefer
import type { z } from 'zod';
type LoginInputAlt = z.infer<typeof Auth.LoginInputSchema>;

Naming Convention

Each schema file exports:

  • Schema: XyzSchema (Zod schema object)
  • Type: Xyz (TypeScript type via z.infer<>)

| Schema | Type | |--------|------| | LoginInputSchema | LoginInput | | UserPublicSchema | UserPublic | | SurveyTemplateSchema | SurveyTemplate | | DonationPublicSchema | DonationPublic | | ObjectIdSchema | ObjectId | | EmailSchema | Email |


Best Practices

1. Always Use safeParse for User Input

// ✅ Good - handles errors gracefully
const result = Auth.LoginInputSchema.safeParse(userInput);
if (!result.success) {
  handleValidationErrors(result.error);
}

// ❌ Bad - throws on invalid input
const data = Auth.LoginInputSchema.parse(userInput);

2. Type Your API Responses

// Define response type using inferred schema
type ApiResponse<T> = {
  success: true;
  data: T;
} | {
  success: false;
  error: { code: string; message: string };
};

type LoginResponse = ApiResponse<{
  user: z.infer<typeof User.UserPublicSchema>;
  tokens: z.infer<typeof Auth.TokenPairSchema>;
}>;

3. Extend Schemas When Needed

// Extend existing schema with additional fields
const ExtendedUserSchema = User.UserPublicSchema.extend({
  customField: z.string(),
});

// Pick specific fields
const UserSummarySchema = User.UserPublicSchema.pick({
  id: true,
  firstName: true,
  lastName: true,
});

// Make fields optional
const PartialUserSchema = User.UserPublicSchema.partial();

4. Use Discriminated Unions for Variants

const ResponseSchema = z.discriminatedUnion('type', [
  z.object({ type: z.literal('success'), data: DataSchema }),
  z.object({ type: z.literal('error'), error: ErrorSchema }),
]);

Contributing

  1. Clone the repository
  2. Install dependencies: npm install
  3. Make changes in src/
  4. Run checks: npm run check
  5. Build: npm run build
  6. Submit PR

Adding New Schemas

  1. Create schema file in appropriate module directory
  2. Export from module's index.ts
  3. Add to this README documentation
  4. Run npm run build && npm run check

License

MIT © Hari