@searchcraft/client
v0.1.0
Published
TypeScript client library for the Searchcraft API
Readme
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
NPM
npm install @searchcraft/clientYarn
yarn add @searchcraft/clientPNPM
pnpm add @searchcraft/clientCDN (UMD)
<script src="https://unpkg.com/@searchcraft/client/dist/index.umd.js"></script>
<script>
const { createClient, createApiKey, createIndexName } = SearchcraftClient;
</script>Quick Start
import { createClient, createApiKey, createIndexName, fuzzy } from '@searchcraft/client';
// Initialize the client
const client = createClient({
endpointUrl: 'https://your-searchcraft-instance.com',
readKey: createApiKey('your-read-key'),
});
// Perform a search
const 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);Usage
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
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'
},
});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 search
const 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
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 official documentation.
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
// Simple field match
exact().field('title', 'laptop').buildRequest();
// IN query
exact().fieldIn('tags', ['tech', 'gadgets', 'mobile']).buildRequest();
// Range query
exact().range('price', 10, 100).buildRequest(); // Inclusive
exact().range('price', 10, 100, false).buildRequest(); // Exclusive
// Comparison queries
exact().compare('rating', '>', 4.5).buildRequest();
exact().compare('stock', '<=', 10).buildRequest();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
// AND
exact().field('active', true).and(exact().compare('price', '<', 100)).buildRequest();
// OR
exact().field('brand', 'Apple').or(exact().field('brand', 'Samsung')).buildRequest();
// NOT (exclusion)
fuzzy().term('laptop').not('refurbished').buildRequest();
// Grouping
const subQuery = exact().field('category', 'tech').or('category:science');
exact().group(subQuery).and(exact().compare('rating', '>', 4)).buildRequest();Pagination and Sorting
fuzzy()
.term('query')
.limit(20)
.offset(40)
.orderBy('created_at', 'desc')
.buildRequest();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
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
import { createIndexName } from '@searchcraft/client';
const indexName = createIndexName('my-index');
// Insert a document
await client.documents.insert(indexName, {
id: '123',
title: 'Product Title',
description: 'Product description',
price: 99.99,
});
// Delete a document by its id
await client.documents.delete(indexName, '123');
// Batch insert documents
await client.documents.batchInsert(indexName, [
{ id: '1', title: 'Product 1' },
{ id: '2', title: 'Product 2' },
]);
// Batch delete documents by their ids
await client.documents.batchDelete(indexName, ['1', '2']);
// Delete all documents from an index
await client.documents.deleteAll(indexName);Index Management
import { createIndexName } from '@searchcraft/client';
const indexName = createIndexName('my-index');
// List all index names
const { index_names } = await client.indices.list();
// Get index configuration
const config = await client.indices.get(indexName);
// Create a new index
await 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 index
await client.indices.delete(indexName);Federation Management
import { createFederationName } from '@searchcraft/client';
const federationName = createFederationName('my-federation');
// List all federations
const federations = await client.federations.list();
// Get a specific federation
const federation = await client.federations.get(federationName);
// Delete a federation
await client.federations.delete(federationName);Synonyms
// Get all synonyms for an index
const 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 key
await client.synonyms.delete(indexName, ['nyc', 'usa']);
// Delete all synonyms
await client.synonyms.deleteAll(indexName);Stopwords
// Get all stopwords for an index
const stopwords = await client.stopwords.get(indexName);
// Add custom stopwords
await client.stopwords.add(indexName, ['foo', 'bar']);
// Delete specific stopwords
await client.stopwords.delete(indexName, ['foo', 'bar']);
// Delete all custom stopwords
await client.stopwords.deleteAll(indexName);Health Check
// check() throws on error, so if it returns the service is healthy
const health = await client.health.check();
console.log('Status:', health.status); // 200
console.log('Message:', health.data); // "Searchcraft is healthy."API Reference
Client
createClient(config: SearchcraftConfig): SearchcraftClient
Search API
searchIndex<T>(indexName: IndexName, request: SearchRequest): Promise<SearchResponse<T>>searchFederation<T>(federationName: FederationName, request: SearchRequest): Promise<SearchResponse<T>>
Document API
insert(indexName: IndexName, document: DocumentWithId): Promise<DocumentOperationResponse>- Insert a documentdelete(indexName: IndexName, documentId: string | number): Promise<DocumentDeleteResponse>- Delete a document by idbatchInsert(indexName: IndexName, documents: DocumentWithId[]): Promise<DocumentOperationResponse>- Batch insert documentsbatchDelete(indexName: IndexName, documentIds: (string | number)[]): Promise<DocumentDeleteResponse>- Batch delete documents by idsdeleteAll(indexName: IndexName): Promise<DocumentOperationResponse>- Delete all documents from an indexget<T>(indexName: IndexName, internalId: string): Promise<SearchHit<T>>- Get a document by its internal Searchcraft ID (_id)
Index API
list(): Promise<IndexListResponse>- List all index namesget(indexName: IndexName): Promise<IndexConfig>- Get the configuration for a specific indexcreate(indexName: IndexName, indexConfig: IndexConfig): Promise<IndexOperationResponse>- Create a new indexupdate(indexName: IndexName, indexConfig: Partial<IndexConfig>): Promise<IndexOperationResponse>- Update an existing index (partial)delete(indexName: IndexName): Promise<IndexOperationResponse>- Delete an index
Federation API
list(): Promise<Federation[]>- List all federationsget(federationName: FederationName): Promise<Federation>- Get a specific federationdelete(federationName: FederationName): Promise<FederationOperationResponse>- Delete a federation
Synonyms API
get(indexName: IndexName): Promise<SynonymsMap>- Get all synonyms for an indexadd(indexName: IndexName, synonyms: string[]): Promise<SynonymOperationResponse>- Add synonyms in"synonym:original-term"formatdelete(indexName: IndexName, synonyms: string[]): Promise<SynonymOperationResponse>- Delete specific synonyms by keydeleteAll(indexName: IndexName): Promise<SynonymOperationResponse>- Delete all synonyms from an index
Stopwords API
get(indexName: IndexName): Promise<string[]>- Get all stopwords for an indexadd(indexName: IndexName, stopwords: string[]): Promise<StopwordOperationResponse>- Add custom stopwordsdelete(indexName: IndexName, stopwords: string[]): Promise<StopwordOperationResponse>- Delete specific stopwordsdeleteAll(indexName: IndexName): Promise<StopwordOperationResponse>- Delete all stopwords from an index
Health API
check(): Promise<HealthCheckResponse>
Query Builder
fuzzy(): QueryBuilder- Create a fuzzy query builderexact(): QueryBuilder- Create an exact query builderdynamic(): QueryBuilder- Create a dynamic query builder
QueryBuilder Methods
All methods return a new QueryBuilder instance (immutable):
term(term: string): QueryBuilder- Add a search termfield(field: string, value: string | number | boolean): QueryBuilder- Add field:value queryfieldIn(field: string, values: (string | number)[]): QueryBuilder- Add field IN queryrange(field: string, from: string | number | Date, to: string | number | Date, inclusive?: boolean): QueryBuilder- Add range querycompare(field: string, operator: '>' | '<' | '>=' | '<=', value: number | Date): QueryBuilder- Add comparison queryand(query: string | QueryBuilder): QueryBuilder- Add AND operatoror(query: string | QueryBuilder): QueryBuilder- Add OR operatornot(term: string): QueryBuilder- Add NOT operatorgroup(query: string | QueryBuilder): QueryBuilder- Group query with parentheseslimit(limit: number): QueryBuilder- Set result limitoffset(offset: number): QueryBuilder- Set result offsetorderBy(field: string, sort?: 'asc' | 'desc'): QueryBuilder- Set orderingoccur(occur: 'should' | 'must'): QueryBuilder- Set occur modebuild(): SearchQuery- Build the query objectbuildRequest(): SearchRequest- Build the complete request object
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
- ✅ 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
Usage
import {
createApiKey,
createIndexName,
createFederationName,
createDocumentId
} from '@searchcraft/client';
const apiKey = createApiKey('key'); // ApiKey type
const indexName = createIndexName('my-index'); // IndexName type
const federationName = createFederationName('my-fed'); // FederationName type
const docId = createDocumentId('123'); // DocumentId type
// ✅ TypeScript ensures you use the correct type
await client.search.searchIndex(indexName, request);
await client.search.searchFederation(federationName, request);
// ❌ TypeScript error - prevents mistakes at compile time
await client.search.searchFederation(indexName, request); // Error!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');
}
}Documentation
For comprehensive documentation on Searchcraft, including:
- Query Language Reference - Complete syntax and operators
- API Reference - All endpoints and parameters
- Search Guides - Best practices and advanced techniques
- Integration Examples - Real-world use cases
Visit the official documentation at docs.searchcraft.io
Development
# Install dependencies
npm install
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
# Build
npm run build
# Lint
npm run lint
# Format
npm run formatIssues
Please file issues in the Searchcraft Issues repository.
