Skip to content

Commit 51a223e

Browse files
committed
contracts
1 parent fcce37a commit 51a223e

32 files changed

+963
-1411
lines changed

jest.config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ module.exports = {
1111
'^.+\\.tsx?$': [
1212
'ts-jest',
1313
{
14-
tsconfig: 'tsconfig.json'
14+
tsconfig: 'tsconfig.json',
15+
isolatedModules: true // Speeds up tests & ignores TS errors
1516
}
1617
]
1718
},

package.json

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
2-
"name": "@tadata/node-sdk",
2+
"name": "@tadata/sdk",
33
"version": "0.1.0",
4-
"description": "A fluent SDK for working with Tadata's API",
4+
"description": "Tadata Node SDK",
55
"main": "dist/cjs/index.js",
66
"module": "dist/esm/index.js",
77
"types": "dist/types/index.d.ts",
@@ -17,15 +17,13 @@
1717
"LICENSE",
1818
"README.md"
1919
],
20-
"sideEffects": false,
2120
"scripts": {
22-
"clean": "rimraf dist",
21+
"clean": "npx rimraf dist",
2322
"build": "npm run clean && npm run build:cjs && npm run build:esm && npm run build:types",
24-
"build:cjs": "tsc -p tsconfig.cjs.json",
25-
"build:esm": "tsc -p tsconfig.esm.json",
26-
"build:types": "tsc -p tsconfig.types.json",
27-
"dev": "ts-node src/dev.ts",
28-
"test": "jest",
23+
"build:cjs": "npx tsc -p tsconfig.cjs.json",
24+
"build:esm": "npx tsc -p tsconfig.esm.json",
25+
"build:types": "npx tsc -p tsconfig.types.json",
26+
"test": "npx jest",
2927
"test:watch": "jest --watch",
3028
"test:coverage": "jest --coverage",
3129
"lint": "eslint . --ext .ts",
@@ -47,6 +45,8 @@
4745
"@apidevtools/swagger-parser": "^10.1.0",
4846
"@ts-rest/core": "^3.30.5",
4947
"axios": "^1.6.2",
48+
"http-status-codes": "^2.3.0",
49+
"openapi-typescript": "^7.8.0",
5050
"zod": "^3.22.4"
5151
},
5252
"devDependencies": {

pnpm-lock.yaml

Lines changed: 144 additions & 956 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/contract/client.ts

Lines changed: 63 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,39 @@
1-
import { initClient, ApiFetcherArgs } from '@ts-rest/core';
1+
import { initClient } from '@ts-rest/core';
22
import axios, { AxiosError } from 'axios';
33
import { tadataContract } from './tadata-contract';
44
import { AuthError, NetworkError, ApiError } from '../errors';
55
import { Logger } from '../core/logger';
66

77
interface ClientOptions {
8-
baseUrl?: string;
9-
version?: string;
8+
baseUrl: string;
9+
version: string;
1010
timeout?: number;
1111
logger?: Logger;
12+
isDev: boolean;
13+
}
14+
15+
// Custom type for the fetcher args that includes query
16+
interface CustomApiFetcherArgs {
17+
path: string;
18+
method: string;
19+
body?: any;
20+
headers: Record<string, string>;
21+
query?: Record<string, string>;
1222
}
1323

1424
/**
1525
* Creates a configured ts-rest client for the Tadata API
1626
*/
17-
export function createApiClient(apiKey: string, options: ClientOptions = {}) {
18-
const {
19-
baseUrl = 'https://api.tadata.com',
20-
version = 'latest',
21-
timeout = 30000,
22-
logger,
23-
} = options;
27+
export function createApiClient(apiKey: string, options: ClientOptions) {
28+
const { baseUrl, version, timeout = 30000, logger } = options;
2429

2530
// Create axios instance with defaults
2631
const axiosInstance = axios.create({
2732
baseURL: baseUrl,
2833
timeout,
2934
headers: {
3035
'Authorization': `Bearer ${apiKey}`,
31-
'x-tadata-version': version,
36+
'x-api-version': version,
3237
'Content-Type': 'application/json',
3338
},
3439
});
@@ -41,7 +46,12 @@ export function createApiClient(apiKey: string, options: ClientOptions = {}) {
4146
return config;
4247
},
4348
error => {
44-
logger.error('Request error:', error);
49+
// Format error for cleaner logs
50+
const errorInfo = {
51+
message: error.message,
52+
code: error.code
53+
};
54+
logger.error('Request error', errorInfo);
4555
return Promise.reject(error);
4656
}
4757
);
@@ -52,7 +62,29 @@ export function createApiClient(apiKey: string, options: ClientOptions = {}) {
5262
return response;
5363
},
5464
error => {
55-
logger.error('Response error:', error);
65+
// Format error for cleaner logs
66+
let errorInfo: Record<string, any> = {
67+
message: error.message,
68+
code: error.code
69+
};
70+
71+
if (error.response) {
72+
errorInfo.status = error.response.status;
73+
74+
// Extract specific error details
75+
if (error.response.data && error.response.data.error) {
76+
errorInfo.errorCode = error.response.data.error.code;
77+
errorInfo.errorMessage = error.response.data.error.message;
78+
79+
// Include first validation error if present
80+
if (error.response.data.error.errors &&
81+
error.response.data.error.errors.length > 0) {
82+
errorInfo.validation = error.response.data.error.errors[0];
83+
}
84+
}
85+
}
86+
87+
logger.error('Response error', errorInfo);
5688
return Promise.reject(error);
5789
}
5890
);
@@ -63,23 +95,33 @@ export function createApiClient(apiKey: string, options: ClientOptions = {}) {
6395
baseUrl,
6496
baseHeaders: {
6597
'Authorization': `Bearer ${apiKey}`,
66-
'x-tadata-version': version,
98+
'x-api-version': version,
6799
},
68-
/* <<<<<<<<<<<<<< ✨ Windsurf Command ⭐ >>>>>>>>>>>>>>>> */
100+
69101
/**
70102
* Adapter for `ts-rest` to perform API requests to the Tadata API.
71103
*
72-
* @param {ApiFetcherArgs<any>} args - Request arguments with path, method, body, and headers.
104+
* @param {CustomApiFetcherArgs} args - Request arguments with path, method, body, headers, and query params.
73105
* @returns {Promise<ApiResponse<any>>} - Response with status, body, and headers.
74106
* @throws {AuthError} - If authentication fails.
75107
* @throws {ApiError} - If the API returns an error response.
76108
* @throws {NetworkError} - If a network error occurs.
77109
*/
78-
/* <<<<<<<<<< 5f3418b3-bf5f-4f26-b9b9-206215461c08 >>>>>>>>>>> */
79-
api: async ({ path, method, body, headers }: ApiFetcherArgs<any>) => {
110+
api: async ({ path, method, body, headers, query = {} }: CustomApiFetcherArgs) => {
80111
try {
112+
// Add the apiKey to query parameters for every request
113+
// This avoids having to manually add it in each method call
114+
const augmentedQuery = {
115+
...query,
116+
apiKey,
117+
};
118+
119+
// Create query string
120+
const queryString = new URLSearchParams(augmentedQuery).toString();
121+
const urlWithQuery = queryString ? `${path}?${queryString}` : path;
122+
81123
const config: any = {
82-
url: path,
124+
url: urlWithQuery,
83125
method,
84126
headers,
85127
};
@@ -105,7 +147,8 @@ export function createApiClient(apiKey: string, options: ClientOptions = {}) {
105147
}
106148

107149
const { status, data } = axiosError.response;
108-
const errorMessage = typeof data === 'object' && data !== null ? (data as any).message : undefined;
150+
const errorMessage =
151+
typeof data === 'object' && data !== null ? (data as any).message : undefined;
109152

110153
if (status === 401 || status === 403) {
111154
throw new AuthError(errorMessage || 'Authentication failed', axiosError);

src/contract/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
export { tadataContract, type McpDeploymentResult, type TadataContract } from './tadata-contract';
1+
export { tadataContract, type TadataContract } from './tadata-contract';
22
export { createApiClient } from './client';

src/contract/tadata-contract.ts

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,14 @@
1-
21
// Schema definitions
32
import { initContract } from '@ts-rest/core';
4-
import { z } from 'zod';
5-
6-
const McpDeploymentSchema = z.object({
7-
id: z.string(),
8-
url: z.string(),
9-
specVersion: z.string(),
10-
createdAt: z.string().transform(str => new Date(str)),
11-
});
3+
// Import the deploymentsContract from the shared location
4+
import { deploymentsContract } from '../http/contracts'; // relies on index.js in that dir
125

136
const c = initContract();
147

15-
// API contract
8+
// Create the contract containing only the REST API endpoints
169
export const tadataContract = c.router({
17-
// MCP endpoints
18-
mcpDeploy: {
19-
method: 'POST',
20-
path: '/v1/mcp',
21-
responses: {
22-
201: McpDeploymentSchema,
23-
400: z.object({ message: z.string(), details: z.any().optional() }),
24-
401: z.object({ message: z.string() }),
25-
403: z.object({ message: z.string() }),
26-
500: z.object({ message: z.string() }),
27-
},
28-
body: z.object({
29-
spec: z.any(),
30-
baseUrl: z.string(),
31-
name: z.string().optional(),
32-
dev: z.boolean().optional(),
33-
}),
34-
summary: 'Deploy an MCP server from an OpenAPI specification',
35-
},
10+
deployments: deploymentsContract,
3611
});
3712

3813
// Type exports
39-
export type McpDeploymentResult = z.infer<typeof McpDeploymentSchema>;
4014
export type TadataContract = typeof tadataContract;

src/core/logger.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,19 @@ export interface Logger {
1313
*/
1414
export class ConsoleLogger implements Logger {
1515
debug(msg: string, ...meta: unknown[]): void {
16-
console.debug(`[debug] ${msg}`, ...meta);
16+
console.debug(msg, ...meta);
1717
}
1818

1919
info(msg: string, ...meta: unknown[]): void {
20-
console.info(`[info] ${msg}`, ...meta);
20+
console.info(msg, ...meta);
2121
}
2222

2323
warn(msg: string, ...meta: unknown[]): void {
24-
console.warn(`[warn] ${msg}`, ...meta);
24+
console.warn(msg, ...meta);
2525
}
2626

2727
error(msg: string, ...meta: unknown[]): void {
28-
console.error(`[error] ${msg}`, ...meta);
28+
console.error(msg, ...meta);
2929
}
3030
}
3131

src/core/sdk.ts

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createApiClient } from '../contract';
2-
import { McpResource } from '../mcp';
2+
import { McpResource } from '../resources/mcp';
33
import { Logger, createDefaultLogger } from './logger';
44

55
/**
@@ -28,12 +28,6 @@ export interface TadataOptions {
2828
* @default ConsoleLogger
2929
*/
3030
logger?: Logger;
31-
32-
/**
33-
* Custom base URL for the API
34-
* @default https://api.tadata.com
35-
*/
36-
baseUrl?: string;
3731
}
3832

3933
/**
@@ -66,24 +60,22 @@ export class TadataNodeSDK {
6660
/**
6761
* MCP resource for deploying and managing Multi-Channel Proxies
6862
*/
69-
readonly mcp: McpResource;
70-
63+
public readonly mcp: McpResource;
7164
/**
7265
* Create a new Tadata Node SDK instance
7366
*/
7467
constructor(options: TadataOptions) {
75-
// Initialize dependencies
7668
const logger = options.logger || createDefaultLogger();
7769
const isDev = options.dev || false;
70+
const baseUrl = isDev ? 'https://api.stage.tadata.com' : 'https://api.tadata.com';
7871

79-
// Create API client
8072
const client = createApiClient(options.apiKey, {
81-
baseUrl: options.baseUrl,
82-
version: options.version,
73+
baseUrl,
74+
version: options.version || 'latest',
8375
logger,
76+
isDev,
8477
});
8578

86-
// Initialize resources
87-
this.mcp = new McpResource(client, logger, isDev);
79+
this.mcp = new McpResource(client.deployments, logger);
8880
}
8981
}

src/errors/error-codes.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { StatusCodes } from 'http-status-codes';
2+
13
/**
24
* All possible error codes returned by the Tadata API
35
*/
@@ -13,23 +15,25 @@ export type TadataErrorCode =
1315

1416
/**
1517
* Maps HTTP status codes to Tadata error codes
18+
* NOTE: This mapping is currently not being used in the codebase.
19+
* Consider removing it if not needed or implementing proper usage.
1620
*/
17-
export const HTTP_STATUS_TO_ERROR_CODE: Record<number, TadataErrorCode> = {
18-
400: 'TADATA/INVALID_SPEC',
19-
401: 'TADATA/AUTH',
20-
404: 'TADATA/NOT_FOUND',
21-
409: 'TADATA/HASH_EXISTS',
22-
422: 'TADATA/INVALID_SPEC',
23-
429: 'TADATA/RATE_LIMIT',
24-
500: 'TADATA/SERVER_ERROR',
25-
502: 'TADATA/SERVER_ERROR',
26-
503: 'TADATA/SERVER_ERROR',
27-
504: 'TADATA/SERVER_ERROR',
28-
};
21+
// export const HTTP_STATUS_TO_ERROR_CODE: Record<number, TadataErrorCode> = {
22+
// [StatusCodes.BAD_REQUEST]: 'TADATA/INVALID_SPEC',
23+
// [StatusCodes.UNAUTHORIZED]: 'TADATA/AUTH',
24+
// [StatusCodes.NOT_FOUND]: 'TADATA/NOT_FOUND',
25+
// [StatusCodes.CONFLICT]: 'TADATA/HASH_EXISTS',
26+
// [StatusCodes.UNPROCESSABLE_ENTITY]: 'TADATA/INVALID_SPEC',
27+
// [StatusCodes.TOO_MANY_REQUESTS]: 'TADATA/RATE_LIMIT',
28+
// [StatusCodes.INTERNAL_SERVER_ERROR]: 'TADATA/SERVER_ERROR',
29+
// [StatusCodes.BAD_GATEWAY]: 'TADATA/SERVER_ERROR',
30+
// [StatusCodes.SERVICE_UNAVAILABLE]: 'TADATA/SERVER_ERROR',
31+
// [StatusCodes.GATEWAY_TIMEOUT]: 'TADATA/SERVER_ERROR',
32+
// };
2933

3034
/**
3135
* Check if an error is retryable based on its status code
3236
*/
3337
export function isRetryableError(status: number): boolean {
34-
return status >= 500 && status < 600;
38+
return status >= StatusCodes.INTERNAL_SERVER_ERROR && status < 600;
3539
}

0 commit comments

Comments
 (0)