Package Exports
- tasc-ts
- tasc-ts/build/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (tasc-ts) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
๐ท TASC-TS
Typed API Schema Client - Generate type-safe API clients from OpenAPI specs
Transform your OpenAPI specification into a fully-typed, production-ready API client with one command.
โจ Features:
- ๐ฏ Full TypeScript type safety - Generate types directly from your OpenAPI specs
- โจ Pre-typed utility types - No need to pass generics manually, everything just works
- ๐ Auto-generate types and operations - One command to generate everything you need
- ๐จ Configurable interceptors - Add authentication, logging, and error handling
- ๐ Watch mode - Automatically regenerate when your API changes
- ๐ Zero config - Works out of the box with sensible defaults
- ๐ช Dual API styles - Use path-based or operation-based methods
npm install tasc-ts๐ Table of Contents
- ๐ Quick Start
- โ๏ธ Configuration
- ๐ง CLI Reference
- ๐ API Client Guide
- ๐ก Examples
- ๐ API Reference
- ๐ Troubleshooting
- โ FAQ
- ๐ ๏ธ Recommended Package Scripts
- ๐ค Contributing
- ๐ License
๐ Quick Start
Get up and running in under 5 minutes:
1. Install
npm install tasc-ts2. Initialize
npx tasc initThis creates a tasc.config.ts file in your project root.
3. Configure
Edit tasc.config.ts to point to your OpenAPI spec:
import type { ConfigOptions } from "tasc-ts";
const config: ConfigOptions = {
api_doc_url: "https://api.example.com/openapi.json",
environment: "development",
outputs: {
base_path: "src",
}
};
export default config;4. Generate Types
npx tasc generateThis fetches your OpenAPI spec and generates TypeScript types and operations.
5. Create and Use Your API Client
import { createTypedApiClient } from './.tasc/operations';
// Create a typed API client with full IntelliSense support
// All paths are automatically derived from your tasc.config.ts
export const api = createTypedApiClient({
baseURL: 'https://api.example.com',
interceptors: {
request: (config) => {
// Add authentication token
const token = localStorage.getItem('auth_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}
}
});
// Use path-based API
const user = await api.get('/users/{id}', { id: '123' });
// Or use operation-based API with full IntelliSense! (using operationId from OpenAPI spec)
const data = await api.op.getUserById({ id: '123' });โ๏ธ Configuration
Config File Overview
The tasc.config.ts file controls how types are generated and where they're saved.
Location: tasc.config.ts (project root)
Type: ConfigOptions
Format: TypeScript or JSON
Configuration Options Reference
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
api_doc_url |
string |
No | http://localhost:8080/doc/openapi.json |
URL to your OpenAPI specification |
environment |
string |
No | development |
Environment name (for reference) |
poll_interval_ms |
number |
No | 5000 |
Polling interval for watch mode (milliseconds) |
outputs.base_path |
string |
No | "" |
Base directory for all outputs |
outputs.dir |
string |
No | "" |
Override output directory (relative to base_path) |
outputs.api_types |
string |
No | ".tasc/types.ts" |
Path for generated types file |
outputs.api_operations |
string |
No | ".tasc/operations.ts" |
Path for generated operations file |
outputs.doc_file |
string |
No | ".tasc/openapi.json" |
Path to save the OpenAPI spec |
Path Resolution Logic
TASC-TS uses intelligent path resolution to give you flexibility:
// Example 1: Default (no config)
outputs: {}
// Result:
// - .tasc/types.ts
// - .tasc/operations.ts
// - .tasc/openapi.json
// Example 2: With base_path
outputs: { base_path: "src" }
// Result:
// - src/.tasc/types.ts
// - src/.tasc/operations.ts
// - src/.tasc/openapi.json
// Example 3: With dir (overrides default directory)
outputs: { dir: "generated" }
// Result:
// - generated/types.ts
// - generated/operations.ts
// - generated/openapi.json
// Example 4: base_path + dir
outputs: { base_path: "src", dir: "api" }
// Result:
// - src/api/types.ts
// - src/api/operations.ts
// - src/api/openapi.json
// Example 5: Custom individual paths
outputs: {
base_path: "src",
api_types: "types/api.ts",
api_operations: "lib/operations.ts",
doc_file: "openapi.json"
}
// Result:
// - src/types/api.ts
// - src/lib/operations.ts
// - src/openapi.jsonFull Config Example
import type { ConfigOptions } from "tasc-ts";
const config: ConfigOptions = {
// URL to your OpenAPI specification
api_doc_url: "https://api.example.com/v1/openapi.json",
// Environment identifier (for your reference)
environment: "production",
// Watch mode polling interval (5 seconds)
poll_interval_ms: 5000,
// Output configuration
outputs: {
// Base path for all generated files
base_path: "src",
// Override default directory (optional)
dir: "api-client",
// Custom paths (optional)
api_types: "types.ts",
api_operations: "operations.ts",
doc_file: "openapi.json"
}
};
export default config;๐ง CLI Reference
tasc init
Initialize a new TASC configuration file.
Usage: tasc init [options]
Options:
-f, --force Overwrite existing config file
-h, --help Display help
Examples:
tasc init # Create new config file
tasc init --force # Overwrite existing configWhat it does:
- Creates a
tasc.config.tsfile in your project root - Pre-filled with sensible defaults
- Ready to customize for your API
tasc config
Display your current configuration.
Usage: tasc config
Description:
Display the current TASC configuration
Example:
tasc configWhat it does:
- Loads your
tasc.config.tsfile - Displays all configuration options
- Shows resolved output paths
tasc generate
Generate types and operations from your OpenAPI spec (one-time).
Usage: tasc generate
Description:
Generate TypeScript types and operations from OpenAPI spec
Steps performed:
1. Fetch OpenAPI spec from api_doc_url
2. Save spec to outputs.doc_file
3. Generate TypeScript types file
4. Generate API operations file
Example:
tasc generateWhat it does:
- Fetches your OpenAPI specification
- Generates fully-typed TypeScript definitions
- Creates operation helper functions
- Saves everything to configured output paths
tasc watch
Watch for API changes and automatically regenerate.
Usage: tasc watch
Description:
Watch for API changes and regenerate types automatically
Features:
- Polls API every poll_interval_ms (default: 5s)
- Detects changes via SHA-256 hash comparison
- Only regenerates when changes are detected
- Graceful shutdown with Ctrl+C
Example:
tasc watchWhat it does:
- Continuously monitors your API for changes
- Automatically regenerates types when API changes
- Perfect for active development
- Press
Ctrl+Cto stop
๐ API Client Guide
Creating a Client Instance
import { createTypedApiClient } from './.tasc/operations';
// Basic client
export const api = createTypedApiClient({
baseURL: process.env.API_URL
});
// Advanced client with configuration
export const api = createTypedApiClient({
baseURL: process.env.API_URL,
timeout: 10000,
withCredentials: true,
headers: {
'X-Custom-Header': 'value',
'Content-Type': 'application/json'
}
});Note: The createTypedApiClient function is generated in your operations file. The import path is automatically determined by your tasc.config.ts output configuration.
Path-Based API
Use REST paths directly with full type safety:
// GET request
const user = await api.get('/users/{id}', { id: '123' });
console.log(user.data); // Fully typed!
// POST request
const newUser = await api.post('/users', {
name: 'John Doe',
email: 'john@example.com'
});
// PUT request
const updated = await api.put('/users/{id}', { id: '123' }, {
name: 'Jane Doe'
});
// DELETE request
await api.delete('/users/{id}', { id: '123' });
// PATCH request
const patched = await api.patch('/users/{id}', { id: '123' }, {
email: 'newemail@example.com'
});
// With query parameters
const users = await api.get('/users', {
params: {
limit: 10,
offset: 0,
sort: 'name'
}
});Operation-Based API
Use operation IDs from your OpenAPI spec:
// Using operationId from OpenAPI spec
const user = await api.op.getUserById({ id: '123' });
const users = await api.op.listUsers({
params: { limit: 10, offset: 0 }
});
const created = await api.op.createUser({
name: 'John Doe',
email: 'john@example.com'
});Authentication Examples
Bearer Token
import { createTypedApiClient } from './.tasc/operations';
export const api = createTypedApiClient({
baseURL: process.env.API_URL,
interceptors: {
request: (config) => {
// Get token from localStorage
const token = localStorage.getItem('auth_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}
}
});API Key
import { createTypedApiClient } from './.tasc/operations';
export const api = createTypedApiClient({
baseURL: process.env.API_URL,
headers: {
'X-API-Key': process.env.API_KEY!
}
});OAuth with Refresh Token
import { createTypedApiClient } from './.tasc/operations';
export const api = createTypedApiClient({
baseURL: process.env.API_URL,
interceptors: {
request: async (config) => {
// Get current access token
let token = getAccessToken();
// Check if token is expired
if (isTokenExpired(token)) {
token = await refreshAccessToken();
}
config.headers.Authorization = `Bearer ${token}`;
return config;
},
responseError: async (error) => {
// Handle 401 unauthorized
if (error.response?.status === 401) {
await handleUnauthorized();
}
return Promise.reject(error);
}
}
});Error Handling
import axios from 'axios';
try {
const user = await api.get('/users/{id}', { id: '123' });
console.log('User:', user.data);
} catch (error) {
if (axios.isAxiosError(error)) {
// HTTP error from the server
console.error('Status:', error.response?.status);
console.error('Data:', error.response?.data);
console.error('Headers:', error.response?.headers);
} else {
// Network error or other error
console.error('Error:', error);
}
}Type Extraction
Extract specific types from your generated operations file (not from tasc-ts!):
import {
createTypedApiClient,
RequestBody,
ResponseData,
Raw,
PathParams,
QueryParams,
type paths
} from './.tasc/operations';
// These utility types are pre-typed to your API!
// No need to pass the paths generic anymore!
// Extract request body type for POST /users
type CreateUserInput = RequestBody<'/users', 'post'>;
// Extract response type for GET /users/{id}
type UserResponse = ResponseData<'/users/{id}', 'get'>;
// Extract response for specific status code
type UserCreatedResponse = ResponseData<'/users', 'post', 201>;
// If your API wraps responses in { success, data, status }, use Raw to unwrap:
type AddressData = Raw<ResponseData<'/address', 'post'>>;
// Extracts just the inner data property
// Extract path parameters for /users/{id}
type UserPathParams = PathParams<'/users/{id}'>;
// Result: { id: string | number }
// Extract query parameters for GET /users
type UsersQueryParams = QueryParams<'/users', 'get'>;
// Use in your functions with full type safety
async function createUser(input: CreateUserInput): Promise<UserResponse> {
const response = await api.post('/users', input);
return response.data;
}
// For wrapped responses
async function createAddress(input: RequestBody<'/address', 'post'>): Promise<Raw<ResponseData<'/address', 'post'>>> {
const response = await api.op.createAddress(input);
return response.data.data; // First .data is Axios, second is your wrapper
}๐ก Pro Tip: Import everything from your generated .tasc/operations.ts file - it has all the utility types pre-configured for your specific API!
๐ก Examples
Next.js Integration
// src/lib/api-client.ts
import { createTypedApiClient } from '@/.tasc/operations';
export const api = createTypedApiClient({
baseURL: process.env.NEXT_PUBLIC_API_URL,
interceptors: {
request: (config) => {
// Only access localStorage in browser
if (typeof window !== 'undefined') {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
}
return config;
}
}
});
// app/users/[id]/page.tsx
import { api } from '@/lib/api-client';
export default async function UserPage({ params }: { params: { id: string } }) {
// Full IntelliSense on both path-based and operation-based APIs!
const user = await api.get('/users/{id}', { id: params.id });
// OR: const user = await api.op.getUserById({ id: params.id });
return (
<div>
<h1>{user.data.name}</h1>
<p>{user.data.email}</p>
</div>
);
}React Query Integration
// hooks/useUser.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { api } from '@/lib/api-client';
// Fetch user
export function useUser(id: string) {
return useQuery({
queryKey: ['user', id],
queryFn: () => api.op.getUserById({ id })
});
}
// Create user
export function useCreateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: { name: string; email: string }) =>
api.op.createUser(data),
onSuccess: () => {
// Invalidate users list
queryClient.invalidateQueries({ queryKey: ['users'] });
}
});
}
// Component usage
function UserProfile({ userId }: { userId: string }) {
const { data, isLoading, error } = useUser(userId);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error loading user</div>;
return (
<div>
<h1>{data?.data.name}</h1>
<p>{data?.data.email}</p>
</div>
);
}Express.js Backend
import express from 'express';
import { createTypedApiClient } from './.tasc/operations';
// Create API client for internal API with full type safety
const api = createTypedApiClient({
baseURL: process.env.INTERNAL_API_URL,
timeout: 5000
});
const app = express();
// Proxy endpoint with type safety
app.get('/proxy/users/:id', async (req, res) => {
try {
const user = await api.get('/users/{id}', { id: req.params.id });
res.json(user.data);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch user' });
}
});
// Create user endpoint with IntelliSense
app.post('/proxy/users', async (req, res) => {
try {
const newUser = await api.op.createUser(req.body);
res.status(201).json(newUser.data);
} catch (error) {
res.status(500).json({ error: 'Failed to create user' });
}
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});Testing with Vitest
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { createTypedApiClient } from './.tasc/operations';
describe('API Client', () => {
let api: ReturnType<typeof createTypedApiClient>;
beforeAll(() => {
// Create test API client
api = createTypedApiClient({
baseURL: 'http://localhost:3000'
});
});
it('should fetch user by id', async () => {
const response = await api.get('/users/{id}', { id: '123' });
expect(response.data).toBeDefined();
expect(response.data.id).toBe('123');
expect(response.data.name).toBeTruthy();
});
it('should create a new user', async () => {
const newUser = {
name: 'Test User',
email: 'test@example.com'
};
const response = await api.post('/users', newUser);
expect(response.status).toBe(201);
expect(response.data.name).toBe(newUser.name);
expect(response.data.email).toBe(newUser.email);
});
it('should test operations with IntelliSense', async () => {
// Test operation-based API
const user = await api.op.getUserById({ id: '123' });
expect(user.data).toBeDefined();
});
it('should handle errors gracefully', async () => {
await expect(
api.get('/users/{id}', { id: 'invalid' })
).rejects.toThrow();
});
});๐ API Reference
createApiClient<T, TOperations>(config?)
Create a typed API client instance with full IntelliSense support.
Type Signature:
function createApiClient<
T = Record<string, any>,
TOperations extends ApiOperations = ApiOperations
>(config?: ApiClientConfig): ApiClient<T, TOperations>Parameters:
config(optional) - Configuration object for the API client
Type Parameters:
T- The OpenAPI paths type (e.g.,pathsfrom generated types)TOperations- The operations type for IntelliSense (e.g.,ApiOperationsfrom generated operations)
Returns:
ApiClient<T, TOperations>- API client instance with typed HTTP methods and operations
Example:
import type { paths } from './.tasc/types';
import type { ApiOperations } from './.tasc/operations';
const api = createApiClient<paths, ApiOperations>({
baseURL: 'https://api.example.com',
timeout: 10000
});
// Now api.op has full IntelliSense!
await api.op.getUserById({ id: '123' });ApiClientConfig
Configuration interface for the API client.
interface ApiClientConfig {
// Base URL for all requests
baseURL?: string;
// Send cookies with cross-origin requests
withCredentials?: boolean;
// Request timeout in milliseconds
timeout?: number;
// Default headers for all requests
headers?: Record<string, string>;
// Request/response interceptors
interceptors?: {
request?: (config: any) => any | Promise<any>;
requestError?: (error: any) => any;
response?: (response: any) => any | Promise<any>;
responseError?: (error: any) => any;
};
// Additional axios configuration
axiosConfig?: AxiosRequestConfig;
}HTTP Methods
get<Path, Method>(path, params?, config?)
Perform a GET request.
const response = await api.get('/users/{id}', { id: '123' });post<Path, Method>(path, data?, config?)
Perform a POST request.
const response = await api.post('/users', { name: 'John', email: 'john@example.com' });put<Path, Method>(path, params?, data?, config?)
Perform a PUT request.
const response = await api.put('/users/{id}', { id: '123' }, { name: 'Jane' });patch<Path, Method>(path, params?, data?, config?)
Perform a PATCH request.
const response = await api.patch('/users/{id}', { id: '123' }, { email: 'newemail@example.com' });delete<Path, Method>(path, params?, config?)
Perform a DELETE request.
await api.delete('/users/{id}', { id: '123' });Utility Types
Generated Utility Types
The generated operations file exports pre-typed utility types that are already bound to your API.
Import from your generated file, not from tasc-ts:
import {
RequestBody,
ResponseData,
PathParams,
QueryParams
} from './.tasc/operations';RequestBody<Path, Method>
Extract request body type for a specific endpoint.
type CreateUserInput = RequestBody<'/users', 'post'>;ResponseData<Path, Method, Status?>
Extract response data type for a specific endpoint. Defaults to status 200.
type UserResponse = ResponseData<'/users/{id}', 'get'>;
type UserCreated = ResponseData<'/users', 'post', 201>;Raw<ResponseType>
Unwraps the data property from wrapped API responses. Use when your API returns { success, data, status } structure.
// If your API returns: { success: true, data: AddressResponse, status: 201 }
type FullResponse = ResponseData<'/address', 'post'>;
// Result: { success: true, data: AddressResponse, status: 201 }
type AddressData = Raw<ResponseData<'/address', 'post'>>;
// Result: AddressResponse (just the inner data)
// Composable with any response type
type RawUser = Raw<ResponseData<'/users/{id}', 'get'>>;PathParams<Path>
Extract path parameters type for a specific endpoint.
type UserPathParams = PathParams<'/users/{id}'>;
// Result: { id: string | number }QueryParams<Path, Method>
Extract query parameters type for a specific endpoint.
type UsersQueryParams = QueryParams<'/users', 'get'>;
// Result: { limit?: number; offset?: number }Why is this better?
- โ
No need to import from
tasc-tspackage - โ
No need to pass the
pathsgeneric type - โ Simpler, cleaner imports
- โ All types are co-located with your API client
- โ
Paths automatically derived from your
tasc.config.ts
๐ Troubleshooting
Common Issues
Issue: "Cannot find module 'tasc-ts'"
# Solution: Install the package
npm install tasc-tsIssue: "Config file not found"
# Solution: Initialize config file
npx tasc initIssue: "Failed to fetch API spec"
Checklist:
- Is your API server running?
- Is the
api_doc_urlcorrect intasc.config.ts? - Are there CORS issues preventing access?
- Can you access the URL in your browser?
# Test if API is accessible
curl https://api.example.com/openapi.jsonIssue: "TypeScript config files not loading"
The tsx package is required for TypeScript config files.
# Solution: Install tsx as a dev dependency
npm install -D tsxIssue: "Generated types not updating"
# Solution: Force regeneration
rm -rf .tasc/
npx tasc generateIssue: "Type errors in generated code"
- Make sure your OpenAPI spec is valid
- Try regenerating:
npx tasc generate - Check for breaking changes in your API
- Verify TypeScript version compatibility (5.0+)
Debug Mode
Enable verbose logging for troubleshooting:
# Set debug environment variable
LOG_LEVEL=debug npx tasc generateGetting Help
- ๐ Open an issue
- ๐ฌ Check existing issues for solutions
- ๐ง Contact the maintainers
โ FAQ
Q: Can I use this with JavaScript projects?
A: Yes! The generated types work with JSDoc annotations for type checking in JavaScript.
Q: Does this work with OpenAPI 2.0 (Swagger)?
A: Yes! The underlying openapi-typescript package supports both OpenAPI 2.0 and 3.0+.
Q: Can I customize the generated code?
A: The operations are auto-generated, but you can extend the client with additional methods and interceptors.
Q: Do I need to commit .tasc/ to git?
A: Yes! Committing generated files ensures team synchronization and CI/CD compatibility. Add them to version control.
Q: Can I use multiple config files?
A: Currently, one tasc.config.ts per project. However, you can create multiple API client instances for different APIs.
Q: Does this support GraphQL?
A: No, TASC-TS is designed specifically for REST APIs with OpenAPI/Swagger specifications.
Q: What happens if my API changes?
A: Run tasc generate to regenerate types, or use tasc watch during development for automatic updates.
Q: Can I use this in a monorepo?
A: Yes! Each package can have its own tasc.config.ts and generated types.
Q: Is this compatible with React Native?
A: Yes! The client uses Axios under the hood, which works great with React Native.
Q: How do I handle different environments (dev/staging/prod)?
A: Use environment variables in your config:
const config: ConfigOptions = {
api_doc_url: process.env.API_DOC_URL || 'http://localhost:8080/openapi.json',
// ...
};๐ ๏ธ Recommended Package Scripts
Add these scripts to your package.json for convenience:
{
"scripts": {
"api:init": "tasc init",
"api:generate": "tasc generate",
"api:watch": "tasc watch",
"api:config": "tasc config"
}
}Then use them:
npm run api:generate # Generate types once
npm run api:watch # Watch for changes
npm run api:config # View current config๐ค Contributing
We welcome contributions! Here's how you can help:
Development Setup
# Clone the repository
git clone https://github.com/oddFEELING/Typed-Api-Schema-Client.git
cd tasc-ts
# Install dependencies
npm install
# Build the project
npm run build
# Run in development mode
npm run devGuidelines
- Follow existing code style
- Add tests for new features
- Update documentation
- Keep commits atomic and descriptive
Submitting Changes
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
๐ License
This project is licensed under the ISC License.
๐ Acknowledgments
Built with:
- openapi-typescript - For OpenAPI type generation
- Axios - For HTTP client functionality
- Commander.js - For CLI framework
- tsx - For TypeScript config file support
๐ Stats
Made with โค๏ธ by oddFEELING