@nmakarov/cli-toolkit 0.44.0 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-runner.cjs +163 -4
- package/dist/cli-runner.cjs.map +1 -1
- package/dist/cli-runner.js +163 -4
- package/dist/cli-runner.js.map +1 -1
- package/dist/db.cjs +163 -4
- package/dist/db.cjs.map +1 -1
- package/dist/db.js +163 -4
- package/dist/db.js.map +1 -1
- package/dist/http-client.cjs +1 -1
- package/dist/http-client.cjs.map +1 -1
- package/dist/http-client.js +1 -1
- package/dist/http-client.js.map +1 -1
- package/dist/index.cjs +163 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +163 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/http-client.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/http-client/index.js","../src/errors.js","../src/http-client/errors.js","../src/http-client/retry.js"],"sourcesContent":["/**\n * HttpClient - Resilient HTTP Client with Retry Logic\n *\n * A production-ready HTTP client that:\n * - Wraps axios with enhanced error handling and retry logic\n * - Never throws exceptions - always returns unified response format\n * - Uses exponential backoff with jitter for retries\n * - Provides human-readable error classifications\n * - Supports comprehensive logging\n * - Handles all HTTP methods consistently\n */\n\nimport axios, { } from \"axios\";\n\n\n\n\n\n\n\n\nimport { HttpClientError } from \"../errors.js\";\nimport { classifyError, getErrorDescription } from \"./errors.js\";\nimport { calculateRetryDelay, sleep, shouldRetryError, } from \"./retry.js\";\n\nexport { HttpClientError };\n\n/**\n * Resilient HTTP Client with automatic retry logic.\n * Use HttpClient.init(context, options) when running with init/context; constructor(config) for standalone.\n */\nexport class HttpClient {\n axiosInstance;\n config;\n logger;\n\n constructor(contextOrConfig = {}, config) {\n const hasContext = config !== undefined;\n const options = hasContext ? config : (contextOrConfig || {});\n const context = hasContext ? contextOrConfig : undefined;\n\n this.config = {\n ...options,\n logger: context?.logger ?? options.logger ?? console,\n };\n this.logger = this.config.logger;\n\n this.axiosInstance = axios.create({\n timeout: this.config.timeout,\n validateStatus: () => true,\n maxRedirects: this.config.maxRedirects,\n headers: { \"User-Agent\": this.config.userAgent },\n httpsAgent: this.config.validateSSL ? undefined : { rejectUnauthorized: false } ,\n });\n\n // Add response interceptor for logging (optional - only if debug enabled)\n this.axiosInstance.interceptors.response.use(\n (response) => response,\n (error) => {\n // Log network-level errors here if needed\n // (HTTP errors are handled in the request method)\n return Promise.reject(error);\n }\n );\n }\n\n /**\n * Static init - discovers params via getAllForModule(defs). Whatever is in options goes.\n */\n static init(context, options = {}) {\n const defs = {\n timeout: \"number default 30000\",\n retryCount: \"number default 3\",\n retryDelay: \"number default 1000\",\n maxRetryDelay: \"number default 30000\",\n retryJitter: \"number default 0.1\",\n userAgent: \"string default HttpClient/v1.0\",\n validateSSL: \"boolean default true\",\n maxRedirects: \"number default 5\",\n };\n const discovered = context?.params?.getAllForModule?.(defs) ?? {};\n const merged = { ...discovered, ...options, logger: options.logger ?? context?.logger };\n return new HttpClient(context, merged);\n }\n\n /**\n * Make an HTTP request with automatic retry logic\n * Never throws - always returns HttpClientResponse\n */\n async request(\n method,\n url,\n options = {}\n ) {\n const startTime = Date.now();\n\n // Merge request options with defaults\n const requestConfig = {\n method,\n url,\n timeout: options.timeout ?? this.config.timeout,\n headers: {\n \"User-Agent\": options.userAgent ?? this.config.userAgent,\n ...options.headers\n },\n params: options.params,\n data: options.data\n };\n\n const retryCount = options.retryCount ?? this.config.retryCount;\n const retryDelay = options.retryDelay ?? this.config.retryDelay;\n\n // Initialize retry context\n const _retryContext = null;\n let _lastError = null;\n\n // Attempt the request with retries\n for (let attempt = 1; attempt <= retryCount + 1; attempt++) {\n try {\n if (options.debug) {\n this.logger.debug?.(`[HttpClient] ${method} ${url} (attempt ${attempt}/${retryCount + 1})`);\n }\n\n const response = await this.axiosInstance.request(requestConfig);\n const duration = Date.now() - startTime;\n\n // Success! Return unified response format\n const customStatus = this.mapHttpStatusToCustomStatus(response.status);\n\n if (options.debug) {\n this.logger.debug?.(`[HttpClient] ${method} ${url} → ${response.status} ${customStatus} (${duration}ms)`);\n }\n\n return {\n status: customStatus,\n code: response.status,\n headers: response.headers ,\n data: response.data,\n duration,\n retryCount: attempt - 1,\n finalUrl: response.request?.res?.responseUrl || url\n };\n\n } catch (error) {\n _lastError = error;\n const duration = Date.now() - startTime;\n\n // Classify the error\n const classification = classifyError(error);\n const errorDescription = getErrorDescription(classification.type);\n\n // Log the error\n if (classification.retryable && attempt <= retryCount) {\n this.logger.warn?.(`[HttpClient] ${method} ${url} failed (${classification.type}): ${errorDescription}. Retrying in ${retryDelay}ms...`);\n } else if (!classification.retryable || attempt > retryCount) {\n this.logger.error?.(`[HttpClient] ${method} ${url} failed (${classification.type}): ${errorDescription}`);\n }\n\n // Check if we should retry\n if (attempt <= retryCount && shouldRetryError(classification)) {\n // Calculate delay and wait\n const delay = calculateRetryDelay(\n attempt,\n retryDelay,\n this.config.maxRetryDelay,\n this.config.retryJitter\n );\n\n if (options.debug) {\n this.logger.debug?.(`[HttpClient] Waiting ${delay}ms before retry ${attempt + 1}`);\n }\n\n await sleep(delay);\n continue;\n }\n\n // No more retries or not retryable - return error response\n return {\n status: classification.status,\n code: error.response?.status || null,\n error: classification.type,\n headers: error.response?.headers || null,\n data: error.response?.data || null,\n duration,\n retryCount: attempt - 1,\n finalUrl: url\n };\n }\n }\n\n // This should never be reached, but just in case\n return {\n status: \"unknown\",\n code: null,\n error: \"unknown\",\n headers: null,\n data: null,\n duration: Date.now() - startTime,\n retryCount: retryCount,\n finalUrl: url\n };\n }\n\n /**\n * GET request\n */\n async get(url, options = {}) {\n return this.request(\"GET\", url, options);\n }\n\n /**\n * POST request\n */\n async post(url, options = {}) {\n return this.request(\"POST\", url, options);\n }\n\n /**\n * PUT request\n */\n async put(url, options = {}) {\n return this.request(\"PUT\", url, options);\n }\n\n /**\n * DELETE request\n */\n async delete(url, options = {}) {\n return this.request(\"DELETE\", url, options);\n }\n\n /**\n * PATCH request\n */\n async patch(url, options = {}) {\n return this.request(\"PATCH\", url, options);\n }\n\n /**\n * HEAD request\n */\n async head(url, options = {}) {\n return this.request(\"HEAD\", url, options);\n }\n\n /**\n * OPTIONS request\n */\n async options(url, options = {}) {\n return this.request(\"OPTIONS\", url, options);\n }\n\n /**\n * Map HTTP status code to custom status\n */\n mapHttpStatusToCustomStatus(httpStatus) {\n if (httpStatus >= 200 && httpStatus < 300) {\n return \"success\";\n } else if (httpStatus === 401) {\n return \"authRequired\";\n } else if (httpStatus === 403) {\n return \"authFailed\";\n } else if (httpStatus >= 400 && httpStatus < 500) {\n return \"clientError\";\n } else if (httpStatus >= 500) {\n return \"serverError\";\n }\n return \"unknown\";\n }\n\n /**\n * Get current configuration (for debugging)\n */\n getConfig() {\n return { ...this.config };\n }\n}\n","/**\n * Error classes for the CLI toolkit\n */\n\nexport class FrameworkError extends Error {\n constructor(message) {\n super(message);\n this.name = \"FrameworkError\";\n }\n}\n\nexport class ParamError extends FrameworkError {\n constructor(message) {\n super(message);\n this.name = \"ParamError\";\n }\n}\n\nexport class InitError extends FrameworkError {\n constructor(message) {\n super(message);\n this.name = \"InitError\";\n }\n}\n\nexport class CriticalRequestError extends FrameworkError {\n constructor(message) {\n super(message);\n this.name = \"CriticalRequestError\";\n }\n}\n\nexport class ControlFlowError extends Error {\n constructor(message) {\n super(message);\n this.name = \"ControlFlowError\";\n }\n}\n\nexport class HttpClientError extends FrameworkError {\n constructor(message, cause) {\n super(message);this.cause = cause;;\n this.name = \"HttpClientError\";\n }\n}\n\nexport class FileDatabaseError extends FrameworkError {\n constructor(message) {\n super(message);\n this.name = \"FileDatabaseError\";\n }\n}\n\n","\n\n\n\n\n\n\n\n/**\n * Classify an error and determine retry behavior\n */\nexport function classifyError(error) {\n // Handle axios response errors (HTTP errors)\n if (error.response) {\n const { status } = error.response;\n\n switch (status) {\n case 400:\n return { type: \"badRequest\", retryable: false, isAuth: false, status: \"clientError\" };\n case 401:\n return { type: \"unauthorized\", retryable: false, isAuth: true, status: \"authRequired\" };\n case 403:\n return { type: \"forbidden\", retryable: false, isAuth: true, status: \"authFailed\" };\n case 404:\n return { type: \"notFound\", retryable: false, isAuth: false, status: \"clientError\" };\n case 405:\n return { type: \"methodNotAllowed\", retryable: false, isAuth: false, status: \"clientError\" };\n case 409:\n return { type: \"conflict\", retryable: false, isAuth: false, status: \"clientError\" };\n case 422:\n return { type: \"unprocessableEntity\", retryable: false, isAuth: false, status: \"clientError\" };\n case 429:\n return { type: \"tooManyRequests\", retryable: true, isAuth: false, status: \"clientError\" };\n case 500:\n return { type: \"internalServerError\", retryable: true, isAuth: false, status: \"serverError\" };\n case 502:\n return { type: \"badGateway\", retryable: true, isAuth: false, status: \"serverError\" };\n case 503:\n return { type: \"serviceUnavailable\", retryable: true, isAuth: false, status: \"serverError\" };\n case 504:\n return { type: \"gatewayTimeout\", retryable: true, isAuth: false, status: \"serverError\" };\n default:\n if (status >= 400 && status < 500) {\n return { type: \"clientError\" , retryable: false, isAuth: false, status: \"clientError\" };\n } else if (status >= 500) {\n return { type: \"serverError\" , retryable: true, isAuth: false, status: \"serverError\" };\n }\n break;\n }\n }\n\n // Handle network/connection errors (no response)\n if (error.code) {\n switch (error.code) {\n case \"ECONNREFUSED\":\n case \"ECONNRESET\":\n case \"EPIPE\":\n case \"ENOTFOUND\":\n case \"EHOSTUNREACH\":\n case \"ENETUNREACH\":\n return { type: \"connectionFailed\", retryable: true, isAuth: false, status: \"networkError\" };\n\n case \"ETIMEDOUT\":\n case \"ECONNABORTED\":\n case \"ESOCKETTIMEDOUT\":\n return { type: \"timeout\", retryable: true, isAuth: false, status: \"timeout\" };\n\n case \"EAUTH\":\n case \"EACCES\":\n return { type: \"unauthorized\", retryable: false, isAuth: true, status: \"authRequired\" };\n\n default:\n return { type: \"networkError\", retryable: true, isAuth: false, status: \"networkError\" };\n }\n }\n\n // Handle timeout errors\n if (error.message && (\n error.message.includes(\"timeout\") ||\n error.message.includes(\"TIMEOUT\") ||\n error.message.includes(\"aborted\")\n )) {\n return { type: \"timeout\", retryable: true, isAuth: false, status: \"timeout\" };\n }\n\n // Handle cancellation\n if (error.name === \"AbortError\" || error.message?.includes(\"cancelled\")) {\n return { type: \"requestCancelled\", retryable: false, isAuth: false, status: \"unknown\" };\n }\n\n // Default fallback\n return { type: \"unknown\", retryable: false, isAuth: false, status: \"unknown\" };\n}\n\n/**\n * Get a human-readable description for an error type\n */\nexport function getErrorDescription(errorType) {\n const descriptions = {\n connectionFailed: \"Failed to establish a connection to the server\",\n timeout: \"Request timed out before completing\",\n networkError: \"Network connection issue occurred\",\n badRequest: \"Request was malformed or invalid\",\n unauthorized: \"Authentication credentials are required\",\n forbidden: \"Access to the requested resource is forbidden\",\n notFound: \"The requested resource was not found\",\n methodNotAllowed: \"HTTP method not allowed for this resource\",\n conflict: \"Request conflicts with current server state\",\n unprocessableEntity: \"Request data could not be processed\",\n tooManyRequests: \"Too many requests sent in a short time\",\n internalServerError: \"Server encountered an internal error\",\n badGateway: \"Invalid response from upstream server\",\n serviceUnavailable: \"Server is temporarily unavailable\",\n gatewayTimeout: \"Upstream server timed out\",\n unknown: \"An unknown error occurred\",\n requestCancelled: \"Request was cancelled before completion\"\n };\n\n return descriptions[errorType] || \"An unknown error occurred\";\n}\n","\n\n\n\n\n\n\n\n/**\n * Calculate the next retry delay using exponential backoff with jitter\n *\n * Formula: delay = baseDelay * (2 ^ (attempt - 1)) + randomJitter\n *\n * Jitter prevents the \"thundering herd\" problem where multiple failed requests\n * all retry at the exact same time, overwhelming the server.\n */\nexport function calculateRetryDelay(\n attempt,\n baseDelay,\n maxDelay,\n jitterFactor = 0.1\n) {\n // Exponential backoff: baseDelay * (2 ^ (attempt - 1))\n const exponentialDelay = baseDelay * Math.pow(2, attempt - 1);\n\n // Cap at maximum delay\n const cappedDelay = Math.min(exponentialDelay, maxDelay);\n\n // Add jitter: random variation up to jitterFactor of the delay\n const jitter = cappedDelay * jitterFactor * Math.random();\n\n return Math.floor(cappedDelay + jitter);\n}\n\n/**\n * Sleep for the specified number of milliseconds\n */\nexport function sleep(ms) {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n/**\n * Determine if an error should be retried based on classification\n */\nexport function shouldRetryError(classification) {\n // Never retry auth errors (they won't succeed with the same credentials)\n if (classification.isAuth) {\n return false;\n }\n\n // Retry if the error is classified as retryable\n return classification.retryable;\n}\n\n/**\n * Create a retry context for tracking retry attempts\n */\nexport function createRetryContext(\n maxAttempts,\n baseDelay,\n maxDelay,\n jitterFactor\n) {\n return {\n attempt: 1,\n maxAttempts,\n lastError: new Error(\"Initial attempt\"),\n totalDelay: 0,\n nextDelay: calculateRetryDelay(1, baseDelay, maxDelay, jitterFactor)\n };\n}\n\n/**\n * Update retry context for the next attempt\n */\nexport function updateRetryContext(\n context,\n lastError,\n baseDelay,\n maxDelay,\n jitterFactor\n) {\n const nextAttempt = context.attempt + 1;\n const nextDelay = calculateRetryDelay(nextAttempt, baseDelay, maxDelay, jitterFactor);\n\n return {\n attempt: nextAttempt,\n maxAttempts: context.maxAttempts,\n lastError,\n totalDelay: context.totalDelay + context.nextDelay,\n nextDelay\n };\n}\n"],"mappings":"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","names":["axios"]}
|
|
1
|
+
{"version":3,"sources":["../src/http-client/index.js","../src/errors.js","../src/http-client/errors.js","../src/http-client/retry.js"],"sourcesContent":["/**\n * HttpClient - Resilient HTTP Client with Retry Logic\n *\n * A production-ready HTTP client that:\n * - Wraps axios with enhanced error handling and retry logic\n * - Never throws exceptions - always returns unified response format\n * - Uses exponential backoff with jitter for retries\n * - Provides human-readable error classifications\n * - Supports comprehensive logging\n * - Handles all HTTP methods consistently\n */\n\nimport axios, { } from \"axios\";\n\n\n\n\n\n\n\n\nimport { HttpClientError } from \"../errors.js\";\nimport { classifyError, getErrorDescription } from \"./errors.js\";\nimport { calculateRetryDelay, sleep, shouldRetryError, } from \"./retry.js\";\n\nexport { HttpClientError };\n\n/**\n * Resilient HTTP Client with automatic retry logic.\n * Use HttpClient.init(context, options) when running with init/context; constructor(config) for standalone.\n */\nexport class HttpClient {\n axiosInstance;\n config;\n logger;\n\n constructor(contextOrConfig = {}, config) {\n const hasContext = config !== undefined;\n const options = hasContext ? config : (contextOrConfig || {});\n const context = hasContext ? contextOrConfig : undefined;\n\n this.config = {\n ...options,\n logger: context?.logger ?? options.logger ?? console,\n };\n this.logger = this.config.logger;\n\n this.axiosInstance = axios.create({\n timeout: this.config.timeout,\n validateStatus: () => true,\n maxRedirects: this.config.maxRedirects,\n headers: { \"User-Agent\": this.config.userAgent },\n httpsAgent: this.config.validateSSL ? undefined : { rejectUnauthorized: false } ,\n });\n\n // Add response interceptor for logging (optional - only if debug enabled)\n this.axiosInstance.interceptors.response.use(\n (response) => response,\n (error) => {\n // Log network-level errors here if needed\n // (HTTP errors are handled in the request method)\n return Promise.reject(error);\n }\n );\n }\n\n /**\n * Static init - discovers params via getAllForModule(defs). Whatever is in options goes.\n */\n static init(context, options = {}) {\n const defs = {\n timeout: \"number default 30000\",\n retryCount: \"number default 3\",\n retryDelay: \"number default 1000\",\n maxRetryDelay: \"number default 30000\",\n retryJitter: \"number default 0.1\",\n userAgent: \"string default HttpClient/v1.0\",\n validateSSL: \"boolean default true\",\n maxRedirects: \"number default 5\",\n };\n const discovered = context?.params?.getAllForModule?.(defs) ?? {};\n const merged = { ...discovered, ...options, logger: options.logger ?? context?.logger };\n return new HttpClient(context, merged);\n }\n\n /**\n * Make an HTTP request with automatic retry logic\n * Never throws - always returns HttpClientResponse\n */\n async request(\n method,\n url,\n options = {}\n ) {\n const startTime = Date.now();\n\n // Merge request options with defaults\n const requestConfig = {\n method,\n url,\n timeout: options.timeout ?? this.config.timeout,\n headers: {\n \"User-Agent\": options.userAgent ?? this.config.userAgent,\n ...options.headers\n },\n params: options.params,\n data: options.data\n };\n\n const retryCount = options.retryCount ?? this.config.retryCount;\n const retryDelay = options.retryDelay ?? this.config.retryDelay;\n\n // Initialize retry context\n const _retryContext = null;\n let _lastError = null;\n\n // Attempt the request with retries\n for (let attempt = 1; attempt <= retryCount + 1; attempt++) {\n try {\n if (options.debug) {\n this.logger.debug?.(`[HttpClient] ${method} ${url} (attempt ${attempt}/${retryCount + 1})`);\n }\n\n const response = await this.axiosInstance.request(requestConfig);\n const duration = Date.now() - startTime;\n\n // Success! Return unified response format\n const customStatus = this.mapHttpStatusToCustomStatus(response.status);\n\n if (options.debug) {\n this.logger.debug?.(`[HttpClient] ${method} ${url} → ${response.status} ${customStatus} (${duration}ms)`);\n }\n\n return {\n status: customStatus,\n code: response.status,\n headers: response.headers ,\n data: response.data,\n duration,\n retryCount: attempt - 1,\n finalUrl: response.request?.res?.responseUrl || url\n };\n\n } catch (error) {\n _lastError = error;\n const duration = Date.now() - startTime;\n\n // Classify the error\n const classification = classifyError(error);\n const errorDescription = getErrorDescription(classification.type);\n\n // Log the error\n if (classification.retryable && attempt <= retryCount) {\n this.logger.warn?.(`[HttpClient] ${method} ${url} failed (${classification.type}): ${errorDescription}. Retrying in ${retryDelay}ms...`);\n } else if (!classification.retryable || attempt > retryCount) {\n this.logger.error?.(`[HttpClient] ${method} ${url} failed (${classification.type}): ${errorDescription}`);\n }\n\n // Check if we should retry\n if (attempt <= retryCount && shouldRetryError(classification)) {\n // Calculate delay and wait\n const delay = calculateRetryDelay(\n attempt,\n retryDelay,\n this.config.maxRetryDelay,\n this.config.retryJitter\n );\n\n if (options.debug) {\n this.logger.debug?.(`[HttpClient] Waiting ${delay}ms before retry ${attempt + 1}`);\n }\n\n await sleep(delay);\n continue;\n }\n\n // No more retries or not retryable - return error response\n return {\n status: classification.status,\n code: error.response?.status || null,\n error: classification.type,\n headers: error.response?.headers || null,\n data: error.response?.data || null,\n duration,\n retryCount: attempt - 1,\n finalUrl: url\n };\n }\n }\n\n // This should never be reached, but just in case\n return {\n status: \"unknown\",\n code: null,\n error: \"unknown\",\n headers: null,\n data: null,\n duration: Date.now() - startTime,\n retryCount: retryCount,\n finalUrl: url\n };\n }\n\n /**\n * GET request\n */\n async get(url, options = {}) {\n return this.request(\"GET\", url, options);\n }\n\n /**\n * POST request\n */\n async post(url, options = {}) {\n return this.request(\"POST\", url, options);\n }\n\n /**\n * PUT request\n */\n async put(url, options = {}) {\n return this.request(\"PUT\", url, options);\n }\n\n /**\n * DELETE request\n */\n async delete(url, options = {}) {\n return this.request(\"DELETE\", url, options);\n }\n\n /**\n * PATCH request\n */\n async patch(url, options = {}) {\n return this.request(\"PATCH\", url, options);\n }\n\n /**\n * HEAD request\n */\n async head(url, options = {}) {\n return this.request(\"HEAD\", url, options);\n }\n\n /**\n * OPTIONS request\n */\n async options(url, options = {}) {\n return this.request(\"OPTIONS\", url, options);\n }\n\n /**\n * Map HTTP status code to custom status\n */\n mapHttpStatusToCustomStatus(httpStatus) {\n if (httpStatus >= 200 && httpStatus < 300) {\n return \"success\";\n } else if (httpStatus === 401) {\n return \"authRequired\";\n } else if (httpStatus === 403) {\n return \"authFailed\";\n } else if (httpStatus >= 400 && httpStatus < 500) {\n return \"clientError\";\n } else if (httpStatus >= 500) {\n return \"serverError\";\n }\n return \"unknown\";\n }\n\n /**\n * Get current configuration (for debugging)\n */\n getConfig() {\n return { ...this.config };\n }\n}\n","/**\n * Error classes for the CLI toolkit\n */\n\nexport class FrameworkError extends Error {\n constructor(message) {\n super(message);\n this.name = \"FrameworkError\";\n }\n}\n\nexport class ParamError extends FrameworkError {\n constructor(message) {\n super(message);\n this.name = \"ParamError\";\n }\n}\n\nexport class InitError extends FrameworkError {\n constructor(message) {\n super(message);\n this.name = \"InitError\";\n }\n}\n\nexport class CriticalRequestError extends FrameworkError {\n constructor(message) {\n super(message);\n this.name = \"CriticalRequestError\";\n }\n}\n\nexport class ControlFlowError extends Error {\n constructor(message) {\n super(message);\n this.name = \"ControlFlowError\";\n }\n}\n\nexport class HttpClientError extends FrameworkError {\n constructor(message, cause) {\n super(message);this.cause = cause;;\n this.name = \"HttpClientError\";\n }\n}\n\nexport class FileDatabaseError extends FrameworkError {\n constructor(message) {\n super(message);\n this.name = \"FileDatabaseError\";\n }\n}\n\n","\n\n\n\n\n\n\n\n/**\n * Classify an error and determine retry behavior\n */\nexport function classifyError(error) {\n // Handle axios response errors (HTTP errors)\n if (error.response) {\n const { status } = error.response;\n\n switch (status) {\n case 400:\n return { type: \"badRequest\", retryable: false, isAuth: false, status: \"clientError\" };\n case 401:\n return { type: \"unauthorized\", retryable: false, isAuth: true, status: \"authRequired\" };\n case 403:\n return { type: \"forbidden\", retryable: false, isAuth: true, status: \"authFailed\" };\n case 404:\n return { type: \"notFound\", retryable: false, isAuth: false, status: \"clientError\" };\n case 405:\n return { type: \"methodNotAllowed\", retryable: false, isAuth: false, status: \"clientError\" };\n case 409:\n return { type: \"conflict\", retryable: false, isAuth: false, status: \"clientError\" };\n case 422:\n return { type: \"unprocessableEntity\", retryable: false, isAuth: false, status: \"clientError\" };\n case 429:\n return { type: \"tooManyRequests\", retryable: true, isAuth: false, status: \"clientError\" };\n case 500:\n return { type: \"internalServerError\", retryable: true, isAuth: false, status: \"serverError\" };\n case 502:\n return { type: \"badGateway\", retryable: true, isAuth: false, status: \"serverError\" };\n case 503:\n return { type: \"serviceUnavailable\", retryable: true, isAuth: false, status: \"serverError\" };\n case 504:\n return { type: \"gatewayTimeout\", retryable: true, isAuth: false, status: \"serverError\" };\n default:\n if (status >= 400 && status < 500) {\n return { type: \"clientError\" , retryable: false, isAuth: false, status: \"clientError\" };\n } else if (status >= 500) {\n return { type: \"serverError\" , retryable: true, isAuth: false, status: \"serverError\" };\n }\n break;\n }\n }\n\n // Handle network/connection errors (no response)\n if (error.code) {\n switch (error.code) {\n case \"ECONNREFUSED\":\n case \"ECONNRESET\":\n case \"EPIPE\":\n case \"ENOTFOUND\":\n case \"EHOSTUNREACH\":\n case \"ENETUNREACH\":\n return { type: \"connectionFailed\", retryable: true, isAuth: false, status: \"networkError\" };\n\n case \"ETIMEDOUT\":\n case \"ECONNABORTED\":\n case \"ESOCKETTIMEDOUT\":\n return { type: \"timeout\", retryable: true, isAuth: false, status: \"timeout\" };\n\n case \"EAUTH\":\n case \"EACCES\":\n return { type: \"unauthorized\", retryable: false, isAuth: true, status: \"authRequired\" };\n\n default:\n return { type: \"networkError\", retryable: true, isAuth: false, status: \"networkError\" };\n }\n }\n\n // Handle timeout errors\n if (error.message && (\n error.message.includes(\"timeout\") ||\n error.message.includes(\"TIMEOUT\") ||\n error.message.includes(\"aborted\")\n )) {\n return { type: \"timeout\", retryable: true, isAuth: false, status: \"timeout\" };\n }\n\n // Handle cancellation\n if (error.name === \"AbortError\" || error.message?.includes(\"cancelled\")) {\n return { type: \"requestCancelled\", retryable: false, isAuth: false, status: \"unknown\" };\n }\n\n // Default fallback — treat as transient (socket/TLS glitches often lack a code).\n return { type: \"unknown\", retryable: true, isAuth: false, status: \"unknown\" };\n}\n\n/**\n * Get a human-readable description for an error type\n */\nexport function getErrorDescription(errorType) {\n const descriptions = {\n connectionFailed: \"Failed to establish a connection to the server\",\n timeout: \"Request timed out before completing\",\n networkError: \"Network connection issue occurred\",\n badRequest: \"Request was malformed or invalid\",\n unauthorized: \"Authentication credentials are required\",\n forbidden: \"Access to the requested resource is forbidden\",\n notFound: \"The requested resource was not found\",\n methodNotAllowed: \"HTTP method not allowed for this resource\",\n conflict: \"Request conflicts with current server state\",\n unprocessableEntity: \"Request data could not be processed\",\n tooManyRequests: \"Too many requests sent in a short time\",\n internalServerError: \"Server encountered an internal error\",\n badGateway: \"Invalid response from upstream server\",\n serviceUnavailable: \"Server is temporarily unavailable\",\n gatewayTimeout: \"Upstream server timed out\",\n unknown: \"An unknown error occurred\",\n requestCancelled: \"Request was cancelled before completion\"\n };\n\n return descriptions[errorType] || \"An unknown error occurred\";\n}\n","\n\n\n\n\n\n\n\n/**\n * Calculate the next retry delay using exponential backoff with jitter\n *\n * Formula: delay = baseDelay * (2 ^ (attempt - 1)) + randomJitter\n *\n * Jitter prevents the \"thundering herd\" problem where multiple failed requests\n * all retry at the exact same time, overwhelming the server.\n */\nexport function calculateRetryDelay(\n attempt,\n baseDelay,\n maxDelay,\n jitterFactor = 0.1\n) {\n // Exponential backoff: baseDelay * (2 ^ (attempt - 1))\n const exponentialDelay = baseDelay * Math.pow(2, attempt - 1);\n\n // Cap at maximum delay\n const cappedDelay = Math.min(exponentialDelay, maxDelay);\n\n // Add jitter: random variation up to jitterFactor of the delay\n const jitter = cappedDelay * jitterFactor * Math.random();\n\n return Math.floor(cappedDelay + jitter);\n}\n\n/**\n * Sleep for the specified number of milliseconds\n */\nexport function sleep(ms) {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n/**\n * Determine if an error should be retried based on classification\n */\nexport function shouldRetryError(classification) {\n // Never retry auth errors (they won't succeed with the same credentials)\n if (classification.isAuth) {\n return false;\n }\n\n // Retry if the error is classified as retryable\n return classification.retryable;\n}\n\n/**\n * Create a retry context for tracking retry attempts\n */\nexport function createRetryContext(\n maxAttempts,\n baseDelay,\n maxDelay,\n jitterFactor\n) {\n return {\n attempt: 1,\n maxAttempts,\n lastError: new Error(\"Initial attempt\"),\n totalDelay: 0,\n nextDelay: calculateRetryDelay(1, baseDelay, maxDelay, jitterFactor)\n };\n}\n\n/**\n * Update retry context for the next attempt\n */\nexport function updateRetryContext(\n context,\n lastError,\n baseDelay,\n maxDelay,\n jitterFactor\n) {\n const nextAttempt = context.attempt + 1;\n const nextDelay = calculateRetryDelay(nextAttempt, baseDelay, maxDelay, jitterFactor);\n\n return {\n attempt: nextAttempt,\n maxAttempts: context.maxAttempts,\n lastError,\n totalDelay: context.totalDelay + context.nextDelay,\n nextDelay\n };\n}\n"],"mappings":"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","names":["axios"]}
|
package/dist/http-client.js
CHANGED
|
@@ -81,7 +81,7 @@ function classifyError(error) {
|
|
|
81
81
|
if (error.name === "AbortError" || error.message?.includes("cancelled")) {
|
|
82
82
|
return { type: "requestCancelled", retryable: false, isAuth: false, status: "unknown" };
|
|
83
83
|
}
|
|
84
|
-
return { type: "unknown", retryable:
|
|
84
|
+
return { type: "unknown", retryable: true, isAuth: false, status: "unknown" };
|
|
85
85
|
}
|
|
86
86
|
function getErrorDescription(errorType) {
|
|
87
87
|
const descriptions = {
|
package/dist/http-client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/http-client/index.js","../src/errors.js","../src/http-client/errors.js","../src/http-client/retry.js"],"sourcesContent":["/**\n * HttpClient - Resilient HTTP Client with Retry Logic\n *\n * A production-ready HTTP client that:\n * - Wraps axios with enhanced error handling and retry logic\n * - Never throws exceptions - always returns unified response format\n * - Uses exponential backoff with jitter for retries\n * - Provides human-readable error classifications\n * - Supports comprehensive logging\n * - Handles all HTTP methods consistently\n */\n\nimport axios, { } from \"axios\";\n\n\n\n\n\n\n\n\nimport { HttpClientError } from \"../errors.js\";\nimport { classifyError, getErrorDescription } from \"./errors.js\";\nimport { calculateRetryDelay, sleep, shouldRetryError, } from \"./retry.js\";\n\nexport { HttpClientError };\n\n/**\n * Resilient HTTP Client with automatic retry logic.\n * Use HttpClient.init(context, options) when running with init/context; constructor(config) for standalone.\n */\nexport class HttpClient {\n axiosInstance;\n config;\n logger;\n\n constructor(contextOrConfig = {}, config) {\n const hasContext = config !== undefined;\n const options = hasContext ? config : (contextOrConfig || {});\n const context = hasContext ? contextOrConfig : undefined;\n\n this.config = {\n ...options,\n logger: context?.logger ?? options.logger ?? console,\n };\n this.logger = this.config.logger;\n\n this.axiosInstance = axios.create({\n timeout: this.config.timeout,\n validateStatus: () => true,\n maxRedirects: this.config.maxRedirects,\n headers: { \"User-Agent\": this.config.userAgent },\n httpsAgent: this.config.validateSSL ? undefined : { rejectUnauthorized: false } ,\n });\n\n // Add response interceptor for logging (optional - only if debug enabled)\n this.axiosInstance.interceptors.response.use(\n (response) => response,\n (error) => {\n // Log network-level errors here if needed\n // (HTTP errors are handled in the request method)\n return Promise.reject(error);\n }\n );\n }\n\n /**\n * Static init - discovers params via getAllForModule(defs). Whatever is in options goes.\n */\n static init(context, options = {}) {\n const defs = {\n timeout: \"number default 30000\",\n retryCount: \"number default 3\",\n retryDelay: \"number default 1000\",\n maxRetryDelay: \"number default 30000\",\n retryJitter: \"number default 0.1\",\n userAgent: \"string default HttpClient/v1.0\",\n validateSSL: \"boolean default true\",\n maxRedirects: \"number default 5\",\n };\n const discovered = context?.params?.getAllForModule?.(defs) ?? {};\n const merged = { ...discovered, ...options, logger: options.logger ?? context?.logger };\n return new HttpClient(context, merged);\n }\n\n /**\n * Make an HTTP request with automatic retry logic\n * Never throws - always returns HttpClientResponse\n */\n async request(\n method,\n url,\n options = {}\n ) {\n const startTime = Date.now();\n\n // Merge request options with defaults\n const requestConfig = {\n method,\n url,\n timeout: options.timeout ?? this.config.timeout,\n headers: {\n \"User-Agent\": options.userAgent ?? this.config.userAgent,\n ...options.headers\n },\n params: options.params,\n data: options.data\n };\n\n const retryCount = options.retryCount ?? this.config.retryCount;\n const retryDelay = options.retryDelay ?? this.config.retryDelay;\n\n // Initialize retry context\n const _retryContext = null;\n let _lastError = null;\n\n // Attempt the request with retries\n for (let attempt = 1; attempt <= retryCount + 1; attempt++) {\n try {\n if (options.debug) {\n this.logger.debug?.(`[HttpClient] ${method} ${url} (attempt ${attempt}/${retryCount + 1})`);\n }\n\n const response = await this.axiosInstance.request(requestConfig);\n const duration = Date.now() - startTime;\n\n // Success! Return unified response format\n const customStatus = this.mapHttpStatusToCustomStatus(response.status);\n\n if (options.debug) {\n this.logger.debug?.(`[HttpClient] ${method} ${url} → ${response.status} ${customStatus} (${duration}ms)`);\n }\n\n return {\n status: customStatus,\n code: response.status,\n headers: response.headers ,\n data: response.data,\n duration,\n retryCount: attempt - 1,\n finalUrl: response.request?.res?.responseUrl || url\n };\n\n } catch (error) {\n _lastError = error;\n const duration = Date.now() - startTime;\n\n // Classify the error\n const classification = classifyError(error);\n const errorDescription = getErrorDescription(classification.type);\n\n // Log the error\n if (classification.retryable && attempt <= retryCount) {\n this.logger.warn?.(`[HttpClient] ${method} ${url} failed (${classification.type}): ${errorDescription}. Retrying in ${retryDelay}ms...`);\n } else if (!classification.retryable || attempt > retryCount) {\n this.logger.error?.(`[HttpClient] ${method} ${url} failed (${classification.type}): ${errorDescription}`);\n }\n\n // Check if we should retry\n if (attempt <= retryCount && shouldRetryError(classification)) {\n // Calculate delay and wait\n const delay = calculateRetryDelay(\n attempt,\n retryDelay,\n this.config.maxRetryDelay,\n this.config.retryJitter\n );\n\n if (options.debug) {\n this.logger.debug?.(`[HttpClient] Waiting ${delay}ms before retry ${attempt + 1}`);\n }\n\n await sleep(delay);\n continue;\n }\n\n // No more retries or not retryable - return error response\n return {\n status: classification.status,\n code: error.response?.status || null,\n error: classification.type,\n headers: error.response?.headers || null,\n data: error.response?.data || null,\n duration,\n retryCount: attempt - 1,\n finalUrl: url\n };\n }\n }\n\n // This should never be reached, but just in case\n return {\n status: \"unknown\",\n code: null,\n error: \"unknown\",\n headers: null,\n data: null,\n duration: Date.now() - startTime,\n retryCount: retryCount,\n finalUrl: url\n };\n }\n\n /**\n * GET request\n */\n async get(url, options = {}) {\n return this.request(\"GET\", url, options);\n }\n\n /**\n * POST request\n */\n async post(url, options = {}) {\n return this.request(\"POST\", url, options);\n }\n\n /**\n * PUT request\n */\n async put(url, options = {}) {\n return this.request(\"PUT\", url, options);\n }\n\n /**\n * DELETE request\n */\n async delete(url, options = {}) {\n return this.request(\"DELETE\", url, options);\n }\n\n /**\n * PATCH request\n */\n async patch(url, options = {}) {\n return this.request(\"PATCH\", url, options);\n }\n\n /**\n * HEAD request\n */\n async head(url, options = {}) {\n return this.request(\"HEAD\", url, options);\n }\n\n /**\n * OPTIONS request\n */\n async options(url, options = {}) {\n return this.request(\"OPTIONS\", url, options);\n }\n\n /**\n * Map HTTP status code to custom status\n */\n mapHttpStatusToCustomStatus(httpStatus) {\n if (httpStatus >= 200 && httpStatus < 300) {\n return \"success\";\n } else if (httpStatus === 401) {\n return \"authRequired\";\n } else if (httpStatus === 403) {\n return \"authFailed\";\n } else if (httpStatus >= 400 && httpStatus < 500) {\n return \"clientError\";\n } else if (httpStatus >= 500) {\n return \"serverError\";\n }\n return \"unknown\";\n }\n\n /**\n * Get current configuration (for debugging)\n */\n getConfig() {\n return { ...this.config };\n }\n}\n","/**\n * Error classes for the CLI toolkit\n */\n\nexport class FrameworkError extends Error {\n constructor(message) {\n super(message);\n this.name = \"FrameworkError\";\n }\n}\n\nexport class ParamError extends FrameworkError {\n constructor(message) {\n super(message);\n this.name = \"ParamError\";\n }\n}\n\nexport class InitError extends FrameworkError {\n constructor(message) {\n super(message);\n this.name = \"InitError\";\n }\n}\n\nexport class CriticalRequestError extends FrameworkError {\n constructor(message) {\n super(message);\n this.name = \"CriticalRequestError\";\n }\n}\n\nexport class ControlFlowError extends Error {\n constructor(message) {\n super(message);\n this.name = \"ControlFlowError\";\n }\n}\n\nexport class HttpClientError extends FrameworkError {\n constructor(message, cause) {\n super(message);this.cause = cause;;\n this.name = \"HttpClientError\";\n }\n}\n\nexport class FileDatabaseError extends FrameworkError {\n constructor(message) {\n super(message);\n this.name = \"FileDatabaseError\";\n }\n}\n\n","\n\n\n\n\n\n\n\n/**\n * Classify an error and determine retry behavior\n */\nexport function classifyError(error) {\n // Handle axios response errors (HTTP errors)\n if (error.response) {\n const { status } = error.response;\n\n switch (status) {\n case 400:\n return { type: \"badRequest\", retryable: false, isAuth: false, status: \"clientError\" };\n case 401:\n return { type: \"unauthorized\", retryable: false, isAuth: true, status: \"authRequired\" };\n case 403:\n return { type: \"forbidden\", retryable: false, isAuth: true, status: \"authFailed\" };\n case 404:\n return { type: \"notFound\", retryable: false, isAuth: false, status: \"clientError\" };\n case 405:\n return { type: \"methodNotAllowed\", retryable: false, isAuth: false, status: \"clientError\" };\n case 409:\n return { type: \"conflict\", retryable: false, isAuth: false, status: \"clientError\" };\n case 422:\n return { type: \"unprocessableEntity\", retryable: false, isAuth: false, status: \"clientError\" };\n case 429:\n return { type: \"tooManyRequests\", retryable: true, isAuth: false, status: \"clientError\" };\n case 500:\n return { type: \"internalServerError\", retryable: true, isAuth: false, status: \"serverError\" };\n case 502:\n return { type: \"badGateway\", retryable: true, isAuth: false, status: \"serverError\" };\n case 503:\n return { type: \"serviceUnavailable\", retryable: true, isAuth: false, status: \"serverError\" };\n case 504:\n return { type: \"gatewayTimeout\", retryable: true, isAuth: false, status: \"serverError\" };\n default:\n if (status >= 400 && status < 500) {\n return { type: \"clientError\" , retryable: false, isAuth: false, status: \"clientError\" };\n } else if (status >= 500) {\n return { type: \"serverError\" , retryable: true, isAuth: false, status: \"serverError\" };\n }\n break;\n }\n }\n\n // Handle network/connection errors (no response)\n if (error.code) {\n switch (error.code) {\n case \"ECONNREFUSED\":\n case \"ECONNRESET\":\n case \"EPIPE\":\n case \"ENOTFOUND\":\n case \"EHOSTUNREACH\":\n case \"ENETUNREACH\":\n return { type: \"connectionFailed\", retryable: true, isAuth: false, status: \"networkError\" };\n\n case \"ETIMEDOUT\":\n case \"ECONNABORTED\":\n case \"ESOCKETTIMEDOUT\":\n return { type: \"timeout\", retryable: true, isAuth: false, status: \"timeout\" };\n\n case \"EAUTH\":\n case \"EACCES\":\n return { type: \"unauthorized\", retryable: false, isAuth: true, status: \"authRequired\" };\n\n default:\n return { type: \"networkError\", retryable: true, isAuth: false, status: \"networkError\" };\n }\n }\n\n // Handle timeout errors\n if (error.message && (\n error.message.includes(\"timeout\") ||\n error.message.includes(\"TIMEOUT\") ||\n error.message.includes(\"aborted\")\n )) {\n return { type: \"timeout\", retryable: true, isAuth: false, status: \"timeout\" };\n }\n\n // Handle cancellation\n if (error.name === \"AbortError\" || error.message?.includes(\"cancelled\")) {\n return { type: \"requestCancelled\", retryable: false, isAuth: false, status: \"unknown\" };\n }\n\n // Default fallback\n return { type: \"unknown\", retryable: false, isAuth: false, status: \"unknown\" };\n}\n\n/**\n * Get a human-readable description for an error type\n */\nexport function getErrorDescription(errorType) {\n const descriptions = {\n connectionFailed: \"Failed to establish a connection to the server\",\n timeout: \"Request timed out before completing\",\n networkError: \"Network connection issue occurred\",\n badRequest: \"Request was malformed or invalid\",\n unauthorized: \"Authentication credentials are required\",\n forbidden: \"Access to the requested resource is forbidden\",\n notFound: \"The requested resource was not found\",\n methodNotAllowed: \"HTTP method not allowed for this resource\",\n conflict: \"Request conflicts with current server state\",\n unprocessableEntity: \"Request data could not be processed\",\n tooManyRequests: \"Too many requests sent in a short time\",\n internalServerError: \"Server encountered an internal error\",\n badGateway: \"Invalid response from upstream server\",\n serviceUnavailable: \"Server is temporarily unavailable\",\n gatewayTimeout: \"Upstream server timed out\",\n unknown: \"An unknown error occurred\",\n requestCancelled: \"Request was cancelled before completion\"\n };\n\n return descriptions[errorType] || \"An unknown error occurred\";\n}\n","\n\n\n\n\n\n\n\n/**\n * Calculate the next retry delay using exponential backoff with jitter\n *\n * Formula: delay = baseDelay * (2 ^ (attempt - 1)) + randomJitter\n *\n * Jitter prevents the \"thundering herd\" problem where multiple failed requests\n * all retry at the exact same time, overwhelming the server.\n */\nexport function calculateRetryDelay(\n attempt,\n baseDelay,\n maxDelay,\n jitterFactor = 0.1\n) {\n // Exponential backoff: baseDelay * (2 ^ (attempt - 1))\n const exponentialDelay = baseDelay * Math.pow(2, attempt - 1);\n\n // Cap at maximum delay\n const cappedDelay = Math.min(exponentialDelay, maxDelay);\n\n // Add jitter: random variation up to jitterFactor of the delay\n const jitter = cappedDelay * jitterFactor * Math.random();\n\n return Math.floor(cappedDelay + jitter);\n}\n\n/**\n * Sleep for the specified number of milliseconds\n */\nexport function sleep(ms) {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n/**\n * Determine if an error should be retried based on classification\n */\nexport function shouldRetryError(classification) {\n // Never retry auth errors (they won't succeed with the same credentials)\n if (classification.isAuth) {\n return false;\n }\n\n // Retry if the error is classified as retryable\n return classification.retryable;\n}\n\n/**\n * Create a retry context for tracking retry attempts\n */\nexport function createRetryContext(\n maxAttempts,\n baseDelay,\n maxDelay,\n jitterFactor\n) {\n return {\n attempt: 1,\n maxAttempts,\n lastError: new Error(\"Initial attempt\"),\n totalDelay: 0,\n nextDelay: calculateRetryDelay(1, baseDelay, maxDelay, jitterFactor)\n };\n}\n\n/**\n * Update retry context for the next attempt\n */\nexport function updateRetryContext(\n context,\n lastError,\n baseDelay,\n maxDelay,\n jitterFactor\n) {\n const nextAttempt = context.attempt + 1;\n const nextDelay = calculateRetryDelay(nextAttempt, baseDelay, maxDelay, jitterFactor);\n\n return {\n attempt: nextAttempt,\n maxAttempts: context.maxAttempts,\n lastError,\n totalDelay: context.totalDelay + context.nextDelay,\n nextDelay\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/http-client/index.js","../src/errors.js","../src/http-client/errors.js","../src/http-client/retry.js"],"sourcesContent":["/**\n * HttpClient - Resilient HTTP Client with Retry Logic\n *\n * A production-ready HTTP client that:\n * - Wraps axios with enhanced error handling and retry logic\n * - Never throws exceptions - always returns unified response format\n * - Uses exponential backoff with jitter for retries\n * - Provides human-readable error classifications\n * - Supports comprehensive logging\n * - Handles all HTTP methods consistently\n */\n\nimport axios, { } from \"axios\";\n\n\n\n\n\n\n\n\nimport { HttpClientError } from \"../errors.js\";\nimport { classifyError, getErrorDescription } from \"./errors.js\";\nimport { calculateRetryDelay, sleep, shouldRetryError, } from \"./retry.js\";\n\nexport { HttpClientError };\n\n/**\n * Resilient HTTP Client with automatic retry logic.\n * Use HttpClient.init(context, options) when running with init/context; constructor(config) for standalone.\n */\nexport class HttpClient {\n axiosInstance;\n config;\n logger;\n\n constructor(contextOrConfig = {}, config) {\n const hasContext = config !== undefined;\n const options = hasContext ? config : (contextOrConfig || {});\n const context = hasContext ? contextOrConfig : undefined;\n\n this.config = {\n ...options,\n logger: context?.logger ?? options.logger ?? console,\n };\n this.logger = this.config.logger;\n\n this.axiosInstance = axios.create({\n timeout: this.config.timeout,\n validateStatus: () => true,\n maxRedirects: this.config.maxRedirects,\n headers: { \"User-Agent\": this.config.userAgent },\n httpsAgent: this.config.validateSSL ? undefined : { rejectUnauthorized: false } ,\n });\n\n // Add response interceptor for logging (optional - only if debug enabled)\n this.axiosInstance.interceptors.response.use(\n (response) => response,\n (error) => {\n // Log network-level errors here if needed\n // (HTTP errors are handled in the request method)\n return Promise.reject(error);\n }\n );\n }\n\n /**\n * Static init - discovers params via getAllForModule(defs). Whatever is in options goes.\n */\n static init(context, options = {}) {\n const defs = {\n timeout: \"number default 30000\",\n retryCount: \"number default 3\",\n retryDelay: \"number default 1000\",\n maxRetryDelay: \"number default 30000\",\n retryJitter: \"number default 0.1\",\n userAgent: \"string default HttpClient/v1.0\",\n validateSSL: \"boolean default true\",\n maxRedirects: \"number default 5\",\n };\n const discovered = context?.params?.getAllForModule?.(defs) ?? {};\n const merged = { ...discovered, ...options, logger: options.logger ?? context?.logger };\n return new HttpClient(context, merged);\n }\n\n /**\n * Make an HTTP request with automatic retry logic\n * Never throws - always returns HttpClientResponse\n */\n async request(\n method,\n url,\n options = {}\n ) {\n const startTime = Date.now();\n\n // Merge request options with defaults\n const requestConfig = {\n method,\n url,\n timeout: options.timeout ?? this.config.timeout,\n headers: {\n \"User-Agent\": options.userAgent ?? this.config.userAgent,\n ...options.headers\n },\n params: options.params,\n data: options.data\n };\n\n const retryCount = options.retryCount ?? this.config.retryCount;\n const retryDelay = options.retryDelay ?? this.config.retryDelay;\n\n // Initialize retry context\n const _retryContext = null;\n let _lastError = null;\n\n // Attempt the request with retries\n for (let attempt = 1; attempt <= retryCount + 1; attempt++) {\n try {\n if (options.debug) {\n this.logger.debug?.(`[HttpClient] ${method} ${url} (attempt ${attempt}/${retryCount + 1})`);\n }\n\n const response = await this.axiosInstance.request(requestConfig);\n const duration = Date.now() - startTime;\n\n // Success! Return unified response format\n const customStatus = this.mapHttpStatusToCustomStatus(response.status);\n\n if (options.debug) {\n this.logger.debug?.(`[HttpClient] ${method} ${url} → ${response.status} ${customStatus} (${duration}ms)`);\n }\n\n return {\n status: customStatus,\n code: response.status,\n headers: response.headers ,\n data: response.data,\n duration,\n retryCount: attempt - 1,\n finalUrl: response.request?.res?.responseUrl || url\n };\n\n } catch (error) {\n _lastError = error;\n const duration = Date.now() - startTime;\n\n // Classify the error\n const classification = classifyError(error);\n const errorDescription = getErrorDescription(classification.type);\n\n // Log the error\n if (classification.retryable && attempt <= retryCount) {\n this.logger.warn?.(`[HttpClient] ${method} ${url} failed (${classification.type}): ${errorDescription}. Retrying in ${retryDelay}ms...`);\n } else if (!classification.retryable || attempt > retryCount) {\n this.logger.error?.(`[HttpClient] ${method} ${url} failed (${classification.type}): ${errorDescription}`);\n }\n\n // Check if we should retry\n if (attempt <= retryCount && shouldRetryError(classification)) {\n // Calculate delay and wait\n const delay = calculateRetryDelay(\n attempt,\n retryDelay,\n this.config.maxRetryDelay,\n this.config.retryJitter\n );\n\n if (options.debug) {\n this.logger.debug?.(`[HttpClient] Waiting ${delay}ms before retry ${attempt + 1}`);\n }\n\n await sleep(delay);\n continue;\n }\n\n // No more retries or not retryable - return error response\n return {\n status: classification.status,\n code: error.response?.status || null,\n error: classification.type,\n headers: error.response?.headers || null,\n data: error.response?.data || null,\n duration,\n retryCount: attempt - 1,\n finalUrl: url\n };\n }\n }\n\n // This should never be reached, but just in case\n return {\n status: \"unknown\",\n code: null,\n error: \"unknown\",\n headers: null,\n data: null,\n duration: Date.now() - startTime,\n retryCount: retryCount,\n finalUrl: url\n };\n }\n\n /**\n * GET request\n */\n async get(url, options = {}) {\n return this.request(\"GET\", url, options);\n }\n\n /**\n * POST request\n */\n async post(url, options = {}) {\n return this.request(\"POST\", url, options);\n }\n\n /**\n * PUT request\n */\n async put(url, options = {}) {\n return this.request(\"PUT\", url, options);\n }\n\n /**\n * DELETE request\n */\n async delete(url, options = {}) {\n return this.request(\"DELETE\", url, options);\n }\n\n /**\n * PATCH request\n */\n async patch(url, options = {}) {\n return this.request(\"PATCH\", url, options);\n }\n\n /**\n * HEAD request\n */\n async head(url, options = {}) {\n return this.request(\"HEAD\", url, options);\n }\n\n /**\n * OPTIONS request\n */\n async options(url, options = {}) {\n return this.request(\"OPTIONS\", url, options);\n }\n\n /**\n * Map HTTP status code to custom status\n */\n mapHttpStatusToCustomStatus(httpStatus) {\n if (httpStatus >= 200 && httpStatus < 300) {\n return \"success\";\n } else if (httpStatus === 401) {\n return \"authRequired\";\n } else if (httpStatus === 403) {\n return \"authFailed\";\n } else if (httpStatus >= 400 && httpStatus < 500) {\n return \"clientError\";\n } else if (httpStatus >= 500) {\n return \"serverError\";\n }\n return \"unknown\";\n }\n\n /**\n * Get current configuration (for debugging)\n */\n getConfig() {\n return { ...this.config };\n }\n}\n","/**\n * Error classes for the CLI toolkit\n */\n\nexport class FrameworkError extends Error {\n constructor(message) {\n super(message);\n this.name = \"FrameworkError\";\n }\n}\n\nexport class ParamError extends FrameworkError {\n constructor(message) {\n super(message);\n this.name = \"ParamError\";\n }\n}\n\nexport class InitError extends FrameworkError {\n constructor(message) {\n super(message);\n this.name = \"InitError\";\n }\n}\n\nexport class CriticalRequestError extends FrameworkError {\n constructor(message) {\n super(message);\n this.name = \"CriticalRequestError\";\n }\n}\n\nexport class ControlFlowError extends Error {\n constructor(message) {\n super(message);\n this.name = \"ControlFlowError\";\n }\n}\n\nexport class HttpClientError extends FrameworkError {\n constructor(message, cause) {\n super(message);this.cause = cause;;\n this.name = \"HttpClientError\";\n }\n}\n\nexport class FileDatabaseError extends FrameworkError {\n constructor(message) {\n super(message);\n this.name = \"FileDatabaseError\";\n }\n}\n\n","\n\n\n\n\n\n\n\n/**\n * Classify an error and determine retry behavior\n */\nexport function classifyError(error) {\n // Handle axios response errors (HTTP errors)\n if (error.response) {\n const { status } = error.response;\n\n switch (status) {\n case 400:\n return { type: \"badRequest\", retryable: false, isAuth: false, status: \"clientError\" };\n case 401:\n return { type: \"unauthorized\", retryable: false, isAuth: true, status: \"authRequired\" };\n case 403:\n return { type: \"forbidden\", retryable: false, isAuth: true, status: \"authFailed\" };\n case 404:\n return { type: \"notFound\", retryable: false, isAuth: false, status: \"clientError\" };\n case 405:\n return { type: \"methodNotAllowed\", retryable: false, isAuth: false, status: \"clientError\" };\n case 409:\n return { type: \"conflict\", retryable: false, isAuth: false, status: \"clientError\" };\n case 422:\n return { type: \"unprocessableEntity\", retryable: false, isAuth: false, status: \"clientError\" };\n case 429:\n return { type: \"tooManyRequests\", retryable: true, isAuth: false, status: \"clientError\" };\n case 500:\n return { type: \"internalServerError\", retryable: true, isAuth: false, status: \"serverError\" };\n case 502:\n return { type: \"badGateway\", retryable: true, isAuth: false, status: \"serverError\" };\n case 503:\n return { type: \"serviceUnavailable\", retryable: true, isAuth: false, status: \"serverError\" };\n case 504:\n return { type: \"gatewayTimeout\", retryable: true, isAuth: false, status: \"serverError\" };\n default:\n if (status >= 400 && status < 500) {\n return { type: \"clientError\" , retryable: false, isAuth: false, status: \"clientError\" };\n } else if (status >= 500) {\n return { type: \"serverError\" , retryable: true, isAuth: false, status: \"serverError\" };\n }\n break;\n }\n }\n\n // Handle network/connection errors (no response)\n if (error.code) {\n switch (error.code) {\n case \"ECONNREFUSED\":\n case \"ECONNRESET\":\n case \"EPIPE\":\n case \"ENOTFOUND\":\n case \"EHOSTUNREACH\":\n case \"ENETUNREACH\":\n return { type: \"connectionFailed\", retryable: true, isAuth: false, status: \"networkError\" };\n\n case \"ETIMEDOUT\":\n case \"ECONNABORTED\":\n case \"ESOCKETTIMEDOUT\":\n return { type: \"timeout\", retryable: true, isAuth: false, status: \"timeout\" };\n\n case \"EAUTH\":\n case \"EACCES\":\n return { type: \"unauthorized\", retryable: false, isAuth: true, status: \"authRequired\" };\n\n default:\n return { type: \"networkError\", retryable: true, isAuth: false, status: \"networkError\" };\n }\n }\n\n // Handle timeout errors\n if (error.message && (\n error.message.includes(\"timeout\") ||\n error.message.includes(\"TIMEOUT\") ||\n error.message.includes(\"aborted\")\n )) {\n return { type: \"timeout\", retryable: true, isAuth: false, status: \"timeout\" };\n }\n\n // Handle cancellation\n if (error.name === \"AbortError\" || error.message?.includes(\"cancelled\")) {\n return { type: \"requestCancelled\", retryable: false, isAuth: false, status: \"unknown\" };\n }\n\n // Default fallback — treat as transient (socket/TLS glitches often lack a code).\n return { type: \"unknown\", retryable: true, isAuth: false, status: \"unknown\" };\n}\n\n/**\n * Get a human-readable description for an error type\n */\nexport function getErrorDescription(errorType) {\n const descriptions = {\n connectionFailed: \"Failed to establish a connection to the server\",\n timeout: \"Request timed out before completing\",\n networkError: \"Network connection issue occurred\",\n badRequest: \"Request was malformed or invalid\",\n unauthorized: \"Authentication credentials are required\",\n forbidden: \"Access to the requested resource is forbidden\",\n notFound: \"The requested resource was not found\",\n methodNotAllowed: \"HTTP method not allowed for this resource\",\n conflict: \"Request conflicts with current server state\",\n unprocessableEntity: \"Request data could not be processed\",\n tooManyRequests: \"Too many requests sent in a short time\",\n internalServerError: \"Server encountered an internal error\",\n badGateway: \"Invalid response from upstream server\",\n serviceUnavailable: \"Server is temporarily unavailable\",\n gatewayTimeout: \"Upstream server timed out\",\n unknown: \"An unknown error occurred\",\n requestCancelled: \"Request was cancelled before completion\"\n };\n\n return descriptions[errorType] || \"An unknown error occurred\";\n}\n","\n\n\n\n\n\n\n\n/**\n * Calculate the next retry delay using exponential backoff with jitter\n *\n * Formula: delay = baseDelay * (2 ^ (attempt - 1)) + randomJitter\n *\n * Jitter prevents the \"thundering herd\" problem where multiple failed requests\n * all retry at the exact same time, overwhelming the server.\n */\nexport function calculateRetryDelay(\n attempt,\n baseDelay,\n maxDelay,\n jitterFactor = 0.1\n) {\n // Exponential backoff: baseDelay * (2 ^ (attempt - 1))\n const exponentialDelay = baseDelay * Math.pow(2, attempt - 1);\n\n // Cap at maximum delay\n const cappedDelay = Math.min(exponentialDelay, maxDelay);\n\n // Add jitter: random variation up to jitterFactor of the delay\n const jitter = cappedDelay * jitterFactor * Math.random();\n\n return Math.floor(cappedDelay + jitter);\n}\n\n/**\n * Sleep for the specified number of milliseconds\n */\nexport function sleep(ms) {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n/**\n * Determine if an error should be retried based on classification\n */\nexport function shouldRetryError(classification) {\n // Never retry auth errors (they won't succeed with the same credentials)\n if (classification.isAuth) {\n return false;\n }\n\n // Retry if the error is classified as retryable\n return classification.retryable;\n}\n\n/**\n * Create a retry context for tracking retry attempts\n */\nexport function createRetryContext(\n maxAttempts,\n baseDelay,\n maxDelay,\n jitterFactor\n) {\n return {\n attempt: 1,\n maxAttempts,\n lastError: new Error(\"Initial attempt\"),\n totalDelay: 0,\n nextDelay: calculateRetryDelay(1, baseDelay, maxDelay, jitterFactor)\n };\n}\n\n/**\n * Update retry context for the next attempt\n */\nexport function updateRetryContext(\n context,\n lastError,\n baseDelay,\n maxDelay,\n jitterFactor\n) {\n const nextAttempt = context.attempt + 1;\n const nextDelay = calculateRetryDelay(nextAttempt, baseDelay, maxDelay, jitterFactor);\n\n return {\n attempt: nextAttempt,\n maxAttempts: context.maxAttempts,\n lastError,\n totalDelay: context.totalDelay + context.nextDelay,\n nextDelay\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -3651,10 +3651,35 @@ async function ensureSchemaEverywhere(dbs, spec, options = {}) {
|
|
|
3651
3651
|
// src/db/index.js
|
|
3652
3652
|
var KNEX_DEFAULTS = {
|
|
3653
3653
|
testConnection: true,
|
|
3654
|
-
|
|
3654
|
+
// min: 0 avoids holding idle sockets that go stale during long-running CLIs
|
|
3655
|
+
pool: { min: 0, max: 10, idleTimeoutMillis: 3e4 },
|
|
3655
3656
|
acquireConnectionTimeout: 1e4,
|
|
3656
3657
|
ssl: { rejectUnauthorized: false }
|
|
3657
3658
|
};
|
|
3659
|
+
var CONNECTION_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
3660
|
+
"ECONNRESET",
|
|
3661
|
+
"ECONNREFUSED",
|
|
3662
|
+
"EPIPE",
|
|
3663
|
+
"ETIMEDOUT",
|
|
3664
|
+
"ENOTFOUND",
|
|
3665
|
+
"EHOSTUNREACH",
|
|
3666
|
+
"ENETUNREACH",
|
|
3667
|
+
"ECONNABORTED",
|
|
3668
|
+
"CONNECTION_ENDED",
|
|
3669
|
+
"CONNECTION_CLOSED",
|
|
3670
|
+
// PostgreSQL SQLSTATE class 08xxx (connection exception) + admin shutdowns
|
|
3671
|
+
"08000",
|
|
3672
|
+
"08001",
|
|
3673
|
+
"08003",
|
|
3674
|
+
"08004",
|
|
3675
|
+
"08006",
|
|
3676
|
+
"08007",
|
|
3677
|
+
"08P01",
|
|
3678
|
+
"57P01",
|
|
3679
|
+
"57P02",
|
|
3680
|
+
"57P03"
|
|
3681
|
+
]);
|
|
3682
|
+
var CONNECTION_ERROR_MESSAGE_RE = /connection (terminated|ended|closed|destroyed|reset|refused|not open)|Connection terminated unexpectedly|Client has encountered a connection error|server closed the connection|Cannot use a pool after calling end|This socket has been ended|connect ECONNRESET|Timeout acquiring a connection/i;
|
|
3658
3683
|
var Db = class _Db {
|
|
3659
3684
|
static async init(context, options = {}) {
|
|
3660
3685
|
const buildConfig = async () => {
|
|
@@ -3902,10 +3927,11 @@ var Db = class _Db {
|
|
|
3902
3927
|
this.knexInstance = null;
|
|
3903
3928
|
this.isConnected = false;
|
|
3904
3929
|
this.queriesLog = [];
|
|
3930
|
+
this._reconnectPromise = null;
|
|
3905
3931
|
this.config = {
|
|
3906
3932
|
testConnection: true,
|
|
3907
3933
|
profile: false,
|
|
3908
|
-
pool: { min:
|
|
3934
|
+
pool: { min: 0, max: 10, idleTimeoutMillis: 3e4 },
|
|
3909
3935
|
acquireConnectionTimeout: 1e4,
|
|
3910
3936
|
ssl: { rejectUnauthorized: false },
|
|
3911
3937
|
logger: console,
|
|
@@ -3923,7 +3949,7 @@ var Db = class _Db {
|
|
|
3923
3949
|
if (!inst.knexInstance) {
|
|
3924
3950
|
throw new Error("Db: Not connected. Call connect() first.");
|
|
3925
3951
|
}
|
|
3926
|
-
return inst.knexInstance(...argumentsList);
|
|
3952
|
+
return inst.wrapQueryBuilder(inst.knexInstance(...argumentsList));
|
|
3927
3953
|
},
|
|
3928
3954
|
get: (target, prop) => {
|
|
3929
3955
|
if (prop === "_instance") {
|
|
@@ -3933,8 +3959,12 @@ var Db = class _Db {
|
|
|
3933
3959
|
const ownMethods = [
|
|
3934
3960
|
"connect",
|
|
3935
3961
|
"disconnect",
|
|
3962
|
+
"reconnect",
|
|
3936
3963
|
"testConnection",
|
|
3937
3964
|
"tableExists",
|
|
3965
|
+
"raw",
|
|
3966
|
+
"withConnectionRetry",
|
|
3967
|
+
"isConnectionError",
|
|
3938
3968
|
"getQueryLog",
|
|
3939
3969
|
"getKnex",
|
|
3940
3970
|
"isConnectedToDb",
|
|
@@ -3954,6 +3984,9 @@ var Db = class _Db {
|
|
|
3954
3984
|
if (inst.knexInstance) {
|
|
3955
3985
|
const knexProp = inst.knexInstance[prop];
|
|
3956
3986
|
if (typeof knexProp === "function") {
|
|
3987
|
+
if (prop === "raw") {
|
|
3988
|
+
return (...args) => inst.raw(...args);
|
|
3989
|
+
}
|
|
3957
3990
|
return knexProp.bind(inst.knexInstance);
|
|
3958
3991
|
}
|
|
3959
3992
|
return knexProp;
|
|
@@ -4033,6 +4066,130 @@ var Db = class _Db {
|
|
|
4033
4066
|
throw error;
|
|
4034
4067
|
}
|
|
4035
4068
|
}
|
|
4069
|
+
/**
|
|
4070
|
+
* True when the error indicates a dead socket / pool that a fresh connect may fix.
|
|
4071
|
+
* Safe to call as `Db.prototype.isConnectionError(err)` or via a connected handle.
|
|
4072
|
+
*/
|
|
4073
|
+
isConnectionError(error) {
|
|
4074
|
+
if (!error) {
|
|
4075
|
+
return false;
|
|
4076
|
+
}
|
|
4077
|
+
if (error instanceof AggregateError && Array.isArray(error.errors)) {
|
|
4078
|
+
return error.errors.some((e) => this.isConnectionError(e));
|
|
4079
|
+
}
|
|
4080
|
+
const code = error.code ?? error.errno;
|
|
4081
|
+
if (code != null && CONNECTION_ERROR_CODES.has(String(code))) {
|
|
4082
|
+
return true;
|
|
4083
|
+
}
|
|
4084
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
4085
|
+
return CONNECTION_ERROR_MESSAGE_RE.test(msg);
|
|
4086
|
+
}
|
|
4087
|
+
/**
|
|
4088
|
+
* Destroy the current knex pool and open a new one. Concurrent callers share one attempt.
|
|
4089
|
+
*/
|
|
4090
|
+
async reconnect() {
|
|
4091
|
+
if (this._reconnectPromise) {
|
|
4092
|
+
await this._reconnectPromise;
|
|
4093
|
+
return;
|
|
4094
|
+
}
|
|
4095
|
+
this._reconnectPromise = (async () => {
|
|
4096
|
+
const old = this.knexInstance;
|
|
4097
|
+
this.isConnected = false;
|
|
4098
|
+
this.knexInstance = null;
|
|
4099
|
+
this.queriesLog = [];
|
|
4100
|
+
if (old) {
|
|
4101
|
+
try {
|
|
4102
|
+
await old.destroy();
|
|
4103
|
+
} catch (error) {
|
|
4104
|
+
this.logger.debug?.(
|
|
4105
|
+
`[Db] destroy during reconnect: ${this.getErrorMessage(error)}`
|
|
4106
|
+
);
|
|
4107
|
+
}
|
|
4108
|
+
}
|
|
4109
|
+
await this.connect();
|
|
4110
|
+
})();
|
|
4111
|
+
try {
|
|
4112
|
+
await this._reconnectPromise;
|
|
4113
|
+
} finally {
|
|
4114
|
+
this._reconnectPromise = null;
|
|
4115
|
+
}
|
|
4116
|
+
}
|
|
4117
|
+
async reconnectAfterConnectionError(error) {
|
|
4118
|
+
this.logger.warn?.(
|
|
4119
|
+
`[Db] Connection lost (${this.getErrorMessage(error)}) \u2014 reconnecting\u2026`
|
|
4120
|
+
);
|
|
4121
|
+
await this.reconnect();
|
|
4122
|
+
}
|
|
4123
|
+
/**
|
|
4124
|
+
* Run `fn`; on a connection error, reconnect once (by default) and retry `fn`.
|
|
4125
|
+
* `fn` should look up `this.knexInstance` each call so the retry uses the new client.
|
|
4126
|
+
*/
|
|
4127
|
+
async withConnectionRetry(fn, { retries = 1 } = {}) {
|
|
4128
|
+
let lastError;
|
|
4129
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
4130
|
+
try {
|
|
4131
|
+
return await fn();
|
|
4132
|
+
} catch (error) {
|
|
4133
|
+
lastError = error;
|
|
4134
|
+
if (!this.isConnectionError(error) || attempt >= retries) {
|
|
4135
|
+
throw error;
|
|
4136
|
+
}
|
|
4137
|
+
await this.reconnectAfterConnectionError(error);
|
|
4138
|
+
}
|
|
4139
|
+
}
|
|
4140
|
+
throw lastError;
|
|
4141
|
+
}
|
|
4142
|
+
/**
|
|
4143
|
+
* Wrap a knex QueryBuilder / Raw so that awaiting it retries once after reconnect
|
|
4144
|
+
* when the first attempt dies with a connection error.
|
|
4145
|
+
*/
|
|
4146
|
+
wrapQueryBuilder(builder) {
|
|
4147
|
+
if (!builder || typeof builder.then !== "function" || builder.__dbReconnectWrapped) {
|
|
4148
|
+
return builder;
|
|
4149
|
+
}
|
|
4150
|
+
builder.__dbReconnectWrapped = true;
|
|
4151
|
+
const inst = this;
|
|
4152
|
+
const protoThen = Object.getPrototypeOf(builder)?.then;
|
|
4153
|
+
if (typeof protoThen !== "function") {
|
|
4154
|
+
return builder;
|
|
4155
|
+
}
|
|
4156
|
+
builder.then = function(onFulfilled, onRejected) {
|
|
4157
|
+
const run2 = async () => {
|
|
4158
|
+
try {
|
|
4159
|
+
return await protoThen.call(builder);
|
|
4160
|
+
} catch (error) {
|
|
4161
|
+
if (!inst.isConnectionError(error)) {
|
|
4162
|
+
throw error;
|
|
4163
|
+
}
|
|
4164
|
+
await inst.reconnectAfterConnectionError(error);
|
|
4165
|
+
if (typeof builder.clone === "function") {
|
|
4166
|
+
const retry = builder.clone();
|
|
4167
|
+
retry.client = inst.knexInstance.client;
|
|
4168
|
+
return await protoThen.call(retry);
|
|
4169
|
+
}
|
|
4170
|
+
if (typeof builder.toSQL === "function" && inst.knexInstance) {
|
|
4171
|
+
const sql = builder.toSQL();
|
|
4172
|
+
const statements = Array.isArray(sql) ? sql : [sql];
|
|
4173
|
+
let last;
|
|
4174
|
+
for (const stmt of statements) {
|
|
4175
|
+
last = await inst.knexInstance.raw(stmt.sql, stmt.bindings);
|
|
4176
|
+
}
|
|
4177
|
+
return last;
|
|
4178
|
+
}
|
|
4179
|
+
throw error;
|
|
4180
|
+
}
|
|
4181
|
+
};
|
|
4182
|
+
return run2().then(onFulfilled, onRejected);
|
|
4183
|
+
};
|
|
4184
|
+
return builder;
|
|
4185
|
+
}
|
|
4186
|
+
/** knex.raw with auto-reconnect on dead connections. */
|
|
4187
|
+
raw(...args) {
|
|
4188
|
+
if (!this.knexInstance) {
|
|
4189
|
+
throw new Error("Db: Not connected. Call connect() first.");
|
|
4190
|
+
}
|
|
4191
|
+
return this.wrapQueryBuilder(this.knexInstance.raw(...args));
|
|
4192
|
+
}
|
|
4036
4193
|
getErrorMessage(error) {
|
|
4037
4194
|
if (error instanceof AggregateError) {
|
|
4038
4195
|
const errors = error.errors || [];
|
|
@@ -4203,7 +4360,9 @@ var Db = class _Db {
|
|
|
4203
4360
|
throw new Error("Db: Not connected. Call connect() first.");
|
|
4204
4361
|
}
|
|
4205
4362
|
try {
|
|
4206
|
-
return await this.
|
|
4363
|
+
return await this.withConnectionRetry(
|
|
4364
|
+
() => this.knexInstance.schema.hasTable(tableName)
|
|
4365
|
+
);
|
|
4207
4366
|
} catch (error) {
|
|
4208
4367
|
this.logger.error?.(`[Db] Error checking table existence: ${error.message}`);
|
|
4209
4368
|
throw error;
|