Claude-skill-registry core-package
Pure function patterns, Zod schemas, database independence, calculation functions for @repo/core
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/core-package" ~/.claude/skills/majiayu000-claude-skill-registry-core-package && rm -rf "$T"
manifest:
skills/data/core-package/SKILL.mdsource content
Core Package Skill
Core Principles
- ZERO Database Dependencies - No Drizzle, Prisma, Supabase, or ORM imports
- Pure Functions Only - No async, no side effects, deterministic
- Zod for Validation - Runtime type safety at boundaries
- Type Inference - Use
for single source of truthz.infer<> - Comprehensive Testing - 100% coverage for calculations
- Self-Documenting - Clear names + JSDoc for public APIs
Patterns to Follow
Pattern 1: Pure Function with Parameter Object
export interface TSSParams { normalizedPower: number; duration: number; // seconds ftp: number; } export function calculateTSS(params: TSSParams): number { const { normalizedPower, duration, ftp } = params; if (!ftp || ftp === 0) return 0; if (!duration || duration === 0) return 0; const intensityFactor = normalizedPower / ftp; const hours = duration / 3600; return ((duration * normalizedPower * intensityFactor) / (ftp * 3600)) * 100; }
Key Points:
- Parameter object for 3+ params
- Guard clauses for edge cases
- No mutations, no side effects
- Return value only
Pattern 2: Zod Schema with Type Inference
export const activitySchema = z.object({ id: z.string().uuid(), name: z.string().min(1, "Name is required"), type: z.enum(["run", "bike", "swim", "other"]), distance: z.number().nonnegative().optional(), duration: z.number().int().positive(), }); export type Activity = z.infer<typeof activitySchema>;
Key Points:
- Schema first, type second
- Custom error messages
- Optional fields explicit
- Single source of truth
Pattern 3: Discriminated Union with Zod
const durationTimeSchema = z.object({ type: z.literal("time"), seconds: z.number().int().positive(), }); const durationDistanceSchema = z.object({ type: z.literal("distance"), meters: z.number().positive(), }); export const durationSchema = z.discriminatedUnion("type", [ durationTimeSchema, durationDistanceSchema, ]); export type Duration = z.infer<typeof durationSchema>; // Usage with type narrowing function formatDuration(duration: Duration): string { if (duration.type === "time") { return `${duration.seconds}s`; // TypeScript knows seconds exists } else { return `${duration.meters}m`; // TypeScript knows meters exists } }
Pattern 4: JSDoc for Public APIs
/** * Calculates Training Stress Score (TSS) from power data. * * TSS quantifies training load based on: * - Normalized Power (30-second rolling average) * - Intensity Factor (NP / FTP) * - Duration * * Formula: TSS = (duration × NP × IF) / (FTP × 3600) × 100 * * @param params - Power stream, timestamps, and FTP * @returns TSS value (0-300+ typical range) * * @example * ```typescript * const tss = calculateTSS({ * normalizedPower: 250, * duration: 3600, * ftp: 250, * }); * console.log(tss); // 100 * ``` */ export function calculateTSS(params: TSSParams): number { // Implementation }
Pattern 5: Comprehensive Testing
describe("calculateTSS", () => { it("should calculate TSS correctly for 1 hour at FTP", () => { const result = calculateTSS({ normalizedPower: 250, duration: 3600, ftp: 250, }); expect(result).toBe(100); }); it("should return 0 for zero FTP", () => { const result = calculateTSS({ normalizedPower: 250, duration: 3600, ftp: 0, }); expect(result).toBe(0); }); it("should handle zero duration", () => { const result = calculateTSS({ normalizedPower: 250, duration: 0, ftp: 250, }); expect(result).toBe(0); }); });
Anti-Patterns to Avoid
Anti-Pattern 1: Database Imports
// ❌ WRONG import { db } from '@repo/supabase'; export async function calculateUserTSS(userId: string) { const activities = await db.activities.findMany({ userId }); return activities.reduce((sum, act) => sum + act.tss, 0); } // ✅ CORRECT - Move to tRPC layer // In packages/trpc/src/routers/activities.ts export const activityRouter = router({ getUserTSS: protectedProcedure.query(async ({ ctx }) => { const activities = await ctx.db.activities.findMany({...}); return activities.reduce((sum, act) => sum + act.tss, 0); }), });
Anti-Pattern 2: Async Operations
// ❌ WRONG export async function calculateTSS(...) { const ftp = await getFTP(); // NO! } // ✅ CORRECT export function calculateTSS(params: { ftp: number, ... }): number { // Pure, synchronous }
Anti-Pattern 3: Side Effects
// ❌ WRONG let cache = {}; export function calculateTSS(...) { cache[key] = value; // MUTATION! } // ✅ CORRECT export function calculateTSS(...): number { return value; // No mutations }
File Organization
packages/core/ ├── calculations/ │ ├── tss.ts │ ├── tss.test.ts │ └── zones.ts ├── schemas/ │ ├── activity.ts │ ├── activity.test.ts │ └── profile.ts ├── utils/ │ ├── time.ts │ └── distance.ts └── index.ts
Dependencies
Allowed:
- Validationzod- Pure utility libraries (no I/O)
Forbidden:
@supabase/*
,drizzle-ormprisma@repo/trpc
,reactreact-native
Checklist
- No database imports
- No async functions
- Pure functions only
- Zod schemas for validation
- Type inference from schemas
- JSDoc on public APIs
- 100% test coverage
- Named exports only
Related Skills
- Backend Skill - Orchestration layer
- Testing Skill - Pure function testing
Version History
- 1.0.0 (2026-01-21): Initial version
Next Review: 2026-02-21
Schema Validation
When to Use
- User needs to create a new data schema
- User wants to add validation to forms
- User needs to validate API inputs
- User asks to update existing schemas
What This Skill Does
- Creates Zod schemas with proper validation
- Infers TypeScript types from schemas
- Adds custom validation rules
- Implements refined schemas with dependencies
- Creates form-specific sub-schemas
Basic Schema Pattern
import { z } from "zod"; export const activitySchema = z .object({ id: z.string().uuid(), name: z.string().min(1, "Name is required"), type: z.enum(["run", "bike", "swim", "other"]), distance: z.number().positive().optional(), duration: z.number().int().positive("Duration must be positive"), startTime: z.date(), endTime: z.date(), }) .refine((data) => data.endTime > data.startTime, { message: "End time must be after start time", path: ["endTime"], }); export type Activity = z.infer<typeof activitySchema>;
Form Schema Pattern
export const createActivitySchema = activitySchema .omit({ id: true, }) .extend({ // Form-specific fields notes: z.string().max(500).optional(), isPrivate: z.boolean().default(false), }); export type CreateActivityInput = z.infer<typeof createActivitySchema>;
Validation in Forms
import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import { createActivitySchema } from "@repo/core/schemas"; export function useCreateActivityForm() { const form = useForm({ resolver: zodResolver(createActivitySchema), defaultValues: { name: "", type: "run", distance: undefined, duration: 0, startTime: new Date(), endTime: new Date(), notes: "", isPrivate: false, }, }); return form; }
API Input Validation
export const activityListInput = z.object({ limit: z.number().min(1).max(100).default(20), offset: z.number().min(0).default(0), type: z.enum(["run", "bike", "swim", "other"]).optional(), search: z.string().optional(), });
Custom Validation
export const profileSchema = z .object({ name: z.string().min(1).max(100), email: z.string().email(), ftp: z.number().min(50).max(500).optional(), }) .refine( (data) => { if (data.email.includes("+")) { return data.name.length > 5; } return true; }, { message: "Name must be longer for email aliases", path: ["name"], }, );