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/paginationidrall-crm.customer.get
- Get a single customer by IDidrall-crm.customer.create
- Create a new customeridrall-crm.customer.update
- Update an existing customeridrall-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
- Equalne
- Not equalgt
- Greater thanlt
- Less thange
- Greater than or equalle
- Less than or equalcontains
- 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
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - 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.