CRUD Engine

The CRUD Engine is the core component of the @repo/crud package, providing enhanced CRUD operations with support for plugins, permissions, and auditing. It wraps a data provider from the @repo/data package and adds additional functionality.

Features

  • Enhanced CRUD Operations: Extends the basic CRUD operations with additional features
  • Plugin System: Support for plugins that can modify behavior before and after operations
  • Permissions Integration: Built-in support for access control
  • Audit Logging: Automatic tracking of data changes
  • Internationalization: Support for multiple languages
  • Error Handling: Consistent error handling across operations

Basic Usage

Creating a CRUD Engine

import { engine } from '@repo/crud';
import { providers } from '@repo/data';

// Create a data provider
const dataProvider = providers.supabase.createProvider({
  url: process.env.SUPABASE_URL,
  key: process.env.SUPABASE_KEY
});

// Create a CRUD engine
const crudEngine = engine.createCrudEngine({
  dataProvider,
  enableAudit: true,
  enablePermissions: true,
  plugins: [],
  defaultLocale: 'en',
  supportedLocales: ['en', 'fr', 'de', 'es', 'tr']
});

Using the CRUD Engine

// Get a list of resources
const { data, total } = await crudEngine.getList({
  resource: 'users',
  pagination: { page: 1, perPage: 10 },
  sort: { field: 'createdAt', order: 'desc' },
  filter: { role: 'admin' }
});

// Get a single resource
const { data: user } = await crudEngine.getOne({
  resource: 'users',
  id: '123'
});

// Create a resource
const { data: newUser } = await crudEngine.create({
  resource: 'users',
  data: { name: 'John Doe', email: 'john@example.com' }
});

// Update a resource
const { data: updatedUser } = await crudEngine.update({
  resource: 'users',
  id: '123',
  data: { name: 'John Smith' }
});

// Delete a resource
const { data: deletedUser } = await crudEngine.delete({
  resource: 'users',
  id: '123'
});

Configuration Options

The CRUD Engine accepts the following configuration options:

interface CrudEngineOptions {
  /** The data provider to use */
  dataProvider: CrudProvider;
  /** Enable auditing of operations */
  enableAudit?: boolean;
  /** Audit provider for custom audit logging */
  auditProvider?: AuditProvider;
  /** Enable permissions checking */
  enablePermissions?: boolean;
  /** Permissions provider for custom permission checks */
  permissionsProvider?: PermissionsProvider;
  /** Plugins to use */
  plugins?: CrudPlugin[];
  /** Default locale for internationalization */
  defaultLocale?: string;
  /** Supported locales for internationalization */
  supportedLocales?: string[];
  /** Error handler for custom error handling */
  errorHandler?: ErrorHandler;
}

Plugin System

The CRUD Engine includes a powerful plugin system that allows you to extend its functionality. Plugins can modify the behavior of CRUD operations by intercepting them before and after they are executed.

Plugin Interface

interface CrudPlugin {
  /** Unique name of the plugin */
  name: string;
  /** Plugin initialization function */
  initialize: (engine: CrudEngine) => void;
  /** Hooks for different CRUD operations */
  hooks?: {
    beforeGetList?: (params: GetListParams) => GetListParams;
    afterGetList?: (result: any, params: GetListParams) => any;
    beforeGetOne?: (params: GetOneParams) => GetOneParams;
    afterGetOne?: (result: any, params: GetOneParams) => any;
    beforeCreate?: (params: CreateParams) => CreateParams;
    afterCreate?: (result: any, params: CreateParams) => any;
    beforeUpdate?: (params: UpdateParams) => UpdateParams;
    afterUpdate?: (result: any, params: UpdateParams) => any;
    beforeDelete?: (params: DeleteParams) => DeleteParams;
    afterDelete?: (result: any, params: DeleteParams) => any;
  };
}

Creating a Plugin

import { engine } from '@repo/crud';

// Create a logging plugin
const loggingPlugin: engine.CrudPlugin = {
  name: 'logging',
  initialize: (engine) => {
    console.log('Logging plugin initialized');
  },
  hooks: {
    beforeGetList: (params) => {
      console.log('Getting list', params);
      return params;
    },
    afterGetList: (result, params) => {
      console.log('Got list', result);
      return result;
    },
    beforeCreate: (params) => {
      console.log('Creating resource', params);
      return params;
    },
    afterCreate: (result, params) => {
      console.log('Created resource', result);
      return result;
    }
    // Add more hooks as needed
  }
};

// Use the plugin
const crudEngine = engine.createCrudEngine({
  dataProvider,
  plugins: [loggingPlugin]
});

Plugin Execution Order

Plugins are executed in the order they are provided in the plugins array. For example, if you have three plugins:

const crudEngine = engine.createCrudEngine({
  dataProvider,
  plugins: [pluginA, pluginB, pluginC]
});

The execution order for a getList operation would be:

  1. pluginA.hooks.beforeGetList
  2. pluginB.hooks.beforeGetList
  3. pluginC.hooks.beforeGetList
  4. Actual getList operation
  5. pluginC.hooks.afterGetList
  6. pluginB.hooks.afterGetList
  7. pluginA.hooks.afterGetList

Note that the “after” hooks are executed in reverse order, allowing each plugin to process the result in the reverse order of the “before” hooks.

Permissions Integration

The CRUD Engine includes built-in support for permissions checking. When enabled, it will check if the current user has permission to perform the requested operation.

Enabling Permissions

import { engine, permissions } from '@repo/crud';

