Searchcraft API TypeScript Client Library
The Searchcraft API TypeScript Client is a fully-featured TypeScript client that acts as a language-specific wrapper around the Searchcraft API, with support for both TypeScript and JavaScript projects.
Features
Section titled “Features”- Full TypeScript support with comprehensive type definitions.
- Works with JavaScript projects as well.
- Functional, immutable API design.
- Composable query builder.
- Support for all Searchcraft query modes (fuzzy, exact, dynamic) and operations.
- Full index, federation, synonyms, and stopwords management.
- Complete query language support.
- Works in both Node.js and browser environments.
- Available via NPM and CDN.
- Zero runtime dependencies.
Installation
Section titled “Installation”npm install @searchcraft/clientyarn add @searchcraft/clientpnpm add @searchcraft/clientCDN (UMD)
Section titled “CDN (UMD)”<script src="https://unpkg.com/@searchcraft/client/dist/index.umd.js"></script><script> const { createClient, createApiKey, createIndexName } = SearchcraftClient;</script>Quick Start
Section titled “Quick Start”import { createClient, createApiKey, createIndexName, fuzzy } from '@searchcraft/client';
// Initialize the clientconst client = createClient({ endpointUrl: 'https://your-searchcraft-instance.com', readKey: createApiKey('your-read-key'),});
// Perform a searchconst indexName = createIndexName('your-index');const request = fuzzy().term('search query').limit(10).buildRequest();
const response = await client.search.searchIndex(indexName, request);
console.log(`Found ${response.data.count} results`);console.log(response.data.hits);Client Initialization
Section titled “Client Initialization”import { createClient, createApiKey } from '@searchcraft/client';
const client = createClient({ endpointUrl: 'https://your-instance.com', readKey: createApiKey('your-read-key'), ingestKey: createApiKey('your-ingest-key'), // Optional, for document operations adminKey: createApiKey('your-admin-key'), // Optional, only needed for self-hosted clusters timeout: 30000, // Optional, default 30 seconds headers: { // Optional Searchcraft headers for analytics and tracking: 'X-Sc-User-Id': 'user-123', // Unique identifier for the current user 'X-Sc-Session-Id': 'session-abc', // Session identifier for tracking user sessions 'X-Sc-User-Type': 'authenticated', // User type: 'authenticated' or 'anonymous' },});Note: The
adminKeyis only required for self-hosted Searchcraft clusters. If you’re using Searchcraft Cloud, you do not need to provide an admin key.
Basic Search
Section titled “Basic Search”import { fuzzy, exact, dynamic } from '@searchcraft/client';
// Fuzzy search (typo-tolerant, uses weight ranking, synonyms and stopwords)const fuzzyQuery = fuzzy().term('search term').limit(20).buildRequest();
// Exact searchconst exactQuery = exact().term('exact match').buildRequest();
// Dynamic search (adapts based on word count)const dynamicQuery = dynamic().term('adaptive search').buildRequest();
const response = await client.search.searchIndex(indexName, fuzzyQuery);Query Builder
Section titled “Query Builder”The query builder provides a fluent, immutable API for constructing complex queries.
📚 Full Query Syntax Documentation: For complete details on the Searchcraft query language, operators, and advanced features, see the Query Language Reference.
import { exact } from '@searchcraft/client';
const query = exact() .field('category', 'electronics') .and(exact().range('price', 10, 100)) .not('discontinued') .orderBy('rating', 'desc') .limit(25) .buildRequest();
const response = await client.search.searchIndex(indexName, query);Field Queries
Section titled “Field Queries”// Simple field matchexact().field('title', 'laptop').buildRequest();
// IN queryexact().fieldIn('tags', ['tech', 'gadgets', 'mobile']).buildRequest();
// Range queryexact().range('price', 10, 100).buildRequest(); // Inclusiveexact().range('price', 10, 100, false).buildRequest(); // Exclusive
// Comparison queriesexact().compare('rating', '>', 4.5).buildRequest();exact().compare('stock', '<=', 10).buildRequest();Date Queries
Section titled “Date Queries”const from = new Date('2024-01-01');const to = new Date('2024-12-31');
exact().range('created_at', from, to).buildRequest();exact().compare('updated_at', '>=', new Date('2024-06-01')).buildRequest();Boolean Operators
Section titled “Boolean Operators”// ANDexact().field('active', true).and(exact().compare('price', '<', 100)).buildRequest();
// ORexact().field('brand', 'Apple').or(exact().field('brand', 'Samsung')).buildRequest();
// NOT (exclusion)fuzzy().term('laptop').not('refurbished').buildRequest();
// Groupingconst subQuery = exact().field('category', 'tech').or('category:science');exact().group(subQuery).and(exact().compare('rating', '>', 4)).buildRequest();Pagination and Sorting
Section titled “Pagination and Sorting”fuzzy() .term('query') .limit(20) .offset(40) .orderBy('created_at', 'desc') .buildRequest();Federated Search
Section titled “Federated Search”Search across multiple indices:
import { createFederationName } from '@searchcraft/client';
const federationName = createFederationName('my-federation');const request = fuzzy().term('search term').buildRequest();
const response = await client.search.searchFederation(federationName, request);Advanced: Raw Query Objects
Section titled “Advanced: Raw Query Objects”For complex queries or when you need maximum control, you can construct request objects directly instead of using the query builder:
const request = { query: [ { occur: 'must', exact: { ctx: 'active:true' } }, { occur: 'must', exact: { ctx: 'category:/electronics' } }, { fuzzy: { ctx: 'wireless headphones' } }, ], limit: 20,};
const response = await client.search.searchIndex(indexName, request);Note: The query builder (e.g., fuzzy().term()...) is recommended for most use cases. Use raw objects when you need to dynamically construct complex queries or have specific requirements the builder doesn’t cover.
Document Management
Section titled “Document Management”import { createIndexName } from '@searchcraft/client';
const indexName = createIndexName('my-index');
// Insert a documentawait client.documents.insert(indexName, { id: '123', title: 'Product Title', description: 'Product description', price: 99.99,});
// Delete a document by its idawait client.documents.delete(indexName, '123');
// Batch insert documentsawait client.documents.batchInsert(indexName, [ { id: '1', title: 'Product 1' }, { id: '2', title: 'Product 2' },]);
// Batch delete documents by their idsawait client.documents.batchDelete(indexName, ['1', '2']);
// Delete all documents from an indexawait client.documents.deleteAll(indexName);Index Management
Section titled “Index Management”import { createIndexName } from '@searchcraft/client';
const indexName = createIndexName('my-index');
// List all index namesconst { index_names } = await client.indices.list();
// Get index configurationconst config = await client.indices.get(indexName);
// Create a new indexawait client.indices.create(indexName, { search_fields: ['title', 'body'], fields: { title: { type: 'text', indexed: true, stored: true }, body: { type: 'text', indexed: true, stored: true }, },});
// Update index configuration (partial)await client.indices.update(indexName, { weight_multipliers: { title: 2.0, body: 0.7 },});
// Delete an indexawait client.indices.delete(indexName);Federation Management
Section titled “Federation Management”import { createFederationName } from '@searchcraft/client';
const federationName = createFederationName('my-federation');
// List all federationsconst federations = await client.federations.list();
// Get a specific federationconst federation = await client.federations.get(federationName);
// Delete a federationawait client.federations.delete(federationName);Synonyms
Section titled “Synonyms”// Get all synonyms for an indexconst synonyms = await client.synonyms.get(indexName);
// Add synonyms — format: "synonym:original-term" or "many,synonyms:original,terms"await client.synonyms.add(indexName, [ 'nyc:new york city', 'usa:united states',]);
// Delete specific synonyms by keyawait client.synonyms.delete(indexName, ['nyc', 'usa']);
// Delete all synonymsawait client.synonyms.deleteAll(indexName);Stopwords
Section titled “Stopwords”// Get all stopwords for an indexconst stopwords = await client.stopwords.get(indexName);
// Add custom stopwordsawait client.stopwords.add(indexName, ['foo', 'bar']);
// Delete specific stopwordsawait client.stopwords.delete(indexName, ['foo', 'bar']);
// Delete all custom stopwordsawait client.stopwords.deleteAll(indexName);Health Check
Section titled “Health Check”// check() throws on error, so if it returns the service is healthyconst health = await client.health.check();console.log('Status:', health.status); // 200console.log('Message:', health.data); // "Searchcraft is healthy."API Reference
Section titled “API Reference”Client
Section titled “Client”// Create a new Searchcraft clientcreateClient(config: SearchcraftConfig): SearchcraftClientSearch API
Section titled “Search API”// Search an index and return typed resultssearchIndex<T>(indexName: IndexName, request: SearchRequest): Promise<SearchResponse<T>>
// Search across a federation of indices and return typed resultssearchFederation<T>(federationName: FederationName, request: SearchRequest): Promise<SearchResponse<T>>Document API
Section titled “Document API”// Insert a documentinsert(indexName: IndexName, document: DocumentWithId): Promise<DocumentOperationResponse>
// Delete a document by iddelete(indexName: IndexName, documentId: string | number): Promise<DocumentDeleteResponse>
// Batch insert documentsbatchInsert(indexName: IndexName, documents: DocumentWithId[]): Promise<DocumentOperationResponse>
// Batch delete documents by idsbatchDelete(indexName: IndexName, documentIds: (string | number)[]): Promise<DocumentDeleteResponse>
// Delete all documents from an indexdeleteAll(indexName: IndexName): Promise<DocumentOperationResponse>
// Get a document by its internal Searchcraft ID (_id)get<T>(indexName: IndexName, internalId: string): Promise<SearchHit<T>>Index API
Section titled “Index API”// List all index nameslist(): Promise<IndexListResponse>
// Get the configuration for a specific indexget(indexName: IndexName): Promise<IndexConfig>
// Create a new indexcreate(indexName: IndexName, indexConfig: IndexConfig): Promise<IndexOperationResponse>
// Update an existing index (partial)update(indexName: IndexName, indexConfig: Partial<IndexConfig>): Promise<IndexOperationResponse>
// Delete an indexdelete(indexName: IndexName): Promise<IndexOperationResponse>Federation API
Section titled “Federation API”// List all federationslist(): Promise<Federation[]>
// Get a specific federationget(federationName: FederationName): Promise<Federation>
// Delete a federationdelete(federationName: FederationName): Promise<FederationOperationResponse>Synonyms API
Section titled “Synonyms API”// Get all synonyms for an indexget(indexName: IndexName): Promise<SynonymsMap>
// Add synonyms in "synonym:original-term" formatadd(indexName: IndexName, synonyms: string[]): Promise<SynonymOperationResponse>
// Delete specific synonyms by keydelete(indexName: IndexName, synonyms: string[]): Promise<SynonymOperationResponse>
// Delete all synonyms from an indexdeleteAll(indexName: IndexName): Promise<SynonymOperationResponse>Stopwords API
Section titled “Stopwords API”// Get all stopwords for an indexget(indexName: IndexName): Promise<string[]>
// Add custom stopwordsadd(indexName: IndexName, stopwords: string[]): Promise<StopwordOperationResponse>
// Delete specific stopwordsdelete(indexName: IndexName, stopwords: string[]): Promise<StopwordOperationResponse>
// Delete all stopwords from an indexdeleteAll(indexName: IndexName): Promise<StopwordOperationResponse>Health API
Section titled “Health API”// Throws on error, so if it returns the service is healthycheck(): Promise<HealthCheckResponse>Query Builder
Section titled “Query Builder”// Create a fuzzy query builder (typo-tolerant, uses weight ranking, synonyms and stopwords)fuzzy(): QueryBuilder
// Create an exact query builderexact(): QueryBuilder
// Create a dynamic query builder (adapts based on word count)dynamic(): QueryBuilderQueryBuilder Methods
Section titled “QueryBuilder Methods”All methods return a new QueryBuilder instance (immutable):
// Add a search termterm(term: string): QueryBuilder
// Add a field:value queryfield(field: string, value: string | number | boolean): QueryBuilder
// Add a field IN queryfieldIn(field: string, values: (string | number)[]): QueryBuilder
// Add a range queryrange(field: string, from: string | number | Date, to: string | number | Date, inclusive?: boolean): QueryBuilder
// Add a comparison querycompare(field: string, operator: '>' | '<' | '>=' | '<=', value: number | Date): QueryBuilder
// Add an AND operatorand(query: string | QueryBuilder): QueryBuilder
// Add an OR operatoror(query: string | QueryBuilder): QueryBuilder
// Add a NOT operatornot(term: string): QueryBuilder
// Group a query with parenthesesgroup(query: string | QueryBuilder): QueryBuilder
// Set result limitlimit(limit: number): QueryBuilder
// Set result offsetoffset(offset: number): QueryBuilder
// Set orderingorderBy(field: string, sort?: 'asc' | 'desc'): QueryBuilder
// Set occur modeoccur(occur: 'should' | 'must'): QueryBuilder
// Build the query objectbuild(): SearchQuery
// Build the complete request objectbuildRequest(): SearchRequestType Safety
Section titled “Type Safety”The library uses branded types for enhanced compile-time type safety. Branded types prevent you from accidentally mixing up different string-based identifiers.
Benefits of Branded Types
Section titled “Benefits of Branded Types”- Compile-time safety - TypeScript catches type mismatches before runtime
- Prevents mixing types - Can’t accidentally use an
IndexNamewhere aFederationNameis expected - IDE support - Better autocomplete and inline error detection
- Zero runtime overhead - Types are erased during compilation
- Works in JavaScript - Functions work normally, just without compile-time checks
import { createApiKey, createIndexName, createFederationName, createDocumentId} from '@searchcraft/client';
const apiKey = createApiKey('key'); // ApiKey typeconst indexName = createIndexName('my-index'); // IndexName typeconst federationName = createFederationName('my-fed'); // FederationName typeconst docId = createDocumentId('123'); // DocumentId type
// ✅ TypeScript ensures you use the correct typeawait client.search.searchIndex(indexName, request);await client.search.searchFederation(federationName, request);
// ❌ TypeScript error - prevents mistakes at compile timeawait client.search.searchFederation(indexName, request); // Error!Error Handling
Section titled “Error Handling”import { SearchcraftError, AuthenticationError, NotFoundError, ValidationError, NetworkError, ApiError,} from '@searchcraft/client';
try { const response = await client.search.searchIndex(indexName, request);} catch (error) { if (error instanceof AuthenticationError) { console.error('Authentication failed'); } else if (error instanceof ValidationError) { console.error('Validation error:', error.field); } else if (error instanceof NetworkError) { console.error('Network error'); }}Feature Requests / Issues
Section titled “Feature Requests / Issues”If you encounter any issues with the client library or wish to request additional features please open an issue at the Searchcraft Issues repository.
License and Source code
Section titled “License and Source code”The Searchcraft API TypeScript client is Apache 2.0 licensed and the source code is available on Github.