Skip to content

/howto-code-in-typescript

Use when writing TypeScript code, reviewing TS implementations, or making decisions about type declarations, function styles, or naming conventions - comprehensive house style covering type vs interface rules, function declarations, FCIS integration, immutability patterns, and

shell
$ npx -y skills add ed3dai/ed3d-plugins --skill howto-code-in-typescript --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.
  • You can call itInvoke it directly when you want it.
  • Slash command/howto-code-in-typescript
How auto-invocation works

Context preview

The summary Claude sees to decide when to auto-load this skill.

Use when writing TypeScript code, reviewing TS implementations, or making decisions about type declarations, function styles, or naming conventions - comprehensive house style covering type vs interface rules, function declarations, FCIS integration, immutability patterns, and

SKILL.md

howto-code-in-typescript.SKILL.md
name: howto-code-in-typescript
description: Use when writing TypeScript code, reviewing TS implementations, or making decisions about type declarations, function styles, or naming conventions - comprehensive house style covering type vs interface rules, function declarations, FCIS integration, immutability patterns, and type safety enforcement
user-invocable: false

TypeScript House Style

Overview

Comprehensive TypeScript coding standards emphasizing type safety, immutability, and integration with Functional Core, Imperative Shell (FCIS) pattern.

**Core principles:**

  • Types as documentation and constraints
  • Immutability by default prevents bugs
  • Explicit over implicit (especially in function signatures)
  • Functional Core returns Results, Imperative Shell may throw
  • Configuration over decoration/magic

Quick Self-Check (Use Under Pressure)

When under deadline pressure or focused on other concerns (performance, accuracy, features), STOP and verify:

  • [ ] Using `Array<T>` not `T[]`
  • [ ] Using `type` not `interface` (unless class contract)
  • [ ] Using math.js for money/currencies/complex math
  • [ ] Parameters are `readonly` or `Readonly<T>`
  • [ ] Using `unknown` not `any`
  • [ ] Using `null` for absent values (not `undefined`)
  • [ ] Using function declarations (not const arrow) for top-level functions
  • [ ] Using named exports (not default exports)
  • [ ] Using `===` not `==`
  • [ ] Using `.sort((a, b) => a - b)` for numeric arrays
  • [ ] Using `parseInt(x, 10)` with explicit radix

**Why this matters:** Under pressure, you'll default to muscle memory. These checks catch the most common violations.

Type Declarations

Type vs Interface

**Always use `type` except for class contracts.**

// GOOD: type for object shapes
type UserData = {
  readonly id: string;
  name: string;
  email: string | null;
};

// GOOD: interface for class contract
interface IUserRepository {
  findById(id: string): Promise<User | null>;
}

class UserRepository implements IUserRepository {
  // implementation
}

// BAD: interface for object shape
interface UserData {
  id: string;
  name: string;
}

**Rationale:** Types compose better with unions and intersections, support mapped types, and avoid declaration merging surprises. Interfaces are only for defining what a class must implement.

**IMPORTANT:** Even when under deadline pressure, even when focused on other concerns (financial accuracy, performance optimization, bug fixes), take 2 seconds to ask: "Is this a class contract?" If no, use `type`. Don't default to `interface` out of habit.

Naming Conventions

Type Suffixes

| Suffix | Usage | Example | |--------|-------|---------| | `FooOptions` | Function parameter objects (3+ args or any optional) | `ProcessUserOptions` | | `FooConfig` | Persistent configuration from storage | `DatabaseConfig` | | `FooResult` | Discriminated union return types | `ValidationResult` | | `FooFn` | Function/callback types | `TransformFn<T>` | | `FooProps` | React component props | `ButtonProps` | | `FooState` | State objects (component/application) | `AppState` |

General Casing

| Element | Convention | Example | |---------|-----------|---------| | Variables & functions | camelCase | `userName`, `getUser()` | | Types & classes | PascalCase | `UserData`, `UserService` | | Constants | UPPER_CASE | `MAX_RETRY_COUNT`, `API_ENDPOINT` | | Files | kebab-case | `user-service.ts`, `process-order.ts` |

Boolean Naming

**Use is/has/can/should/will prefixes. Avoid negative names.**

// GOOD
const isActive = true;
const hasPermission = checkPermission();
const canEdit = user.role === 'admin';
const shouldRetry = attempts < MAX_RETRIES;
const willTimeout = elapsed > threshold;

// Also acceptable: adjectives for state
type User = {
  active: boolean;
  visible: boolean;
  disabled: boolean;
};

// BAD: negative names
const isDisabled = false; // prefer isEnabled
const notReady = true;    // prefer isReady

Type Suffix Details

FooOptions - Parameter Objects

**Use for functions with 3+ arguments OR any optional arguments.**

type ProcessUserOptions = {
  readonly name: string;
  readonly email: string;
  readonly age: number;
  readonly sendWelcome?: boolean;
};

// GOOD: destructure in body, not in parameters
function processUser(options: ProcessUserOptions): void {
  const {name, email, age, sendWelcome = true} = options;
  // implementation
}

// BAD: inline destructuring in parameters
function processUser({name, email, age}: {name: string, email: string, age: number}) {
  // causes duplication when destructuring
}

// BAD: not using options pattern for 3+ args
function processUser(name: string, email: string, age: number, sendWelcome?: boolean) {
  // hard to call, positional arguments
}

FooResult - Discriminated Unions

**Always use discriminated unions for Result types. Integrate with neverthrow.**

// GOOD: discriminated union with success/error
type ValidationResult =
  | { success: true; data: ValidUser }
  | { success: false; error: ValidationError };

// GOOD: use neverthrow for Result types
import {Result, ok, err} from 'neverthrow';

type ValidationError = {
  field: string;
  message: string;
};

function validateUser(data: Readonly<UserData>): Result<ValidUser, ValidationError> {
  if (!data.email) {
    return err({field: 'email', message: 'Email is required'});
  }
  return ok({...data, validated: true});
}

// Usage
const result = validateUser(userData);
if (result.isOk()) {
  console.log(result.value); // ValidUser
} else {
  console.error(result.error); // ValidationError
}

**Rule:** Functional Core functions should return `Result<T, E>` types. Imperative Shell functions may throw exceptions for HTTP errors and similar.

Functions

Declaration Style

**Use `function` declarations for top-level functions. Use arrow functions for inline callbacks.**

// GOOD: function decla
Read more
Read it on GitHub ↗

Showing the first part of this file.

Ships withed3d-plugins

This is my collection of plugins that I use on a day-to-day basis for getting stuff done with Claude Code. Most of these are development-oriented in some way or another, but also often end up being useful for other things.

Get the whole plugin, auto-invoked