@goatlab/typesense
Version:
Modern TypeScript wrapper for Typesense search engine API
162 lines • 6.1 kB
JavaScript
;
// 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