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 🙏

© 2024 – Pkg Stats / Ryan Hefner

crud-node

v1.2.4

Published

Agnostic nodejs client that simplifies crud operations to a database

Downloads

23

Readme

crud-node

  

crud-node is an agnostic nodejs client that simplifies crud operations to a database. The package is written in javaScript, and supports typeScript bindings.

🤡 Goal

The goal of crud-node is to offer a consistent way of executing CRUD (Create, Read, Update, and Delete) operations across numerous databases, including MySQL, MySQLX, Mongo, Cassandra, and more.

⚡️ Installation

Install crud-node package running the following command:

npm install crud-node

OR

yarn add crud-node

👀 Features

  • CRUD
  • Sorting
  • Filtering
  • Grouping
  • Pagination

📃 Available methods

  • init()
  • toString()
  • createDocument()
  • createDocumentIfNotExists() 🆕
  • updateDocument()
  • deleteDocument()
  • getDocument()
  • getDocuments()
  • getDocumentByCriteria() 🆕
  • searchDocumentsByCriteria() 🆕
  • searchDocuments() 🆕
  • groupByDocuments() 🆕
  • filterDocumentsByCriteria() 🆕
  • filterDocuments() 🆕
  • filterDocumentsByIds() 🆕
  • existsDocument()
  • findDocument() 🆕
  • fetchAll() 🆕
  • getCount() 🆕
  • getTotal()
  • deleteAll()
  • callStoredProcedure() 🆕

❗ Schemas

To ensure consistency of implementation across multiple databases we use json schema to valiate data types.

💨 Examples

In this examples we will use MySQLX controller to show how the package works and what benefits it brings. For MySQL check the examples directory.

Connection config example for MySQL Document Store

A connection with a MySQL server can be established by creating an instance of MySQLX. The connection will be established via call connect. Check also examples directory.

// config.{ts|js}
import { MySQLX } from 'crud-node';

// Connection configuration object
export const connection = {
  host: 'localhost',
  port: 33060,
  schema: 'db',
  password: 'user',
  user: 'user',
};

// Automated connection pool
export const pooling = {
  enabled: true,
  maxSize: 25,
  maxIdleTime: 0,
  queueTimeout: 0,
};

export const settings = {
  ciCollation: 'utf8mb4_0900_ai_ci',
};

export const db = new MySQLX(connection, { pooling }, settings);
await db.connect();

Define schema

You have to define a schema like in the example bellow for you data that you want to insert in the database.

The package use schema approach to help user understand what data will insert in database. Doesn't matter if we speak about MySQL adapter or MySQLX adapter you have to define a schema. Each adapter has is own schema definition. Check examples for a better understanding.

// employeeSchema.{ts|js}
import { IDocument, IDocumentSchema, IDocumentValidation, generateId, getDocument } from 'crud-node';

export enum EmployeeProps {
  _id = '_id',
  createdAt = 'createdAt',
  email = 'email',
  lastName = 'lastName',
  firstName = 'firstName',
  responsibilities = 'responsibilities',
  officeId = 'officeId',
  fired = 'fired',
}

export const validation: IDocumentValidation<EmployeeProps> = {
  level: 'strict',
  schema: {
    type: 'object',
    description: 'Employee',
    properties: {
      _id: { type: 'string' },
      createdAt: { type: 'string', description: 'Timestamp when the record was created' },
      email: {
        type: 'string',
        description: 'The email of an employee, used as unique identifier for account registration',
      },
      lastName: { type: 'string', description: 'Last name of an employee' },
      firstName: { type: 'string', description: 'First name of an employee' },
      responsibilities: {
        type: 'array',
        items: { type: 'string' },
        uniqueItems: true,
        description: 'The responsibilities of an employee',
      },
      officeId: { type: 'string', description: 'The id of office, employee works at' },
      fired: { type: 'boolean', description: '' },
    },
    required: [EmployeeProps._id, EmployeeProps.email],
  },
};

export const employeeSchema: IDocumentSchema<EmployeeProps> = {
  name: 'employee',
  alias: 'emp',
  validation,
  generatedId: false,
  unique: [[EmployeeProps.email]],
  getDocument: (data: Partial<IDocument<EmployeeProps>>): IDocument<EmployeeProps> => {
    const createdAt = Date.now().toString();
    const defaults: Partial<IDocument<EmployeeProps>> = {
      _id: generateId(employeeSchema.alias),
      createdAt,
    };
    return getDocument(EmployeeProps, data, defaults);
  },
  toString: (data: IDocument<EmployeeProps>) => {
    return `${data.firstName} ${data.lastName}`;
  },
};

Create schema on the fly

A schema in a database can be created by using .init() function of a controller. If a schema already exists, it will not be recreated!

This method is available only for MySQL X Protocol (Document Store)

await db.usingSession(async session => {
  await employeeController.init(session);
});

Access schema

For a clean architecture, you can create a controller responsible for accessing the desired schema (table) or simply you can use it inside a route.

