Skip to content
Back to skills

Glean Sdk Patterns

ASecurity

'Apply production-ready Glean API patterns with typed clients, batch

  • 2,719 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 21, 2026
ai-agentstypescriptgotestingapi

Works with

  • claude code
  • cursor
  • cli
  • api

Security analysis

A100/100

Scanned September 21, 2026

npx -y skills add jeremylongshore/claude-code-plugins-plus --skill glean-sdk-patterns --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Glean Sdk Patterns?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Glean Sdk Patterns
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/jeremylongshore-glean-sdk-patterns-440ddd8b/badge)](https://www.skillsdirectory.com/skills/jeremylongshore-glean-sdk-patterns-440ddd8b)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: glean-sdk-patterns
description: 'Apply production-ready Glean API patterns with typed clients, batch
  indexing, pagination, and error handling.

  Trigger: "glean SDK patterns", "glean best practices", "glean API client".

  '
allowed-tools: Read, Write, Edit
version: 1.8.0
license: MIT
author: Jeremy Longshore <jeremy@intentsolutions.io>
tags:
- saas
- enterprise-search
- glean
compatibility: Designed for Claude Code
---
# Glean SDK Patterns

## Overview

Production-ready patterns for the Glean enterprise search platform. Glean uses POST-based REST endpoints for both search and indexing. Search queries go to the Client API while document ingestion uses the Indexing API. A structured client centralizes token management, enforces batch pagination for bulk indexing, and provides typed responses for search results.

## Singleton Client

```typescript
let _client: GleanClient | null = null;
export function getClient(): GleanClient {
  if (!_client) {
    const domain = process.env.GLEAN_DOMAIN, key = process.env.GLEAN_API_KEY;
    if (!domain || !key) throw new Error('GLEAN_DOMAIN and GLEAN_API_KEY must be set');
    _client = new GleanClient(domain, key);
  }
  return _client;
}
class GleanClient {
  private base: string; private h: Record<string, string>;
  constructor(domain: string, key: string) {
    this.base = `https://${domain}/api`;
    this.h = { 'Authorization': `Bearer ${key}`, 'Content-Type': 'application/json' };
  }
  async search(query: string, opts: { pageSize?: number; datasource?: string } = {}) {
    const r = await fetch(`${this.base}/client/v1/search`, { method: 'POST',
      headers: { ...this.h, 'X-Glean-Auth-Type': 'BEARER' },
      body: JSON.stringify({ query, pageSize: opts.pageSize ?? 20,
        requestOptions: opts.datasource ? { datasourceFilter: opts.datasource } : undefined }) });
    if (!r.ok) throw new GleanError(r.status, await r.text()); return r.json() as Promise<GleanSearchResponse>;
  }
  async indexDocuments(datasource: string, docs: GleanDocument[]): Promise<void> {
    const r = await fetch(`${this.base}/index/v1/indexdocuments`, {
      method: 'POST', headers: this.h, body: JSON.stringify({ datasource, documents: docs }) });
    if (!r.ok) throw new GleanError(r.status, await r.text());
  }
  async bulkIndex(ds: string, docs: GleanDocument[], batch = 100): Promise<void> {
    for (let i = 0; i < docs.length; i += batch) await this.indexDocuments(ds, docs.slice(i, i + batch));
  }
}
```

## Error Wrapper

```typescript
export class GleanError extends Error {
  constructor(public status: number, message: string) { super(message); this.name = 'GleanError'; }
}
export async function safeCall<T>(operation: string, fn: () => Promise<T>): Promise<T> {
  try { return await fn(); }
  catch (err: any) {
    if (err instanceof GleanError && err.status === 429) { await new Promise(r => setTimeout(r, 3000)); return fn(); }
    if (err instanceof GleanError && err.status === 401) throw new GleanError(401, 'Invalid GLEAN_API_KEY');
    throw new GleanError(err.status ?? 0, `${operation} failed: ${err.message}`);
  }
}
```

## Request Builder

```typescript
class GleanSearchBuilder {
  private body: Record<string, any> = {};
  query(q: string) { this.body.query = q; return this; }
  datasource(ds: string) { this.body.requestOptions = { datasourceFilter: ds }; return this; }
  pageSize(n: number) { this.body.pageSize = Math.min(n, 100); return this; }
  cursor(token: string) { this.body.cursor = token; return this; }
  facets(fields: string[]) { this.body.facetFilters = fields; return this; }
  build() { return this.body; }
}
// Usage: new GleanSearchBuilder().query('onboarding docs').datasource('confluence').pageSize(10).build();
```

## Response Types

```typescript
interface GleanDocument {
  id: string; title: string; url: string;
  body: { mimeType: string; textContent: string };
  author?: { email: string }; updatedAt?: string;
}
interface GleanSearchResponse {
  results: Array<{ document: GleanDocument; snippets: string[]; score: number }>;
  totalResults: number; cursor?: string;
}
interface GleanDatasource { name: string; displayName: string; documentCount: number; lastCrawledAt: string; }
```

## Testing Utilities

```typescript
export function mockDocument(o: Partial<GleanDocument> = {}): GleanDocument {
  return { id: 'doc-001', title: 'Onboarding Guide', url: 'https://wiki.example.com/onboarding',
    body: { mimeType: 'text/plain', textContent: 'Welcome to the team...' },
    author: { email: 'hr@example.com' }, updatedAt: '2025-03-01T00:00:00Z', ...o };
}
export function mockSearchResponse(n = 3): GleanSearchResponse {
  return { results: Array.from({ length: n }, (_, i) => ({
    document: mockDocument({ id: `doc-${i}` }), snippets: ['...match...'], score: 0.95 - i * 0.1 })), totalResults: n };
}
```

## Error Handling

| Pattern | When to Use | Example |
|---------|-------------|---------|
| `safeCall` wrapper | All search and index calls | Structured error with operation context |
| Retry on 429 | Bulk indexing pipelines | 3s delay before retry |
| Batch pagination | Indexing > 100 documents | `bulkIndex` with batch tracking |
| Auth validation | Client init | Fail fast on missing `GLEAN_API_KEY` |

## Prerequisites

- A typed client boundary, secret-manager-backed configuration, and a sandbox base URL allowlist.
- Contract fixtures containing fictional data plus an explicit error and retry budget.
- A policy that unknown ACL, destination, or response shape fails closed rather than falling back to a broad client.

## Instructions

1. Construct clients from environment-specific, scoped configuration and reject missing or production-unsafe destinations.
2. Centralize request validation, bounded response parsing, redacted telemetry, and idempotency keys in the client boundary.
3. Model authorization and partial failure explicitly; never convert a denied result into an empty success or retry writes without a key.
4. Test client behavior against fixtures for malformed payloads, rate limits, deny-by-default ACLs, and rollback of a failed submission.

## Output

Produce a client-contract receipt showing SDK revision, target environment, request/response schema revisions, fixture results, error classification, observability fields, and rollback behavior. Do not log credentials, query text, or document bodies.

## Examples

`sdk=v3; env=sandbox; contract=search-r5; malformed=blocked; 429=backoff; deny=preserved; telemetry=redacted` captures a safe client result.

## Resources

- [Glean Developer Portal](https://developers.glean.com/)

## Next Steps

Apply patterns in `glean-core-workflow-a`.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.