Skip to content

TypeScript Usage

The iRacing Data Client SDK is built with TypeScript first, providing complete type safety and excellent developer experience. This guide shows you how to leverage TypeScript features effectively.

🎯 IntelliSense Support

Get autocomplete suggestions for all methods, parameters, and response properties in your IDE.

🛡️ Compile-Time Checking

Catch errors before runtime with TypeScript’s static type checking.

📚 Self-Documenting Code

Types serve as inline documentation, making code more readable and maintainable.

The SDK automatically infers all types - no manual type annotations needed:

// TypeScript knows all the types automatically
const iracing = new IRacingDataClient({ /* ... */ });
// 'info' is typed as MemberInfoResponse
const info = await iracing.member.info();
// TypeScript knows these properties exist
console.log(info.custId); // ✅ Valid
console.log(info.displayName); // ✅ Valid
console.log(info.wrongProp); // ❌ TypeScript error

Import specific types when needed:

import {
IRacingDataClient,
type MemberInfoResponse,
type CarGetResponse,
type License
} from 'iracing-data-client';
// Use types for function parameters
function processLicense(license: License) {
console.log(`Class: ${license.licenseLevel}`);
console.log(`SR: ${license.safetyRating / 100}`);
}
// Use types for variables
let memberData: MemberInfoResponse | null = null;
memberData = await iracing.member.info();

TypeScript provides full type safety for nested properties:

const info = await iracing.member.info();
// TypeScript knows the structure
console.log(info.displayName);
console.log(info.licenses.oval.irating);
console.log(info.licenses.sportsCar.safetyRating);
// Optional-style handling still works where fields may be nullable
if (info.account.countryRules) {
console.log('Country-specific rules are configured');
}
// Type narrowing with conditional checks
const results = await iracing.results.get({ subsessionId: 12345 });
if (results.sessionResults.length > 0) {
const session = results.sessionResults[0];
// TypeScript knows session is defined here
session.results.forEach(result => {
if (result.reasonOut === 'Running') {
// Type narrowed to running cars
console.log(`P${result.finishPosition}: ${result.displayName}`);
}
});
}

The SDK enforces required parameters at compile time:

// ❌ TypeScript error - missing required parameter
await iracing.member.get();
// ✅ Valid - required parameter provided
await iracing.member.get({
custIds: [123456]
});
// Optional parameters
await iracing.results.searchSeries({
seasonId: 3456, // Required
raceWeekNum: 5, // Optional
official: true // Optional
});

All parameters are validated using Zod schemas:

// The SDK validates types at runtime too
try {
await iracing.member.get({
custIds: 'not-an-array' // ❌ TypeScript error
});
} catch (error) {
// Zod validation error if types are bypassed
}

Some endpoints support additional semantic preflight checks beyond shape validation.

const iracing = new IRacingDataClient({
auth: {
type: 'password-limited',
clientId: process.env.IRACING_CLIENT_ID!,
clientSecret: process.env.IRACING_CLIENT_SECRET!,
username: process.env.IRACING_USERNAME!,
password: process.env.IRACING_PASSWORD!,
},
validateParams: true, // Keep Zod schema checks enabled
validateSemanticParams: false, // Disable extra metadata-based preflight checks
});

Use this when you want strict type/schema validation but do not want additional API metadata requests.

Create reusable typed helpers:

// Generic retry function with type preservation
async function retryWithBackoff<T>(
fn: () => Promise<T>,
retries = 3
): Promise<T> {
try {
return await fn();
} catch (error) {
if (retries > 0) {
await new Promise(r => setTimeout(r, 1000));
return retryWithBackoff(fn, retries - 1);
}
throw error;
}
}
// Type is preserved through the helper
const data = await retryWithBackoff(
() => iracing.member.info()
);
// 'data' is typed as MemberInfoResponse

Create custom type guards:

import { IRacingError } from 'iracing-data-client';
// Type predicate function
function isRateLimitError(error: unknown): error is IRacingError {
return error instanceof IRacingError && error.isRateLimited;
}
try {
const data = await iracing.member.info();
} catch (error) {
if (isRateLimitError(error)) {
// TypeScript knows error is IRacingError here
console.log(`Rate limited with status: ${error.status}`);
}
}

Work with different response types:

type RaceResult =
| { status: 'finished'; position: number; time: string }
| { status: 'dnf'; reason: string; lapsCompleted: number }
| { status: 'dq'; reason: string };
function processResult(result: RaceResult) {
switch (result.status) {
case 'finished':
// TypeScript knows 'position' exists here
console.log(`Finished P${result.position}`);
break;
case 'dnf':
// TypeScript knows 'lapsCompleted' exists here
console.log(`DNF after ${result.lapsCompleted} laps`);
break;
case 'dq':
console.log(`DQ: ${result.reason}`);
break;
}
}

Extract nested types from responses:

import type { MemberGetResponse } from 'iracing-data-client';
// Extract the member type from the response
type Member = MemberGetResponse['members'][0];
// Extract license type
type License = Member['licenses'][0];
// Use extracted types
function formatLicense(license: License): string {
return `${license.categoryName} - Class ${license.licenseLevel}`;
}

Use TypeScript utilities for flexible types:

import type { MemberGetParams } from 'iracing-data-client';
// Make all properties optional
type PartialParams = Partial<MemberGetParams>;
// Make all properties required
type StrictParams = Required<MemberGetParams>;
// Pick specific properties
type IdOnly = Pick<MemberGetParams, 'custIds'>;
// Omit properties
type NoLicenses = Omit<MemberGetParams, 'includeLicenses'>;

Optimize VS Code for TypeScript development:

.vscode/settings.json
{
"typescript.preferences.includePackageJsonAutoImports": "on",
"typescript.updateImportsOnFileMove.enabled": "always",
"typescript.suggest.autoImports": true,
"typescript.inlayHints.parameterNames.enabled": "all",
"typescript.inlayHints.propertyDeclarationTypes.enabled": true
}

Recommended tsconfig.json settings:

{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"lib": ["ES2022"],
"strict": true,
"strictNullChecks": true,
"noImplicitAny": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

Create a type-safe query builder:

class MemberQueryBuilder {
private params: Partial<MemberGetParams> = {};
withIds(...ids: number[]): this {
this.params.custIds = ids;
return this;
}
includeLicenses(): this {
this.params.includeLicenses = true;
return this;
}
async execute(): Promise<MemberGetResponse> {
if (!this.params.custIds) {
throw new Error('Customer IDs required');
}
return iracing.member.get(this.params as MemberGetParams);
}
}
// Usage with chaining
const members = await new MemberQueryBuilder()
.withIds(123456, 789012)
.includeLicenses()
.execute();

Create type-safe data repositories:

class MemberRepository {
constructor(private iracing: IRacingDataClient) {}
async findById(id: number): Promise<Member | null> {
const response = await this.iracing.member.get({
custIds: [id]
});
return response.members[0] || null;
}
async findByIds(ids: number[]): Promise<Member[]> {
const response = await this.iracing.member.get({
custIds: ids
});
return response.members;
}
async getCurrentMember(): Promise<MemberInfo> {
return this.iracing.member.info();
}
}

Problem: Types not recognized in IDE

Solution:

Terminal window
# Restart TypeScript server in VS Code
Cmd/Ctrl + Shift + P -> "TypeScript: Restart TS Server"
# Or reinstall types
npm install --save-dev @types/node