UNPKG

@smartsamurai/krapi-sdk

Version:

KRAPI TypeScript SDK - Easy-to-use client SDK for connecting to self-hosted KRAPI servers (like Appwrite SDK)

1,498 lines (1,487 loc) 74.2 kB
import { HttpError, KrapiError, init_error_handler, init_http_error, init_krapi_error, normalizeError } from "./chunk-CUJMHNHY.mjs"; // src/client/backup-api-manager.ts var BackupApiManager = class { constructor(axiosInstance) { this.axiosInstance = axiosInstance; } /** * Create a project backup */ async createProject(projectId, options) { const response = await this.axiosInstance.post(`/krapi/k1/projects/${projectId}/backup`, options || {}); return response.data; } /** * Restore a project from backup */ async restoreProject(projectId, backupId, password, options) { const response = await this.axiosInstance.post(`/krapi/k1/projects/${projectId}/restore`, { backup_id: backupId, password, ...options }); return response.data; } /** * List backups */ async list(projectId, type) { const url = projectId ? `/krapi/k1/projects/${projectId}/backups${type ? `?type=${type}` : ""}` : `/krapi/k1/backups${type ? `?type=${type}` : ""}`; const response = await this.axiosInstance.get( url ); return response.data; } /** * Delete a backup */ async delete(backupId) { const response = await this.axiosInstance.delete(`/krapi/k1/backups/${backupId}`); return response.data; } /** * Create a system backup */ async createSystem(options) { const response = await this.axiosInstance.post("/krapi/k1/backup/system", options || {}); return response.data; } }; // src/utils/response-normalizer.ts var ResponseNormalizer = class { /** * Normalize document list responses from different API formats */ static normalizeDocumentListResponse(response) { if (!response) return []; if (Array.isArray(response)) { return response; } if (typeof response === "object" && response !== null) { if ("data" in response) { const data = response.data; if (Array.isArray(data)) { return data; } } if ("documents" in response) { const documents = response.documents; if (Array.isArray(documents)) { return documents; } } } return []; } /** * Normalize single document responses */ static normalizeDocumentResponse(response) { if (!response) return {}; if (typeof response === "object" && response !== null) { if ("data" in response) { const data = response.data; if (data && typeof data === "object") { return data; } } return response; } return {}; } /** * Normalize collection responses */ static normalizeCollectionResponse(response) { if (!response) return {}; if (typeof response === "object" && response !== null) { if ("data" in response) { const data = response.data; if (data && typeof data === "object") { return data; } } return response; } return {}; } }; // src/client/collections-api-manager.ts var CollectionsApiManager = class { constructor(axiosInstance, collectionsClient) { /** * Documents API methods */ this.documents = { /** * List documents in a collection */ list: async (projectId, collectionName, options) => { const url = `/projects/${projectId}/collections/${collectionName}/documents`; const params = new URLSearchParams(); if (options?.limit) params.append("limit", options.limit.toString()); if (options?.offset) params.append("offset", options.offset.toString()); if (options?.filter) params.append("filter", JSON.stringify(options.filter)); if (options?.sort) params.append("sort", JSON.stringify(options.sort)); const response = await this.axiosInstance.get( `/krapi/k1${url}${params.toString() ? `?${params.toString()}` : ""}` ); const normalizedData = ResponseNormalizer.normalizeDocumentListResponse(response.data); return { success: true, data: normalizedData }; }, /** * Get a single document */ get: async (projectId, collectionName, documentId) => { const response = await this.collectionsClient.getDocument(projectId, collectionName, documentId); const result = { success: response.success, data: ResponseNormalizer.normalizeDocumentResponse(response.data) }; if (response.error) { result.error = response.error; } return result; }, /** * Create a new document */ create: async (projectId, collectionName, data) => { const response = await this.axiosInstance.post( `/krapi/k1/projects/${projectId}/collections/${collectionName}/documents`, { data, created_by: "client" } ); const normalized = ResponseNormalizer.normalizeDocumentResponse(response.data); return { success: true, data: normalized }; }, /** * Update a document */ update: async (projectId, collectionName, documentId, data) => { return this.collectionsClient.updateDocument( projectId, collectionName, documentId, { data, updated_by: "client" } ); }, /** * Delete a document */ delete: async (projectId, collectionName, documentId) => { return this.collectionsClient.deleteDocument(projectId, collectionName, documentId); }, /** * Bulk create documents */ bulkCreate: async (projectId, collectionName, documents) => { const createRequests = documents.map((doc) => ({ data: doc, created_by: "client" })); return this.collectionsClient.bulkCreateDocuments(projectId, collectionName, createRequests); }, /** * Bulk update documents with filter */ bulkUpdate: async (projectId, collectionName, filter, updates) => { const response = await this.axiosInstance.put( `/krapi/k1/projects/${projectId}/collections/${collectionName}/documents/bulk`, { filter, updates } ); return response.data; }, /** * Bulk delete documents with filter */ bulkDelete: async (projectId, collectionName, filter) => { const response = await this.axiosInstance.post( `/krapi/k1/projects/${projectId}/collections/${collectionName}/documents/bulk-delete`, { filter } ); return response.data; }, /** * Search documents */ search: async (projectId, collectionName, query, options) => { const searchOptions = { text: query }; if (options?.fields !== void 0) searchOptions.fields = options.fields; if (options?.limit !== void 0) searchOptions.limit = options.limit; return this.collectionsClient.searchDocuments(projectId, collectionName, searchOptions); }, /** * Aggregate documents */ aggregate: async (projectId, collectionName, options) => { const aggregations = {}; if (options.operations) { for (const op of options.operations) { const opType = op.operation; if (["count", "sum", "avg", "min", "max"].includes(op.operation)) { aggregations[op.field] = { type: opType, field: op.field }; } } } const aggregateOptions = { aggregations }; if (options.groupBy) { aggregateOptions.group_by = [options.groupBy]; } return this.collectionsClient.aggregateDocuments(projectId, collectionName, aggregateOptions); } }; /** * Collection management methods */ this.collections = { /** * List collections in a project */ list: async (projectId) => { const result = await this.collectionsClient.getProjectCollections(projectId); if (result && typeof result === "object") { if ("collections" in result && Array.isArray(result.collections)) { return { success: true, data: result.collections }; } if ("data" in result && Array.isArray(result.data)) { return { success: true, data: result.data }; } } return { success: true, data: [] }; }, /** * Get a single collection */ get: async (projectId, collectionName) => { return this.collectionsClient.getCollection(projectId, collectionName); }, /** * Create a new collection */ create: async (projectId, collectionData) => { const response = await this.collectionsClient.createCollection( projectId, collectionData ); const normalizedData = ResponseNormalizer.normalizeCollectionResponse(response.data); const result = { success: response.success, data: normalizedData }; if (response.error) { result.error = response.error; } return result; }, /** * Update a collection */ update: async (projectId, collectionName, updates) => { return this.collectionsClient.updateCollection(projectId, collectionName, updates); }, /** * Delete a collection */ delete: async (projectId, collectionName) => { return this.collectionsClient.deleteCollection(projectId, collectionName); } }; this.axiosInstance = axiosInstance; this.collectionsClient = collectionsClient; } }; // src/client/project-api-manager.ts init_krapi_error(); init_error_handler(); var ProjectApiManager = class { constructor(axiosInstance) { this.axiosInstance = axiosInstance; } /** * Get all projects * @returns {Promise<Project[]>} Array of projects */ async getAll() { try { const response = await this.axiosInstance.get( "/krapi/k1/projects" ); return Array.isArray(response.data) ? response.data : []; } catch (error) { throw normalizeError(error, "INTERNAL_ERROR", { operation: "getProjects" }); } } /** * Get project by ID * @param {string} id - Project ID * @returns {Promise<Project>} Project object */ async get(id) { if (!id || typeof id !== "string") { throw KrapiError.validationError("Project ID is required", "id"); } try { const response = await this.axiosInstance.get( `/krapi/k1/projects/${id}` ); return response.data; } catch (error) { throw normalizeError(error, "INTERNAL_ERROR", { operation: "getProject", projectId: id }); } } /** * Create a new project * @param {CreateProjectRequest} data - Project data * @returns {Promise<Project>} Created project */ async create(data) { if (!data || !data.name || typeof data.name !== "string" || data.name.trim() === "") { throw KrapiError.validationError("Project name is required", "name"); } try { const response = await this.axiosInstance.post( "/krapi/k1/projects", data ); return response.data; } catch (error) { throw normalizeError(error, "INTERNAL_ERROR", { operation: "createProject" }); } } /** * Update a project * @param {string} id - Project ID * @param {UpdateProjectRequest} data - Updated project data * @returns {Promise<Project>} Updated project */ async update(id, data) { if (!id || typeof id !== "string") { throw KrapiError.validationError("Project ID is required", "id"); } if (!data || typeof data !== "object") { throw KrapiError.validationError("Update data is required", "data"); } try { const response = await this.axiosInstance.put( `/krapi/k1/projects/${id}`, data ); return response.data; } catch (error) { throw normalizeError(error, "INTERNAL_ERROR", { operation: "updateProject", projectId: id }); } } /** * Delete a project * @param {string} id - Project ID * @returns {Promise<void>} */ async delete(id) { if (!id || typeof id !== "string") { throw KrapiError.validationError("Project ID is required", "id"); } try { await this.axiosInstance.delete(`/krapi/k1/projects/${id}`); } catch (error) { throw normalizeError(error, "INTERNAL_ERROR", { operation: "deleteProject", projectId: id }); } } }; // src/http-clients/base-http-client.ts init_krapi_error(); import axios, { isAxiosError } from "axios"; // src/utils/endpoint-utils.ts function normalizeEndpoint(endpoint, options = {}) { const { warnOnBackendPort = true, autoAppendPath = true, logger = console } = options; let normalized = endpoint.replace(/\/+$/, ""); try { const url = new URL(normalized); const port = url.port ? parseInt(url.port, 10) : url.protocol === "https:" ? 443 : 80; if (warnOnBackendPort && port === 3470) { const hostname = url.hostname.toLowerCase(); const isLocalhost = hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === ""; if (!isLocalhost) { const frontendPort = 3498; const suggestedUrl = normalized.replace(`:${port}`, `:${frontendPort}`); logger.warn( `\u26A0\uFE0F Warning: You've connected to the backend port (3470). External clients should connect to the frontend port (${frontendPort}). Did you mean: ${suggestedUrl}? Note: Backend port is typically for internal use only.` ); } } } catch { } if (autoAppendPath && !normalized.includes("/api/krapi/k1") && !normalized.includes("/krapi/k1")) { try { const url = new URL(normalized); const port = url.port ? parseInt(url.port, 10) : url.protocol === "https:" ? 443 : 80; if (port === 3470) { normalized = `${normalized}/krapi/k1`; } else { normalized = `${normalized}/api/krapi/k1`; } } catch { normalized = `${normalized}/api/krapi/k1`; } } return normalized; } function validateEndpoint(endpoint) { if (!endpoint || typeof endpoint !== "string") { return { valid: false, error: "Endpoint must be a non-empty string" }; } try { const url = new URL(endpoint); if (!["http:", "https:"].includes(url.protocol)) { return { valid: false, error: "Endpoint must use http:// or https:// protocol" }; } return { valid: true }; } catch (error) { return { valid: false, error: `Invalid URL format: ${error instanceof Error ? error.message : "Unknown error"}` }; } } function extractPort(endpoint) { try { const url = new URL(endpoint); if (url.port) { return parseInt(url.port, 10); } return url.protocol === "https:" ? 443 : 80; } catch { return null; } } function isBackendUrl(endpoint) { const port = extractPort(endpoint); return port === 3470; } // src/http-clients/base-http-client.ts init_http_error(); var BaseHttpClient = class { /** * Create a new BaseHttpClient instance * * @param {HttpClientConfig} config - HTTP client configuration */ constructor(config) { this.baseUrl = config.baseUrl.replace(/\/$/, ""); if (config.apiKey) this.apiKey = config.apiKey; if (config.sessionToken) this.sessionToken = config.sessionToken; if (config.projectId) this.projectId = config.projectId; if (config.retry) this.retryConfig = config.retry; } /** * Initialize the HTTP client with interceptors * * Sets up axios instance with authentication interceptors and error handling. * * @returns {Promise<void>} * * @example * await client.initializeClient(); */ async initializeClient() { if (this.httpClient !== void 0) return; let baseURL = this.baseUrl; const isBackend = isBackendUrl(baseURL); const hasApiPath = /\/api\/krapi\/k1(\/|$)/.test(baseURL); const hasKrapiPath = /\/krapi\/k1(\/|$)/.test(baseURL) && !hasApiPath; if (hasApiPath) { } else if (hasKrapiPath) { if (isBackend) { } else { baseURL = baseURL.replace("/krapi/k1", "/api/krapi/k1"); } } else { if (isBackend) { baseURL = `${baseURL}/krapi/k1`; } else { baseURL = `${baseURL}/api/krapi/k1`; } } this.httpClient = axios.create({ baseURL, timeout: 3e4, headers: { "Content-Type": "application/json" } }); this.httpClient.interceptors.request.use( (config) => { if (config.data instanceof FormData) { delete config.headers["Content-Type"]; delete config.headers["content-type"]; } const token = this.getCurrentToken(); if (token) { config.headers.Authorization = `Bearer ${token}`; delete config.headers["X-API-Key"]; } if (config.url) { let normalizedPath = config.url; normalizedPath = normalizedPath.replace(/^(\/api)?\/krapi\/k1/, ""); const isProjectListOrCreate = /^\/projects\/?(\?|$)/.test( normalizedPath ); const isProjectScoped = /^\/projects\/[^/]+/.test(normalizedPath); if (isProjectListOrCreate) { delete config.headers["X-Project-ID"]; delete config.headers["x-project-id"]; } else if (isProjectScoped && this.projectId) { config.headers["X-Project-ID"] = this.projectId; } } if (process.env.NODE_ENV === "development" || process.env.DEBUG_SDK_HEADERS) { const normalizedPath = config.url?.replace(/^(\/api)?\/krapi\/k1/, "") || ""; const isProjectListOrCreate = /^\/projects(\/|\?|$)/.test( normalizedPath ); const isProjectScoped = /^\/projects\/[^/]+/.test(normalizedPath); const hasProjectIdHeader = !!(config.headers["X-Project-ID"] || config.headers["x-project-id"]); const relevantHeaders = Object.keys(config.headers).filter( (k) => k.toLowerCase().includes("project") || k.toLowerCase() === "authorization" ).reduce((acc, k) => { acc[k] = config.headers[k] ? "present" : "missing"; return acc; }, {}); console.log("[SDK Interceptor]", { method: config.method?.toUpperCase(), url: config.url, normalizedPath, hasProjectId: !!this.projectId, isProjectListOrCreate, isProjectScoped, willAddHeader: isProjectScoped && !!this.projectId, willRemoveHeader: isProjectListOrCreate, actualHeaderPresent: hasProjectIdHeader, relevantHeaders }); } return config; } ); this.httpClient.interceptors.response.use( (response) => response.data, // Return just the data (error) => { if (isAxiosError(error)) { const axiosError = error; const status = axiosError.response?.status; const responseData = axiosError.response?.data; const method = axiosError.config?.method?.toUpperCase(); const relativeUrl = axiosError.config?.url; const baseUrl = axiosError.config?.baseURL || ""; const fullUrl = relativeUrl ? `${baseUrl}${relativeUrl}` : baseUrl; const requestBody = axiosError.config?.data; const requestQuery = axiosError.config?.params || {}; const responseHeaders = {}; if (axiosError.response?.headers) { Object.keys(axiosError.response.headers).forEach((key) => { const value = axiosError.response?.headers[key]; if (typeof value === "string") { responseHeaders[key] = value; } else if (Array.isArray(value) && value.length > 0) { responseHeaders[key] = value.join(", "); } }); } const requestHeaders = {}; if (axiosError.config && axiosError.config.headers) { const headers = axiosError.config.headers; Object.keys(headers).forEach((key) => { const value = headers[key]; if (typeof value === "string") { if (key.toLowerCase() === "authorization") { requestHeaders[key] = `${value.substring(0, 20)}...`; } else { requestHeaders[key] = value; } } }); } let errorMessage = "HTTP request failed"; let errorCode; if (status) { if (responseData) { if (typeof responseData === "object" && responseData !== null) { const data = responseData; errorMessage = data.error || data.message || `HTTP ${status} ${axiosError.response?.statusText || "Error"}`; if (data.code && typeof data.code === "string") { errorCode = data.code; } } else if (typeof responseData === "string") { errorMessage = responseData; } } else { errorMessage = `HTTP ${status} ${axiosError.response?.statusText || "Error"}`; } if (status === 404) { const isBackend2 = isBackendUrl(baseUrl); if (isBackend2) { errorMessage += ` - Backend URL detected (port 3470) - SDK should use /krapi/k1 path - Verify the endpoint path is correct - Check that backend routes are accessible at /krapi/k1/...`; } else { errorMessage += ` - Frontend URL detected (port 3498) - SDK should use /api/krapi/k1 path - The SDK should automatically append /api/krapi/k1/ to your endpoint - Verify the endpoint path is correct`; } } else if (status === 401) { const currentToken = this.getCurrentToken(); const isSessionToken = !!this.sessionToken; if (isSessionToken) { errorMessage += ` - Invalid or expired session token - Verify the session token is correct - Check if the session has expired - Ensure you're logged in and the session is active - Try logging in again to get a new session token`; } else if (currentToken) { errorMessage += ` - Invalid or expired API key - Check that your API key is correct - Verify the API key has the required scopes - Ensure the API key hasn't been revoked`; } else { errorMessage += ` - Authentication required - No session token or API key provided - Set a session token using setSessionToken() or provide an API key`; } } else if (status === 403) { const isSessionToken = !!this.sessionToken; if (isSessionToken) { errorMessage += ` - Your session token may not have permission for this operation - Check the user's role and permissions - Verify the session token belongs to a user with sufficient access - Ensure you're using the correct authentication method`; } else { errorMessage += ` - Your API key may not have permission for this operation - Check the API key scopes and permissions - Verify you're using the correct authentication method`; } } const httpErrorOptions = {}; if (status !== void 0) httpErrorOptions.status = status; if (method !== void 0) httpErrorOptions.method = method; if (fullUrl !== void 0) httpErrorOptions.url = fullUrl; if (Object.keys(requestHeaders).length > 0) httpErrorOptions.requestHeaders = requestHeaders; if (requestBody !== void 0) httpErrorOptions.requestBody = requestBody; if (Object.keys(requestQuery).length > 0) httpErrorOptions.requestQuery = requestQuery; if (responseData !== void 0) httpErrorOptions.responseData = responseData; if (Object.keys(responseHeaders).length > 0) httpErrorOptions.responseHeaders = responseHeaders; if (errorCode !== void 0) httpErrorOptions.code = errorCode; if (error !== void 0) httpErrorOptions.originalError = error; const httpError2 = new HttpError(errorMessage, httpErrorOptions); return Promise.reject(httpError2); } else { const networkDisplayUrl = relativeUrl ? `${baseUrl.replace(/^https?:\/\/[^/]+/, "")}${relativeUrl}` : fullUrl || "endpoint"; if (axiosError.code === "ECONNABORTED" || axiosError.message.includes("timeout")) { errorMessage = `Request timeout: ${method || "Request"} to ${networkDisplayUrl} exceeded ${axiosError.config?.timeout || 3e4}ms`; errorCode = "TIMEOUT"; } else if (axiosError.code === "ENOTFOUND" || axiosError.code === "ECONNREFUSED") { const isBackend2 = isBackendUrl(baseUrl); const connectionType = isBackend2 ? "backend URL (port 3470)" : "frontend URL (port 3498)"; errorMessage = `Cannot connect to Krapi Server at ${networkDisplayUrl}. - Is the server running? - Are you using the ${connectionType}? - Check firewall settings if accessing remotely.`; errorCode = "NETWORK_ERROR"; } else if (axiosError.code === "ECONNRESET" || axiosError.message.includes("socket hang up")) { errorMessage = `Connection reset by server at ${networkDisplayUrl}. - The server may have closed the connection unexpectedly - This can happen with long-running queries or large result sets - Try reducing query scope or increasing timeout if applicable - Check server logs for errors`; errorCode = "CONNECTION_RESET"; } else { errorMessage = `Network error: ${axiosError.message || "Failed to connect to server"} - Verify the endpoint URL is correct - Check network connectivity - Ensure the server is accessible`; errorCode = "NETWORK_ERROR"; } const requestBody2 = axiosError.config?.data; const requestQuery2 = axiosError.config?.params || {}; const httpErrorOptions = {}; if (method !== void 0) httpErrorOptions.method = method; if (fullUrl !== void 0) httpErrorOptions.url = fullUrl; if (Object.keys(requestHeaders).length > 0) httpErrorOptions.requestHeaders = requestHeaders; if (requestBody2 !== void 0) httpErrorOptions.requestBody = requestBody2; if (Object.keys(requestQuery2).length > 0) httpErrorOptions.requestQuery = requestQuery2; if (errorCode !== void 0) httpErrorOptions.code = errorCode; if (error !== void 0) httpErrorOptions.originalError = error; const httpError2 = new HttpError(errorMessage, httpErrorOptions); return Promise.reject(httpError2); } } if (error instanceof Error) { const httpError2 = new HttpError(error.message, { originalError: error, code: "UNKNOWN_ERROR" }); return Promise.reject(httpError2); } const httpError = new HttpError("Unknown error occurred", { originalError: error, code: "UNKNOWN_ERROR" }); return Promise.reject(httpError); } ); } /** * Get current authentication token (session token or API key) * * This method is used by the request interceptor to get the current token * at request time, ensuring we always use the latest token value. * * @returns {string | undefined} Current token or undefined if none set * @private */ getCurrentToken() { return this.sessionToken || this.apiKey; } /** * Set project ID for project-scoped operations * * The project ID will be automatically added as X-Project-ID header * only for project-scoped routes (e.g., /projects/{id}/...). * It will NOT be added for list/create operations (e.g., /projects). * * @param {string} projectId - Project ID * @returns {void} * * @example * client.setProjectId('project-id-here'); */ setProjectId(projectId) { this.projectId = projectId; } /** * Set session token for authentication * * @param {string} token - Session token * @returns {void} * @throws {Error} If HTTP client is not initialized * * @example * client.setSessionToken('session-token-here'); */ // Authentication methods setSessionToken(token) { if (!this.httpClient) { throw KrapiError.serviceUnavailable( "HTTP client not initialized. Call initializeClient() first or ensure connect() was called." ); } this.sessionToken = token; delete this.apiKey; this.httpClient.defaults.headers.common["Authorization"] = `Bearer ${token}`; delete this.httpClient.defaults.headers.common["X-API-Key"]; } /** * Set API key for authentication * * @param {string} key - API key * @returns {void} * * @example * client.setApiKey('api-key-here'); */ setApiKey(key) { if (!this.httpClient) { throw KrapiError.serviceUnavailable( "HTTP client not initialized. Call initializeClient() first or ensure connect() was called." ); } this.apiKey = key; delete this.sessionToken; this.httpClient.defaults.headers.common["Authorization"] = `Bearer ${key}`; delete this.httpClient.defaults.headers.common["X-API-Key"]; } /** * Clear authentication credentials * * Removes both session token and API key. * * @returns {void} * * @example * client.clearAuth(); */ clearAuth() { delete this.sessionToken; delete this.apiKey; } /** * Send GET request * * @template T * @param {string} endpoint - API endpoint * @param {QueryOptions} [params] - Query parameters * @returns {Promise<ApiResponse<T>>} API response * * @example * const response = await client.get('/projects', { limit: 10 }); */ /** * Execute request with retry logic if configured * * @template T * @param {() => Promise<T>} requestFn - Request function to execute * @returns {Promise<T>} Request result */ async executeWithRetry(requestFn) { if (!this.retryConfig?.enabled) { return requestFn(); } const maxRetries = this.retryConfig.maxRetries ?? 3; const retryDelay = this.retryConfig.retryDelay ?? 1e3; const retryableStatusCodes = this.retryConfig.retryableStatusCodes ?? [ 408, 429, 500, 502, 503, 504 ]; const retryableErrorCodes = this.retryConfig.retryableErrorCodes ?? [ "TIMEOUT", "NETWORK_ERROR" ]; let lastError; for (let attempt = 0; attempt <= maxRetries; attempt++) { try { return await requestFn(); } catch (error) { lastError = error; if (attempt >= maxRetries) { break; } let shouldRetry = false; if (error instanceof HttpError) { if (error.status && retryableStatusCodes.includes(error.status)) { shouldRetry = true; } if (error.code && retryableErrorCodes.includes(error.code)) { shouldRetry = true; } } else if (error instanceof Error) { if (error.message.includes("timeout") || error.message.includes("ECONNREFUSED") || error.message.includes("ENOTFOUND")) { shouldRetry = true; } } if (!shouldRetry) { break; } const delay = retryDelay * Math.pow(2, attempt); await new Promise((resolve) => setTimeout(resolve, delay)); } } throw lastError; } // Common HTTP methods async get(endpoint, params) { await this.initializeClient(); return this.executeWithRetry( () => this.httpClient.get(endpoint, { params }) ); } async post(endpoint, data) { await this.initializeClient(); return this.executeWithRetry(() => this.httpClient.post(endpoint, data)); } async put(endpoint, data) { await this.initializeClient(); return this.executeWithRetry(() => this.httpClient.put(endpoint, data)); } async patch(endpoint, data) { await this.initializeClient(); return this.executeWithRetry(() => this.httpClient.patch(endpoint, data)); } async delete(endpoint, data) { await this.initializeClient(); return this.executeWithRetry( () => this.httpClient.delete(endpoint, { data }) ); } // Utility method to build query strings buildQueryString(params) { const query = new URLSearchParams(); Object.entries(params).forEach(([key, value]) => { if (value !== void 0 && value !== null) { query.append(key, String(value)); } }); return query.toString(); } // Handle paginated responses async getPaginated(endpoint, params) { await this.initializeClient(); return this.httpClient.get(endpoint, { params }); } }; // src/http-clients/admin-http-client.ts var AdminHttpClient = class extends BaseHttpClient { // User Management async getAllUsers(options) { const params = new URLSearchParams(); if (options?.limit) params.append("limit", options.limit.toString()); if (options?.offset) params.append("offset", options.offset.toString()); if (options?.search) params.append("search", options.search); if (options?.role) params.append("role", options.role); if (options?.status) params.append("status", options.status); if (options?.project_id) params.append("project_id", options.project_id); const url = params.toString() ? `/admin/users?${params}` : "/admin/users"; return this.getPaginated(url); } async getUser(userId) { return this.get(`/admin/users/${userId}`); } async createUser(userData) { return this.post("/admin/users", userData); } async updateUser(userId, updates) { return this.put(`/admin/users/${userId}`, updates); } async deleteUser(userId) { return this.delete(`/admin/users/${userId}`); } async updateUserRole(userId, role) { return this.put(`/admin/users/${userId}/role`, { role }); } async updateUserPermissions(userId, permissions) { return this.put( `/admin/users/${userId}/permissions`, { permissions } ); } async activateUser(userId) { return this.put( `/admin/users/${userId}/activate`, {} ); } async deactivateUser(userId) { return this.put( `/admin/users/${userId}/deactivate`, {} ); } // API Key Management async createApiKey(userId, keyData) { return this.post( `/admin/api-keys`, { user_id: userId, ...keyData } ); } // Project Management async getAllProjects(options) { const params = new URLSearchParams(); if (options?.limit) params.append("limit", options.limit.toString()); if (options?.offset) params.append("offset", options.offset.toString()); if (options?.search) params.append("search", options.search); if (options?.status) params.append("status", options.status); if (options?.owner_id) params.append("owner_id", options.owner_id); const url = params.toString() ? `/admin/projects?${params}` : "/admin/projects"; return this.getPaginated(url); } async getProject(projectId) { return this.get(`/admin/projects/${projectId}`); } async updateProject(projectId, updates) { return this.put( `/admin/projects/${projectId}`, updates ); } async deleteProject(projectId) { return this.delete(`/admin/projects/${projectId}`); } async suspendProject(projectId, reason) { return this.put( `/admin/projects/${projectId}/suspend`, { reason } ); } async activateProject(projectId) { return this.put( `/admin/projects/${projectId}/activate`, {} ); } // System Monitoring async getSystemOverview() { return this.get("/admin/overview"); } async getSystemMetrics(options) { const params = new URLSearchParams(); if (options?.period) params.append("period", options.period); if (options?.start_date) params.append("start_date", options.start_date); if (options?.end_date) params.append("end_date", options.end_date); const url = params.toString() ? `/admin/metrics?${params}` : "/admin/metrics"; return this.get(url); } // Security Management async getSecurityLogs(options) { const params = new URLSearchParams(); if (options?.level) params.append("level", options.level); if (options?.user_id) params.append("user_id", options.user_id); if (options?.action_type) params.append("action_type", options.action_type); if (options?.limit) params.append("limit", options.limit.toString()); if (options?.offset) params.append("offset", options.offset.toString()); if (options?.start_date) params.append("start_date", options.start_date); if (options?.end_date) params.append("end_date", options.end_date); const url = params.toString() ? `/admin/security/logs?${params}` : "/admin/security/logs"; return this.getPaginated(url); } async getFailedLoginAttempts(options) { const params = new URLSearchParams(); if (options?.limit) params.append("limit", options.limit.toString()); if (options?.offset) params.append("offset", options.offset.toString()); if (options?.user_id) params.append("user_id", options.user_id); if (options?.ip_address) params.append("ip_address", options.ip_address); if (options?.start_date) params.append("start_date", options.start_date); if (options?.end_date) params.append("end_date", options.end_date); const url = params.toString() ? `/admin/security/failed-logins?${params}` : "/admin/security/failed-logins"; return this.getPaginated(url); } async blockIP(ipAddress, reason, duration_hours) { return this.post("/admin/security/block-ip", { ip_address: ipAddress, reason, duration_hours }); } async unblockIP(ipAddress) { return this.delete( `/admin/security/block-ip/${ipAddress}` ); } async getBlockedIPs() { return this.get("/admin/security/blocked-ips"); } // Maintenance Operations async runSystemMaintenance() { return this.post("/admin/maintenance/run"); } async backupSystem(options) { return this.post("/admin/maintenance/backup", options); } async restoreSystem(backupPath) { return this.post("/admin/maintenance/restore", { backup_path: backupPath }); } async clearOldLogs(options) { return this.delete("/admin/maintenance/clear-logs", options); } }; // src/http-clients/auth-http-client.ts var AuthHttpClient = class extends BaseHttpClient { // Constructor inherited from BaseHttpClient async register(registerData) { return await this.post( "/auth/register", registerData ); } async logout(sessionId) { return await this.post( "/auth/logout", sessionId ? { session_id: sessionId } : {} ); } // Admin Authentication async adminLogin(credentials) { const response = await this.post( "/auth/admin/login", credentials ); if (response.data?.token) { this.setSessionToken(response.data.token); } return response; } async adminApiLogin(request) { const response = await this.post( "/auth/admin/api-login", request ); if (response.data?.token) { this.setSessionToken(response.data.token); } return response; } // Project User Authentication async projectLogin(projectId, credentials) { const response = await this.post( `/auth/projects/${projectId}/login`, credentials ); if (response.data?.token) { this.setSessionToken(response.data.token); } return response; } async projectApiLogin(projectId, request) { const response = await this.post( `/auth/projects/${projectId}/api-login`, request ); if (response.data?.token) { this.setSessionToken(response.data.token); } return response; } // Session Management async getCurrentSession() { return this.get("/auth/me"); } async refreshSession() { const response = await this.post("/auth/refresh"); if (response.data?.session_token) { this.setSessionToken(response.data.session_token); } return response; } async createSession(apiKey) { return this.post("/auth/sessions", { api_key: apiKey }); } async revokeSession(sessionId) { return this.delete(`/auth/sessions/${sessionId}`); } async revokeAllSessions() { return this.post( "/auth/revoke-all" ); } // Password Management async changePassword(userId, userType, passwordData) { const endpoint = userType === "admin" ? `/auth/change-password` : `/auth/users/${userId}/change-password`; return this.post(endpoint, passwordData); } async resetPassword(resetData) { return this.post( "/auth/reset-password", resetData ); } // Session Queries async getUserSessions(userId, userType) { const endpoint = userType === "admin" ? `/auth/admin/sessions` : `/auth/users/${userId}/sessions`; return this.get(endpoint); } // Validation Methods async validateSession(sessionToken) { return this.post( "/auth/session/validate", { token: sessionToken } ); } async validateApiKey(apiKey) { return this.post("/auth/validate-key", { api_key: apiKey }); } async regenerateApiKey(req) { return this.post("/auth/regenerate-api-key", req); } }; // src/http-clients/email-http-client.ts var EmailHttpClient = class extends BaseHttpClient { // Email Configuration async getConfig(projectId) { return this.get(`/projects/${projectId}/email/config`); } async updateConfig(projectId, config) { return this.put(`/projects/${projectId}/email/config`, config); } async testConfig(projectId, testEmail) { return this.post( `/projects/${projectId}/email/test`, { email: testEmail } ); } // Email Templates async getTemplates(projectId, options) { const params = new URLSearchParams(); if (options?.limit) params.append("limit", options.limit.toString()); if (options?.offset) params.append("offset", options.offset.toString()); if (options?.search) params.append("search", options.search); if (options?.type) params.append("type", options.type); const url = params.toString() ? `/projects/${projectId}/email/templates?${params}` : `/projects/${projectId}/email/templates`; return this.getPaginated(url); } async getTemplate(projectId, templateId) { return this.get( `/projects/${projectId}/email/templates/${templateId}` ); } async createTemplate(projectId, template) { return this.post( `/projects/${projectId}/email/templates`, template ); } async updateTemplate(projectId, templateId, updates) { return this.put( `/projects/${projectId}/email/templates/${templateId}`, updates ); } async deleteTemplate(projectId, templateId) { return this.delete( `/projects/${projectId}/email/templates/${templateId}` ); } async duplicateTemplate(projectId, templateId, newName) { return this.post( `/projects/${projectId}/email/templates/${templateId}/duplicate`, { name: newName } ); } // Email Sending async sendEmail(projectId, emailRequest) { return this.post(`/projects/${projectId}/email/send`, emailRequest); } async sendBulkEmail(projectId, bulkRequest) { return this.post(`/projects/${projectId}/email/bulk-send`, bulkRequest); } async sendTemplateEmail(projectId, templateId, emailData) { return this.post(`/projects/${projectId}/email/templates/${templateId}/send`, emailData); } // Email Scheduling async scheduleEmail(projectId, scheduledEmail) { return this.post(`/projects/${projectId}/email/schedule`, scheduledEmail); } async getScheduledEmails(projectId, options) { const params = new URLSearchParams(); if (options?.limit) params.append("limit", options.limit.toString()); if (options?.offset) params.append("offset", options.offset.toString()); if (options?.status) params.append("status", options.status); if (options?.scheduled_after) params.append("scheduled_after", options.scheduled_after); if (options?.scheduled_before) params.append("scheduled_before", options.scheduled_before); const url = params.toString() ? `/projects/${projectId}/email/scheduled?${params}` : `/projects/${projectId}/email/scheduled`; return this.getPaginated(url); } async cancelScheduledEmail(projectId, scheduledId) { return this.delete( `/projects/${projectId}/email/scheduled/${scheduledId}` ); } // Email History async getEmailHistory(projectId, options) { const params = new URLSearchParams(); if (options?.limit) params.append("limit", options.limit.toString()); if (options?.offset) params.append("offset", options.offset.toString()); if (options?.status) params.append("status", options.status); if (options?.recipient) params.append("recipient", options.recipient); if (options?.template_id) params.append("template_id", options.template_id); if (options?.sent_after) params.append("sent_after", options.sent_after); if (options?.sent_before) params.append("sent_before", options.sent_before); const url = params.toString() ? `/projects/${projectId}/email/history?${params}` : `/projects/${projectId}/email/history`; return this.getPaginated(url); } async getEmailDetails(projectId, messageId) { return this.get(`/projects/${projectId}/email/history/${messageId}`); } // Email Analytics async getEmailAnalytics(projectId, options) { const params = new URLSearchParams(); if (options?.period) params.append("period", options.period); if (options?.start_date) params.append("start_date", options.start_date); if (options?.end_date) params.append("end_date", options.end_date); if (options?.template_id) params.append("template_id", options.template_id); const url = params.toString() ? `/projects/${projectId}/email/analytics?${params}` : `/projects/${projectId}/email/analytics`; return this.get(url); } // Email Providers async getEmailProviders() { return this.get("/email/providers"); } async testEmailProvider(providerId, testConfig) { return this.post( `/email/providers/${providerId}/test`, testConfig ); } // Email Validation async validateEmail(email) { return this.post("/email/validate", { email }); } async validateBulkEmails(emails) { return this.post("/email/validate-bulk", { emails }); } // Email Unsubscribe async unsubscribeEmail(projectId, email) { return this.post( `/projects/${projectId}/email/unsubscribe`, { email } ); } }; // src/http-clients/health-http-client.ts var HealthHttpClient = class extends BaseHttpClient { // System Health async check() { return this.get("/health"); } async checkDatabase() { return this.get("/health/database"); } async checkStorage() { return this.get("/health/storage"); } async checkEmail() { return this.get("/health/email"); } async checkAll() { return this.get("/health/all"); } // Diagnostics async runDiagnostics() { return this.post("/health/diagnostics"); } async runDatabaseDiagnostics() { return this.post("/health/diagnostics/database"); } async runStorageDiagnostics() { return this.post("/health/diagnostics/storage"); } async runEmailDiagnostics() { return this.post("/health/diagnostics/email"); } // Schema Validation async validateSchema() { return this.get("/health/validate-schema"); } async validateProjectSchema(projectId) { return this.get( `/projects/${projectId}/health/schema` ); } async validateCollectionSchema(projectId, collectionId) { return this.get( `/projects/${projectId}/collections/${collectionId}/health/schema` ); } // Auto-Fix async autoFix() { return this.post("/health/repair"); } async autoFixDatabase() { return this.post("/health/auto-fix/database"); } async autoFixSchema() { return this.post("/health/auto-fix/schema"); } async autoFixProject(projectId) { return this.post(`/projects/${projectId}/health/auto-fix`); } async autoFixCollection(projectId, collectionId) { return this.post( `/projects/${projectId}/collections/${collectionId}/health/auto-fix` ); } // Database Operations async repairDatabase() { return this.post("/health/repair"); } async optimizeDatabase() { return this.post("/health/database/optimize"); } async backupDatabase() { return this.post("/health/database/backup"); } async restoreDatabase(backupPath) { return this.post("/health/database/restore", { backup_path: backupPath }); } // Migrations async getMigrations() { return this.get("/health/migrations"); } async runMigrations() { return this.post("/health/migrate"); } async runMigration(migrationId) { return this.post(`/health/migrations/${migrationId}/run`); } async rollbackMigration(migrationId) { return this.post(`/health/migrations/${migrationId}/rollback`); } // Performance Monitoring async getPerformanceMetrics(optio