JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 11
  • Score
    100M100P100Q30701F
  • License MIT

Generic CRUD core functionality for iDrall ERP microservices

Package Exports

  • @idrall/crud-core
  • @idrall/crud-core/dist/index.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@idrall/crud-core) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

@idrall/crud-core

Generic CRUD core functionality for iDrall ERP microservices built with NestJS, Kysely, and PostgreSQL.

Features

  • 🚀 Generic CRUD Operations: List, Get, Create, Update, Delete operations with filtering, sorting, and pagination
  • 🔧 Configurable: Per-entity configuration with table mapping and field exclusion
  • 🎣 Lifecycle Hooks: Pre/post hooks for create, update, and delete operations
  • 🔍 Advanced Filtering: Enhanced query filter system with validation and sanitization
  • 📨 Message Pattern Support: Built-in NestJS microservice message pattern handling
  • 🛡️ Type Safe: Full TypeScript support with comprehensive type definitions
  • 🏗️ Modular Architecture: Dynamic module system with configuration support

Installation

npm install @idrall/crud-core

Peer Dependencies

Make sure you have the following peer dependencies installed:

npm install @nestjs/common @nestjs/core @nestjs/microservices kysely reflect-metadata rxjs

Quick Start

1. Configure the Module

import { Module } from "@nestjs/common";
import { DynamicCrudModule, CrudConfig } from "@idrall/crud-core";
import { KyselyModule } from "./kysely/kysely.module";

const crudConfig: CrudConfig = {
  serviceName: "idrall-crm",
  entities: ["customer", "contact", "opportunity", "lead", "campaign"],
  defaultTableMapping: {
    opportunity: "crm_opportunity",
  },
  entityConfigs: {
    contact: {
      excludeFields: ["password_hash"],
      hooks: {
        preCreate: async (entity, data, user) => {
          // Email validation hook
          if (
            data.email &&
            !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email as string)
          ) {
            throw new Error("Invalid email format");
          }
          return data;
        },
      },
    },
  },
};

@Module({
  imports: [KyselyModule, DynamicCrudModule.forRoot(crudConfig)],
})
export class AppModule {}

2. Provide KyselyService

Create a provider for the KyselyService:

import { Module } from "@nestjs/common";
import { KyselyService } from "./kysely.service";

@Module({
  providers: [
    KyselyService,
    {
      provide: "KYSELY_SERVICE",
      useExisting: KyselyService,
    },
  ],
  exports: ["KYSELY_SERVICE"],
})
export class KyselyModule {}

3. Use Message Patterns

Your microservice will automatically handle these message patterns:

  • idrall-crm.customer.list - List customers with filtering/pagination
  • idrall-crm.customer.get - Get a single customer by ID
  • idrall-crm.customer.create - Create a new customer
  • idrall-crm.customer.update - Update an existing customer
  • idrall-crm.customer.delete - Delete a customer

Configuration

CrudConfig

interface CrudConfig {
  serviceName: string; // Microservice name
  entities: string[]; // Supported entities
  entityConfigs?: Record<string, EntityConfig>; // Per-entity config
  defaultTableMapping?: Record<string, string>; // Entity to table mapping
  globalHooks?: {
    // Global lifecycle hooks
    preCreate?: PreCreateHook;
    preUpdate?: PreUpdateHook;
    postCreate?: PostCreateHook;
    postUpdate?: PostUpdateHook;
    preDelete?: PreDeleteHook;
    postDelete?: PostDeleteHook;
  };
}

EntityConfig

interface EntityConfig {
  tableName?: string; // Override table name
  excludeFields?: string[]; // Fields to exclude from responses
  hooks?: {
    // Entity-specific hooks
    preCreate?: PreCreateHook;
    preUpdate?: PreUpdateHook;
    postCreate?: PostCreateHook;
    postUpdate?: PostUpdateHook;
    preDelete?: PreDeleteHook;
    postDelete?: PostDeleteHook;
  };
}

Lifecycle Hooks

Hooks allow you to customize behavior at different points in the CRUD lifecycle:

const config: CrudConfig = {
  serviceName: "my-service",
  entities: ["user"],
  entityConfigs: {
    user: {
      hooks: {
        preCreate: async (entity, data, user) => {
          // Hash password before creating user
          if (data.password) {
            data.password_hash = await hashPassword(data.password);
            delete data.password;
          }
          return data;
        },

        preUpdate: async (entity, id, data, user) => {
          // Validate email format
          if (
            data.email &&
            !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email as string)
          ) {
            throw new ValidationError("Invalid email format");
          }
          return data;
        },

        postCreate: async (entity, result, user) => {
          // Send welcome email after user creation
          await sendWelcomeEmail(result.email);
          return result;
        },
      },
    },
  },
};

Query Filtering

The package includes advanced filtering capabilities:

import { QueryFilter } from "@idrall/crud-core";

const filters: QueryFilter[] = [
  { field: "name", operator: "contains", value: "John" },
  { field: "age", operator: "ge", value: 18 },
  { field: "status", operator: "in", value: ["active", "pending"] },
];

Supported Operators

  • eq - Equal
  • ne - Not equal
  • gt - Greater than
  • lt - Less than
  • ge - Greater than or equal
  • le - Less than or equal
  • contains - String contains (case-insensitive)
  • startswith - String starts with (case-insensitive)
  • endswith - String ends with (case-insensitive)
  • in - Value in array

