UNPKG

@smartsamurai/krapi-sdk

Version:

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

729 lines (724 loc) 22.6 kB
var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/core/krapi-error.ts function maskSensitiveValue(value) { if (typeof value === "string") { if (value.length > 10 && (value.toLowerCase().includes("password") || value.toLowerCase().includes("token") || value.toLowerCase().includes("secret") || value.toLowerCase().includes("key"))) { return `${value.substring(0, 4)}****`; } if (value.length > 50) { return `${value.substring(0, 20)}...[${value.length - 40} chars]...${value.substring(value.length - 20)}`; } } return value; } function getErrorCodeFromMessage(message) { const lowerMessage = message.toLowerCase(); if (lowerMessage.includes("unauthorized") || lowerMessage.includes("not authorized")) { return "UNAUTHORIZED"; } if (lowerMessage.includes("forbidden") || lowerMessage.includes("permission denied")) { return "FORBIDDEN"; } if (lowerMessage.includes("invalid credentials") || lowerMessage.includes("authentication failed")) { return "UNAUTHORIZED"; } if (lowerMessage.includes("not found") || lowerMessage.includes("does not exist")) { return "NOT_FOUND"; } if (lowerMessage.includes("already exists") || lowerMessage.includes("duplicate")) { return "CONFLICT"; } if (lowerMessage.includes("validation") || lowerMessage.includes("invalid") || lowerMessage.includes("required") || lowerMessage.includes("missing")) { return "VALIDATION_ERROR"; } if (lowerMessage.includes("network") || lowerMessage.includes("connection") || lowerMessage.includes("timeout")) { return "NETWORK_ERROR"; } if (lowerMessage.includes("rate limit")) { return "RATE_LIMIT_EXCEEDED"; } if (lowerMessage.includes("internal server") || lowerMessage.includes("database error") || lowerMessage.includes("query failed")) { return "INTERNAL_ERROR"; } if (lowerMessage.includes("service unavailable") || lowerMessage.includes("temporarily unavailable")) { return "SERVICE_UNAVAILABLE"; } if (lowerMessage.includes("bad request")) { return "BAD_REQUEST"; } return void 0; } var KrapiError; var init_krapi_error = __esm({ "src/core/krapi-error.ts"() { "use strict"; KrapiError = class _KrapiError extends Error { /** * Create a new KrapiError instance * * @param {string} message - Error message * @param {ErrorCode} [code] - Error code * @param {number} [status] - HTTP status code * @param {Record<string, unknown>} [details] - Additional error details * @param {string} [requestId] - Request ID for tracking * @param {unknown} [cause] - Original error that caused this error */ constructor(message, code = "INTERNAL_ERROR", status, details, requestId, cause) { super(message); this.name = "KrapiError"; this.code = code; if (status !== void 0) this.status = status; if (details !== void 0) this.details = details; if (requestId !== void 0) this.requestId = requestId; if (cause !== void 0) this.cause = cause; this.timestamp = (/* @__PURE__ */ new Date()).toISOString(); if (Error.captureStackTrace) { Error.captureStackTrace(this, _KrapiError); } } // Static factory methods /** * Create KrapiError from HttpError * * @param httpError - HttpError instance to convert * @param context - Additional context to include * @returns New KrapiError instance */ static fromHttpError(httpError, context) { let errorCode = "INTERNAL_ERROR"; if (httpError.status) { switch (httpError.status) { case 400: errorCode = "BAD_REQUEST"; break; case 401: errorCode = "UNAUTHORIZED"; break; case 403: errorCode = "FORBIDDEN"; break; case 404: errorCode = "NOT_FOUND"; break; case 409: errorCode = "CONFLICT"; break; case 422: errorCode = "UNPROCESSABLE_ENTITY"; break; case 429: errorCode = "RATE_LIMIT_EXCEEDED"; break; case 500: errorCode = "SERVER_ERROR"; break; case 502: case 503: errorCode = "SERVICE_UNAVAILABLE"; break; case 408: errorCode = "TIMEOUT"; break; } } return new _KrapiError( httpError.message, errorCode, httpError.status, { httpError: { code: httpError.code, responseData: httpError.responseData }, ...context } ); } /** * Create KrapiError from generic Error * * @param error - Generic error to convert * @param defaultCode - Default error code if cannot be inferred * @param context - Additional context to include * @returns New KrapiError instance */ static fromError(error, defaultCode = "INTERNAL_ERROR", context) { const code = getErrorCodeFromMessage(error.message) || defaultCode; return new _KrapiError( error.message, code, void 0, { originalStack: error.stack, ...context }, void 0, error ); } /** * Create validation error * * @param message - Error message * @param field - Field that failed validation * @param value - Invalid value (will be masked for security) * @param context - Additional context * @returns New validation error */ static validationError(message, field, value, context) { return new _KrapiError( message, "VALIDATION_ERROR", 400, { field, value: maskSensitiveValue(value), ...context } ); } /** * Create not found error * * @param message - Error message or resource type * @param context - Additional context (optional) * @returns New not found error */ static notFound(message, context) { return new _KrapiError( message, "NOT_FOUND", 404, context ); } /** * Create authentication error * * @param message - Error message * @param context - Additional context * @returns New authentication error */ static authError(message = "Authentication required", context) { return new _KrapiError( message, "UNAUTHORIZED", 401, context ); } /** * Create authorization error * * @param message - Error message * @param context - Additional context * @returns New authorization error */ static forbidden(message = "Access forbidden", context) { return new _KrapiError( message, "FORBIDDEN", 403, context ); } /** * Create conflict error * * @param message - Error message * @param context - Additional context * @returns New conflict error */ static conflict(message, context) { return new _KrapiError( message, "CONFLICT", 409, context ); } /** * Create internal server error * * @param message - Error message * @param context - Additional context * @returns New internal server error */ static internalError(message = "Internal server error", context) { return new _KrapiError( message, "INTERNAL_ERROR", 500, context ); } /** * Create bad request error * * @param message - Error message * @param context - Additional context * @returns New bad request error */ static badRequest(message, context) { return new _KrapiError( message, "BAD_REQUEST", 400, context ); } /** * Create service unavailable error * * @param message - Error message * @param context - Additional context * @returns New service unavailable error */ static serviceUnavailable(message = "Service unavailable", context) { return new _KrapiError( message, "SERVICE_UNAVAILABLE", 503, context ); } // Instance methods /** * Create a new error with additional context * * @param context - Additional context to merge * @returns New KrapiError with merged context */ withContext(context) { return new _KrapiError( this.message, this.code, this.status, { ...this.details || {}, ...context }, this.requestId, this.cause ); } /** * Check if this is a validation error * * @returns True if this is a validation error */ isValidationError() { return this.code === "VALIDATION_ERROR"; } /** * Check if this is a not found error * * @returns True if this is a not found error */ isNotFound() { return this.code === "NOT_FOUND"; } /** * Check if this is an authentication error * * @returns True if this is an authentication error */ isAuthError() { return this.code === "UNAUTHORIZED" || this.code === "FORBIDDEN"; } /** * Check if this is a client error (4xx) * * @returns True if this is a client error */ isClientError() { return this.status !== void 0 && this.status >= 400 && this.status < 500; } /** * Check if this is a server error (5xx) * * @returns True if this is a server error */ isServerError() { return this.status !== void 0 && this.status >= 500; } /** * Convert error to JSON format * * @returns {Object} Error as JSON object */ toJSON() { const result = { code: this.code, message: this.message, timestamp: this.timestamp }; if (this.status !== void 0) result.status = this.status; if (this.details !== void 0) result.details = this.details; if (this.requestId !== void 0) result.request_id = this.requestId; if (this.cause !== void 0) { result.cause = this.cause instanceof Error ? this.cause.message : String(this.cause); } return result; } /** * Get detailed error message * * @returns {string} Detailed error message */ getDetailedMessage() { let message = this.message; if (this.code) { message = `[${this.code}] ${message}`; } if (this.status) { message = `${message} (HTTP ${this.status})`; } return message; } }; } }); // src/http-clients/http-error.ts var HttpError; var init_http_error = __esm({ "src/http-clients/http-error.ts"() { "use strict"; HttpError = class _HttpError extends Error { /** * Create a new HttpError instance * * @param {string} message - Error message * @param {Object} options - Error options * @param {number} [options.status] - HTTP status code * @param {string} [options.method] - HTTP method * @param {string} [options.url] - Request URL * @param {Record<string, string>} [options.requestHeaders] - Request headers * @param {unknown} [options.requestBody] - Request body/data sent * @param {Record<string, unknown>} [options.requestQuery] - Request query parameters * @param {unknown} [options.responseData] - Response data * @param {Record<string, string>} [options.responseHeaders] - Response headers * @param {string} [options.code] - Error code * @param {unknown} [options.originalError] - Original error */ constructor(message, options = {}) { super(message); this.name = "HttpError"; if (options.status !== void 0) { this.status = options.status; } if (options.method !== void 0) { this.method = options.method; } if (options.url !== void 0) { this.url = options.url; } if (options.requestHeaders !== void 0) { this.requestHeaders = options.requestHeaders; } if (options.requestBody !== void 0) { this.requestBody = options.requestBody; } if (options.requestQuery !== void 0) { this.requestQuery = options.requestQuery; } if (options.responseData !== void 0) { this.responseData = options.responseData; } if (options.responseHeaders !== void 0) { this.responseHeaders = options.responseHeaders; } if (options.code !== void 0) { this.code = options.code; } if (options.originalError !== void 0) { this.originalError = options.originalError; } this.isApiError = options.status !== void 0 && options.status >= 400; this.isNetworkError = options.status === void 0; this.isAuthError = options.status === 401 || options.status === 403; this.isClientError = options.status !== void 0 && options.status >= 400 && options.status < 500; this.isServerError = options.status !== void 0 && options.status >= 500; if (Error.captureStackTrace) { Error.captureStackTrace(this, _HttpError); } } /** * Get a detailed error message with all available information * * @returns {string} Detailed error message */ getDetailedMessage() { const parts = [this.message]; if (this.status) { parts.push(`(HTTP ${this.status})`); } if (this.method && this.url) { parts.push(`[${this.method} ${this.url}]`); } if (this.code) { parts.push(`[Code: ${this.code}]`); } if (this.responseData && typeof this.responseData === "object") { const response = this.responseData; if (response.error && typeof response.error === "string") { parts.push(`Backend error: ${response.error}`); } else if (response.message && typeof response.message === "string") { parts.push(`Backend message: ${response.message}`); } } return parts.join(" "); } /** * Convert error to JSON for logging * * @returns {Record<string, unknown>} JSON representation */ toJSON() { return { name: this.name, message: this.message, status: this.status, method: this.method, url: this.url, code: this.code, isApiError: this.isApiError, isNetworkError: this.isNetworkError, isAuthError: this.isAuthError, isClientError: this.isClientError, isServerError: this.isServerError, requestHeaders: this.requestHeaders, requestBody: this.requestBody, requestQuery: this.requestQuery, responseData: this.responseData, responseHeaders: this.responseHeaders, originalError: this.originalError, stack: this.stack }; } }; } }); // src/utils/error-handler.ts var error_handler_exports = {}; __export(error_handler_exports, { createRequestId: () => createRequestId, enrichError: () => enrichError, getErrorCodeFromMessage: () => getErrorCodeFromMessage2, getErrorCodeFromStatus: () => getErrorCodeFromStatus, normalizeError: () => normalizeError, preserveErrorContext: () => preserveErrorContext, transformHttpError: () => transformHttpError }); function normalizeError(error, defaultCode = "INTERNAL_ERROR", context) { if (error instanceof KrapiError) { return context ? enrichError(error, context) : error; } if (error instanceof HttpError) { return transformHttpError(error, context); } if (error instanceof Error) { const code = getErrorCodeFromMessage2(error.message); return new KrapiError( error.message, code, void 0, { ...context, originalError: error, stack: error.stack } ); } if (typeof error === "string") { const code = getErrorCodeFromMessage2(error); return new KrapiError( error, code, void 0, context ); } return new KrapiError( "An unexpected error occurred", defaultCode, void 0, { ...context, originalError: error } ); } function transformHttpError(httpError, context) { const errorCode = getErrorCodeFromStatus(httpError.status); let message = httpError.message; if (httpError.status && !message.includes(`HTTP ${httpError.status}`)) { message = `${message} (HTTP ${httpError.status})`; } const krapiError = new KrapiError( message, errorCode, httpError.status, { ...context, httpError: { method: httpError.method, url: httpError.url, status: httpError.status, code: httpError.code, isApiError: httpError.isApiError, isNetworkError: httpError.isNetworkError, isAuthError: httpError.isAuthError, isClientError: httpError.isClientError, isServerError: httpError.isServerError, // Request context - what was sent requestHeaders: httpError.requestHeaders, requestBody: httpError.requestBody, requestQuery: httpError.requestQuery, // Response context - what was received responseData: httpError.responseData, responseHeaders: httpError.responseHeaders }, originalError: httpError } ); return krapiError; } function enrichError(error, context) { const mergedDetails = { ...error.details || {}, ...context }; if (error.details?.originalError) { mergedDetails.originalError = error.details.originalError; } return new KrapiError( error.message, error.code, error.status, mergedDetails, error.requestId, error.cause ); } function getErrorCodeFromStatus(status) { if (!status) { return "NETWORK_ERROR"; } switch (status) { case 400: return "BAD_REQUEST"; case 401: return "UNAUTHORIZED"; case 403: return "FORBIDDEN"; case 404: return "NOT_FOUND"; case 409: return "CONFLICT"; case 422: return "UNPROCESSABLE_ENTITY"; case 429: return "RATE_LIMIT_EXCEEDED"; case 500: return "SERVER_ERROR"; case 502: case 503: case 504: return "SERVICE_UNAVAILABLE"; case 408: return "TIMEOUT"; default: if (status >= 400 && status < 500) { return "BAD_REQUEST"; } if (status >= 500) { return "SERVER_ERROR"; } return "INTERNAL_ERROR"; } } function getErrorCodeFromMessage2(message) { const lowerMessage = message.toLowerCase(); if (lowerMessage.includes("unauthorized") || lowerMessage.includes("not authorized")) { return "UNAUTHORIZED"; } if (lowerMessage.includes("forbidden") || lowerMessage.includes("permission denied")) { return "FORBIDDEN"; } if (lowerMessage.includes("invalid credentials") || lowerMessage.includes("authentication failed")) { return "UNAUTHORIZED"; } if (lowerMessage.includes("not found") || lowerMessage.includes("does not exist")) { return "NOT_FOUND"; } if (lowerMessage.includes("already exists") || lowerMessage.includes("duplicate")) { return "CONFLICT"; } if (lowerMessage.includes("validation") || lowerMessage.includes("invalid") || lowerMessage.includes("required") || lowerMessage.includes("missing")) { return "VALIDATION_ERROR"; } if (lowerMessage.includes("network") || lowerMessage.includes("connection") || lowerMessage.includes("timeout")) { return "NETWORK_ERROR"; } if (lowerMessage.includes("rate limit")) { return "RATE_LIMIT_EXCEEDED"; } if (lowerMessage.includes("internal server") || lowerMessage.includes("database error") || lowerMessage.includes("query failed")) { return "INTERNAL_ERROR"; } if (lowerMessage.includes("service unavailable") || lowerMessage.includes("temporarily unavailable")) { return "SERVICE_UNAVAILABLE"; } if (lowerMessage.includes("bad request")) { return "BAD_REQUEST"; } return "INTERNAL_ERROR"; } function preserveErrorContext(error, additionalContext) { if (error instanceof KrapiError) { return additionalContext ? enrichError(error, additionalContext) : error; } return normalizeError(error, void 0, additionalContext); } function createRequestId() { return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } var init_error_handler = __esm({ "src/utils/error-handler.ts"() { "use strict"; init_krapi_error(); init_http_error(); } }); export { __toCommonJS, KrapiError, init_krapi_error, HttpError, init_http_error, normalizeError, enrichError, createRequestId, error_handler_exports, init_error_handler };