cmboulanger's picture
disaster-recovery deploy of pdf-tei-editor
6a49f21 verified
|
Raw
History Blame Contribute Delete
10.1 kB

API Client Usage Guide

Guide for using the automatically-generated, type-safe FastAPI client.

For comprehensive API client documentation, see the full guide at fastapi_app/prompts/api-client-usage.md.

Overview

The FastAPI backend provides an automatically-generated JavaScript client with complete JSDoc type annotations for all API endpoints.

Client File: app/src/modules/api-client-v1.js Generator: bin/generate-api-client.js

Quick Start

import { apiClient } from './client.js';

// Use typed methods
const files = await apiClient.filesList({ collection: 'corpus1' });
const status = await apiClient.authStatus();

Regenerating the Client

# Regenerate client from current OpenAPI schema
npm run generate-client

# Check if client is outdated
npm run generate-client:check

Automatic regeneration:

  • Before production build (npm run build)
  • Pre-commit hook when router files change

Architecture

Dependency Injection Pattern

The generated client uses dependency injection for transport:

export class ApiClientV1 {
  constructor(callApiFn) {
    this.callApi = callApiFn;  // Injected transport function
  }

  async authLogin(requestBody) {
    return this.callApi('/api/v1/auth/login', 'POST', requestBody);
  }
}

Benefits:

  • Framework-agnostic (no fetch/axios dependency)
  • Transport handles retries, authentication, errors
  • Easy to mock for testing
  • Client focuses on API method signatures

Type Safety

Every method includes complete JSDoc annotations:

/**
 * List all files with optional collection filter
 * @param {Object} [params]
 * @param {string} [params.collection] - Filter by collection name
 * @returns {Promise<FileMetadata[]>}
 */
async filesList(params) {
  return this.callApi('/api/v1/files', 'GET', params);
}

Key Method Categories

Authentication

await apiClient.authLogin({ username: 'user', passwd_hash: 'hash' });
await apiClient.authLogout();
await apiClient.authStatus();

Files

// List and retrieve
await apiClient.filesList({ collection: 'corpus1' });
await apiClient.filesGet({ document_id: 'abc123' });

// Save TEI content
await apiClient.filesSave({
  document_id: 'doc123',
  content: '<TEI>...</TEI>',
  label: 'v2',
  variant: 'edited',
  version: 2
});

// File operations
await apiClient.filesDelete({ document_ids: ['doc1', 'doc2'] });
await apiClient.filesMove({ document_ids: ['doc1'], target_collection: 'archive' });
await apiClient.filesCopy({ document_ids: ['doc1'], target_collection: 'backup' });

// Lock management
await apiClient.filesAcquireLock({ document_id: 'doc123' });
await apiClient.filesReleaseLock({ document_id: 'doc123' });
await apiClient.filesCheckLock({ document_id: 'doc123' });
await apiClient.filesHeartbeat({ document_id: 'doc123' });

Configuration

await apiClient.configList();
await apiClient.configGet({ key: 'some_key' });
await apiClient.configSet({ key: 'some_key', value: 'value' });
await apiClient.configDelete({ key: 'some_key' });

Validation

await apiClient.validationValidate({
  content: '<TEI>...</TEI>',
  schema_type: 'tei'
});

await apiClient.validationAutocomplete({ schema_type: 'tei' });

Excluded Endpoints (Manual Implementation Required)

File Uploads

Upload endpoints use multipart/form-data and must be handled manually:

async function uploadFile(file, metadata) {
  const formData = new FormData();
  formData.append('file', file);
  Object.entries(metadata).forEach(([k, v]) =>
    formData.append(k, v)
  );
  return callApi('/api/v1/files/upload', 'POST', formData);
}

Excluded upload endpoints:

  • POST /api/v1/files/upload
  • POST /api/v1/files/create-version
  • POST /api/v1/files/upload-rng

Server-Sent Events

SSE endpoints use text/event-stream and require EventSource:

function subscribeToSSE(clientId) {
  const url = `/api/v1/sse/subscribe?client_id=${clientId}`;
  const eventSource = new EventSource(url);

  eventSource.onmessage = (event) => {
    const data = JSON.parse(event.data);
    // Handle SSE message
  };

  return eventSource;
}

Excluded SSE endpoints:

  • GET /api/v1/sse/subscribe
  • POST /api/v1/sse/test-message

Key Type Definitions

FileMetadata

/**
 * @typedef {Object} FileMetadata
 * @property {string} document_id - Stable document identifier
 * @property {string} file_hash - Content hash
 * @property {string} file_type - 'pdf' | 'tei' | 'rng'
 * @property {number} file_size - Size in bytes
 * @property {string} created_at - ISO timestamp
 * @property {string} updated_at - ISO timestamp
 * @property {string[]} doc_collections - Collections containing document
 * @property {Object} doc_metadata - Document metadata (title, author, etc.)
 * @property {string} [label] - Optional version label
 * @property {string} [variant] - Variant identifier (TEI only)
 * @property {number} [version] - Version number
 * @property {boolean} [is_gold_standard] - Gold standard flag
 * @property {Object} [file_metadata] - Extraction metadata
 */

