Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.doclo.ai/llms.txt

Use this file to discover all available pages before exploring further.

The Flow Registry allows you to register flows by string ID for reuse across your application. This is useful for:
  • Centralizing flow definitions
  • Referencing flows from serialized configurations
  • Building flow libraries

Basic Usage

Register a Flow

import { registerFlow, createFlow, parse, extract } from '@doclo/flows';

registerFlow('invoice-processing', (providers) =>
  createFlow()
    .step('parse', parse({ provider: providers?.ocr }))
    .step('extract', extract({
      provider: providers?.vlm,
      schema: invoiceSchema
    }))
);

Retrieve and Run

import { getFlow } from '@doclo/flows';

const flowBuilder = getFlow('invoice-processing');

if (flowBuilder) {
  const flow = flowBuilder({
    ocr: ocrProvider,
    vlm: vlmProvider
  });

  const result = await flow.build().run({ base64: documentData });
}

Registry API

registerFlow

Register a flow builder function:
registerFlow<TInput, TOutput>(
  id: string,
  builder: (providers?: ProviderRegistry) => Flow<TInput, TOutput>
): void
If a flow with the same ID already exists, it will be overwritten with a warning.

getFlow

Retrieve a flow builder by ID:
getFlow<TInput, TOutput>(id: string): FlowBuilder<TInput, TOutput> | undefined
Returns undefined if the flow is not registered.

hasFlow

Check if a flow exists:
hasFlow(id: string): boolean

unregisterFlow

Remove a flow from the registry:
unregisterFlow(id: string): boolean
Returns true if the flow was removed, false if it didn’t exist.

listFlows

Get all registered flow IDs:
listFlows(): string[]

getFlowCount

Get the number of registered flows:
getFlowCount(): number

clearRegistry

Remove all registered flows:
clearRegistry(): void

Provider Registry

Flow builders receive an optional ProviderRegistry for dependency injection:
interface ProviderRegistry {
  ocr?: OCRProvider;
  llm?: LLMProvider;
  vlm?: VLMProvider;
  [key: string]: unknown;
}
This allows flows to be configured with different providers at runtime:
// Register with provider placeholders
registerFlow('document-extractor', (providers) =>
  createFlow()
    .step('parse', parse({ provider: providers?.ocr }))
    .step('extract', extract({ provider: providers?.vlm, schema }))
);

// Use with production providers
const prodFlow = getFlow('document-extractor')!({
  ocr: productionOCR,
  vlm: productionVLM
});

// Use with test providers
const testFlow = getFlow('document-extractor')!({
  ocr: mockOCR,
  vlm: mockVLM
});

Global Registry

The SDK provides a global registry instance:
import { FLOW_REGISTRY } from '@doclo/flows';

// Direct access to the Map
FLOW_REGISTRY.set('my-flow', flowBuilder);
FLOW_REGISTRY.get('my-flow');
FLOW_REGISTRY.has('my-flow');
FLOW_REGISTRY.delete('my-flow');
FLOW_REGISTRY.clear();
The helper functions (registerFlow, getFlow, etc.) operate on this global registry.

Use Cases

Flow Library

Create a library of reusable flows:
// flows/index.ts
import { registerFlow, createFlow, parse, extract, split, combine } from '@doclo/flows';

// Simple extraction
registerFlow('simple-extraction', (providers) =>
  createFlow()
    .step('extract', extract({
      provider: providers?.vlm,
      schema: providers?.schema
    }))
);

// OCR + extraction
registerFlow('ocr-extraction', (providers) =>
  createFlow()
    .step('parse', parse({ provider: providers?.ocr }))
    .step('extract', extract({
      provider: providers?.llm,
      schema: providers?.schema
    }))
);

// Multi-document processing
registerFlow('multi-document', (providers) =>
  createFlow()
    .step('split', split({
      provider: providers?.vlm,
      schemas: providers?.schemas
    }))
    .forEach('process', (doc) =>
      createFlow()
        .step('extract', extract({
          provider: providers?.vlm,
          schema: doc.schema
        }))
    )
    .step('combine', combine())
);

