@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
JavaScript
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