UNPKG

@goatlab/typesense

Version:

Modern TypeScript wrapper for Typesense search engine API

469 lines 17.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TypesenseApi = exports.createTypedApi = void 0; // Collections const createCollection_1 = require("./actions/collections/createCollection"); const getCollection_1 = require("./actions/collections/getCollection"); const updateCollection_1 = require("./actions/collections/updateCollection"); const deleteCollection_1 = require("./actions/collections/deleteCollection"); const listCollections_1 = require("./actions/collections/listCollections"); const getOrCreateCollection_1 = require("./actions/collections/getOrCreateCollection"); // Documents const insertDocument_1 = require("./actions/documents/insertDocument"); const upsertDocument_1 = require("./actions/documents/upsertDocument"); const updateDocument_1 = require("./actions/documents/updateDocument"); const deleteDocument_1 = require("./actions/documents/deleteDocument"); const getDocumentById_1 = require("./actions/documents/getDocumentById"); const importDocuments_1 = require("./actions/documents/importDocuments"); const exportDocuments_1 = require("./actions/documents/exportDocuments"); const deleteByFilter_1 = require("./actions/documents/deleteByFilter"); const clearCollection_1 = require("./actions/documents/clearCollection"); // Search const search_1 = require("./actions/search/search"); const multiSearch_1 = require("./actions/search/multiSearch"); // Admin const health_1 = require("./actions/admin/health"); const metrics_1 = require("./actions/admin/metrics"); const getCollectionStats_1 = require("./actions/admin/getCollectionStats"); // Aliases const createOrUpdateAlias_1 = require("./actions/aliases/createOrUpdateAlias"); const getAlias_1 = require("./actions/aliases/getAlias"); const listAliases_1 = require("./actions/aliases/listAliases"); const deleteAlias_1 = require("./actions/aliases/deleteAlias"); // Synonyms const upsertSynonym_1 = require("./actions/synonyms/upsertSynonym"); const getSynonym_1 = require("./actions/synonyms/getSynonym"); const listSynonyms_1 = require("./actions/synonyms/listSynonyms"); const deleteSynonym_1 = require("./actions/synonyms/deleteSynonym"); // Overrides const upsertOverride_1 = require("./actions/overrides/upsertOverride"); const getOverride_1 = require("./actions/overrides/getOverride"); const listOverrides_1 = require("./actions/overrides/listOverrides"); const deleteOverride_1 = require("./actions/overrides/deleteOverride"); // Presets const upsertPreset_1 = require("./actions/presets/upsertPreset"); const getPreset_1 = require("./actions/presets/getPreset"); const listPresets_1 = require("./actions/presets/listPresets"); const deletePreset_1 = require("./actions/presets/deletePreset"); const schema_to_types_1 = require("./utils/schema-to-types"); const schema_typed_api_1 = require("./utils/schema-typed-api"); const http_client_1 = require("./components/http-client"); const resilience_policy_1 = require("./components/resilience-policy"); const schema_manager_1 = require("./components/schema-manager"); const tenant_1 = require("./utils/tenant"); /** * Binds context to API functions by prepending context as first argument * Preserves generic types through currying */ function bindCtx(ctx) { return (fn) => (...args) => fn(ctx, ...args); } /** * Modern, modular Typesense API client with grouped functionality * * @example * ```typescript * const api = new TypesenseApi({ * prefixUrl: 'http://localhost:8108', * token: 'xyz', * collectionName: 'products' * }) * * // Collections * await api.collections.create({ name: 'products', fields: [...] }) * await api.collections.getOrCreate({ name: 'products', fields: [...] }) * * // Documents * await api.documents.insert({ id: '1', title: 'Product 1' }) * await api.documents.search({ q: 'product', query_by: 'title' }) * * // Admin * await api.admin.health() * await api.admin.getMetrics() * ``` */ /** * Factory helper for creating typed TypesenseApi instances * @example * ```typescript * interface Product { id: string; title: string; price: number; } * const productApi = createTypedApi<Product>()({ * prefixUrl: 'http://localhost:8108', * token: 'xyz', * collectionName: 'products' * }) * * // Now all document operations are typed * await productApi.documents.insert({ id: "1", title: "Foo", price: 42 }) // ✅ typed * ``` */ const createTypedApi = () => (options) => new TypesenseApi(options); exports.createTypedApi = createTypedApi; class TypesenseApi { ctx; withCtx; options; // Expose components for advanced usage httpClient; resilience; schemaManager; /** * Define a strongly-typed collection schema * @example * ```typescript * const ProductCollection = TypesenseApi.defineCollection({ * name: 'products', * fields: [ * { name: 'id', type: 'string' as const }, * { name: 'title', type: 'string' as const }, * { name: 'price', type: 'float' as const }, * { name: 'inStock', type: 'bool' as const } * ] as const * } as const) * ``` */ static defineCollection = schema_to_types_1.defineCollection; /** * Create a strongly-typed API instance from a collection schema * @example * ```typescript * const ProductCollection = TypesenseApi.defineCollection({...}) * * const api = TypesenseApi.createSchemaTypedApi(ProductCollection)({ * prefixUrl: 'http://localhost:8108', * token: 'xyz' * }) * * // Now all document operations are fully typed * await api.documents.insert({ * id: '1', * title: 'Product', * price: 99.99, * inStock: true * }) * ``` */ static createSchemaTypedApi = schema_typed_api_1.createSchemaTypedApi; /** * Create a typed API from an inline collection definition (convenience method) * @example * ```typescript * const api = TypesenseApi.createFromSchema({ * name: 'products', * fields: [ * { name: 'id', type: 'string' as const }, * { name: 'title', type: 'string' as const }, * { name: 'price', type: 'float' as const } * ] as const * } as const)({ * prefixUrl: 'http://localhost:8108', * token: 'xyz' * }) * ``` */ static createFromSchema(collection) { return TypesenseApi.createSchemaTypedApi(TypesenseApi.defineCollection(collection)); } constructor(options) { this.options = options; // Initialize resilience policy with observability callbacks this.resilience = new resilience_policy_1.ResiliencePolicy({ ...options.resilience, onStateChange: options.onCircuitBreakerStateChange, onRateLimitUpdate: options.onRateLimitUpdate }); // Create request/response interceptors for circuit breaker const beforeRequestHooks = [ async (_request) => { // Check circuit breaker before making request if (this.resilience.isCircuitOpen()) { const error = new Error('Circuit breaker is open - service unavailable'); error.isCircuitBreakerError = true; error.retriesRemaining = 0; throw error; } }, ...(options.beforeRequest || []) ]; const afterResponseHooks = [ async (_request, _options, response) => { // Only record success for successful responses if (response.ok) { this.resilience.recordSuccess(); } // Update rate limit info this.resilience.updateRateLimit(response.headers); return response; }, ...(options.afterResponse || []) ]; const beforeErrorHooks = [ (error) => { // Record failure for circuit breaker before any error transformation this.resilience.recordFailure(); // Check if circuit is now open after recording failure if (this.resilience.isCircuitOpen()) { // Replace the error with circuit breaker error const circuitError = new Error('Circuit breaker is open'); circuitError.isCircuitBreakerError = true; throw circuitError; } // Re-throw the original error throw error; } ]; // Initialize components with interceptors this.httpClient = new http_client_1.TypesenseHttpClient({ prefixUrl: options.prefixUrl, token: options.token, searchTimeout: options.searchTimeout, importTimeout: options.importTimeout, defaultTimeout: options.defaultTimeout, beforeRequest: beforeRequestHooks, afterResponse: afterResponseHooks, beforeError: beforeErrorHooks, kyInstance: options.kyInstance, enforceTLS: options.enforceTLS }); this.schemaManager = new schema_manager_1.CollectionSchemaManager({ typesenseVersion: options.typesenseVersion, cacheSize: options.schemaCacheSize, cacheTtl: options.schemaCacheTtl, suppressLogs: options.suppressLogs }); // Validate and sanitize tenant ID if provided const sanitizedTenantId = options.tenantId ? (0, tenant_1.sanitizeTenantId)(options.tenantId) : undefined; // Create context this.ctx = { httpClient: this.httpClient, resilience: this.resilience, schemaManager: this.schemaManager, tenantId: sanitizedTenantId, collectionName: options.collectionName || 'documents', typesenseVersion: options.typesenseVersion, autoCreateCollection: options.autoCreateCollection, suppressLogs: options.suppressLogs, fqcn: (baseCollectionName) => { const base = baseCollectionName || this.ctx.collectionName; return sanitizedTenantId ? (0, tenant_1.createFQCN)(sanitizedTenantId, base) : base; } }; this.withCtx = bindCtx(this.ctx); // Check version if enabled if (options.enableVersionCheck) { this.checkVersion(); } } async checkVersion() { try { const stats = await this.admin.getStats(); if (stats.server_version) { this.ctx.typesenseVersion = stats.server_version; this.schemaManager.setTypesenseVersion(stats.server_version); if (!this.options.suppressLogs) { console.info(`Connected to Typesense v${stats.server_version}`); } } } catch (error) { // Version check is optional, continue silently } } /** * Collection management operations */ get collections() { return { create: this.withCtx(createCollection_1.createCollection), get: this.withCtx(getCollection_1.getCollection), update: this.withCtx(updateCollection_1.updateCollection), delete: this.withCtx(deleteCollection_1.deleteCollection), list: this.withCtx(listCollections_1.listCollections), getOrCreate: this.withCtx(getOrCreateCollection_1.getOrCreateCollection) }; } /** * Document CRUD operations */ get documents() { const ctx = this.ctx; return { insert: (document, options) => (0, insertDocument_1.insertDocument)(ctx, document, options), upsert: (document, options) => (0, upsertDocument_1.upsertDocument)(ctx, document, options), update: (document, options) => (0, updateDocument_1.updateDocument)(ctx, document, options), delete: this.withCtx(deleteDocument_1.deleteDocument), getById: this.withCtx(getDocumentById_1.getDocumentById), import: this.withCtx(importDocuments_1.importDocuments), export: this.withCtx(exportDocuments_1.exportDocuments), exportStream: this.withCtx(exportDocuments_1.exportDocumentsStream), deleteByFilter: this.withCtx(deleteByFilter_1.deleteByFilter), clear: this.withCtx(clearCollection_1.clearCollection), // Search operations search: this.withCtx(search_1.search), searchText: this.withCtx(search_1.searchText), searchVector: this.withCtx(search_1.searchVector) }; } /** * Search operations (alias for documents.search*) */ get search() { return { query: this.withCtx(search_1.search), text: this.withCtx(search_1.searchText), vector: this.withCtx(search_1.searchVector), multi: this.withCtx(multiSearch_1.multiSearch) }; } /** * Admin operations */ get admin() { return { health: this.withCtx(health_1.health), waitForHealth: this.withCtx(health_1.waitForHealth), getMetrics: this.withCtx(metrics_1.getMetrics), getStats: this.withCtx(metrics_1.getStats), getCollectionStats: this.withCtx(getCollectionStats_1.getCollectionStats) }; } /** * Alias management (v29+) */ get aliases() { return { createOrUpdate: this.withCtx(createOrUpdateAlias_1.createOrUpdateAlias), get: this.withCtx(getAlias_1.getAlias), list: this.withCtx(listAliases_1.listAliases), delete: this.withCtx(deleteAlias_1.deleteAlias) }; } /** * Synonym management (v29+) */ get synonyms() { return { upsert: this.withCtx(upsertSynonym_1.upsertSynonym), get: this.withCtx(getSynonym_1.getSynonym), list: this.withCtx(listSynonyms_1.listSynonyms), delete: this.withCtx(deleteSynonym_1.deleteSynonym) }; } /** * Search override management (v29+) */ get overrides() { return { upsert: this.withCtx(upsertOverride_1.upsertOverride), get: this.withCtx(getOverride_1.getOverride), list: this.withCtx(listOverrides_1.listOverrides), delete: this.withCtx(deleteOverride_1.deleteOverride) }; } /** * Preset management (v29+) */ get presets() { return { upsert: this.withCtx(upsertPreset_1.upsertPreset), get: this.withCtx(getPreset_1.getPreset), list: this.withCtx(listPresets_1.listPresets), delete: this.withCtx(deletePreset_1.deletePreset) }; } /** * Get current resilience status */ getResilienceStatus() { return this.resilience.getStatus(); } /** * Get current rate limit info */ getRateLimit() { return this.resilience.getRateLimit(); } /** * Get cache statistics */ getCacheStats() { return this.schemaManager.getCacheStats(); } /** * Get client version */ getVersion() { return this.options.appVersion || '1.0.0'; } /** * Get Typesense server version */ getTypesenseVersion() { return this.ctx.typesenseVersion || 'unknown'; } /** * Admin helper: List all collections for the current tenant * Returns only collections that belong to the configured tenant */ async listTenantCollections() { if (!this.ctx.tenantId) { throw new Error('Tenant ID is required for listing tenant collections'); } const allCollections = await this.collections.list(); const tenantPrefix = `${this.ctx.tenantId}__`; return allCollections .map(c => c.name) .filter(name => name.startsWith(tenantPrefix)); } /** * Admin helper: Get base collection names for the current tenant * Returns collection names without the tenant prefix */ async listTenantBaseCollectionNames() { const tenantCollections = await this.listTenantCollections(); const tenantPrefix = `${this.ctx.tenantId}__`; return tenantCollections.map(name => name.substring(tenantPrefix.length)); } /** * Admin helper: Delete all collections for the current tenant * Use with caution - this will permanently delete all tenant data */ async deleteAllTenantCollections() { if (!this.ctx.tenantId) { throw new Error('Tenant ID is required for deleting tenant collections'); } const tenantCollections = await this.listTenantCollections(); for (const collectionName of tenantCollections) { await this.httpClient.request(`/collections/${collectionName}`, { method: 'DELETE' }); } } /** * Admin helper: Check if a collection exists for the current tenant */ async tenantCollectionExists(baseCollectionName) { try { const collectionName = this.ctx.fqcn(baseCollectionName); await this.collections.get(collectionName); return true; } catch (error) { if (error?.status === 404 || error?.response?.status === 404) { return false; } throw error; } } /** * Destroy the client and clean up resources */ destroy() { this.schemaManager.clearCache(); this.resilience.reset(); } } exports.TypesenseApi = TypesenseApi; //# sourceMappingURL=TypesenseApi.js.map