Usage from Gateway

Send requests to your CRM microservice from the gateway:

import { Injectable, Inject } from "@nestjs/common";
import { ClientProxy } from "@nestjs/microservices";
import { lastValueFrom, timeout } from "rxjs";

@Injectable()
export class CustomerGatewayService {
  constructor(@Inject("NATS_SERVICE") private client: ClientProxy) {}

  async listCustomers(filters: any[] = [], pagination?: any) {
    const payload = {
      entity: "customer",
      filters,
      sort: [{ field: "created_at", order: "desc" }],
      pagination: pagination || { skip: 0, limit: 20 },
    };

    return lastValueFrom(
      this.client.send("idrall-crm.customer.list", payload).pipe(timeout(10000))
    );
  }

  async createCustomer(data: any) {
    const payload = {
      entity: "customer",
      data,
    };

    return lastValueFrom(
      this.client
        .send("idrall-crm.customer.create", payload)
        .pipe(timeout(10000))
    );
  }
}

Advanced Usage

Async Configuration

DynamicCrudModule.forRootAsync({
  imports: [ConfigModule],
  inject: [ConfigService],
  useFactory: async (configService: ConfigService): Promise<CrudConfig> => {
    return {
      serviceName: configService.get("SERVICE_NAME"),
      entities: configService.get("SUPPORTED_ENTITIES").split(","),
      // ... other config
    };
  },
});

Multiple Feature Modules

@Module({
  imports: [
    DynamicCrudModule.forFeature({
      serviceName: "crm-contacts",
      entities: ["contact", "address"],
    }),
    DynamicCrudModule.forFeature({
      serviceName: "crm-sales",
      entities: ["opportunity", "quote"],
    }),
  ],
})
export class CrmModule {}

Error Handling

The package includes custom error types:

import {
  CrudError,
  ValidationError,
  EntityNotFoundError,
  UnauthorizedError,
} from "@idrall/crud-core";

// These errors are automatically handled by the controller
// and converted to appropriate response formats

Docker & AWS Fargate Optimization

The package is optimized for containerized deployments:

  • Minimal dependencies to reduce image size
  • Efficient connection pooling for PostgreSQL
  • Proper error handling for distributed systems
  • Health check endpoints included

API Reference

DynamicCrudService

  • list<T>(query: DynamicQuery): Promise<ListResponse<T>>
  • getOne<T>(entity: string, id: string | number): Promise<T | null>
  • create<T>(entity: string, data: Record<string, unknown>, user?: any): Promise<T>
  • update<T>(entity: string, id: string | number, data: Record<string, unknown>, user?: any): Promise<T>
  • delete<T>(entity: string, id: string | number, user?: any): Promise<T>

DynamicCrudController

Automatically handles message patterns:

  • *.*.list - List entities
  • *.*.get - Get single entity
  • *.*.create - Create entity
  • *.*.update - Update entity
  • *.*.delete - Delete entity
  • *.*.bulk-create - Bulk create
  • *.*.bulk-update - Bulk update
  • *.*.bulk-delete - Bulk delete
  • *.*.count - Count entities
  • *.health - Health check

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Support

For support, please contact the Idrall Development Team or create an issue in the repository.

Entity Registry Integration (NEW)

You can now pass your entity registry metadata/config as a third argument to DynamicCrudModule.forRoot. This allows you to centralize and share your entity definitions across your microservices, and makes the metadata available as a provider (ENTITY_REGISTRY_CONFIG).

Example Usage

import { DynamicCrudModule, CrudConfig } from "@idrall/crud-core";
import { KyselyModule } from "./kysely/kysely.module";
import { entityRegistryConfig } from "./config/entity-registry";

const crudConfig: CrudConfig = {
  serviceName: "idrall-crm",
  entities: [
    "agent",
    "sales_team",
    // ...
  ],
  // ...
};

@Module({
  imports: [
    DynamicCrudModule.forRoot(crudConfig, KyselyModule, entityRegistryConfig),
    // ...
  ],
})
export class AppModule {}

How to Inject the Metadata

You can inject the entity registry config anywhere in your app:

import { Inject, Injectable } from "@nestjs/common";

@Injectable()
export class MyService {
  constructor(
    @Inject("ENTITY_REGISTRY_CONFIG") private readonly entityRegistry: any
  ) {}

  getEntityList() {
    return this.entityRegistry.entities;
  }
}

Difference between crudConfig and entityRegistryConfig

  • crudConfig: This is the configuration object used by the CRUD engine itself. It tells the CRUD module which entities to expose, how to map them to tables, what hooks to use, and what fields to exclude. It is focused on the runtime CRUD logic and how your API behaves.

  • entityRegistryConfig: This is a metadata object that describes your entities in a more detailed and structured way. It is typically used for sharing entity definitions across services, for dynamic API generation, validation, documentation, and for registering your entities in a central registry microservice. It usually contains field types, relationships, permissions, and more. It is not required for CRUD to work, but is very useful for advanced scenarios and for interoperability between services.

In summary:

  • Use crudConfig to control CRUD behavior and mapping.
  • Use entityRegistryConfig to describe your entities for registry, validation, and cross-service metadata sharing.