ed3dai/ed3d-plugins/howto-code-in-typescript

Claude Code and agent skill by ed3dai/ed3d-plugins

Trust tier 2
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

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

Install

npx vskill@latest install ed3dai/ed3d-plugins/howto-code-in-typescript

SKILL.md

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.

typescript
// 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
SuffixUsageExample
FooOptionsFunction parameter objects (3+ args or any optional)ProcessUserOptions
FooConfigPersistent configuration from storageDatabaseConfig
FooResultDiscriminated union return typesValidationResult
FooFnFunction/callback typesTransformFn<T>
FooPropsReact component propsButtonProps
FooStateState objects (component/application)AppState
General Casing
ElementConventionExample
Variables & functionscamelCaseuserName, getUser()
Types & classesPascalCaseUserData, UserService
ConstantsUPPER_CASEMAX_RETRY_COUNT, API_ENDPOINT
Fileskebab-caseuser-service.ts, process-order.ts
Boolean Naming

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

typescript
// 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.

typescript
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.

typescript
// 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.

typescript
// GOOD: function declaration for top-level
function processUser(data: Readonly<UserData>): ProcessResult {
return {success: true, user: data};
}
// GOOD: arrow functions for inline callbacks
const users = rawData.map(u => transformUser(u));
button.addEventListener('click', (e) => handleClick(e));
fetch(url).then(data => processData(data));
// BAD: const arrow for top-level function
const processUser = (data: UserData): ProcessResult => {
return {success: true, user: data};
};

Rationale: Function declarations are hoisted and more visible. Arrow functions capture lexical this and are concise for callbacks.

Const Arrow Functions

Use `const foo = () => {}` declarations only for stable references.

typescript
// GOOD: stable reference for React hooks
const handleSubmit = (event: FormEvent) => {
event.preventDefault();
// implementation
};
useEffect(() => {
// handleSubmit reference is stable
}, [handleSubmit]);
// GOOD: long event listener passed from variable
const handleComplexClick = (event: MouseEvent) => {
// many lines of logic
};
element.addEventListener('click', handleComplexClick);
// BAD: const arrow for regular top-level function
const calculateTotal = (items: Array<Item>): number => {
return items.reduce((sum, item) => sum + item.price, 0);
};
// GOOD: use function declaration
function calculateTotal(items: ReadonlyArray<Item>): number {
return items.reduce((sum, item) => sum + item.price, 0);
}
Parameter Objects

Use parameter objects for 3+ arguments OR any optional arguments.

typescript
// GOOD: options object for 3+ args
type CreateUserOptions = {
readonly name: string;
readonly email: string;
readonly age: number;
readonly newsletter?: boolean;
};
function createUser(options: CreateUserOptions): User {
const {name, email, age, newsletter = false} = options;
// implementation
}
// GOOD: 2 args, but one is optional - use options
type SendEmailOptions = {
readonly to: string;
readonly subject: string;
readonly body?: string;
};
function sendEmail(options: SendEmailOptions): void {
// implementation
}
// GOOD: 2 required args - no options needed
function divide(numerator: number, denominator: number): number {
return numerator / denominator;
}
Async Functions

Always explicitly type Promise returns. Avoid async void.

typescript
// GOOD: explicit Promise return type
async function fetchUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
// GOOD: Promise<void> for side effects
async function saveUser(user: User): Promise<void> {
await fetch('/api/users', {
method: 'POST',
body: JSON.stringify(user),
});
}
// BAD: implicit return type
async function fetchUser(id: string) {
const response = await fetch(`/api/users/${id}`);
return response.json();
}

Prefer async/await over `.then()` chains.

typescript
// GOOD: async/await
async function processUserData(id: string): Promise<ProcessedUser> {
const user = await fetchUser(id);
const enriched = await enrichUserData(user);
return transformUser(enriched);
}
// BAD: promise chains
function processUserData(id: string): Promise<ProcessedUser> {
return fetchUser(id)
.then(user => enrichUserData(user))
.then(enriched => transformUser(enriched));
}
When to Use Async

Be selective with async. Not everything needs to be async. Sync code is simpler to reason about and debug.

Use async for:

  • Network requests, database operations, file I/O
  • Operations that benefit from concurrent execution (Promise.all)
  • External service calls

Stay sync for:

  • Pure calculations and transformations
  • Simple data structure operations
  • Code that doesn't touch external systems
typescript
// GOOD: sync for pure transformation
function transformUser(user: User): TransformedUser {
return {
fullName: `${user.firstName} ${user.lastName}`,
email: user.email.toLowerCase(),
};
}
// GOOD: async for I/O
async function loadAndTransformUser(id: string): Promise<TransformedUser> {
const user = await fetchUser(id);
return transformUser(user); // Sync call inside async function is fine
}
// BAD: unnecessary async
async function transformUser(user: User): Promise<TransformedUser> {
return {
fullName: `${user.firstName} ${user.lastName}`,
email: user.email.toLowerCase(),
};
}

Why this matters: Async adds complexity—error propagation, cleanup, and stack traces become harder to follow. Keep the async boundary as close to the I/O as possible.

Classes

When to Use Classes

Prefer functions over classes, EXCEPT for dependency injection patterns.

typescript
// GOOD: class as dependency container
class UserService {
constructor(
private readonly db: Database,
private readonly logger: Logger,
private readonly cache: Cache,
) {}
async getUser(id: string): Promise<User | null> {
this.logger.info(`Fetching user ${id}`);
const cached = await this.cache.get(`user:${id}`);
if (cached) return cached;
const user = await this.db.users.findById(id);
if (user) await this.cache.set(`user:${id}`, user);
return user;
}
}
// BAD: class with no dependencies
class MathUtils {
add(a: number, b: number): number {
return a + b;
}
}
// GOOD: plain functions
function add(a: number, b: number): number {
return a + b;
}
Class Structure

Use constructor injection into private readonly fields.

typescript
// GOOD: constructor injection, private readonly
class OrderProcessor {
constructor(
private readonly orderRepo: OrderRepository,
private readonly paymentService: PaymentService,
private readonly notifier: NotificationService,
) {}
async processOrder(orderId: string): Promise<void> {
const order = await this.orderRepo.findById(orderId);
// implementation
}
}
// BAD: public mutable fields
class OrderProcessor {
public orderRepo: OrderRepository;
public paymentService: PaymentService;
constructor(orderRepo: OrderRepository, paymentService: PaymentService) {
this.orderRepo = orderRepo;
this.paymentService = paymentService;
}
}
The 'this' Keyword

Use `this` only in class methods. Avoid elsewhere.

typescript
// GOOD: this in class method
class Counter {
private count = 0;

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
249
Repository forks
32
7-day trend score
28

Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 30 Mar 2026, 7 months ago.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/ed3dai/ed3d-plugins/howto-code-in-typescript/badge)](https://verified-skill.com/skills/ed3dai/ed3d-plugins/howto-code-in-typescript)

Something wrong with this skill or its page? Tell us.