A TypeScript ORM library inspired by Hibernate for working with MySQL, MariaDB, and PostgreSQL databases using decorators and async/await patterns
Database configuration, schema updates, and utility functions for entity management and persistence. HibernateTS provides comprehensive configuration options for database schema management, automatic change tracking, and entity lifecycle management.
Automatic database schema synchronization based on entity model definitions.
/**
* Updates database schema to match model definitions
* @param modelRootPath - Path to directory containing model files
* @param opts - Update options including database configuration
* @returns Promise that resolves when schema update is complete
*/
function updateDatabase(modelRootPath: string, opts?: UpdateOpts): Promise<void>;
interface UpdateOpts {
/** Database adapter class (constructor) for creating connections */
dbPoolGEnerator?: DataBaseBaseStatic;
/** Database name to update schema for */
modelDb?: string;
}
interface DataBaseBaseStatic {
new(...args: any[]): DataBaseBase;
}Usage Examples:
import { updateDatabase, MariaDbBase } from "hibernatets";
import path from "path";
// Basic schema update
await updateDatabase(path.join(__dirname, "models"));
// Schema update with custom database adapter
await updateDatabase(path.join(__dirname, "models"), {
dbPoolGEnerator: MariaDbBase
});
// Schema update with specific database name
await updateDatabase(path.join(__dirname, "models"), {
modelDb: "my_custom_database"
});
// Automated schema update in application startup
async function initializeApplication() {
try {
console.log("Updating database schema...");
await updateDatabase("./src/entities");
console.log("Database schema updated successfully");
} catch (error) {
console.error("Schema update failed:", error);
process.exit(1);
}
}Automatic property change tracking and database synchronization.
/**
* Intercepts property setters to automatically trigger database updates
* @param object - Entity instance to intercept
* @param opts - Interception configuration options
*/
function intercept<T>(object: T, opts?: InterceptParams): void;
interface InterceptParams {
/** Enable interception of array methods like push/filter */
interceptArrayFunctions?: boolean;
/** Database connection to use for updates */
db?: DataBaseBase;
}Usage Examples:
import { intercept, load, queries } from "hibernatets";
// Basic property interception
const user = await load(User, 1);
intercept(user);
// Changes are automatically tracked
user.name = "Updated Name";
user.email = "new@example.com";
// Execute all pending updates
await queries(user);
// Interception with array function tracking
const userWithPosts = await load(User, 1, [], { deep: true });
intercept(userWithPosts, {
interceptArrayFunctions: true
});
// Array operations are tracked
userWithPosts.posts.push(new Post());
userWithPosts.posts[0].title = "Updated Title";
// All changes are persisted
await queries(userWithPosts);Core utility functions for working with entities and their database representations.
/**
* Gets the primary key value of an entity
* @param object - Entity instance
* @returns Primary key value
*/
function getId(object: any): number;
/**
* Sets the primary key value of an entity
* @param object - Entity instance
* @param id - New primary key value
*/
function setId(object: any, id: number): void;
/**
* Checks if an entity is persisted to database
* @param object - Entity instance
* @returns True if entity exists in database
*/
function isPersisted(object: any): boolean;
/**
* Gets database configuration for an entity or constructor
* @param obj - Entity instance or constructor
* @returns Database configuration object
*/
function getDBConfig<TableClass = any>(obj: ISaveAbleObject | ConstructorClass<TableClass> | TableClass): DataBaseConfig<TableClass>;
/**
* Gets database representation of an entity for storage
* @param object - Entity instance
* @returns Object with database column mappings
*/
function getRepresentation(object: any): { [key: string]: unknown };Usage Examples:
import {
getId, setId, isPersisted,
getDBConfig, getRepresentation,
load, save
} from "hibernatets";
// Working with entity IDs
const user = new User();
user.name = "Alice";
console.log(getId(user)); // undefined (not persisted yet)
console.log(isPersisted(user)); // false
await save(user);
console.log(getId(user)); // 123 (assigned after save)
console.log(isPersisted(user)); // true
// Manual ID assignment
const newUser = new User();
setId(newUser, 999);
newUser.name = "Bob";
// Get database configuration
const config = getDBConfig(User);
console.log("Table name:", config.table);
console.log("Primary key:", config.modelPrimary);
console.log("Columns:", Object.keys(config.columns));
// Get database representation
const dbRepresentation = getRepresentation(user);
console.log("DB data:", dbRepresentation);
// Output: { id: 123, name: "Alice", email: "alice@example.com", ... }Advanced update tracking and batch processing for entity changes.
/**
* Adds a pending database update to an entity's update queue
* @param obj - Entity instance
* @param update - Update operation to queue
*/
function pushUpdate(obj: any, update: any): void;
/**
* Updates a specific property of an entity in the database
* @param object - Entity instance
* @param key - Property key to update
* @param value - New value for the property
* @param opts - Update options
* @returns Promise resolving to number of affected rows
*/
function update(object: any, key: string, value: any, opts?: any): Promise<number>;Usage Examples:
import { pushUpdate, update, queries, load } from "hibernatets";
// Manual update queuing
const user = await load(User, 1);
// Queue multiple updates
pushUpdate(user, { field: "name", value: "New Name" });
pushUpdate(user, { field: "email", value: "new@email.com" });
// Execute all queued updates
await queries(user);
// Direct property update
const affectedRows = await update(user, "lastLogin", new Date());
console.log(`Updated ${affectedRows} rows`);
// Batch property updates
async function batchUpdateUser(userId: number, updates: Record<string, any>) {
const user = await load(User, userId);
for (const [key, value] of Object.entries(updates)) {
await update(user, key, value);
}
return user;
}
const updatedUser = await batchUpdateUser(123, {
name: "Alice Smith",
email: "alice.smith@example.com",
lastLogin: new Date()
});Complete database configuration management for entities.
/**
* Configuration class for entity database mapping
*/
interface DataBaseConfig<T> {
/** Primary key field name */
modelPrimary: string;
/** Database table name */
table: string;
/** Column definitions mapped by property name */
columns: { [key: string]: ColumnDefinition<any> };
/** Table-level options */
options: any;
/** Reference key field name */
referenceKey: string;
/**
* Creates new instance of the entity
* @returns New entity instance
*/
createInstance(): T;
}
/**
* Individual column configuration
*/
interface ColumnDefinition<K> {
/** Property name in the model class */
modelName: string;
/** Corresponding column name in database */
dbTableName: string;
/** Relationship mapping configuration */
mapping?: any;
/** Inverse relationship definitions */
inverseMappingDef?: any;
/** Primary key generation strategy */
primaryType?: "auto-increment" | "custom";
/** Column-specific options */
opts?: any;
}Usage Examples:
import { getDBConfig } from "hibernatets";
@table({ name: "user_accounts" })
class User {
@primary()
id: number;
@column()
name: string;
@mapping(Mappings.OneToMany, Post, "userId")
posts: Post[];
}
// Access complete configuration
const userConfig = getDBConfig(User);
console.log("Configuration details:");
console.log("- Table:", userConfig.table); // "user_accounts"
console.log("- Primary key:", userConfig.modelPrimary); // "id"
console.log("- Reference key:", userConfig.referenceKey); // "id"
// Inspect column configurations
Object.entries(userConfig.columns).forEach(([propName, columnDef]) => {
console.log(`Column ${propName}:`, {
dbName: columnDef.dbTableName,
hasMapping: !!columnDef.mapping,
isPrimary: !!columnDef.primaryType
});
});
// Create new instance using configuration
const newUser = userConfig.createInstance();
console.log("New instance:", newUser); // Empty User instanceType-safe utilities for working with entity objects and their properties.
/**
* Type-safe Object.values() implementation
* @param obj - Object to extract values from
* @returns Array of object values with preserved typing
*/
function objectValues<T>(obj: T): Array<T[keyof T]>;
/**
* Custom utility type for omitting keys from objects
*/
type CustomOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;Usage Examples:
import { objectValues } from "hibernatets";
interface UserData {
id: number;
name: string;
email: string;
}
const userData: UserData = {
id: 1,
name: "Alice",
email: "alice@example.com"
};
// Type-safe value extraction
const values = objectValues(userData);
// Type: (string | number)[]
// Value: [1, "Alice", "alice@example.com"]
// Usage with entity configurations
const config = getDBConfig(User);
const columnDefs = objectValues(config.columns);
// Type: ColumnDefinition<any>[]
// Iterate with type safety
columnDefs.forEach(column => {
console.log(`Column: ${column.modelName} -> ${column.dbTableName}`);
});Complex configuration scenarios and best practices for application setup.
import {
updateDatabase, intercept, getDBConfig,
MariaDbBase, withMariaDbPool, setMariaDbPoolDefaults
} from "hibernatets";
import path from "path";
// Application configuration manager
class HibernateTSConfig {
private static initialized = false;
private static modelPaths: string[] = [];
static async initialize(options: {
modelPaths: string[];
autoSchema?: boolean;
dbPoolGenerator?: DataBaseBaseStatic;
interceptByDefault?: boolean;
}) {
if (this.initialized) return;
this.modelPaths = options.modelPaths;
// Set up database defaults
if (options.dbPoolGenerator) {
setMariaDbPoolDefaults({
connectionLimit: 20,
minimumIdle: 5,
idleTimeout: 300000
});
}
// Update database schema if requested
if (options.autoSchema !== false) {
for (const modelPath of options.modelPaths) {
await updateDatabase(modelPath, {
dbPoolGEnerator: options.dbPoolGenerator
});
}
}
this.initialized = true;
console.log("HibernateTS configuration initialized");
}
static createEntityWithDefaults<T>(
EntityClass: new () => T,
data: Partial<T>,
options: { enableInterception?: boolean } = {}
): T {
const entity = Object.assign(new EntityClass(), data);
if (options.enableInterception !== false) {
intercept(entity, {
interceptArrayFunctions: true
});
}
return entity;
}
static async validateEntityConfiguration<T>(
EntityClass: new () => T
): Promise<boolean> {
try {
const config = getDBConfig(EntityClass);
// Validate basic configuration
if (!config.table || !config.modelPrimary) {
throw new Error("Missing table or primary key configuration");
}
// Validate columns
if (Object.keys(config.columns).length === 0) {
throw new Error("No columns configured");
}
// Test instance creation
const instance = config.createInstance();
if (!instance) {
throw new Error("Cannot create entity instance");
}
return true;
} catch (error) {
console.error(`Entity validation failed:`, error);
return false;
}
}
static getModelSummary() {
// This would typically iterate through loaded models
return {
initialized: this.initialized,
modelPaths: this.modelPaths,
loadedModels: [] // Would be populated with actual model information
};
}
}
// Usage example
async function setupApplication() {
await HibernateTSConfig.initialize({
modelPaths: [
path.join(__dirname, "entities"),
path.join(__dirname, "models")
],
autoSchema: true,
dbPoolGenerator: MariaDbBase,
interceptByDefault: true
});
// Validate entity configurations
const isUserValid = await HibernateTSConfig.validateEntityConfiguration(User);
const isPostValid = await HibernateTSConfig.validateEntityConfiguration(Post);
if (!isUserValid || !isPostValid) {
throw new Error("Entity configuration validation failed");
}
console.log("Application setup complete");
}Enhanced error handling and debugging utilities for database operations.
/**
* Enhanced Error class with cause chain support
*/
class Exception extends Error {
/**
* Creates exception with nested error information
* @param message - Error message
* @param error - Underlying error that caused this exception
*/
constructor(message: string, error: Error);
}Usage Examples:
import { Exception, load, save } from "hibernatets";
// Error handling with context
async function safeDatabaseOperation<T>(
operation: () => Promise<T>,
context: string
): Promise<T> {
try {
return await operation();
} catch (error) {
throw new Exception(
`Database operation failed in ${context}`,
error as Error
);
}
}
// Usage with comprehensive error context
try {
const user = await safeDatabaseOperation(
() => load(User, 999),
"user loading"
);
await safeDatabaseOperation(
() => save(user),
"user saving"
);
} catch (error) {
if (error instanceof Exception) {
console.error("Operation failed:", error.message);
console.error("Root cause:", error.cause);
}
}Type-safe extended map implementations for managing key-value pairs with automatic JSON serialization.
/**
* Extended Map implementation with type-safe value access and JSON serialization
* @template T - ExtendedMapItem type defining key and value structure
* @template ValueMap - Mapped type for value types by key
*/
class ExtendedMap<T extends ExtendedMapItem<string, any>, ValueMap extends { [key in T["key"]]: ReturnType<T["parsed"]> } = {
[key in T["key"]]: any;
}> extends Map<T["key"], ExtendedMapItem<T["key"], ValueMap[T["key"]]>> {
/**
* Creates new ExtendedMap instance
* @param itemClass - Constructor for map items
* @param parentArray - Optional array to sync with
*/
constructor(itemClass: new () => T, parentArray?: Array<T>);
/**
* Gets parsed value for a key with fallback support
* @param key - Map key
* @param fallback - Optional fallback value
* @returns Parsed value of type ValueMap[K]
*/
getValue<K extends T["key"]>(key: K, fallback?: ValueMap[K]): ValueMap[K];
/**
* Sets parsed value for a key
* @param key - Map key
* @param val - Value to set
*/
setValue<K extends T["key"]>(key: K, val: ValueMap[K]): void;
/**
* Iterate over parsed values
* @returns Iterator of parsed values
*/
entryValues(): IterableIterator<ReturnType<T["parsed"]>>;
/**
* Iterate over each value with callback
* @param callbackfn - Function called for each key-value pair
*/
forEachValue(callbackfn: <K extends T["key"]>(value: ValueMap[K], key: K, map: ExtendedMap<T>) => void): void;
}
/**
* Extended Map Item for storing key-value pairs with JSON serialization
* @template K - Key type (string-based)
* @template T - Value type
*/
@table()
class ExtendedMapItem<K extends string = string, T = any> {
/** Primary key for database storage */
@primary()
id: number;
/** JSON-serialized value */
@column()
value: string;
/** Map key */
@column()
key: K;
/**
* Parse stored JSON value
* @returns Parsed value of type T
*/
parsed(): T;
/**
* Set value with JSON serialization
* @param value - Value to serialize and store
*/
setStringified(value: T): void;
}Usage Examples:
import { ExtendedMap, ExtendedMapItem } from "hibernatets";
// Define custom map item type
interface UserSettings {
theme: "dark" | "light";
notifications: boolean;
language: "en" | "es" | "fr";
}
interface SettingsMap {
theme: "dark" | "light";
notifications: boolean;
language: "en" | "es" | "fr";
}
// Create extended map for user settings
const settingsMap = new ExtendedMap<ExtendedMapItem<keyof SettingsMap, any>, SettingsMap>(
ExtendedMapItem,
[]
);
// Set values with automatic JSON serialization
settingsMap.setValue("theme", "dark");
settingsMap.setValue("notifications", true);
settingsMap.setValue("language", "en");
// Get values with type safety and fallbacks
const theme = settingsMap.getValue("theme", "light"); // "dark"
const notifications = settingsMap.getValue("notifications", false); // true
const language = settingsMap.getValue("language", "en"); // "en"
// Iterate over all values
settingsMap.forEachValue((value, key) => {
console.log(\`Setting \${key}: \${value}\`);
});
// Working with extended map items directly
const themeItem = new ExtendedMapItem<"theme", "dark" | "light">();
themeItem.key = "theme";
themeItem.setStringified("dark");
console.log(themeItem.parsed()); // "dark"
console.log(themeItem.value); // "\"dark\""Install with Tessl CLI
npx tessl i tessl/npm-hibernatets