UNPKG

trasorio-sdk

Version:

Official Node.js SDK for Trasor.io trust infrastructure platform

392 lines (345 loc) 12.7 kB
/** * Trasor.io Node.js SDK * * Official Node.js SDK for Trasor.io's trust infrastructure platform. * Provides secure, immutable audit trails for AI agent workflows. * * @version 1.0.1 * @author Trasor.io <support@trasor.io> * @license MIT */ const { URL } = require('url'); /** * Custom error classes for Trasor.io SDK */ class TrasorError extends Error { constructor(message, statusCode = null) { super(message); this.name = 'TrasorError'; this.statusCode = statusCode; Error.captureStackTrace(this, this.constructor); } } class AuthenticationError extends TrasorError { constructor(message = 'Authentication failed') { super(message, 401); this.name = 'AuthenticationError'; } } class ValidationError extends TrasorError { constructor(message = 'Validation failed') { super(message, 400); this.name = 'ValidationError'; } } class APIError extends TrasorError { constructor(message = 'API request failed', statusCode = 500) { super(message, statusCode); this.name = 'APIError'; } } /** * Trasor.io Client for Node.js * * A simple, developer-friendly client for recording audit logs with Trasor.io's * trust infrastructure platform. * * @example * const { TrasorClient } = require('trasor-sdk-node'); * * const client = new TrasorClient('trasor_live_your_api_key'); * * // Log a simple event * await client.logEvent({ * agentName: 'data_processor', * action: 'process_customer_data', * status: 'success' * }); */ class TrasorClient { /** * Create a new Trasor.io client instance * * @param {string} apiKey - Your Trasor.io API key (format: trasor_live_*) * @param {Object} options - Configuration options * @param {string} [options.baseUrl='https://api.trasor.io'] - Base URL for the API * @param {number} [options.timeout=30000] - Request timeout in milliseconds * @param {boolean} [options.debug=false] - Enable debug logging * * @throws {ValidationError} If API key is missing or invalid format * * @example * const client = new TrasorClient('trasor_live_abc123...', { * baseUrl: 'https://api.trasor.io', * timeout: 30000, * debug: process.env.NODE_ENV === 'development' * }); */ constructor(apiKey, options = {}) { if (!apiKey) { throw new ValidationError('API key is required'); } if (typeof apiKey !== 'string' || !apiKey.startsWith('trasor_')) { throw new ValidationError('Invalid API key format. Must start with "trasor_"'); } this.apiKey = apiKey; this.baseUrl = options.baseUrl || 'https://api.trasor.io'; this.timeout = options.timeout || 30000; this.debug = options.debug || process.env.VERIFIK_DEBUG === 'true'; // Ensure baseUrl doesn't end with slash this.baseUrl = this.baseUrl.replace(/\/$/, ''); // Validate baseUrl format try { new URL(this.baseUrl); } catch (error) { throw new ValidationError('Invalid baseUrl format'); } this._log('TrasorClient initialized', { baseUrl: this.baseUrl, timeout: this.timeout, debug: this.debug }); } /** * Create a new audit log entry * * Records an audit log event with the specified parameters. All logs are * automatically hash-chained and verified for integrity. * * @param {Object} params - Log event parameters * @param {string} params.agentName - Name/identifier of the AI agent or service * @param {string} params.action - The action that was performed * @param {Object} [params.inputs] - Input data/parameters for the action * @param {Object} [params.outputs] - Output data/results from the action * @param {Object} [params.metadata] - Additional metadata about the event * @param {string} [params.workflowId] - Workflow or session identifier * @param {string} [params.status] - Status of the action (e.g., "success", "error", "pending") * * @returns {Promise<Object>} The created audit log entry with ID, hash, and verification details * * @throws {ValidationError} If required parameters are missing or invalid * @throws {AuthenticationError} If API key is invalid or unauthorized * @throws {APIError} If the API returns an error response * @throws {TrasorError} For other SDK-related errors * * @example * const response = await client.logEvent({ * agentName: 'email_agent', * action: 'send_notification', * inputs: { recipient: 'user@example.com', template: 'welcome' }, * outputs: { messageId: 'msg_123', status: 'sent' }, * metadata: { campaignId: 'camp_456' }, * workflowId: 'workflow_789', * status: 'success' * }); * * console.log(`Log created with ID: ${response.id}`); */ async logEvent(params) { // Validate required parameters if (!params || typeof params !== 'object') { throw new ValidationError('Parameters must be an object'); } const { agentName, action, inputs, outputs, metadata, workflowId, status } = params; if (!agentName || typeof agentName !== 'string') { throw new ValidationError('agentName must be a non-empty string'); } if (!action || typeof action !== 'string') { throw new ValidationError('action must be a non-empty string'); } // Validate optional parameters if (inputs !== undefined && (typeof inputs !== 'object' || inputs === null)) { throw new ValidationError('inputs must be an object'); } if (outputs !== undefined && (typeof outputs !== 'object' || outputs === null)) { throw new ValidationError('outputs must be an object'); } if (metadata !== undefined && (typeof metadata !== 'object' || metadata === null)) { throw new ValidationError('metadata must be an object'); } if (workflowId !== undefined && typeof workflowId !== 'string') { throw new ValidationError('workflowId must be a string'); } if (status !== undefined && typeof status !== 'string') { throw new ValidationError('status must be a string'); } // Prepare payload const payload = { agentId: agentName, action: action }; // Add optional parameters if provided if (inputs !== undefined) payload.inputs = inputs; if (outputs !== undefined) payload.outputs = outputs; if (metadata !== undefined) payload.metadata = metadata; if (workflowId !== undefined) payload.workflowId = workflowId; if (status !== undefined) payload.status = status; this._log('Logging event', payload); // Make API request return await this._makeRequest('POST', '/v1/logs', payload); } /** * Retrieve audit logs with pagination * * @param {Object} [options] - Query options * @param {number} [options.limit=50] - Number of logs to return (max 100) * @param {number} [options.offset=0] - Number of logs to skip * @param {string} [options.workflowId] - Filter by workflow ID * * @returns {Promise<Object>} Paginated list of audit logs * * @throws {ValidationError} If parameters are invalid * @throws {AuthenticationError} If API key is invalid or unauthorized * @throws {APIError} If the API returns an error response * * @example * const logs = await client.getLogs({ limit: 20, offset: 0 }); * console.log(`Retrieved ${logs.logs.length} logs`); */ async getLogs(options = {}) { const { limit = 50, offset = 0, workflowId } = options; if (!Number.isInteger(limit) || limit < 1 || limit > 100) { throw new ValidationError('limit must be an integer between 1 and 100'); } if (!Number.isInteger(offset) || offset < 0) { throw new ValidationError('offset must be a non-negative integer'); } const params = new URLSearchParams({ limit: limit.toString(), offset: offset.toString() }); if (workflowId) { params.append('workflowId', workflowId); } this._log('Getting logs', { limit, offset, workflowId }); return await this._makeRequest('GET', `/v1/logs?${params}`); } /** * Verify the integrity of the audit log chain * * @returns {Promise<Object>} Verification results including status and any integrity issues * * @throws {AuthenticationError} If API key is invalid or unauthorized * @throws {APIError} If the API returns an error response * * @example * const verification = await client.verifyChain(); * console.log(`Chain integrity: ${verification.isValid}`); */ async verifyChain() { this._log('Verifying chain integrity'); return await this._makeRequest('GET', '/v1/verify'); } /** * Get account statistics and metrics * * @returns {Promise<Object>} Account statistics including log count and usage metrics * * @throws {AuthenticationError} If API key is invalid or unauthorized * @throws {APIError} If the API returns an error response * * @example * const stats = await client.getStats(); * console.log(`Total logs: ${stats.totalLogs}`); */ async getStats() { this._log('Getting account stats'); return await this._makeRequest('GET', '/v1/stats'); } /** * Make an authenticated request to the Trasor.io API * * @private * @param {string} method - HTTP method (GET, POST, etc.) * @param {string} endpoint - API endpoint path * @param {Object} [data] - Request body data * @returns {Promise<Object>} Parsed JSON response * * @throws {AuthenticationError} If API key is invalid or unauthorized * @throws {APIError} If the API returns an error response * @throws {TrasorError} For connection or other errors */ async _makeRequest(method, endpoint, data = null) { const url = `${this.baseUrl}${endpoint}`; const headers = { 'Authorization': `Bearer ${this.apiKey}`, 'Content-Type': 'application/json', 'User-Agent': 'trasor-node-sdk/2.0.0' }; const options = { method, headers, timeout: this.timeout }; if (data && (method === 'POST' || method === 'PUT' || method === 'PATCH')) { options.body = JSON.stringify(data); } this._log(`Making ${method} request to ${url}`, { headers: { ...headers, Authorization: '[REDACTED]' } }); let response; try { // Use native fetch (Node.js 18+) or polyfill for older versions const fetchFn = globalThis.fetch || require('node-fetch'); response = await fetchFn(url, options); } catch (error) { if (error.name === 'AbortError' || error.message.includes('timeout')) { throw new TrasorError('Request timed out'); } throw new TrasorError(`Network error: ${error.message}`); } this._log(`Response status: ${response.status}`); // Handle different response status codes if (response.status === 401) { throw new AuthenticationError('Invalid API key or unauthorized access'); } else if (response.status === 400) { let errorMessage = 'Bad request'; try { const errorData = await response.json(); errorMessage = errorData.message || errorMessage; } catch (e) { // Ignore JSON parsing errors } throw new ValidationError(`Validation error: ${errorMessage}`); } else if (response.status === 429) { throw new APIError('Rate limit exceeded. Please try again later.', 429); } else if (response.status >= 500) { throw new APIError(`Server error: ${response.status}`, response.status); } else if (!response.ok) { throw new APIError(`API request failed: ${response.status}`, response.status); } // Parse JSON response let responseData; try { responseData = await response.json(); } catch (error) { throw new APIError('Invalid JSON response from API'); } this._log('Request successful', responseData); return responseData; } /** * Internal logging method * * @private * @param {string} message - Log message * @param {Object} [data] - Additional data to log */ _log(message, data = null) { if (this.debug) { const timestamp = new Date().toISOString(); console.log(`[${timestamp}] [TrasorClient] ${message}`); if (data) { console.log(`[${timestamp}] [TrasorClient] Data:`, JSON.stringify(data, null, 2)); } } } } // Export classes and functions module.exports = { TrasorClient, TrasorError, AuthenticationError, ValidationError, APIError }; // For ES6 modules compatibility module.exports.default = TrasorClient;