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