agentsclimarketplace

Algolia sdk patterns

Skill jeremylongshore/claude-code-plugins-plus-skills/plugins/saas-packs/algolia-pack/skills/algolia-sdk-patterns

'Apply production-ready algoliasearch v5 patterns: singleton client, typed search,From its SKILL.md

Install
npx -y skills add jeremylongshore/claude-code-plugins-plus-skills --skill algolia-sdk-patterns

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its file declares

Copied from the file, not written here

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

6.6 KB, ~1.5k tokens by cl100k_base, as published. Nobody here has run it

Algolia SDK Patterns

Overview

Production-ready patterns for algoliasearch v5. Key architectural change from v4: all methods live on the client directly — no more client.initIndex(). Index name is passed as a parameter to every call.

Prerequisites

  • algoliasearch v5+ installed
  • Completed algolia-install-auth setup
  • TypeScript project (patterns work in JS too, you just lose type safety)

Instructions

Pattern 1: Typed Singleton Client

// src/algolia/client.ts
import { algoliasearch, type Algoliasearch } from 'algoliasearch';

let _client: Algoliasearch | null = null;

export function getClient(): Algoliasearch {
  if (!_client) {
    const appId = process.env.ALGOLIA_APP_ID;
    const apiKey = process.env.ALGOLIA_ADMIN_KEY;
    if (!appId || !apiKey) {
      throw new Error(
        'ALGOLIA_APP_ID and ALGOLIA_ADMIN_KEY must be set. '
        + 'Get them from dashboard.algolia.com > Settings > API Keys'
      );
    }
    _client = algoliasearch(appId, apiKey);
  }
  return _client;
}

// For testing: reset singleton
export function resetClient(): void {
  _client = null;
}

Pattern 2: Typed Search Results

// src/algolia/types.ts

// Define your record shape — extends Algolia's Hit type
interface Product {
  objectID: string;
  name: string;
  category: string;
  price: number;
  description: string;
  image_url: string;
}

// src/algolia/search.ts
import { getClient } from './client';

export async function searchProducts(
  query: string,
  options?: {
    filters?: string;
    facetFilters?: string[][];
    hitsPerPage?: number;
    page?: number;
  }
) {
  const client = getClient();

  const { hits, nbHits, nbPages, page } = await client.searchSingleIndex<Product>({
    indexName: 'products',
    searchParams: {
      query,
      filters: options?.filters,
      facetFilters: options?.facetFilters,
      hitsPerPage: options?.hitsPerPage ?? 20,
      page: options?.page ?? 0,
      attributesToRetrieve: ['name', 'category', 'price', 'image_url'],
      attributesToHighlight: ['name', 'description'],
    },
  });

  return { hits, totalHits: nbHits, totalPages: nbPages, currentPage: page };
}

// Usage: const { hits } = await searchProducts('laptop', { filters: 'price < 1000' });

Pattern 3: Error Handling with Algolia Error Types

// src/algolia/errors.ts
import { ApiError } from 'algoliasearch';

export async function safeAlgoliaCall<T>(
  operation: string,
  fn: () => Promise<T>
): Promise<{ data: T | null; error: string | null }> {
  try {
    const data = await fn();
    return { data, error: null };
  } catch (err) {
    if (err instanceof ApiError) {
      // ApiError has status and message from Algolia API
      const msg = `Algolia ${operation} failed [${err.status}]: ${err.message}`;
      console.error(msg);

      // Specific handling for common codes
      if (err.status === 429) {
        console.warn('Rate limited — reduce request frequency or contact Algolia');
      } else if (err.status === 404) {
        console.warn('Index or object not found — verify index name');
      }

      return { data: null, error: msg };
    }
    // Non-Algolia error (network, etc.)
    const msg = err instanceof Error ? err.message : 'Unknown error';
    console.error(`${operation} error: ${msg}`);
    return { data: null, error: msg };
  }
}

// Usage:
// const { data, error } = await safeAlgoliaCall('search', () =>
//   client.searchSingleIndex({ indexName: 'products', searchParams: { query: 'foo' } })
// );

Pattern 4: Batch Operations

// src/algolia/batch.ts
import { getClient } from './client';

// saveObjects handles batching internally — send up to 1000 objects per call
export async function bulkIndex(indexName: string, records: Record<string, any>[]) {
  const client = getClient();
  const BATCH_SIZE = 1000;

  for (let i = 0; i < records.length; i += BATCH_SIZE) {
    const batch = records.slice(i, i + BATCH_SIZE);
    const { taskID } = await client.saveObjects({
      indexName,
      objects: batch,
    });
    await client.waitForTask({ indexName, taskID });
    console.log(`Indexed ${Math.min(i + BATCH_SIZE, records.length)}/${records.length}`);
  }
}

// Partial update — only send changed fields
export async function updateFields(
  indexName: string,
  objectID: string,
  fields: Record<string, any>
) {
  const client = getClient();
  return client.partialUpdateObject({
    indexName,
    objectID,
    attributesToUpdate: fields,
  });
}

Pattern 5: Multi-Tenant Client Factory

// src/algolia/multi-tenant.ts
import { algoliasearch, type Algoliasearch } from 'algoliasearch';

const tenantClients = new Map<string, Algoliasearch>();

export function getClientForTenant(tenantId: string): Algoliasearch {
  if (!tenantClients.has(tenantId)) {
    // Each tenant might have their own Algolia app, or use index prefixes
    const appId = process.env[`ALGOLIA_APP_ID_${tenantId.toUpperCase()}`]
      || process.env.ALGOLIA_APP_ID!;
    const apiKey = process.env[`ALGOLIA_ADMIN_KEY_${tenantId.toUpperCase()}`]
      || process.env.ALGOLIA_ADMIN_KEY!;

    tenantClients.set(tenantId, algoliasearch(appId, apiKey));
  }
  return tenantClients.get(tenantId)!;
}

// Or use a single app with index prefixing
export function tenantIndex(tenantId: string, base: string): string {
  return `${tenantId}_${base}`; // "acme_products"
}

Error Handling

PatternUse CaseBenefit
safeAlgoliaCall wrapperAll API callsPrevents uncaught exceptions, structured error info
ApiError checkDistinguishing API vs network errorsTargeted retry/recovery logic
waitForTaskAfter every write operationEnsures reads see latest data
Batch chunkingLarge datasetsAvoids record-too-big and timeout errors

Resources

Next Steps

Apply patterns in algolia-core-workflow-a for search implementation.

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,144. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.