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", "migrate to TypeScript", "add type checking", "create TypeScript config", "fix TypeScript errors", or work with .ts/.tsx files. Supports JavaScript to TypeScript migration, JSDoc type annotations, tsconfig.json configuration, and type-safe code patterns.
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.