npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@idrall/crud-core

v1.1.4

Published

Generic CRUD core functionality for iDrall ERP microservices

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.