@flavoai/fastfold
Version:
Flavo frontend package
232 lines • 8.84 kB
JavaScript
import { SecurityEnforcer } from '../security';
export class CrudGenerator {
db;
tables;
constructor(db, tables) {
this.db = db;
this.tables = tables;
}
/**
* Generate CRUD operations for a specific table
*/
generateTableOperations(tableName) {
const tableDefinition = this.tables[tableName];
if (!tableDefinition) {
throw new Error(`Table '${tableName}' not found in configuration`);
}
return {
// READ operations
findMany: this.createFindManyOperation(tableName, tableDefinition),
findOne: this.createFindOneOperation(tableName, tableDefinition),
count: this.createCountOperation(tableName, tableDefinition),
// WRITE operations
create: this.createCreateOperation(tableName, tableDefinition),
update: this.createUpdateOperation(tableName, tableDefinition),
delete: this.createDeleteOperation(tableName, tableDefinition),
};
}
/**
* Create READ MANY operation
*/
createFindManyOperation(tableName, table) {
return async (params, context) => {
// Security check
const securityContext = {
...context,
operation: 'read',
tableName
};
const hasAccess = await SecurityEnforcer.checkAccess(table.security, securityContext);
if (!hasAccess) {
throw SecurityEnforcer.createUnauthorizedError('read', tableName);
}
// Filter results based on security context for owner-based security
const securityParams = this.applySecurityFilters(params, table, context);
return await this.db.query(tableName, securityParams);
};
}
/**
* Create READ ONE operation
*/
createFindOneOperation(tableName, table) {
return async (id, context) => {
// First fetch the record to check permissions
const record = await this.db.query(tableName, { where: { id }, limit: 1 });
if (record.length === 0) {
throw new Error(`Record with id ${id} not found in table '${tableName}'`);
}
// Security check with existing data
const securityContext = {
...context,
operation: 'read',
tableName,
existingData: record[0]
};
const hasAccess = await SecurityEnforcer.checkAccess(table.security, securityContext);
if (!hasAccess) {
throw SecurityEnforcer.createUnauthorizedError('read', tableName);
}
return record[0];
};
}
/**
* Create COUNT operation
*/
createCountOperation(tableName, table) {
return async (where, context) => {
// Security check
const securityContext = {
...context,
operation: 'read',
tableName
};
const hasAccess = await SecurityEnforcer.checkAccess(table.security, securityContext);
if (!hasAccess) {
throw SecurityEnforcer.createUnauthorizedError('read', tableName);
}
// Apply security filters to where clause
const securityWhere = this.applySecurityToWhere(where || {}, table, context);
return await this.db.count(tableName, securityWhere);
};
}
/**
* Create CREATE operation
*/
createCreateOperation(tableName, table) {
return async (data, context) => {
// Validate data against schema
this.validateData(data, table.schema);
// Security check
const securityContext = {
...context,
operation: 'create',
tableName,
data
};
const hasAccess = await SecurityEnforcer.checkAccess(table.security, securityContext);
if (!hasAccess) {
throw SecurityEnforcer.createUnauthorizedError('create', tableName);
}
return await this.db.create(tableName, data);
};
}
/**
* Create UPDATE operation
*/
createUpdateOperation(tableName, table) {
return async (id, data, context) => {
// Validate partial data against schema
this.validateData(data, table.schema, true);
// First fetch the existing record
const existing = await this.db.query(tableName, { where: { id }, limit: 1 });
if (existing.length === 0) {
throw new Error(`Record with id ${id} not found in table '${tableName}'`);
}
// Security check with existing data
const securityContext = {
...context,
operation: 'update',
tableName,
data,
existingData: existing[0]
};
const hasAccess = await SecurityEnforcer.checkAccess(table.security, securityContext);
if (!hasAccess) {
throw SecurityEnforcer.createUnauthorizedError('update', tableName);
}
return await this.db.update(tableName, id, data);
};
}
/**
* Create DELETE operation
*/
createDeleteOperation(tableName, table) {
return async (id, context) => {
// First fetch the existing record
const existing = await this.db.query(tableName, { where: { id }, limit: 1 });
if (existing.length === 0) {
throw new Error(`Record with id ${id} not found in table '${tableName}'`);
}
// Security check with existing data
const securityContext = {
...context,
operation: 'delete',
tableName,
existingData: existing[0]
};
const hasAccess = await SecurityEnforcer.checkAccess(table.security, securityContext);
if (!hasAccess) {
throw SecurityEnforcer.createUnauthorizedError('delete', tableName);
}
return await this.db.delete(tableName, id);
};
}
/**
* Apply security filters to query parameters
*/
applySecurityFilters(params, table, context) {
// For owner-based security, automatically filter by owner field
if (table.security.type === 'owner' && context.user) {
const ownerField = table.security.ownerField || 'userId';
const securityWhere = { [ownerField]: context.user.id };
return {
...params,
where: { ...params.where, ...securityWhere }
};
}
return params;
}
/**
* Apply security filters to where clause
*/
applySecurityToWhere(where, table, context) {
// For owner-based security, automatically filter by owner field
if (table.security.type === 'owner' && context.user) {
const ownerField = table.security.ownerField || 'userId';
return { ...where, [ownerField]: context.user.id };
}
return where;
}
/**
* Validate data against table schema
*/
validateData(data, schema, partial = false) {
for (const [field, type] of Object.entries(schema)) {
const value = data[field];
// Skip validation for partial updates if field is not provided
if (partial && value === undefined)
continue;
// Check required fields for non-partial operations
if (!partial && value === undefined) {
throw new Error(`Missing required field: ${field}`);
}
// Type validation
if (value !== undefined && value !== null) {
const isValid = this.validateFieldType(value, type);
if (!isValid) {
throw new Error(`Invalid type for field '${field}'. Expected ${type}, got ${typeof value}`);
}
}
}
}
/**
* Validate individual field type
*/
validateFieldType(value, expectedType) {
switch (expectedType) {
case 'string':
return typeof value === 'string';
case 'number':
return typeof value === 'number' && !isNaN(value);
case 'boolean':
return typeof value === 'boolean';
case 'date':
return value instanceof Date || !isNaN(Date.parse(value));
case 'json':
return true; // Any value can be JSON
default:
return true;
}
}
}
//# sourceMappingURL=generator.js.map