typescript-write
Installation
SKILL.md
TypeScript/JavaScript Development Skill
When to Invoke
- Writing new TypeScript or JavaScript files
- Refactoring existing TS/JS code
- Reviewing code for type safety and best practices
- Converting JavaScript to TypeScript
- Designing module APIs and type interfaces
- Fixing type errors or improving type coverage
Code Style
Naming Conventions
camelCasefor variables, functions, parametersPascalCasefor types, interfaces, classes, enums, React componentsUPPER_SNAKE_CASEfor constants and enum members- Prefix interfaces with
Ionly if project convention requires it - otherwise plainPascalCase - Boolean variables: use
is,has,should,canprefixes (isLoading,hasPermission) - Event handlers:
handleClick,onSubmitpattern
File Organization
- One primary export per file when possible
- Group related types with their implementation
- Barrel exports (
index.ts) for public module APIs only - avoid deep barrel re-exports - File naming:
kebab-case.tsfor utilities,PascalCase.tsxfor React components
Import Ordering
- Node built-in modules (
node:fs,node:path) - External packages (
react,lodash) - Internal aliases (
@/utils,@/components) - Relative imports (
./helpers,../types) - Type-only imports (
import type { Foo })
- Blank line between each group
TypeScript Patterns
Strict Mode
- Enable
strict: trueintsconfig.json- never disable individual strict checks - No
// @ts-ignoreor// @ts-expect-errorwithout an explanatory comment - Prefer
unknownoverany- narrow with type guards
Proper Typing
- Avoid
any- useunknownand narrow, or define a proper type - Prefer
interfacefor object shapes that may be extended - Prefer
typefor unions, intersections, mapped types, and utility types - Use
readonlyfor properties that should not be mutated - Use
as constfor literal type inference on objects and arrays
Discriminated Unions
type Result<T> =
| { success: true; data: T }
| { success: false; error: Error };
function handle<T>(result: Result<T>) {
if (result.success) {
// result.data is T here
return result.data;
}
// result.error is Error here
throw result.error;
}
Type Guards
// User-defined type guard
function isString(value: unknown): value is string {
return typeof value === "string";
}
// Assertion function
function assertDefined<T>(value: T | undefined, name: string): asserts value is T {
if (value === undefined) {
throw new Error(`Expected ${name} to be defined`);
}
}
Generic Constraints
// Constrain generics to what you actually need
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
// Use defaults for common cases
type ApiResponse<T = unknown> = {
data: T;
status: number;
timestamp: string;
};
Utility Types
Partial<T>- all properties optional (use for update/patch operations)Required<T>- all properties requiredPick<T, K>- select subset of propertiesOmit<T, K>- exclude propertiesRecord<K, V>- typed key-value mapExtract<T, U>/Exclude<T, U>- filter union members- Prefer built-in utility types over manual type manipulation
Enums vs Union Types
- Prefer string union types for simple sets:
type Status = "active" | "inactive" - Use
const enumonly if you need numeric values and tree-shaking - Use regular
enumwhen you need runtime reverse mapping or iteration
React Patterns
Component Typing
// Function components - type props inline or with interface
interface ButtonProps {
label: string;
variant?: "primary" | "secondary";
onClick: () => void;
children?: React.ReactNode;
}
function Button({ label, variant = "primary", onClick, children }: ButtonProps) {
return <button className={variant} onClick={onClick}>{children ?? label}</button>;
}
Hooks Rules
- Call hooks at the top level only - never inside conditions, loops, or nested functions
- Custom hooks must start with
useprefix - Specify dependency arrays accurately - never suppress exhaustive-deps lint
- Use
useCallbackfor functions passed as props to memoized children - Use
useMemofor expensive computations - not for every variable
State Management
- Colocate state as close to where it is used as possible
- Lift state up only when siblings need to share it
- Use
useReducerfor complex state with multiple sub-values or transitions - Context for truly global state (theme, auth, locale) - not for frequently changing data
Event Handling
// Type event handlers properly
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
setValue(e.target.value);
}
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
// ...
}
Testing
File Naming
- Test files:
*.test.tsor*.spec.tsalongside the source file - Or in
__tests__/directory mirroring the source structure - Test utilities:
test-utils.tsortesting/directory
Test Structure
describe("calculateTotal", () => {
it("returns 0 for empty cart", () => {
expect(calculateTotal([])).toBe(0);
});
it("sums item prices with quantities", () => {
const items = [
{ price: 10, quantity: 2 },
{ price: 5, quantity: 1 },
];
expect(calculateTotal(items)).toBe(25);
});
it("throws for negative quantities", () => {
expect(() => calculateTotal([{ price: 10, quantity: -1 }])).toThrow();
});
});
Assertion Patterns
- Use
toBefor primitives,toEqualfor objects/arrays - Use
toThrowfor error cases - wrap in arrow function - Use
toHaveBeenCalledWithfor spy/mock assertions - Prefer specific matchers over generic
toBeTruthy/toBeFalsy - Type test fixtures and mocks - avoid
as anyin tests
Mocking
- Mock external dependencies, not internal implementation
- Use
vi.fn()(Vitest) orjest.fn()for function mocks - Use
vi.spyOn/jest.spyOnto mock methods while preserving type safety - Reset mocks in
beforeEachor useafterEach(() => vi.restoreAllMocks())
Common Anti-Patterns
Using any Instead of Proper Types
// BAD
function parse(data: any) { return data.name; }
// GOOD
function parse(data: unknown): string {
if (typeof data === "object" && data !== null && "name" in data) {
return String((data as { name: unknown }).name);
}
throw new Error("Invalid data");
}
Non-Null Assertion Overuse
// BAD
const name = user!.name!;
// GOOD
if (!user?.name) throw new Error("User name required");
const name = user.name;
Barrel File Performance Issues
// BAD - importing everything through deep barrel
import { Button } from "@/components"; // pulls entire component tree
// GOOD - direct import
import { Button } from "@/components/Button";
Ignoring Return Types
// BAD - return type inferred as complex union
function getData(id: string) {
if (!id) return null;
return fetch(`/api/${id}`).then(r => r.json());
}
// GOOD - explicit return type
async function getData(id: string): Promise<ApiResponse | null> {
if (!id) return null;
const r = await fetch(`/api/${id}`);
return r.json() as Promise<ApiResponse>;
}
Mutating Function Parameters
// BAD
function addItem(items: Item[], item: Item) {
items.push(item); // mutates input
return items;
}
// GOOD
function addItem(items: readonly Item[], item: Item): Item[] {
return [...items, item];
}
Error Handling
Result Pattern
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E };
async function fetchUser(id: string): Promise<Result<User>> {
try {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) return { ok: false, error: new Error(`HTTP ${res.status}`) };
const user = await res.json();
return { ok: true, value: user };
} catch (e) {
return { ok: false, error: e instanceof Error ? e : new Error(String(e)) };
}
}
Async Error Handling
- Always
try/catcharoundawaitcalls that can fail - Never use
.catch()andawaiton the same promise chain - Prefer returning error results over throwing in library code
- Throw only for programmer errors (assertion failures, invariant violations)
- Log errors at the boundary, not at every level
Error Boundaries (React)
- Wrap major UI sections in error boundaries
- Provide meaningful fallback UI - not blank screens
- Log errors to monitoring service in
componentDidCatch - Reset error boundary state on navigation changes
Typed Errors
class NotFoundError extends Error {
readonly code = "NOT_FOUND" as const;
constructor(resource: string, id: string) {
super(`${resource} ${id} not found`);
this.name = "NotFoundError";
}
}
class ValidationError extends Error {
readonly code = "VALIDATION" as const;
constructor(public readonly fields: Record<string, string>) {
super("Validation failed");
this.name = "ValidationError";
}
}
Related skills
More from acaprino/alfio-claude-plugins
python-refactor
>
159file-organizer
>
60legal-advisor
Use PROACTIVELY for any legal question -- contracts, compliance, privacy, IP, employment law, terms of service, NDAs, corporate governance. Expert legal advisor specializing in technology law, compliance, and risk mitigation.
39deep-dive-analysis
>
34python-comments
>
34tauri2-mobile
Expert guidance for developing, testing, and deploying mobile applications with Tauri 2. Use when working with Tauri 2 mobile development for Android/iOS, including project setup, Rust backend patterns, frontend integration, plugin usage (biometric, geolocation, notifications, IAP), emulator/ADB testing, code signing, and Play Store/App Store deployment.
29