| name | typescript-patterns |
| description | TypeScript patterns and best practices for StackOne SDK |
TypeScript Patterns and Best Practices
This skill provides guidance on TypeScript patterns and best practices for writing clean, type-safe code in the StackOne SDK.
Exhaustiveness Checking with satisfies never
When branching on string unions, use the satisfies never pattern to guarantee compile-time exhaustiveness without introducing temporary variables.
Pattern:
switch (bodyType) {
case 'json':
// ...
break;
case 'form':
// ...
break;
case 'multipart-form':
// ...
break;
default: {
bodyType satisfies never; // Error if new variant added
throw new Error(`Unsupported HTTP body type: ${String(bodyType)}`);
}
}
Benefits:
- Adding a new union member will trigger a compile-time error
- Keeps union definition and switch statement in sync automatically
- No temporary variables needed
- Clear intent about exhaustiveness checking
Avoiding any Type
Always use specific types instead of any when possible.
Bad:
function processData(data: any): any {
return data.value;
}
Good:
function processData<T extends { value: U }, U>(data: T): U {
return data.value;
}
// Or with type narrowing
function processData(data: unknown): unknown {
if (typeof data === 'object' && data !== null && 'value' in data) {
return (data as Record<string, unknown>).value;
}
throw new Error('Invalid data format');
}
Alternatives to any:
- Use
unknownwhen type is truly not known, then narrow with type guards - Use
Record<string, unknown>for objects with unknown property values - Use union types to represent multiple possible types
- Use generics with type constraints
Non-null Assertions (!) - Avoid
Never use non-null assertions. Instead, use proper null checking.
Bad:
function getConfig(configMap: Map<string, Config>): Config {
const config = configMap.get('default');
return config!; // Dangerous!
}
Good:
function getConfig(configMap: Map<string, Config>): Config {
const config = configMap.get('default');
if (!config) {
throw new Error('Default config not found');
}
return config;
}
// Or with optional chaining and nullish coalescing
function getConfig(configMap: Map<string, Config>): Config {
return configMap.get('default') ?? getDefaultConfig();
}
Parameter Reassignment - Avoid
Create new variables instead of reassigning function parameters.
Bad:
function mergeOptions(options: Options, overrides?: Options): Options {
options = { ...options, ...overrides }; // Reassignment
return options;
}
Good:
function mergeOptions(options: Options, overrides?: Options): Options {
const mergedOptions = { ...options, ...overrides };
return mergedOptions;
}
Classes with Only Static Members - Avoid
Use namespaces or simple exported functions instead.
Bad:
export class Utils {
public static formatDate(date: Date): string {
return date.toISOString();
}
public static parseDate(dateStr: string): Date {
return new Date(dateStr);
}
}
Good - Namespace:
export namespace Utils {
export const formatDate = (date: Date): string => {
return date.toISOString();
};
export const parseDate = (dateStr: string): Date => {
return new Date(dateStr);
};
}
Better - Simple exports:
export const formatDate = (date: Date): string => {
return date.toISOString();
};
export const parseDate = (dateStr: string): Date => {
return new Date(dateStr);
};
Explicit Return Types
Always specify return types for functions.
Bad:
const calculateTotal = (items: Item[]) => {
return items.reduce((sum, item) => sum + item.price, 0);
};
Good:
const calculateTotal = (items: Item[]): number => {
return items.reduce((sum, item) => sum + item.price, 0);
};
// Or for function declarations
function calculateTotal(items: Item[]): number {
return items.reduce((sum, item) => sum + item.price, 0);
}
Property Deletion - Type-Safe Methods
Use proper type-safe methods to remove properties instead of setting to undefined or using delete.
Bad:
function removeProperty(obj: Record<string, JSONSchema7Definition>): void {
obj['propertyToRemove'] = undefined; // Type error!
delete obj['propertyToRemove']; // Linter warning
}
Good - Destructuring (Immutable):
function removeProperty(obj: Record<string, JSONSchema7Definition>): Record<string, JSONSchema7Definition> {
const { propertyToRemove, ...rest } = obj;
return rest;
}
Good - Delete (In-place):
function removeProperty(obj: Record<string, JSONSchema7Definition>): void {
delete obj['propertyToRemove'];
}
Recommendations
- Use
satisfies neverfor all union type switches - Prefer
unknownoveranyand use type guards - Use optional chaining (
?.) and nullish coalescing (??) - Always specify return types
- Use destructuring for immutable property removal
- Write functions as simple exports, not class static methods
- Create new variables instead of reassigning parameters