--- name: algolia-core-workflow-b description: 'Implement Algolia indexing pipeline: data sync, partial updates, synonyms, and rules. The secondary money-path workflow: keep your index in sync with source data. Trigger: "algolia indexing", "sync data to algolia", "algolia synonyms", "algolia rules", "algolia partial update", "algolia reindex". ' allowed-tools: Read, Write, Edit, Bash(npm:*), Grep version: 1.0.0 license: MIT author: Jeremy Longshore tags: - saas - search - algolia compatibility: Designed for Claude Code --- # Algolia Core Workflow B — Indexing & Data Sync ## Overview Keep your Algolia index synchronized with your source database. Covers full reindex, incremental updates, partial updates, synonyms, and query rules. ## Prerequisites - Completed `algolia-install-auth` setup - Familiarity with `algolia-core-workflow-a` (search) - Source database or API with change tracking (timestamps, events) ## Instructions ### Step 1: Full Reindex with replaceAllObjects ```typescript import { algoliasearch } from 'algoliasearch'; const client = algoliasearch(process.env.ALGOLIA_APP_ID!, process.env.ALGOLIA_ADMIN_KEY!); // replaceAllObjects atomically swaps index content // Internally: creates temp index → indexes all records → moves temp to target // Search continues on old data until swap is complete — zero downtime async function fullReindex(records: Record[]) { const { taskID } = await client.replaceAllObjects({ indexName: 'products', objects: records, batchSize: 1000, // Records per batch (default 1000) }); await client.waitForTask({ indexName: 'products', taskID }); console.log(`Full reindex complete: ${records.length} records`); } ``` ### Step 2: Incremental Updates with partialUpdateObject ```typescript // Only update changed fields — much faster than full saveObjects async function updateProductPrice(objectID: string, newPrice: number) { await client.partialUpdateObject({ indexName: 'products', objectID, attributesToUpdate: { price: newPrice, updated_at: new Date().toISOString(), }, createIfNotExists: false, // Don't create if missing }); } // Batch partial updates async function syncPriceChanges(changes: { id: string; price: number }[]) { const { taskID } = await client.partialUpdateObjects({ indexName: 'products', objects: changes.map(c => ({ objectID: c.id, price: c.price, updated_at: new Date().toISOString(), })), createIfNotExists: false, }); await client.waitForTask({ indexName: 'products', taskID }); } ``` ### Step 3: Manage Synonyms ```typescript // Synonyms help users find products with different terminology await client.saveSynonyms({ indexName: 'products', synonymHit: [ // Two-way synonym: any of these terms match each other { objectID: 'syn-1', type: 'synonym', synonyms: ['laptop', 'notebook', 'portable computer'], }, // One-way synonym: "phone" also searches for "smartphone" but not reverse { objectID: 'syn-2', type: 'oneWaySynonym', input: 'phone', synonyms: ['smartphone', 'mobile phone', 'cell phone'], }, // Alt correction: minor typos/variations { objectID: 'syn-3', type: 'altCorrection1', word: 'color', corrections: ['colour'], }, // Placeholder: replace pattern with alternatives { objectID: 'syn-4', type: 'placeholder', placeholder: '', replacements: ['small', 'medium', 'large', 'XL'], }, ], forwardToReplicas: true, replaceExistingSynonyms: false, // true = wipe existing first }); ``` ### Step 4: Configure Query Rules ```typescript // Rules let you pin, hide, boost, or filter results for specific queries await client.saveRule({ indexName: 'products', objectID: 'rule-sale-banner', rule: { conditions: [{ anchoring: 'contains', pattern: 'sale', }], consequence: { // Pin a specific record to position 1 promote: [{ objectID: 'promo-banner-sale', position: 0 }], // Add automatic filter params: { filters: 'on_sale = true', }, }, description: 'When user searches "sale", filter to sale items and pin banner', enabled: true, }, }); // Hide a product from search results await client.saveRule({ indexName: 'products', objectID: 'rule-hide-discontinued', rule: { conditions: [{ anchoring: 'is', pattern: '' }], // Matches all queries consequence: { hide: [{ objectID: 'discontinued-product-123' }], }, description: 'Hide discontinued product from all searches', enabled: true, }, }); ``` ## Error Handling | Error | Cause | Solution | |-------|-------|----------| | `Record is too big (limit: 10KB)` | Object exceeds free-tier limit | Strip unnecessary fields; paid plans allow 100KB | | `Synonym already exists` | Duplicate objectID | Use `replaceExistingSynonyms: true` or unique IDs | | `Invalid rule condition` | Wrong `anchoring` value | Use `is`, `startsWith`, `endsWith`, or `contains` | | Partial update creates new record | `createIfNotExists` default is `true` | Set `createIfNotExists: false` | ## Examples ### Database Change Listener → Algolia Sync ```typescript // Listen for DB changes and push to Algolia import { getClient } from './algolia/client'; async function onDatabaseChange(event: { type: string; record: any }) { const client = getClient(); const idx = 'products'; switch (event.type) { case 'INSERT': case 'UPDATE': await client.saveObject({ indexName: idx, body: event.record }); break; case 'DELETE': await client.deleteObject({ indexName: idx, objectID: event.record.id }); break; } } ``` ### Search for Synonyms ```typescript // List all synonyms matching a query const { hits } = await client.searchSynonyms({ indexName: 'products', searchSynonymsParams: { query: 'phone', type: 'synonym' }, }); console.log(`Found ${hits.length} synonym sets matching "phone"`); ``` ## Resources - [Indexing Guide](https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/) - [Synonyms Guide](https://www.algolia.com/doc/guides/managing-results/optimize-search-results/adding-synonyms/) - [Rules Guide](https://www.algolia.com/doc/guides/managing-results/rules/rules-overview/) - [replaceAllObjects Reference](https://www.algolia.com/doc/api-reference/api-methods/replace-all-objects/) ## Next Steps For common errors, see `algolia-common-errors`.