@bradsearch/search-sdk
v2.4.1
Published
TypeScript SDK for BradSearch API with JWT authentication, field mapping, and faceted search capabilities
Readme
@bradsearch/search-sdk
A TypeScript SDK for BradSearch API with JWT authentication, field mapping, and faceted search capabilities.
🚀 Installation
npm install @bradsearch/search-sdk📋 Quick Start
import { ApiClient } from "@bradsearch/search-sdk";
// Initialize the client
const client = new ApiClient({
token: "your-jwt-token",
baseUrl: "http://localhost:8080",
fields: {
name: { type: "text_keyword", filterable: false },
brand: { type: "text_keyword", filterable: true },
categories: { type: "hierarchy", filterable: true },
price: { type: "integer", filterable: true },
variants: {
type: "variants",
filterable: true,
fields: {
color: { type: "text_keyword", filterable: true },
size: { type: "keyword", filterable: true },
material: { type: "text_keyword", filterable: true },
},
},
},
mapping: {
title: "name",
reference: "sku",
link: "productUrl",
imageUrl: "imageUrl",
},
});
// Perform a search
const results = await client.query("laptop", {
brand: ["Dell", "HP"],
categories: ["Electronics > Computers"],
});
console.log(results.documents);
console.log(results.facets);🔧 Configuration
ApiClient Constructor
interface ApiSdkConfig {
token: string; // JWT authentication token
baseUrl: string; // API base URL (e.g., 'http://localhost:8080')
fields: Record<string, FieldConfig>; // Field configurations
mapping?: ItemMapping; // Response field mapping (optional)
fields?: Record<string, FieldConfig>;
}Field Configuration
interface FieldConfig {
type: FieldType; // Field type
filterable?: boolean; // Whether field can be used as filter (default: false)
fields?: Record<string, FieldConfig>; // For variants type - subfield configurations
label?: string; // Human-readable label for the field (optional)
count?: number // display number of items in suggestions (optional, default: 4)
}
type FieldType =
| "text" // Full-text search
| "keyword" // Exact match
| "text_keyword" // Both full-text and exact match
| "hierarchy" // Hierarchical categories (e.g., "Electronics > Computers")
| "variants" // Product variants
| "object" // Complex objects
| "url" // URLs
| "image_url" // Image URLs
| "integer"; // Numeric valuesResponse Mapping
Map API response fields to the standard Item interface:
interface ItemMapping {
title?: string; // Maps to item title
reference?: string; // Maps to item reference/SKU
link?: string; // Maps to item URL
imageUrl?: string; // Maps to item image URL
}
// Standard Item interface
interface Item {
id: string;
title: string;
link: string;
imageUrl: { small: string; medium: string };
reference: string;
_highlights?: Record<string, string>;
}🔍 Search Methods
Basic Search
// Simple text search
const results = await client.query("laptop");
// Search with options
const results = await client.query(
"laptop",
{},
{
limit: 20,
offset: 0,
sortBy: "name",
order: "asc",
}
);Search with Filters
// Single filter
const results = await client.query("laptop", {
brand: "Dell",
});
// Multiple filters
const results = await client.query("laptop", {
brand: ["Dell", "HP"],
categories: ["Electronics > Computers > Laptops"],
price: ["100-500"], // Range filter
});
// Variant filters (using subfield names directly)
const results = await client.query("shirt", {
color: ["blue", "red"], // Variant attribute filter
size: ["M", "L"], // Another variant attribute filter
brand: ["Nike"], // Regular field filter
});Search All (Browse Mode)
// Get all items without search query
const results = await client.query(
"",
{},
{
searchAll: true,
limit: 50,
}
);Request Cancellation
const controller = new AbortController();
const results = await client.query(
"laptop",
{},
{
signal: controller.signal,
}
);
// Cancel the request
controller.abort();📊 Response Format
interface SearchResponse<T = Item> {
limit: number; // Items per page
total: number; // Total items found
offset: number; // Current offset
facets: Record<string, FacetValue[]>; // Available filters
documents: T[]; // Search results
}
interface FacetValue {
value: string; // Filter value
count: number; // Number of items with this value
}🛠️ Advanced Usage
Product Variants
Configure variant filtering for products with multiple options (color, size, etc.):
// Configuration
const client = new ApiClient({
// ... other config
fields: {
// ... other fields
variants: {
type: "variants",
filterable: true,
fields: {
color: { type: "text_keyword", filterable: true },
size: { type: "keyword", filterable: true },
material: { type: "text_keyword", filterable: true },
},
},
},
});
// Filter by variant attributes (use subfield names directly)
const results = await client.query("", {
color: ["blue", "red", "green"],
size: ["S", "M", "L"],
});Custom Item Types
interface CustomProduct {
id: string;
productName: string;
brandName: string;
categoryPath: string;
price: number;
}
const results = await client.query<CustomProduct>("laptop");
// results.documents will be CustomProduct[]Error Handling
import {
ApiError,
AuthenticationError,
ValidationError,
NetworkError,
} from "@bradsearch/search-sdk";
try {
const results = await client.query("laptop");
} catch (error) {
if (error instanceof AuthenticationError) {
console.error("Invalid or expired JWT token");
} else if (error instanceof ValidationError) {
console.error("Invalid request parameters");
} else if (error instanceof NetworkError) {
console.error("Network connection failed");
} else if (error instanceof ApiError) {
console.error("API error:", error.statusCode, error.message);
}
}Utility Methods
// Get filterable fields
const filterableFields = client.getFilterableFields();
// Update configuration
client.updateConfig({
fields: {
...client.config.fields,
newField: { type: "keyword", filterable: true },
},
});🌐 Hierarchy Filters
For hierarchical fields (categories), use " > " separator:
const client = new ApiClient({
// ... other config
fields: {
categories: { type: "hierarchy", filterable: true },
},
});
// Filter by category hierarchy
const results = await client.query("", {
categories: [
"Electronics", // Top level
"Electronics > Computers", // Second level
"Electronics > Computers > Laptops", // Third level
],
});🧪 Development
Prerequisites
- Node.js >= 16.0.0
- npm or yarn
Setup
# Clone the repository
git clone <repository-url>
cd SDK/typescript
# Install dependencies
npm install
# Build the package
npm run build
# Run tests
npm run test
# Run linting
npm run lintDevelopment Scripts
npm run dev # Watch mode for development
npm run build # Build for production
npm run test # Run tests
npm run lint # Run ESLint
npm run storybook # Start Storybook demoStorybook Demo
Interactive demo with real API connections:
npm run storybookOpen http://localhost:6006 to see:
- Complete search interface
- Hierarchy filters with tree structure
- Configuration testing
- Field mapping examples
📦 Publishing
Preparing for Publication
Update version:
npm version patch # 1.0.0 -> 1.0.1 npm version minor # 1.0.0 -> 1.1.0 npm version major # 1.0.0 -> 2.0.0Build and test:
npm run build npm run test npm run lintPreview package contents:
npm pack --dry-run
Authentication
Login to npm
# Login with your npm account
npm login
# Verify authentication
npm whoamiSwitch npm User
# Logout current user
npm logout
# Login with different account
npm login --registry https://registry.npmjs.org/Publishing to npm
First Time Publication
# Publish as public package (required for scoped packages)
npm publish --access publicSubsequent Releases
# Regular publish (after version bump)
npm publishPublishing Beta/Alpha Versions
# Tag as beta
npm version prerelease --preid=beta
npm publish --tag beta
# Tag as alpha
npm version prerelease --preid=alpha
npm publish --tag alphaAutomated Publishing
The package includes a prepublishOnly script that automatically builds before publishing:
{
"scripts": {
"prepublishOnly": "npm run build"
}
}Package Configuration
Key package.json fields for publishing:
{
"name": "@bradsearch/search-sdk",
"version": "1.0.0",
"main": "dist/index.js",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts",
"files": [
"dist/**/*.js",
"dist/**/*.d.ts",
"dist/**/*.js.map",
"!dist/stories/**",
"!dist/setupTests.*",
"README.md"
],
"engines": {
"node": ">=16.0.0"
}
}🔒 Security
- JWT Tokens: Store securely, never commit to version control
- HTTPS: Always use HTTPS in production
- CORS: Configure your API server for cross-origin requests:
// Example CORS headers for your API
Access-Control-Allow-Origin: https://your-domain.com
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization🐛 Troubleshooting
CORS Errors
If you get CORS errors, ensure your API server handles preflight OPTIONS requests:
# Test preflight request
curl -X OPTIONS http://localhost:8080/api/v1/query \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Authorization"Authentication Errors
- Verify JWT token is valid and not expired
- Ensure token contains necessary field configurations
- Check API endpoint accessibility
Build Errors
# Clear dist folder and rebuild
rm -rf dist
npm run build
# Clear node_modules and reinstall
rm -rf node_modules package-lock.json
npm install🤝 Contributing
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Commit changes:
git commit -m 'Add amazing feature' - Push to branch:
git push origin feature/amazing-feature - Open a Pull Request
📄 License
MIT License - see LICENSE file for details.
🔗 Links
📞 Support
For questions and support:
- Create an issue
- Email: [email protected]
