@flowlab/all
Version:
A cool library focusing on handling various flows
80 lines (72 loc) • 4.17 kB
text/typescript
// src/loaders/prismaLoader.ts
import { ILoader, PipelineContext, DatabaseTargetConfig } from '../core/interfaces';
import { PrismaClient } from '@prisma/client';
interface PrismaTargetConfig extends DatabaseTargetConfig {
type: 'postgresql' | 'mysql';
connection: PrismaClient;
model: string;
operation?: 'create' | 'upsert' | 'update'; // Default to 'create'
upsertWhereField?: string; // Field to use for the 'where' in upsert (e.g. 'id' or 'email')
// Add batch transaction options if needed
}
// MARK: - PrismaLoader
export class PrismaLoader<TInput extends object> implements ILoader<TInput> {
private config: PrismaTargetConfig;
constructor(config: PrismaTargetConfig) {
if (!config.connection || !config.model) {
throw new Error('PrismaLoader requires "connection" (PrismaClient instance) and "model" in config.');
}
this.config = config;
this.config.operation = config.operation || 'create';
if (this.config.operation === 'upsert' && !this.config.upsertWhereField) {
throw new Error("PrismaLoader with 'upsert' operation requires 'upsertWhereField' config.");
}
}
// MARK: - loadBatch
async loadBatch(batch: TInput[], context: PipelineContext): Promise<void> {
if (batch.length === 0) return; // Nothing to load
const modelDelegate = (this.config.connection as any)[this.config.model];
if (!modelDelegate) {
throw new Error(`Prisma model "${this.config.model}" not found on the provided client.`);
}
context.logger.debug(`Loading batch of ${batch.length} items to Prisma model ${this.config.model} using operation ${this.config.operation}`);
try {
// Prisma's createMany is often faster for simple inserts
if (this.config.operation === 'create') {
const result = await modelDelegate.createMany({
data: batch,
skipDuplicates: true, // Optional: ignore errors if a record already exists (depends on constraints)
});
context.logger.info(`Prisma createMany inserted ${result.count} records.`);
} else if (this.config.operation === 'upsert') {
// Upsert needs to be done one by one or in a transaction
// Using transaction for potentially better performance
const upsertPromises = batch.map(item => {
const whereValue = (item as any)[this.config.upsertWhereField!];
if(whereValue === undefined || whereValue === null) {
context.logger.warn({ item, field: this.config.upsertWhereField }, `Skipping upsert: where field '${this.config.upsertWhereField}' is missing or null.`);
return Promise.resolve(); // Skip this item
}
return modelDelegate.upsert({
where: { [this.config.upsertWhereField!]: whereValue },
update: item, // Update with the full item
create: item, // Create with the full item
});
});
// Execute upserts sequentially for now, batch transaction is better
// await this.config.connection.$transaction(upsertPromises); // Requires Prisma preview feature or check version
for (const promise of upsertPromises) { // Sequential execution
await promise;
}
context.logger.info(`Prisma upsert processed ${batch.length} records.`);
} else {
// TODO: Implement 'update' if needed (likely requires a unique key in each item)
throw new Error(`PrismaLoader operation '${this.config.operation}' not yet implemented.`);
}
} catch (error) {
context.logger.error({ err: error }, `Error loading batch to Prisma model ${this.config.model}`);
// Handle batch errors - potentially log failed items?
throw error; // Rethrow to be caught by retry logic
}
}
}