// employeeRouter.{ts|js}
import { MySQLX } from 'crud-node';
import { employeeSchema } from './schemas/employee';

...

const db = new MySQLX(connection, pooling, settings);
db.connect().then(() => {
  const employeeController = new CRUDMySQLX(db, employeeSchema);
});

Use the power of JavaScript inheritance and extend CRUD Controller with custom logic:

// employeeController.{ts|js}
import { CRUDMySQLX, IAppWithDatabase, MySQLX } from 'crud-node';

import { EmployeeProps, employeeSchema } from './schemas/employee';

export class EmployeeController extends CRUDMySQLX<EmployeeProps> {
  constructor(app: IAppWithDatabase<MySQLX>) {
    super(app.db, employeeSchema);
  }
}

// This can be placed in a middleware where will leave all the controllers or can be called inside a route where you have access to app object.
export const employeeController = new EmployeeController(app);

Create record with transaction

// employeeRouter.{ts|js}
import { employeeController } from './employeeController';

// Executes operations in a single transaction
const transacted = true;

await db.usingSession(async session => {
  const payload = {
    email: '[email protected]',
    firstName: 'Leslie',
    lastName: 'Brett',
  };
  const data = await employeeController.createDocument(session, payload);
}, transacted);

Create record, if not exists

// employeeRouter.{ts|js}
import { employeeController } from './employeeController';

const payload = {
  email: '[email protected]',
  firstName: 'Leslie',
  lastName: 'Brett',
};
const data = await employeeController.createDocumentIfNotExists(session, payload);

Update record

// employeeRouter.{ts|js}
import { employeeController } from './employeeController';

const employeeId = '<_id>';
const payload = {
  email: '[email protected]',
  firstName: 'Leslie',
  lastName: 'Brett',
};

const data = await employeeController.updateDocument(session, employeeId, payload);

Delete record

// employeeRouter.{ts|js}
import { employeeController } from './employeeController';

const employeeId = '<_id>';

const data = await employeeController.deleteDocument(session, employeeId, payload);

Delete all records

// employeeRouter.{ts|js}
! WARNING This deletes all rows from a table

import { employeeController } from './employeeController';

await employeeController.deleteAll(session);

Retrieve record

// employeeRouter.{ts|js}
import { employeeController } from './employeeController';

const employeeId = '<_id>';

const data = await employeeController.getDocument(session, employeeId);

List records

// officeRouter.{ts|js}
import { OffsetPagination, SortBy } from 'crud-node';

import { officeController } from './officeController';
import { OfficeProps } from './schemas/office';

const pagination = OffsetPagination(1, 10);
const sort = SortBy().asc(OfficeProps.places).toCriteria();

const data = await officeController.getDocuments(session, pagination, sort);

Retrieve record by criteria

// employeeRouter.{ts|js}
import { employeeController } from './employeeController';
import { EmployeeProps } from './schemas/employee';

const officeId = '<_id>';

const data = await employeeController.getDocumentByCriteria(session, { [EmployeeProps.officeId]: officeId });

Search records by criteria (Case-insensitive)

// officeRouter.{ts|js}
import { officeController } from './officeController';
import { OfficeProps } from './schemas/office';

const data = await officeController.searchDocumentsByCriteria(
  session,
  `${officeController.getSearchCriteria(OfficeProps.name, 'keyword1')}
      OR ${officeController.getSearchCriteria(OfficeProps.name, 'keyword2')}
      OR ${officeController.getSearchCriteria(OfficeProps.name, 'keyword3')}`,
  {
    keyword1: '%coworking%',
    keyword2: '%flexible workspace%',
    keyword3: '%serviced office space%',
  },
);

Search records (Case-insensitive)

// officeRouter.{ts|js}
import { officeController } from './officeController';

const data = await officeController.searchDocuments(
  session,
  {
    name: '%coworking%',
    officeCode: '%coworking%',
  },
  'OR',
);

Filter records by criteria

// officeRouter.{ts|js}
import { Condition, Filter, OffsetPagination, SortBy } from 'crud-node';

import { officeController } from './officeController';
import { OfficeProps } from './schemas/office';

const filterOfficesInNYC = Filter.toCriteria(
  Filter.and(Condition.like('address.city', '%New York%'), Condition.gre(OfficeProps.places, 1)),
);
const sortOfficesByAvailablePlaces = SortBy().asc(OfficeProps.places).toCriteria();
const pagination = OffsetPagination(1, 10);

const data = await officeController.filterDocumentsByCriteria(
  session,
  filterOfficesInNYC,
  pagination,
  sortOfficesByAvailablePlaces,
);

Group records

// employeeRouter.{ts|js}
import { GroupBy } from 'crud-node';

import { employeeController } from './employeeController';
import { EmployeeProps } from './schemas/employee';