Configuration-Driven Flows

Reference flows from configuration:
// config.json
{
  "documentProcessing": {
    "flowId": "invoice-processing",
    "providers": {
      "ocr": "surya",
      "vlm": "gemini-flash"
    }
  }
}

// runtime.ts
import { getFlow } from '@doclo/flows';
import config from './config.json';

const flowBuilder = getFlow(config.documentProcessing.flowId);
if (!flowBuilder) {
  throw new Error(`Unknown flow: ${config.documentProcessing.flowId}`);
}

const providers = resolveProviders(config.documentProcessing.providers);
const flow = flowBuilder(providers).build();

Testing

Use the registry to swap providers for testing:
import { registerFlow, getFlow, clearRegistry } from '@doclo/flows';

describe('Invoice Flow', () => {
  beforeEach(() => {
    clearRegistry();
    registerFlow('invoice', (providers) =>
      createFlow()
        .step('extract', extract({ provider: providers?.vlm, schema }))
    );
  });

  it('extracts invoice data', async () => {
    const mockVLM = createMockVLMProvider({
      response: { invoiceNumber: 'INV-001', total: 100 }
    });

    const flow = getFlow('invoice')!({ vlm: mockVLM }).build();
    const result = await flow.run({ base64: testDocument });

    expect(result.output.invoiceNumber).toBe('INV-001');
  });
});

Multi-Tenant Configuration

Different flows per tenant:
// Register tenant-specific flows
registerFlow('tenant-a-invoice', (providers) =>
  createFlow()
    .step('extract', extract({
      provider: providers?.vlm,
      schema: tenantASchema,
      additionalInstructions: 'Use Tenant A field mapping'
    }))
);

registerFlow('tenant-b-invoice', (providers) =>
  createFlow()
    .step('extract', extract({
      provider: providers?.vlm,
      schema: tenantBSchema,
      additionalInstructions: 'Use Tenant B field mapping'
    }))
);

// Runtime selection
function processDocument(tenantId: string, document: FlowInput) {
  const flowId = `tenant-${tenantId}-invoice`;
  const flowBuilder = getFlow(flowId);

  if (!flowBuilder) {
    throw new Error(`No flow configured for tenant: ${tenantId}`);
  }

  return flowBuilder({ vlm: vlmProvider }).build().run(document);
}

Best Practices

Use Descriptive IDs

// Good
registerFlow('invoice-extraction-v2', ...);
registerFlow('receipt-with-consensus', ...);

// Avoid
registerFlow('flow1', ...);
registerFlow('extract', ...);

Version Your Flows

registerFlow('invoice-v1', ...);
registerFlow('invoice-v2', ...);

// Or use a versioning scheme
registerFlow('invoice@1.0.0', ...);
registerFlow('invoice@2.0.0', ...);

Handle Missing Flows

const flowBuilder = getFlow(flowId);

if (!flowBuilder) {
  // Log available flows for debugging
  console.error(`Flow "${flowId}" not found. Available: ${listFlows().join(', ')}`);
  throw new Error(`Unknown flow: ${flowId}`);
}

Initialize Registry on Startup

// flows/registry.ts
import { registerFlow } from '@doclo/flows';
import { invoiceFlow } from './invoice';
import { receiptFlow } from './receipt';
import { contractFlow } from './contract';

export function initializeFlowRegistry() {
  registerFlow('invoice', invoiceFlow);
  registerFlow('receipt', receiptFlow);
  registerFlow('contract', contractFlow);
}

// app.ts
import { initializeFlowRegistry } from './flows/registry';

initializeFlowRegistry();
// Now flows are available throughout the app

Next Steps

Creating Flows

Build custom flows

Pre-built Flows

Ready-to-use flow templates