The DatasourceCatalog class manages datasource definitions and instances, providing registration, creation, serialization, and persistence capabilities.
The catalog serves as a registry for datasource definitions and a factory for creating datasource instances. It also provides powerful persistence capabilities including database adapters and file-based storage.
import { DatasourceCatalog } from '@statuscompliance/databinder';
import { RestApiDatasource, GithubApiDatasource } from '@statuscompliance/databinder/Datasources';
// Create catalog instance
const catalog = new DatasourceCatalog();
// Register datasource definitions
catalog.registerDatasource(RestApiDatasource);
catalog.registerDatasource(GithubApiDatasource);
// Create datasource instances
const restInstance = catalog.createDatasourceInstance('rest-api', {
baseUrl: 'https://api.example.com',
apiKey: 'your-key'
}, 'my-rest-api');
const githubInstance = catalog.createDatasourceInstance('github-api', {
token: 'github-token'
}, 'my-github-api');Registers a datasource definition in the catalog.
const myDatasourceDefinition = {
id: 'custom-api',
name: 'Custom API',
description: 'My custom API datasource',
configSchema: {
type: 'object',
properties: {
apiKey: { type: 'string' },
baseUrl: { type: 'string' }
},
required: ['apiKey', 'baseUrl']
},
createInstance: (config) => ({
id: '',
definitionId: 'custom-api',
config,
methods: {
getData: async (options) => {
// Implementation
}
}
})
};
catalog.registerDatasource(myDatasourceDefinition);Creates a datasource instance from a registered definition.
// Auto-generated ID
const instance1 = catalog.createDatasourceInstance('rest-api', config);
// Custom ID
const instance2 = catalog.createDatasourceInstance('rest-api', config, 'my-custom-id');Retrieves a datasource instance by ID.
const instance = catalog.getDatasourceInstance('my-rest-api');
if (instance) {
// Use the instance
}Lists all registered definitions or created instances.
const definitions = catalog.listDatasourceDefinitions();
const instances = catalog.listDatasourceInstances();// Save instances to file (with optional metadata)
await catalog.saveInstancesToFile('./datasources.json', true);
// Load instances from file
await catalog.loadInstancesFromFile('./datasources.json');The DatabaseAdapter interface allows you to integrate with any database system:
interface DatabaseAdapter {
save(instances: SerializedDatasourceInstance[]): Promise<void>;
load(): Promise<SerializedDatasourceInstance[]>;
saveOne?(instance: SerializedDatasourceInstance): Promise<void>;
loadOne?(id: string): Promise<SerializedDatasourceInstance | null>;
}Serialized instances now support metadata for better database management:
interface SerializedDatasourceInstance {
id: string;
definitionId: string;
config: DatasourceConfig;
metadata?: {
createdAt?: Date;
updatedAt?: Date;
tags?: string[];
description?: string;
[key: string]: any;
};
}saveToDatabaseAdapter(adapter, includeMetadata?)- Save all instances to databaseloadFromDatabaseAdapter(adapter)- Load all instances from databasesaveInstanceToDatabaseAdapter(adapter, instanceId, includeMetadata?)- Save single instanceloadInstanceFromDatabaseAdapter(adapter, instanceId)- Load single instance
restoreInstance(id, definitionId, config)- Restore a single instance by ID
saveInstancesToFile(filePath, includeMetadata?)- Enhanced with metadata supportserializeInstances(includeMetadata?)- Enhanced with metadata support
import { DatabaseAdapter, SerializedDatasourceInstance } from '@statuscompliance/databinder';
class MyDatabaseAdapter implements DatabaseAdapter {
async save(instances: SerializedDatasourceInstance[]): Promise<void> {
// Save all instances to your database
await this.db.collection('datasources').replaceMany(instances);
}
async load(): Promise<SerializedDatasourceInstance[]> {
// Load all instances from your database
return await this.db.collection('datasources').find({}).toArray();
}
// Optional: Single instance operations
async saveOne(instance: SerializedDatasourceInstance): Promise<void> {
await this.db.collection('datasources').replaceOne(
{ id: instance.id },
instance,
{ upsert: true }
);
}
async loadOne(id: string): Promise<SerializedDatasourceInstance | null> {
return await this.db.collection('datasources').findOne({ id });
}
}// Node-RED startup
const catalog = new DatasourceCatalog();
const dbAdapter = new MyDatabaseAdapter();
// Register datasource definitions
catalog.registerDatasource(RestApiDatasource);
catalog.registerDatasource(GithubApiDatasource);
// Load existing instances from database
await catalog.loadFromDatabaseAdapter(dbAdapter);
// In Node-RED flow node
node.on('input', async (msg) => {
const datasourceId = msg.datasourceId;
// Get instance from catalog
const instance = catalog.getDatasourceInstance(datasourceId);
if (!instance) {
// Try to load single instance from database
const loaded = await catalog.loadInstanceFromDatabaseAdapter(dbAdapter, datasourceId);
if (!loaded) {
node.error(`Datasource ${datasourceId} not found`);
return;
}
}
// Use the datasource
const linker = new Linker({ datasources: [instance] });
const dataBinder = new DataBinder({ linker });
const result = await dataBinder.fetchFromDatasource(datasourceId, msg.options);
msg.payload = result;
node.send(msg);
});// Create instance
const instance = catalog.createDatasourceInstance('rest-api', config, 'my-api-1');
// Save with metadata
await catalog.saveToDatabaseAdapter(dbAdapter, true); // includeMetadata = true
// The serialized instance will include:
// {
// id: 'my-api-1',
// definitionId: 'rest-api',
// config: { ... },
// metadata: {
// createdAt: '2024-01-15T10:30:00Z',
// updatedAt: '2024-01-15T10:30:00Z',
// tags: [],
// description: 'Datasource instance of type rest-api'
// }
// }// Restore a single instance
const instance = await catalog.restoreInstance(
'new-instance-1',
'github-api',
{ token: 'abc123', baseUrl: 'https://api.github.com' }
);
// Save only this instance to database
await catalog.saveInstanceToDatabaseAdapter(dbAdapter, 'new-instance-1', true);
// Load a specific instance from database
const loaded = await catalog.loadInstanceFromDatabaseAdapter(dbAdapter, 'new-instance-1');The new features are backwards compatible. Existing code will continue to work without changes:
// This still works exactly as before
const serialized = catalog.serializeInstances();
catalog.restoreInstances(serialized);
// But you can now also use:
const serializedWithMeta = catalog.serializeInstances(true); // Include metadata
await catalog.saveToDatabaseAdapter(dbAdapter); // Database persistenceSee /examples/database-persistence.ts for a complete example with a MongoDB-style adapter.
Common database adapters you might implement:
- MongoDB with Mongoose
- PostgreSQL with node-postgres
- Redis for caching
- SQLite for local storage
- Cloud databases (Azure Cosmos DB, AWS DynamoDB, etc.)