typescript-type
World Pulse: When adding new types, modifying shared interfaces, or working in `src/shared/types.ts`.
SKILL.md.claude/skills/typescript-type/SKILL.md
GitHubTypeScript Type
When to use: When adding new types, modifying shared interfaces, or working in src/shared/types.ts.
Strict Mode
The project runs TypeScript in strict mode (tsconfig.json):
{
"compilerOptions": {
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler"
}
}
Every variable, parameter, and return type must be explicitly typed or provably inferred. noUnusedLocals/noUnusedParameters cause build failures — prefix intentionally unused parameters with _.
Shared Contract Location
All types shared between frontend, backend, and Electron live in src/shared/types.ts. Do not duplicate types in individual packages.
Changes to src/shared/types.ts are high-risk surface — they require architect review (see risk_surface severity gate).
Core Types (src/shared/types.ts)
// Base event — all collector output extends this
interface Event {
id: string;
timestamp: number;
type: EventType;
source: string;
location: GeoLocation | null;
title: string;
severity?: number; // 0–10 scale
expiresAt?: number; // Unix ms; undefined = no expiry
data: Record<string, unknown>;
}
type EventType =
| 'earthquake' | 'weather' | 'news' | 'astronomy'
| 'volcano' | 'iss' | 'aurora' | 'asteroid' | 'planet'
| 'music' | 'ocean' | 'calendar' | 'historical';
interface GeoLocation { lat: number; lon: number; }
type ConnectionStatus =
| 'connecting' | 'connected' | 'disconnected'
| 'error' | 'dormant-reconnecting';
Adding a New EventType
- Add the string literal to the
EventTypeunion insrc/shared/types.ts. - Create a typed interface extending
Event:
interface MoonEvent extends Event {
type: 'moon';
data: {
phase: string;
illumination: number;
age: number;
};
}
- Export the new interface.
- Add the
EventTypevalue to collectors and tests.
Discriminated Unions
Use discriminated unions for pattern matching on event type:
function renderEvent(event: Event) {
switch (event.type) {
case 'earthquake':
return renderEarthquake(event as EarthquakeEvent);
case 'weather':
return renderWeather(event as WeatherEvent);
default:
return renderGeneric(event);
}
}
Avoiding any
ESLint warns on any. Use these alternatives:
// Unknown input from external APIs
const data: unknown = await response.json();
if (typeof data === 'object' && data !== null && 'magnitude' in data) {
const magnitude = (data as { magnitude: number }).magnitude;
}
// Record for dynamic objects
const props: Record<string, unknown> = {};
// Generic with constraint
function processEvents<T extends Event>(events: T[]): T[] { ... }
Path Aliases
Always use aliases for cross-package imports — never relative paths across boundaries:
import type { Event } from '@shared/types'; // correct
import type { Event } from '../../../shared/types'; // wrong
Anti-Patterns
- Do not use
as any— it defeats strict mode. - Do not cast with
as Tunless you've verified the shape (use a type guard instead). - Do not use
interfacemerging to extendEvent— extend viaextends Event. - Do not put frontend-only or backend-only types in
src/shared/types.ts— only cross-boundary contracts belong there. - Do not use
namespace— use ES module exports.