| title | RevealUI TypeScript Quality Standards |
| visibility | internal |
| status | verified |
| audience | agent |
| name | revealui-typescript-quality |
| description | TypeScript best practices to eliminate `any` types (run `pnpm audit:any` for the current count) and improve code quality |
| version | 0.1.0 |
| author | RevealUI Team |
| tags | ["typescript","type-safety","code-quality"] |
| compatibility | ["claude-code","universal"] |
| allowedTools | ["Read","Write","Edit","Grep","Glob"] |
RevealUI TypeScript Quality Standards
Guidelines to eliminate any types in RevealUI and maintain type safety throughout the codebase. Run pnpm audit:any for the current count before starting a fix session.
Critical Goal
Target: Reduce any types to 0 (run pnpm audit:any for the current count)
Priority: High - Type safety prevents runtime errors and improves developer experience
Rule 1: Never Use any - Use These Instead
Use unknown for Truly Unknown Types
function processData(data: any) {
return data.value
}
function processData(data: unknown) {
if (typeof data === 'object' && data !== null && 'value' in data) {
return (data as { value: string }).value
}
throw new Error('Invalid data')
}
Use Generic Types
function firstElement(arr: any[]) {
return arr[0]
}
function firstElement<T>(arr: T[]): T | undefined {
return arr[0]
}
Use Type Guards
function process(value: any) {
if (value.length) {
return value.length
}
}
function isString(value: unknown): value is string {
return typeof value === 'string'
}
function process(value: unknown) {
if (isString(value)) {
return value.length
}
}
Rule 2: Type External Data Properly
API Responses
async function fetchUser(id: string): Promise<any> {
const response = await fetch(`/api/users/${id}`)
return response.json()
}
interface User {
id: string
name: string
email: string
role: 'admin' | 'user' | 'super-admin'
}
async function fetchUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`)
const data = await response.json()
return data as User
}
import { z } from 'zod'
const UserSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string().email(),
role: z.enum(['admin', 'user', 'super-admin']),
})
type User = z.infer<typeof UserSchema>
async function fetchUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`)
const data = await response.json()
return UserSchema.parse(data)
}
Form Data
function handleSubmit(formData: any) {
const email = formData.email
const password = formData.password
}
interface FormData {
email: string
password: string
}
function handleSubmit(formData: FormData) {
const { email, password } = formData
}
function handleSubmit(formData: FormData) {
const email = formData.get('email') as string
const password = formData.get('password') as string
if (!email || !password) {
throw new Error('Missing required fields')
}
return { email, password }
}
Rule 3: Type Component Props Properly
React Component Props
export function Card(props: any) {
return <div>{props.title}</div>
}
interface CardProps {
title: string
description?: string
onClick?: () => void
children?: React.ReactNode
}
export function Card({ title, description, onClick, children }: CardProps) {
return (
<div onClick={onClick}>
<h2>{title}</h2>
{description && <p>{description}</p>}
{children}
</div>
)
}
Polymorphic Components
type ButtonProps<T extends React.ElementType> = {
as?: T
children: React.ReactNode
} & React.ComponentPropsWithoutRef<T>
function Button<T extends React.ElementType = 'button'>({
as,
children,
...props
}: ButtonProps<T>) {
const Component = as || 'button'
return <Component {...props}>{children}</Component>
}
<Button onClick={() => {}}>Click</Button>
<Button as="a" href="/page">Link</Button>
Rule 4: Type Event Handlers
function handleClick(event: any) {
event.preventDefault()
}
function handleClick(event: React.MouseEvent<HTMLButtonElement>) {
event.preventDefault()
console.log(event.currentTarget.value)
}
function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
console.log(event.target.value)
}
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault()
const formData = new FormData(event.currentTarget)
}
Rule 5: Type Async Functions
async function loadData(): Promise<any> {
return await fetch('/api/data')
}
interface Data {
items: Array<{ id: string; name: string }>
total: number
}
async function loadData(): Promise<Data> {
const response = await fetch('/api/data')
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
return await response.json()
}
Rule 6: Type Third-Party Libraries
declare module 'some-library' {
export function doSomething(arg: any): any
}
declare module 'some-library' {
export interface Options {
timeout?: number
retries?: number
}
export function doSomething(
arg: string,
options?: Options
): Promise<{ success: boolean; data: unknown }>
}
Rule 7: Use Utility Types
type PartialUser = Partial<User>
type RequiredConfig = Required<Config>
type UserPreview = Pick<User, 'id' | 'name' | 'avatar'>
type UserWithoutPassword = Omit<User, 'password'>
type UserRoles = Record<string, 'admin' | 'user'>
interface User {
id: string
name: string
email: string
password: string
avatar?: string
}
function updateUser(id: string, updates: Partial<User>) {
}
function displayUser(user: UserWithoutPassword) {
}
Rule 8: Type Guards for Union Types
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'square'; size: number }
| { kind: 'rectangle'; width: number; height: number }
function getArea(shape: any) {
if (shape.kind === 'circle') {
return Math.PI * shape.radius ** 2
}
}
function getArea(shape: Shape): number {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2
case 'square':
return shape.size ** 2
case 'rectangle':
return shape.width * shape.height
}
}
Rule 9: Type Collections Access
const collections: any = {
users: { },
posts: { },
}
interface Collection {
name: string
fields: Record<string, unknown>
}
const collections: Record<string, Collection> = {
users: { name: 'users', fields: {} },
posts: { name: 'posts', fields: {} },
}
type CollectionName = 'users' | 'posts' | 'media'
interface Collections {
users: UserCollection
posts: PostCollection
media: MediaCollection
}
function getCollection<T extends CollectionName>(
name: T
): Collections[T] {
return collections[name]
}
const users = getCollection('users')
Rule 10: Avoid Type Assertions (as)
const user = data as User
function isUser(data: unknown): data is User {
return (
typeof data === 'object' &&
data !== null &&
'id' in data &&
'name' in data &&
'email' in data
)
}
const data = await fetchData()
if (isUser(data)) {
console.log(data.email)
}
const UserSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string().email(),
})
const data = await fetchData()
const user = UserSchema.parse(data)
Common Patterns in RevealUI
RevealUI Collections
import type { RevealUI } from '@revealui/core'
async function createPost(
revealui: RevealUI,
data: {
title: string
content: string
author: string
}
) {
return await revealui.create({
collection: 'posts',
data,
})
}
Access Control Functions
import type { AccessControl } from '@revealui/core'
export const isAdmin: AccessControl = ({ req: { user } }) => {
return user?.role === 'admin'
}
export const isAdminOrSelf: AccessControl = ({ req: { user }, id }) => {
if (user?.role === 'admin') return true
return user?.id === id
}
Server Actions
'use server'
import { z } from 'zod'
import { revalidatePath } from 'next/cache'
const CreatePostSchema = z.object({
title: z.string().min(1),
content: z.string(),
})
export async function createPost(formData: FormData) {
const rawData = {
title: formData.get('title'),
content: formData.get('content'),
}
const validated = CreatePostSchema.parse(rawData)
revalidatePath('/posts')
return { success: true, id: 'new-post-id' }
}
Finding and Fixing any Types
Search for any types
grep -r ": any" packages/ apps/ --include="*.ts" --include="*.tsx"
grep -r "any)" packages/ apps/ --include="*.ts" --include="*.tsx"
Priority Order
- Fix public APIs first (exported functions, components)
- Fix type definitions (interfaces, types)
- Fix function parameters
- Fix return types
- Fix variable declarations
Quick Wins
Replace common any patterns:
event: any → event: React.MouseEvent<HTMLElement>
data: any → data: FormData or data: Record<string, unknown>
response: any → response: { data: T; status: number }
props: any → props: ComponentProps
callback: any → callback: (value: T) => void
items: any[] → items: T[] or items: Array<T>
obj: any → obj: Record<string, unknown> or define interface
VSCode Settings
Add to .vscode/settings.json:
{
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"editor.codeActionsOnSave": {
"source.fixAll": true
},
"typescript.preferences.strictNullChecks": true
}
Resources
Tracking Progress
Current status: run pnpm audit:any for the current any-type count.
After each fix session, run:
pnpm audit:any
Goal: 0 any types in RevealUI codebase!
Every any type eliminated improves:
- ✅ Type safety
- ✅ Developer experience
- ✅ Refactoring confidence
- ✅ IDE autocomplete
- ✅ Bug prevention
Let's make RevealUI 100% type-safe! 🎯