Skip to main content
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 '@docloai/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 '@docloai/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 '@docloai/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 '@docloai/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 '@docloai/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 '@docloai/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 '@docloai/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