UNPKG

@goatlab/typesense

Version:

Modern TypeScript wrapper for Typesense search engine API

162 lines 6.1 kB
"use strict"; // TypesenseHttpClient - Handles network wrapper concerns Object.defineProperty(exports, "__esModule", { value: true }); exports.TypesenseHttpClient = void 0; const js_utils_1 = require("@goatlab/js-utils"); const typesense_model_1 = require("../typesense.model"); class TypesenseHttpClient { kyInstance; enforceTLS; options; constructor(options) { this.enforceTLS = options.enforceTLS ?? process.env.NODE_ENV === 'production'; this.options = options; // Security: Enforce HTTPS in production if (this.enforceTLS && !options.prefixUrl.startsWith('https://')) { throw new Error('HTTPS is required in production environment'); } this.kyInstance = options.kyInstance || this.createDefaultClient(options); } get importTimeout() { return this.options.importTimeout; } createDefaultClient(options) { return js_utils_1.Http.getClient({ prefixUrl: options.prefixUrl, timeout: options.defaultTimeout || 10000, headers: { 'X-TYPESENSE-API-KEY': options.token, 'Content-Type': 'application/json', }, hooks: { beforeRequest: options.beforeRequest || [], afterResponse: options.afterResponse || [], beforeError: [ ...(options.beforeError || []), async (error) => { const { response } = error; if (response) { try { const errorBody = await response.json(); // Security: Redact token from headers before throwing const sanitizedHeaders = this.sanitizeHeaders(error.request.headers); throw new typesense_model_1.TypesenseError(errorBody.message || error.message, response.status, errorBody, sanitizedHeaders); } catch (_parseError) { // If we can't parse the response, fall back to basic error throw new typesense_model_1.TypesenseError(error.message, response.status, null, this.sanitizeHeaders(error.request.headers)); } } throw error; }, ], }, }); } sanitizeHeaders(headers) { const sanitized = {}; headers.forEach((value, key) => { // Security: Redact sensitive headers if (key.toLowerCase().includes('api-key') || key.toLowerCase().includes('authorization')) { sanitized[key] = '[REDACTED]'; } else { sanitized[key] = value; } }); return sanitized; } async request(endpoint, options = {}) { const { method = 'GET', body, searchParams, timeout, signal } = options; // Remove leading slash for prefixUrl compatibility const cleanEndpoint = endpoint.startsWith('/') ? endpoint.slice(1) : endpoint; const requestOptions = { method, timeout: timeout || 10000, signal, }; if (body) { requestOptions.json = body; } if (searchParams) { requestOptions.searchParams = searchParams; } const response = await this.kyInstance(cleanEndpoint, requestOptions); return response.json(); } async requestText(endpoint, options = {}) { const { method = 'GET', body, searchParams, timeout, signal } = options; // Remove leading slash for prefixUrl compatibility const cleanEndpoint = endpoint.startsWith('/') ? endpoint.slice(1) : endpoint; const requestOptions = { method, timeout: timeout || 10000, signal, }; if (body) { requestOptions.json = body; } if (searchParams) { requestOptions.searchParams = searchParams; } const response = await this.kyInstance(cleanEndpoint, requestOptions); return response.text(); } async requestTextWithRawBody(endpoint, options = {}) { const { method = 'GET', body, searchParams, timeout, signal } = options; // Remove leading slash for prefixUrl compatibility const cleanEndpoint = endpoint.startsWith('/') ? endpoint.slice(1) : endpoint; const requestOptions = { method, timeout: timeout || 10000, signal, }; if (body) { // Use raw body instead of JSON encoding requestOptions.body = body; } if (searchParams) { requestOptions.searchParams = searchParams; } const response = await this.kyInstance(cleanEndpoint, requestOptions); return response.text(); } async stream(endpoint, options = {}) { const { method = 'GET', body, searchParams, signal } = options; // Remove leading slash for prefixUrl compatibility const cleanEndpoint = endpoint.startsWith('/') ? endpoint.slice(1) : endpoint; const requestOptions = { method, signal, }; if (body) { requestOptions.body = body; } if (searchParams) { requestOptions.searchParams = searchParams; } const response = await this.kyInstance(cleanEndpoint, requestOptions); if (!response.body) { throw new Error('Response body is not available for streaming'); } return response.body; } getClient() { return this.kyInstance; } getOptions() { return this.options; } } exports.TypesenseHttpClient = TypesenseHttpClient; //# sourceMappingURL=http-client.js.map