Skip to main content

Adapter Registry

The V2 adapter system uses a centralized registry for dynamic adapter registration and discovery. Adapters are no longer imported directly from @idpass/data-collect-core — they live in standalone packages and must be registered at startup.

Overview

The AdapterRegistry provides:

  • Dynamic registration — register adapters by name at application startup
  • Zod validation — adapter configurations are validated against schemas before use
  • Type safety — the V2 adapter interface is fully typed

Registering an Adapter

import { AdapterRegistry } from '@idpass/data-collect-core';
import { OpenSppV2Adapter } from '@idpass/adapter-openspp';
import { OpenFnAdapter } from '@idpass/adapter-openfn';

// Register adapters at application startup
AdapterRegistry.register('openspp', OpenSppV2Adapter);
AdapterRegistry.register('openfn', OpenFnAdapter);

Available Adapter Packages

PackageAdapterDescription
@idpass/adapter-opensppOpenSPP V1 & V2Sync with OpenSPP beneficiary registry
@idpass/adapter-openfnOpenFnSync via OpenFn integration platform
@idpass/adapter-mockMockOAuth2 HTTP client for the reference mock registry server

Creating a Custom Adapter

To create a V2-compatible adapter:

1. Implement the Adapter Interface

import type { ExternalSyncAdapterV2, SyncResult } from '@idpass/data-collect-core';

export class MyAdapter implements ExternalSyncAdapterV2 {
constructor(private config: MyAdapterConfig) {}

async initialize(): Promise<void> {
// Set up connections, validate config
}

async push(entities: EntityDoc[]): Promise<SyncResult> {
const result: SyncResult = { succeeded: [], failed: [] };

for (const entity of entities) {
try {
await this.sendToExternalSystem(entity);
result.succeeded.push(entity.guid);
} catch (error) {
// Never throw for per-entity errors — collect them in result
result.failed.push({
guid: entity.guid,
error: error.message,
});
}
}

return result;
}

async pull(since: string): Promise<PullResult> {
// Fetch changes from external system
}
}

2. Define a Configuration Schema

import { z } from 'zod';

export const myAdapterConfigSchema = z.object({
url: z.string().url(),
apiKey: z.string().min(1),
batchSize: z.number().int().positive().default(100),
});

export type MyAdapterConfig = z.infer<typeof myAdapterConfigSchema>;

3. Register the Adapter

AdapterRegistry.register('my-adapter', MyAdapter);

4. Use in App Configuration

{
"externalSync": {
"type": "my-adapter",
"url": "https://api.example.com",
"apiKey": "secret",
"batchSize": 50
}
}

Error Handling

V2 adapters must never throw for per-entity errors. Instead, collect errors in the SyncResult:

// Correct: collect per-entity errors
result.failed.push({ guid: entity.guid, error: error.message });

// Incorrect: throwing aborts the entire batch
throw new Error(`Failed to sync ${entity.guid}`);

Adapter-level errors (authentication failure, network down) may throw — these indicate the entire sync operation cannot proceed.