Install
openclaw skills install typescript-coderExpert 10x engineer with extensive knowledge of TypeScript fundamentals, migration strategies, and best practices. Use when asked to "add TypeScript", "migra...
openclaw skills install typescript-coderMaster TypeScript development with expert-level knowledge of type systems, migration strategies, and modern JavaScript/TypeScript patterns. This skill transforms you into a 10x engineer capable of writing type-safe, maintainable code and migrating existing JavaScript projects to TypeScript with confidence.
tsconfig.json for a project.ts, .tsx, .mts, or .d.ts filestypescript package (can be installed if needed)Keywords to trigger this skill as if using a command-line tool:
openPrompt = ["typescript-coder", "ts-coder"]
Use these shorthand commands to quickly invoke TypeScript expertise without lengthy explanations. For example:
typescript-coder --check "this code"typescript-coder check this type guardts-coder migrate this filets-coder --migrate project-to-typescriptAs a TypeScript expert, you operate with:
TypeScript uses structural typing (duck typing) rather than nominal typing:
interface Point {
x: number;
y: number;
}
// This works because the object has the right structure
const point: Point = { x: 10, y: 20 };
// This also works - structural compatibility
const point3D = { x: 1, y: 2, z: 3 };
const point2D: Point = point3D; // OK - has x and y
TypeScript infers types when possible, reducing boilerplate:
// Type inferred as string
const message = "Hello, TypeScript!";
// Type inferred as number
const count = 42;
// Type inferred as string[]
const names = ["Alice", "Bob", "Charlie"];
// Return type inferred as number
function add(a: number, b: number) {
return a + b; // Returns number
}
| Feature | Purpose | When to Use |
|---|---|---|
| Interfaces | Define object shapes | Defining data structures, API contracts |
| Type Aliases | Create custom types | Union types, complex types, type utilities |
| Generics | Type-safe reusable components | Functions/classes that work with multiple types |
| Enums | Named constants | Fixed set of related values |
| Type Guards | Runtime type checking | Narrowing union types safely |
| Utility Types | Transform types | Partial<T>, Pick<T, K>, Omit<T, K>, etc. |
For a new or existing JavaScript project:
Install TypeScript as a dev dependency:
npm install --save-dev typescript
Install type definitions for Node.js (if using Node.js):
npm install --save-dev @types/node
Initialize TypeScript configuration:
npx tsc --init
Configure tsconfig.json for your project:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Add build script to package.json:
{
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"check": "tsc --noEmit"
}
}
Safe, incremental migration strategy:
Enable TypeScript to process JavaScript files:
{
"compilerOptions": {
"allowJs": true,
"checkJs": false,
"noEmit": true
}
}
Add JSDoc type annotations to JavaScript files (optional intermediate step):
// @ts-check
/**
* Calculates the sum of two numbers
* @param {number} a - First number
* @param {number} b - Second number
* @returns {number} The sum
*/
function add(a, b) {
return a + b;
}
/** @type {string[]} */
const names = ["Alice", "Bob"];
/** @typedef {{ id: number, name: string, email?: string }} User */
/** @type {User} */
const user = {
id: 1,
name: "Alice"
};
Rename files incrementally from .js to .ts:
# Start with utility files and leaf modules
mv src/utils/helpers.js src/utils/helpers.ts
Fix TypeScript errors in converted files:
any types appropriatelyGradually convert remaining files:
Enable strict mode progressively:
{
"compilerOptions": {
"strict": false,
"noImplicitAny": true,
"strictNullChecks": true
// Enable other strict flags one at a time
}
}
Creating robust type definitions:
Define interfaces for data structures:
// User data model
interface User {
id: number;
name: string;
email: string;
age?: number; // Optional property
readonly createdAt: Date; // Read-only property
}
// API response structure
interface ApiResponse<T> {
success: boolean;
data?: T;
error?: {
code: string;
message: string;
};
}
Use type aliases for complex types:
// Union type
type Status = 'pending' | 'active' | 'completed' | 'failed';
// Intersection type
type Employee = User & {
employeeId: string;
department: string;
salary: number;
};
// Function type
type TransformFn<T, U> = (input: T) => U;
// Conditional type
type NonNullable<T> = T extends null | undefined ? never : T;
Create type definitions in .d.ts files for external modules:
// types/custom-module.d.ts
declare module 'custom-module' {
export interface Config {
apiKey: string;
timeout?: number;
}
export function initialize(config: Config): Promise<void>;
export function fetchData<T>(endpoint: string): Promise<T>;
}
Type-safe reusable components:
Generic functions:
// Basic generic function
function identity<T>(value: T): T {
return value;
}
const num = identity(42); // Type: number
const str = identity("hello"); // Type: string
// Generic with constraints
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "Alice", age: 30 };
const name = getProperty(user, "name"); // Type: string
const age = getProperty(user, "age"); // Type: number
Generic classes:
class DataStore<T> {
private items: T[] = [];
add(item: T): void {
this.items.push(item);
}
get(index: number): T | undefined {
return this.items[index];
}
filter(predicate: (item: T) => boolean): T[] {
return this.items.filter(predicate);
}
}
const numberStore = new DataStore<number>();
numberStore.add(42);
const userStore = new DataStore<User>();
userStore.add({ id: 1, name: "Alice", email: "alice@example.com" });
Generic interfaces:
interface Repository<T> {
findById(id: string): Promise<T | null>;
findAll(): Promise<T[]>;
create(item: Omit<T, 'id'>): Promise<T>;
update(id: string, item: Partial<T>): Promise<T>;
delete(id: string): Promise<boolean>;
}
class UserRepository implements Repository<User> {
async findById(id: string): Promise<User | null> {
// Implementation
return null;
}
// ... other methods
}
Common type errors and solutions:
"Property does not exist" errors:
// ❌ Error: Property 'name' does not exist on type '{}'
const user = {};
user.name = "Alice";
// ✅ Solution 1: Define interface
interface User {
name: string;
}
const user: User = { name: "Alice" };
// ✅ Solution 2: Type assertion (use cautiously)
const user = {} as User;
user.name = "Alice";
// ✅ Solution 3: Index signature
interface DynamicObject {
[key: string]: any;
}
const user: DynamicObject = {};
user.name = "Alice";
"Cannot find name" errors:
// ❌ Error: Cannot find name 'process'
const env = process.env.NODE_ENV;
// ✅ Solution: Install type definitions
// npm install --save-dev @types/node
const env = process.env.NODE_ENV; // Now works
any type issues:
// ❌ Implicit any (with noImplicitAny: true)
function process(data) {
return data.value;
}
// ✅ Solution: Add explicit types
function process(data: { value: number }): number {
return data.value;
}
// ✅ Or use generic
function process<T>(data: T): T {
return data;
}
Union type narrowing:
function processValue(value: string | number) {
// ❌ Error: Property 'toUpperCase' does not exist on type 'string | number'
return value.toUpperCase();
// ✅ Solution: Type guard
if (typeof value === "string") {
return value.toUpperCase(); // TypeScript knows it's string here
}
return value.toString();
}
Environment-specific configurations:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"types": ["node"],
"moduleResolution": "node",
"esModuleInterop": true
}
}
{
"compilerOptions": {
"target": "ES2020",
"module": "esnext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"moduleResolution": "bundler",
"resolveJsonModule": true,
"noEmit": true
}
}
{
"compilerOptions": {
"target": "ES2020",
"module": "esnext",
"declaration": true,
"declarationMap": true,
"outDir": "./dist",
"rootDir": "./src"
}
}
"strict": true) for maximum type safetyunknown instead of any - forces type checking before useas const) for literal typesany everywhere - defeats the purpose of TypeScript@ts-ignore without good reasonstrictNullChecks@types/*Type-safe state management:
type LoadingState = { status: 'loading' };
type SuccessState<T> = { status: 'success'; data: T };
type ErrorState = { status: 'error'; error: Error };
type AsyncState<T> = LoadingState | SuccessState<T> | ErrorState;
function handleState<T>(state: AsyncState<T>) {
switch (state.status) {
case 'loading':
console.log('Loading...');
break;
case 'success':
console.log('Data:', state.data); // TypeScript knows state.data exists
break;
case 'error':
console.log('Error:', state.error.message); // TypeScript knows state.error exists
break;
}
}
Type-safe fluent API:
class QueryBuilder<T> {
private filters: Array<(item: T) => boolean> = [];
private sortFn?: (a: T, b: T) => number;
private limitCount?: number;
where(predicate: (item: T) => boolean): this {
this.filters.push(predicate);
return this;
}
sortBy(compareFn: (a: T, b: T) => number): this {
this.sortFn = compareFn;
return this;
}
limit(count: number): this {
this.limitCount = count;
return this;
}
execute(data: T[]): T[] {
let result = data.filter(item =>
this.filters.every(filter => filter(item))
);
if (this.sortFn) {
result = result.sort(this.sortFn);
}
if (this.limitCount) {
result = result.slice(0, this.limitCount);
}
return result;
}
}
// Usage
const users = [/* ... */];
const result = new QueryBuilder<User>()
.where(u => u.age > 18)
.where(u => u.email.includes('@example.com'))
.sortBy((a, b) => a.name.localeCompare(b.name))
.limit(10)
.execute(users);
type EventMap = {
'user:created': { id: string; name: string };
'user:updated': { id: string; changes: Partial<User> };
'user:deleted': { id: string };
};
class TypedEventEmitter<T extends Record<string, any>> {
private listeners: { [K in keyof T]?: Array<(data: T[K]) => void> } = {};
on<K extends keyof T>(event: K, listener: (data: T[K]) => void): void {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event]!.push(listener);
}
emit<K extends keyof T>(event: K, data: T[K]): void {
const eventListeners = this.listeners[event];
if (eventListeners) {
eventListeners.forEach(listener => listener(data));
}
}
}
// Usage with type safety
const emitter = new TypedEventEmitter<EventMap>();
emitter.on('user:created', (data) => {
console.log(data.id, data.name); // TypeScript knows the shape
});
emitter.emit('user:created', { id: '123', name: 'Alice' }); // Type-checked
| Issue | Cause | Solution |
|---|---|---|
| Module not found | Missing type definitions | Install @types/[package-name] or add declare module |
| Implicit any errors | noImplicitAny enabled | Add explicit type annotations |
| Cannot find global types | Missing lib in compilerOptions | Add to lib: ["ES2020", "DOM"] |
| Type errors in node_modules | Third-party library types | Add skipLibCheck: true to tsconfig.json |
| Import errors with .ts extension | Import resolving issues | Use imports without extensions |
| Build takes too long | Compiling too many files | Use incremental: true and tsBuildInfoFile |
| Type inference not working | Complex inferred types | Add explicit type annotations |
| Circular dependency errors | Import cycles | Refactor to break cycles, use interfaces |
Transform existing types:
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type Partial<T> = {
[P in keyof T]?: T[P];
};
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
// Usage
interface User {
id: number;
name: string;
email: string;
}
type ReadonlyUser = Readonly<User>; // All properties readonly
type PartialUser = Partial<User>; // All properties optional
type UserNameEmail = Pick<User, 'name' | 'email'>; // Only name and email
Types that depend on conditions:
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
// Practical example: Extract function return type
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function getUser() {
return { id: 1, name: "Alice" };
}
type User = ReturnType<typeof getUser>; // { id: number; name: string }
String manipulation at type level:
type Greeting<T extends string> = `Hello, ${T}!`;
type WelcomeMessage = Greeting<"World">; // "Hello, World!"
// Practical: Create event names
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<"click">; // "onClick"
type HoverEvent = EventName<"hover">; // "onHover"
tsconfig.json Options| Option | Purpose | Recommended |
|---|---|---|
strict | Enable all strict type checking | true |
target | ECMAScript target version | ES2020 or higher |
module | Module system | commonjs (Node) or esnext (bundlers) |
lib | Include type definitions | ["ES2020"] + DOM if browser |
outDir | Output directory | ./dist |
rootDir | Root source directory | ./src |
sourceMap | Generate source maps | true for debugging |
declaration | Generate .d.ts files | true for libraries |
esModuleInterop | Enable interop between CommonJS and ES modules | true |
skipLibCheck | Skip type checking of .d.ts files | true for performance |
forceConsistentCasingInFileNames | Enforce consistent file casing | true |
resolveJsonModule | Allow importing JSON files | true if needed |
allowJs | Allow JavaScript files | true during migration |
checkJs | Type check JavaScript files | false during migration |
noEmit | Don't emit files (use external bundler) | true with bundlers |
incremental | Enable incremental compilation | true for faster builds |
When migrating a JavaScript project to TypeScript:
tsconfig.json with permissive settingsallowJs: true and checkJs: false.tsnoImplicitAny: truestrictNullChecks: trueany types where possiblestrict: trueThis skill includes bundled reference documentation for TypeScript essentials.
references/)The TypeScript Coder skill empowers you to write type-safe, maintainable code with expert-level TypeScript knowledge. Whether migrating existing JavaScript projects or starting new TypeScript projects, apply these proven patterns, workflows, and best practices to deliver production-quality code with confidence.
Remember: TypeScript is a tool for developer productivity and code quality. Use it to catch errors early, improve code documentation, and enable better tooling—but don't let perfect types prevent shipping working code.