Adding New Endpoints

When you add new FastAPI endpoints:

  1. Define router endpoint in fastapi_app/routers/*.py
  2. Add Pydantic models for request/response validation
  3. Regenerate client: npm run generate-client
  4. Add shim in client.js if needed for backward compatibility

The generator automatically:

  • Creates typed method from OpenAPI operation
  • Generates JSDoc annotations from Pydantic schemas
  • Handles path parameters, query params, request bodies
  • Skips upload/SSE endpoints

Common Patterns

Error Handling

The callApi function handles errors:

try {
  await apiClient.filesSave({ ... });
} catch (error) {
  if (error.status === 401) {
    // Handle authentication error
  }
}

Query Parameters (GET requests)

GET requests with parameters become query strings:

await apiClient.filesList({ collection: 'corpus1' });
// β†’ GET /api/v1/files?collection=corpus1

Request Bodies (POST/PUT/PATCH)

Request bodies are sent as JSON:

await apiClient.filesSave({
  document_id: 'doc123',
  content: '<TEI>...</TEI>'
});
// β†’ POST /api/v1/files/save
// β†’ Content-Type: application/json

Testing Generated Client

Mock the callApi function for tests:

import { ApiClientV1 } from './app/src/modules/api-client-v1.js';
import { test } from 'node:test';
import assert from 'node:assert';

test('auth login calls correct endpoint', async () => {
  const mockCallApi = async (endpoint, method, body) => {
    assert.strictEqual(endpoint, '/api/v1/auth/login');
    assert.strictEqual(method, 'POST');
    return { session_id: 'abc', message: 'ok' };
  };

  const client = new ApiClientV1(mockCallApi);
  const result = await client.authLogin({
    username: 'test',
    passwd_hash: 'hash'
  });

  assert.strictEqual(result.session_id, 'abc');
});

Programmatic API Access (CLI Scripts)

Environment Variables for HTTP API Access

When creating CLI scripts or external tools that access the HTTP API programmatically, support these standard environment variables:

# API credentials
API_USER=admin
API_PASSWORD=admin

# API base URL (default: http://localhost:8000)
API_BASE_URL=http://localhost:8000

Standard pattern for CLI scripts:

import dotenv from 'dotenv';
import { createHash } from 'crypto';

// Load .env file
dotenv.config({ path: envPath });

// Get credentials from env or CLI args
const username = cliUser || process.env.API_USER;
const password = cliPassword || process.env.API_PASSWORD;
const baseUrl = cliBaseUrl || process.env.API_BASE_URL || 'http://localhost:8000';

// Hash password (SHA-256, matching frontend)
function hashPassword(password) {
  return createHash('sha256').update(password).digest('hex');
}

// Login and get session
const response = await fetch(`${baseUrl}/api/v1/auth/login`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    username,
    passwd_hash: hashPassword(password)
  })
});
const { sessionId } = await response.json();

// Make authenticated requests with X-Session-ID header
await fetch(`${baseUrl}/api/v1/files/list`, {
  headers: { 'X-Session-ID': sessionId }
});

CLI parameter conventions:

  • --env <path> - Path to .env file (default: ./.env)
  • --user <username> - Override API_USER from env
  • --password <password> - Override API_PASSWORD from env
  • --base-url <url> - Override API_BASE_URL from env

Example implementations:

Best Practices

DO βœ…

  • Regenerate after router changes: npm run generate-client
  • Use typed parameters: Let IDE autocomplete guide you
  • Commit generated client: Check in with router changes
  • Handle errors at transport layer: Let callApi manage retries
  • Add JSDoc to shims: Type annotations in wrapper functions
  • Support standard env vars in CLI scripts: Use API_USER, API_PASSWORD, API_BASE_URL

DON'T ❌

  • Don't modify generated client: Changes will be overwritten
  • Don't bypass the client: Use generated methods instead of raw callApi
  • Don't generate for uploads/SSE: Keep manual implementations
  • Don't skip pre-commit checks: Client freshness is critical

Troubleshooting

Client Generation Fails

  1. Ensure FastAPI server is running: npm run start:dev
  2. Verify OpenAPI endpoint: curl http://localhost:8000/openapi.json

Type Errors in IDE

  1. Ensure client is up-to-date: npm run generate-client:check
  2. Regenerate if outdated: npm run generate-client
  3. Restart IDE/TypeScript server

Pre-commit Hook Blocks Commit

  1. Regenerate client: npm run generate-client
  2. Stage updated client: git add app/src/modules/api-client-v1.js
  3. Retry commit