// Create a permissions provider
const permissionsProvider = permissions.createPermissionsProvider({
  getPermissions: async (user) => {
    return {
      users: {
        read: true,
        create: user.role === 'admin',
        update: (user, resource) => user.id === resource.id || user.role === 'admin',
        delete: user.role === 'admin'
      }
    };
  }
});

// Create a CRUD engine with permissions
const crudEngine = engine.createCrudEngine({
  dataProvider,
  enablePermissions: true,
  permissionsProvider
});

Permissions Context

When using permissions, you need to set the current user in the permissions context:

import { permissions } from '@repo/crud';

// Set the current user
permissions.setUser({
  id: '123',
  role: 'admin'
});

// Now CRUD operations will be checked against this user
const result = await crudEngine.getList({
  resource: 'users',
  pagination: { page: 1, perPage: 10 }
});

Audit Logging

The CRUD Engine includes built-in support for audit logging. When enabled, it will log all changes to data.

Enabling Audit Logging

import { engine, audit } from '@repo/crud';

// Create an audit provider
const auditProvider = audit.createAuditProvider({
  logAudit: async (auditLog) => {
    // Store audit log in database
    await db.auditLogs.create(auditLog);
  }
});

// Create a CRUD engine with audit logging
const crudEngine = engine.createCrudEngine({
  dataProvider,
  enableAudit: true,
  auditProvider
});

Audit Context

When using audit logging, you need to set the current user in the audit context:

import { audit } from '@repo/crud';

// Set the current user
audit.setUser({
  id: '123',
  name: 'John Doe'
});

// Now CRUD operations will include this user in audit logs
const result = await crudEngine.create({
  resource: 'posts',
  data: { title: 'New Post', content: 'Hello World' }
});

Internationalization

The CRUD Engine includes built-in support for internationalization. You can specify the default locale and supported locales when creating the engine.

Configuring Internationalization

import { engine } from '@repo/crud';

// Create a CRUD engine with internationalization
const crudEngine = engine.createCrudEngine({
  dataProvider,
  defaultLocale: 'en',
  supportedLocales: ['en', 'fr', 'de', 'es', 'tr']
});

Using Translations

The CRUD Engine uses the next-intl package for translations. You can use the useTranslation hook to get translations:

import { useTranslation } from 'next-intl';

function MyComponent() {
  const { t } = useTranslation();
  
  return (
    <div>
      <h1>{t('users.title')}</h1>
      <p>{t('users.description')}</p>
    </div>
  );
}

Error Handling

The CRUD Engine includes built-in error handling. You can provide a custom error handler when creating the engine.

Custom Error Handler

import { engine } from '@repo/crud';

// Create a custom error handler
const errorHandler: engine.ErrorHandler = (error, operation, params) => {
  console.error(`Error in ${operation} operation:`, error);
  console.error('Params:', params);
  
  // You can transform the error
  if (error.code === 'PERMISSION_DENIED') {
    return new Error('You do not have permission to perform this operation');
  }
  
  return error;
};

// Create a CRUD engine with custom error handling
const crudEngine = engine.createCrudEngine({
  dataProvider,
  errorHandler
});

Advanced Usage

Creating a CRUD Engine with a Provider Type

You can create a CRUD Engine with a specific provider type:

import { engine } from '@repo/crud';

// Create a CRUD engine with a Supabase provider
const crudEngine = engine.createCrudEngineWithProvider('supabase', {
  url: process.env.SUPABASE_URL,
  key: process.env.SUPABASE_KEY
}, {
  enableAudit: true,
  enablePermissions: true
});

Extending the CRUD Engine

You can extend the CRUD Engine with custom methods:

import { engine } from '@repo/crud';

// Create a custom CRUD engine class
class CustomCrudEngine extends engine.CrudEngine {
  constructor(options) {
    super(options);
  }
  
  // Add custom methods
  async getByEmail(resource, email) {
    const { data, total } = await this.getList({
      resource,
      filter: { email }
    });
    
    return data[0] || null;
  }
  
  async bulkCreate(resource, items) {
    const results = [];
    
    for (const item of items) {
      const result = await this.create({
        resource,
        data: item
      });
      
      results.push(result.data);
    }
    
    return results;
  }
}

// Create a custom CRUD engine
const customEngine = new CustomCrudEngine({
  dataProvider,
  enableAudit: true,
  enablePermissions: true
});

// Use custom methods
const user = await customEngine.getByEmail('users', 'john@example.com');
const posts = await customEngine.bulkCreate('posts', [
  { title: 'Post 1', content: 'Content 1' },
  { title: 'Post 2', content: 'Content 2' }
]);

API Reference

CrudEngine Class

class CrudEngine {
  constructor(options: CrudEngineOptions);
  
  // Get the data provider
  getDataProvider(): CrudProvider;
  
  // CRUD operations
  getList(params: GetListParams): Promise<GetListResult>;
  getOne(params: GetOneParams): Promise<GetOneResult>;
  create(params: CreateParams): Promise<CreateResult>;
  update(params: UpdateParams): Promise<UpdateResult>;
  delete(params: DeleteParams): Promise<DeleteResult>;
}

Factory Functions

// Create a CRUD engine
function createCrudEngine(options: CrudEngineOptions): CrudEngine;

// Create a CRUD engine with a provider type
function createCrudEngineWithProvider(
  providerType: string,
  providerConfig: any,
  engineOptions?: Partial<CrudEngineOptions>
): CrudEngine;

For a complete API reference, please refer to the TypeScript definitions in the package.