
Research
/Security News
Popular Tinycolor npm Package Compromised in Supply Chain Attack Affecting 40+ Packages
Malicious update to @ctrl/tinycolor on npm is part of a supply-chain attack hitting 40+ packages across maintainers
@decaf-ts/db-decorators
Advanced tools
The db-decorators library provides a comprehensive set of TypeScript decorators and utilities for database operations. It implements the repository pattern with support for model definition, validation, identity management, and operation hooks. The library enables developers to define database models with decorators, perform CRUD operations with validation, and customize behavior during database operations through operation hooks.
Documentation available here
The db-decorators library is a powerful TypeScript framework for database operations that leverages decorators to simplify database interactions. It provides a comprehensive solution for implementing the repository pattern with built-in support for model definition, validation, identity management, and operation hooks.
BaseRepository
class providing the foundation for CRUD operationsRepository
class with validation supportBulkCrudOperator
interface@id()
decorator@composed()
and @composedFromKeys()
decorators@version()
decorator@transient()
decorator@on()
, @onCreate()
, @onUpdate()
, etc.@after()
, @afterCreate()
, @afterUpdate()
, etc.Operations
registryThe library is designed to be extensible and adaptable to different database backends, providing a consistent API regardless of the underlying storage mechanism.
Description: Create a User model with ID, name, email, and password fields. The ID is marked as required and readonly, the password is hashed, and the email is required.
import { Model } from "@decaf-ts/decorator-validation";
import { id, hash, version, transient } from "@decaf-ts/db-decorators";
import { required, minLength, email as emailValidator } from "@decaf-ts/decorator-validation";
class User extends Model {
@id()
id: string;
@required()
@minLength(3)
name: string;
@required()
@emailValidator()
email: string;
@required()
@minLength(8)
@hash()
password: string;
@version()
version: number;
@transient()
temporaryData: any;
}
Description: Create a Product model with a SKU that is automatically composed from other properties.
import { Model } from "@decaf-ts/decorator-validation";
import { id, composed, composedFromKeys } from "@decaf-ts/db-decorators";
import { required } from "@decaf-ts/decorator-validation";
class Product extends Model {
@id()
id: string;
@required()
category: string;
@required()
name: string;
@required()
variant: string;
@composed(['category', 'name', 'variant'], '-')
sku: string;
@composedFromKeys(['category', 'name'], '_', true, 'PROD_', '_KEY')
productKey: string;
}
Description: Create a repository for the User model that implements the required CRUD operations.
import { Repository } from "@decaf-ts/db-decorators";
import { User } from "./models/User";
class UserRepository extends Repository<User> {
constructor() {
super(User);
}
async create(model: User, ...args: any[]): Promise<User> {
// Implementation for creating a user in the database
console.log(`Creating user: ${model.name}`);
// Assign an ID if not already present
if (!model.id) {
model.id = Date.now().toString();
}
return model;
}
async read(key: string | number, ...args: any[]): Promise<User> {
// Implementation for reading a user from the database
console.log(`Reading user with ID: ${key}`);
return new User({ id: key, name: "Example User", email: "user@example.com" });
}
async update(model: User, ...args: any[]): Promise<User> {
// Implementation for updating a user in the database
console.log(`Updating user: ${model.name}`);
return model;
}
async delete(key: string | number, ...args: any[]): Promise<User> {
// Implementation for deleting a user from the database
console.log(`Deleting user with ID: ${key}`);
const user = await this.read(key);
return user;
}
}
Description: Implement bulk operations for efficient batch processing of multiple models.
import { Repository } from "@decaf-ts/db-decorators";
import { Product } from "./models/Product";
class ProductRepository extends Repository<Product> {
constructor() {
super(Product);
}
// Implement required CRUD methods
async create(model: Product, ...args: any[]): Promise<Product> {
// Implementation
return model;
}
async read(key: string | number, ...args: any[]): Promise<Product> {
// Implementation
return new Product({ id: key });
}
async update(model: Product, ...args: any[]): Promise<Product> {
// Implementation
return model;
}
async delete(key: string | number, ...args: any[]): Promise<Product> {
// Implementation
return await this.read(key);
}
// Override bulk methods for optimized implementation
async createAll(models: Product[], ...args: any[]): Promise<Product[]> {
console.log(`Bulk creating ${models.length} products`);
// Custom implementation for bulk creation
return models.map(model => {
if (!model.id) {
model.id = Date.now().toString();
}
return model;
});
}
async readAll(keys: string[] | number[], ...args: any[]): Promise<Product[]> {
console.log(`Bulk reading ${keys.length} products`);
// Custom implementation for bulk reading
return keys.map(key => new Product({ id: key }));
}
}
Description: Use operation hooks to transform property values during database operations.
import { Model } from "@decaf-ts/decorator-validation";
import { id, onCreate, onUpdate, onCreateUpdate } from "@decaf-ts/db-decorators";
import { required } from "@decaf-ts/decorator-validation";
// Handler function for setting creation timestamp
function setCreationTimestamp(repo, context, data, key, model) {
model[key] = new Date().toISOString();
}
// Handler function for setting update timestamp
function setUpdateTimestamp(repo, context, data, key, model) {
model[key] = new Date().toISOString();
}
// Handler function for normalizing email
function normalizeEmail(repo, context, data, key, model) {
if (model[key]) {
model[key] = model[key].toLowerCase().trim();
}
}
class User extends Model {
@id()
id: string;
@required()
name: string;
@required()
@onCreateUpdate(normalizeEmail)
email: string;
@onCreate(setCreationTimestamp)
createdAt: string;
@onUpdate(setUpdateTimestamp)
updatedAt: string;
}
Description: Use post-operation hooks to perform actions after database operations.
import { Model } from "@decaf-ts/decorator-validation";
import { id, afterCreate, afterUpdate, afterDelete } from "@decaf-ts/db-decorators";
// Handler function for logging after creation
function logCreation(repo, context, data, key, model) {
console.log(`User created: ${model.id} - ${model.name}`);
}
// Handler function for logging after update
function logUpdate(repo, context, data, key, model) {
console.log(`User updated: ${model.id} - ${model.name}`);
}
// Handler function for logging after deletion
function logDeletion(repo, context, data, key, model) {
console.log(`User deleted: ${model.id} - ${model.name}`);
}
class User extends Model {
@id()
id: string;
@required()
name: string;
@required()
email: string;
@afterCreate(logCreation)
@afterUpdate(logUpdate)
@afterDelete(logDeletion)
_log: any; // This property is just a placeholder for the decorators
}
Description: Create and use contexts to manage operation state and configuration.
import { Context, Repository } from "@decaf-ts/db-decorators";
import { User } from "./models/User";
class UserRepository extends Repository<User> {
constructor() {
super(User);
}
// Implement required CRUD methods
async create(model: User, ...args: any[]): Promise<User> {
// Implementation
return model;
}
async read(key: string | number, ...args: any[]): Promise<User> {
// Implementation
return new User({ id: key });
}
async update(model: User, ...args: any[]): Promise<User> {
// Implementation
return model;
}
async delete(key: string | number, ...args: any[]): Promise<User> {
// Implementation
return await this.read(key);
}
// Example of using context
async createWithAudit(model: User, userId: string): Promise<User> {
// Create a context with audit information
const context = new Context().accumulate({
auditUser: userId,
auditTimestamp: new Date(),
skipValidation: false
});
// Pass the context to the create method
return this.create(model, context);
}
}
// Usage
const userRepo = new UserRepository();
const newUser = new User({ name: "John Doe", email: "john@example.com" });
const createdUser = await userRepo.createWithAudit(newUser, "admin123");
Description: Create hierarchical contexts for complex operations.
import { Context, OperationKeys } from "@decaf-ts/db-decorators";
import { User } from "./models/User";
// Create a parent context
const parentContext = new Context().accumulate({
transactionId: "tx123",
batchOperation: true
});
// Create a child context for a specific operation
const childContext = parentContext.child<User, Context<any>>(
OperationKeys.CREATE,
User
).accumulate({
operationId: "op456",
validationLevel: "strict"
});
// Access values from the context hierarchy
console.log(childContext.get("transactionId")); // "tx123" (inherited from parent)
console.log(childContext.get("operationId")); // "op456" (from child)
Description: Perform basic CRUD operations using a repository.
import { User } from "./models/User";
import { UserRepository } from "./repositories/UserRepository";
async function userCrudExample() {
const userRepo = new UserRepository();
// Create a new user
const newUser = new User({
name: "Alice Smith",
email: "alice@example.com",
password: "securePassword123"
});
const createdUser = await userRepo.create(newUser);
console.log("Created user:", createdUser);
// Read a user
const userId = createdUser.id;
const retrievedUser = await userRepo.read(userId);
console.log("Retrieved user:", retrievedUser);
// Update a user
retrievedUser.name = "Alice Johnson";
const updatedUser = await userRepo.update(retrievedUser);
console.log("Updated user:", updatedUser);
// Delete a user
const deletedUser = await userRepo.delete(userId);
console.log("Deleted user:", deletedUser);
}
Description: Perform bulk operations for efficient batch processing.
import { Product } from "./models/Product";
import { ProductRepository } from "./repositories/ProductRepository";
async function productBulkExample() {
const productRepo = new ProductRepository();
// Create multiple products
const products = [
new Product({ category: "Electronics", name: "Laptop", variant: "15-inch" }),
new Product({ category: "Electronics", name: "Laptop", variant: "13-inch" }),
new Product({ category: "Electronics", name: "Smartphone", variant: "Pro" })
];
const createdProducts = await productRepo.createAll(products);
console.log("Created products:", createdProducts);
// Read multiple products
const productIds = createdProducts.map(p => p.id);
const retrievedProducts = await productRepo.readAll(productIds);
console.log("Retrieved products:", retrievedProducts);
// Update multiple products
const updatedProducts = retrievedProducts.map(p => {
p.name = p.name + " (Updated)";
return p;
});
const savedProducts = await productRepo.updateAll(updatedProducts);
console.log("Updated products:", savedProducts);
// Delete multiple products
const deletedProducts = await productRepo.deleteAll(productIds);
console.log("Deleted products:", deletedProducts);
}
Description: Validate models during CRUD operations to ensure data integrity.
import { Model, ModelErrorDefinition } from "@decaf-ts/decorator-validation";
import { id } from "@decaf-ts/db-decorators";
import { required, minLength, maxLength, email, pattern } from "@decaf-ts/decorator-validation";
class User extends Model {
@id()
id: string;
@required()
@minLength(2)
@maxLength(50)
name: string;
@required()
@email()
emailAddress: string;
@required()
@minLength(8)
@pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/,
"Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character")
password: string;
// Manual validation example
hasErrors(): ModelErrorDefinition | undefined {
const errors = super.hasErrors();
// Add custom validation logic
if (this.name && this.name.includes('admin') && !this.emailAddress.includes('admin')) {
if (!errors) {
return {
name: ["Admin users must have an admin email address"]
};
}
errors.name = errors.name || [];
errors.name.push("Admin users must have an admin email address");
}
return errors;
}
}
// Usage in a repository
class UserRepository extends Repository<User> {
// ... other methods
async create(model: User, ...args: any[]): Promise<User> {
// The Repository class will automatically validate the model
// and throw a ValidationError if validation fails
// Custom validation can also be performed
const errors = model.hasErrors();
if (errors) {
throw new ValidationError(errors.toString());
}
// Proceed with creation if validation passes
return model;
}
}
Description: Use the identity module to work with model IDs.
import { Model } from "@decaf-ts/decorator-validation";
import { id } from "@decaf-ts/db-decorators";
import { findPrimaryKey, findModelId } from "@decaf-ts/db-decorators";
class Document extends Model {
@id()
documentId: string;
title: string;
content: string;
}
// Create a document instance
const doc = new Document({
documentId: "doc-123",
title: "Sample Document",
content: "This is a sample document."
});
// Find the primary key property
const pkInfo = findPrimaryKey(doc);
console.log("Primary key property:", pkInfo.id); // "documentId"
console.log("Primary key metadata:", pkInfo.props);
// Get the primary key value
const docId = findModelId(doc);
console.log("Document ID:", docId); // "doc-123"
// Try to get ID from a model without an ID value
const emptyDoc = new Document();
try {
const id = findModelId(emptyDoc); // Will throw an error
} catch (error) {
console.error("Error:", error.message);
}
// Get ID with returnEmpty option
const emptyId = findModelId(emptyDoc, true); // Returns undefined instead of throwing
console.log("Empty ID:", emptyId);
If you have bug reports, questions or suggestions please create a new issue.
I am grateful for any contributions made to this project. Please read this to get started.
The first and easiest way you can support it is by Contributing. Even just finding a typo in the documentation is important.
Financial support is always welcome and helps keep both me and the project alive and healthy.
So if you can, if this project in any way. either by learning something or simply by helping you save precious time, please consider donating.
This project is released under the MIT License.
By developers, for developers...
FAQs
Agnostic database decorators and repository
We found that @decaf-ts/db-decorators demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 5 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
/Security News
Malicious update to @ctrl/tinycolor on npm is part of a supply-chain attack hitting 40+ packages across maintainers
Security News
pnpm's new minimumReleaseAge setting delays package updates to prevent supply chain attacks, with other tools like Taze and NCU following suit.
Security News
The Rust Security Response WG is warning of phishing emails from rustfoundation.dev targeting crates.io users.