const data = await employeeController.groupByDocuments<'fired' | EmployeeProps.createdAt>(
  session,
  GroupBy<EmployeeProps, 'fired' | EmployeeProps.createdAt>()
    .fields(EmployeeProps.createdAt)
    .aggregate(EmployeeProps._id, 'fired', AGG.COUNT)
    .toCriteria(),
);

Filter records

// employeeRouter.{ts|js}
import { OffsetPagination } from 'crud-node';
import { employeeController } from './employeeController';

const pagination = OffsetPagination(1, 10);

const data = await employeeController.filterDocuments(session, { fired: true }, 'AND', pagination);

Filter records by ids

// officeRouter.{ts|js}
import { officeController } from './officeController';
import { OfficeProps } from './schemas/office';

const officeIds = ['<id1>', '<id2>'];
const pagination = OffsetPagination(1, 10);
const sort = SortBy().asc(OfficeProps.places).toCriteria();
const data = await officeController.filterDocumentsByIds(session, officeIds, pagination, sort);

Retrieve all records

// employeeRouter.{ts|js}
import { employeeController } from './employeeController';

const data = await employeeController.fetchAll(session);

Find record

// employeeRouter.{ts|js}
import { employeeController } from './employeeController';

const employeeId = '<_id>';

const data = await employeeController.findDocument(session, { employeeId });

Exists record

// employeeRouter.{ts|js}
import { employeeController } from './employeeController';

const employeeId = '<_id>';

const data = await employeeController.existsDocument(session, { employeeId });

Count records by filter

// employeeRouter.{ts|js}
import { EmployeeProps } from './schemas/employee';

const officeId = '<_id>';

const employeesByOffice = await this.employeeController.getCount(session, {
  [EmployeeProps.officeId]: officeId,
});

Retrieve total records

// employeeRouter.{ts|js}
import { employeeController } from './employeeController';

const data = await employeeController.getTotal(session);

Call store procedure

// employeeRouter.{ts|js}
import { employeeController } from './employeeController';

const data = await employeeController.callStoredProcedure(session, '<sp_name>', ['<parameter>']);

Filters

Filter Operations

  • eq Equal
  • in In
  • gr Greater
  • gre Greater or Equal
  • like Like
  • ls Less
  • lse Less or Equal
  • noteq Not Equal
  • empty Empty

Reference

  • /crud-node/lib/filter/Filter.ts (ln. 9)
  • /crud-node/lib/filter/FilterBy.ts (ln. 63)

Sort

Sorting options

  • asc Ascending
  • desc Descending

Reference

  • /crud-node/lib/filter/Sort.ts (ln. 31)

Pagination

Pagination methods

  • OffsetPagination (/crud-node/lib/pagination/OffsetPagination.ts (ln. 10))
  • calculateLimit (/crud-node/lib/pagination/OffsetPagination.ts (ln. 25))
  • calculateTotalPages (/crud-node/lib/pagination/OffsetPagination.ts (ln. 44))
  • resultSet (/crud-node/lib/pagination/OffsetPagination.ts (ln. 54))
  • limitOffset (/crud-node/lib/pagination/OffsetPagination.ts (ln. 71))

Errors

| Code | Name | Description | | -------- | -------------------------- | ------------------------------------- | | ERRDB001 | forbidden | Forbidden | | ERRDB002 | notFound | Not found | | ERRDB003 | internalServerError | Sorry, something went wrong | | ERRDB004 | notImplemented | Not impemented | | ERRDB005 | errorConnectionNotOpen | Database connection is not opened | | ERRDB006 | errorConnectionAlreadyOpen | Database connection is already opened | | ERRDB007 | errorDuplicatedDocument | Duplicated document | | ERRDB008 | errorNothingWasDeleted | Nothing was deleted | | ERRDB009 | errorNoIdProvided | Cannot get document without [id] | | ERRDB010 | errorNoCriteriaProvided | Cannot get document without criteria | | ERRDB011 | errorDocumentNotFound | Document not found | | ERRDB012 | errorDbInstruction | Fail to receive data | | ERRDB013 | unsupportedFilterOperation | Unsupported filter operation | | ERRDB014 | duplicatedSortingCondition | Duplicated sorting condition | | ERRDB015 | dbAnyError | Something went wrong! | | | | |

🔨 Issues

If you identify any errors in this module, or have an idea for an improvement, please open an issue. We're excited to see what the community thinks of this project, and we would love your input!

📖 API Documentation

In addition to the above getting-started guide, we have API documentation.

👉🏻 Contributing

We welcome contributions large and small.

👽 Supported databases

  • MySQL
  • MySQL Document Store
  • Percona MySQL
  • Percona MySQL Document Store

🔜 Roadmap

  • MongoDB October 2023
  • PostgreSQL October 2023
  • Cassandra November 2023
  • OracleDB November 2023
  • SQLite December 2023
  • CouchDB December 2023

📝 Notes

No notes!

🔝 Used in production by

Delimia - On-demand courier delivery service

⚠️ License

MIT