
DB-Decorators: Database Operations Made Simple
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
DB-Decorators
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.
Key Features
- Repository Pattern Implementation
- Abstract
BaseRepository
class providing the foundation for CRUD operations
- Concrete
Repository
class with validation support
- Support for bulk operations through the
BulkCrudOperator
interface
- Model definition with TypeScript decorators
- Identity management with the
@id()
decorator
- Property composition with
@composed()
and @composedFromKeys()
decorators
- Versioning support with the
@version()
decorator
- Transient properties with the
@transient()
decorator
- Pre-operation hooks with
@on()
, @onCreate()
, @onUpdate()
, etc.
- Post-operation hooks with
@after()
, @afterCreate()
, @afterUpdate()
, etc.
- Custom operation handlers through the
Operations
registry
- Hierarchical context chains with parent-child relationships
- Context accumulation for state management
- Operation-specific context creation
- Integration with decorator-validation library
- Automatic validation during CRUD operations
- Custom validation rules through decorators
The library is designed to be extensible and adaptable to different database backends, providing a consistent API regardless of the underlying storage mechanism.
How to Use
DB-Decorators Examples
1. Defining Models with Decorators
Basic Model Definition
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;
}
Composed Properties
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;
}
2. Implementing Repositories
Basic Repository Implementation
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> {
console.log(`Creating user: ${model.name}`);
if (!model.id) {
model.id = Date.now().toString();
}
return model;
}
async read(key: string | number, ...args: any[]): Promise<User> {
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> {
console.log(`Updating user: ${model.name}`);
return model;
}
async delete(key: string | number, ...args: any[]): Promise<User> {
console.log(`Deleting user with ID: ${key}`);
const user = await this.read(key);
return user;
}
}
Using Bulk Operations
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);
}
async create(model: Product, ...args: any[]): Promise<Product> {
return model;
}
async read(key: string | number, ...args: any[]): Promise<Product> {
return new Product({ id: key });
}
async update(model: Product, ...args: any[]): Promise<Product> {
return model;
}
async delete(key: string | number, ...args: any[]): Promise<Product> {
return await this.read(key);
}
async createAll(models: Product[], ...args: any[]): Promise<Product[]> {
console.log(`Bulk creating ${models.length} products`);
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`);
return keys.map(key => new Product({ id: key }));
}
}
3. Using Operation Hooks
Property Transformation Hooks
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";
function setCreationTimestamp(repo, context, data, key, model) {
model[key] = new Date().toISOString();
}
function setUpdateTimestamp(repo, context, data, key, model) {
model[key] = new Date().toISOString();
}
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;
}
Post-Operation Hooks
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";
function logCreation(repo, context, data, key, model) {
console.log(`User created: ${model.id} - ${model.name}`);
}
function logUpdate(repo, context, data, key, model) {
console.log(`User updated: ${model.id} - ${model.name}`);
}
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;
}
4. Working with Contexts
Creating and Using Contexts
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);
}
async create(model: User, ...args: any[]): Promise<User> {
return model;
}
async read(key: string | number, ...args: any[]): Promise<User> {
return new User({ id: key });
}
async update(model: User, ...args: any[]): Promise<User> {
return model;
}
async delete(key: string | number, ...args: any[]): Promise<User> {
return await this.read(key);
}
async createWithAudit(model: User, userId: string): Promise<User> {
const context = new Context().accumulate({
auditUser: userId,
auditTimestamp: new Date(),
skipValidation: false
});
return this.create(model, context);
}
}
const userRepo = new UserRepository();
const newUser = new User({ name: "John Doe", email: "john@example.com" });
const createdUser = await userRepo.createWithAudit(newUser, "admin123");
Context Hierarchies
Description: Create hierarchical contexts for complex operations.
import { Context, OperationKeys } from "@decaf-ts/db-decorators";
import { User } from "./models/User";
const parentContext = new Context().accumulate({
transactionId: "tx123",
batchOperation: true
});
const childContext = parentContext.child<User, Context<any>>(
OperationKeys.CREATE,
User
).accumulate({
operationId: "op456",
validationLevel: "strict"
});
console.log(childContext.get("transactionId"));
console.log(childContext.get("operationId"));
5. Performing CRUD Operations
Basic CRUD Operations
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();
const newUser = new User({
name: "Alice Smith",
email: "alice@example.com",
password: "securePassword123"
});
const createdUser = await userRepo.create(newUser);
console.log("Created user:", createdUser);
const userId = createdUser.id;
const retrievedUser = await userRepo.read(userId);
console.log("Retrieved user:", retrievedUser);
retrievedUser.name = "Alice Johnson";
const updatedUser = await userRepo.update(retrievedUser);
console.log("Updated user:", updatedUser);
const deletedUser = await userRepo.delete(userId);
console.log("Deleted user:", deletedUser);
}
Bulk Operations
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();
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);
const productIds = createdProducts.map(p => p.id);
const retrievedProducts = await productRepo.readAll(productIds);
console.log("Retrieved products:", retrievedProducts);
const updatedProducts = retrievedProducts.map(p => {
p.name = p.name + " (Updated)";
return p;
});
const savedProducts = await productRepo.updateAll(updatedProducts);
console.log("Updated products:", savedProducts);
const deletedProducts = await productRepo.deleteAll(productIds);
console.log("Deleted products:", deletedProducts);
}
6. Validation
Model Validation
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;
hasErrors(): ModelErrorDefinition | undefined {
const errors = super.hasErrors();
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;
}
}
class UserRepository extends Repository<User> {
async create(model: User, ...args: any[]): Promise<User> {
const errors = model.hasErrors();
if (errors) {
throw new ValidationError(errors.toString());
}
return model;
}
}
7. Identity Management
Working with Model IDs
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;
}
const doc = new Document({
documentId: "doc-123",
title: "Sample Document",
content: "This is a sample document."
});
const pkInfo = findPrimaryKey(doc);
console.log("Primary key property:", pkInfo.id);
console.log("Primary key metadata:", pkInfo.props);
const docId = findModelId(doc);
console.log("Document ID:", docId);
const emptyDoc = new Document();
try {
const id = findModelId(emptyDoc);
} catch (error) {
console.error("Error:", error.message);
}
const emptyId = findModelId(emptyDoc, true);
console.log("Empty ID:", emptyId);
Related

Social

Languages

Getting help
If you have bug reports, questions or suggestions please create a new issue.
Contributing
I am grateful for any contributions made to this project. Please read this to get started.
Supporting
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.
License
This project is released under the MIT License.
By developers, for developers...