Table Modifiers
Built-in Modifiers
EncryptionModifier
The EncryptionModifier encrypts field values before storing them and decrypts them on retrieval. Both operations happen transparently.
import { Table, Field, EncryptionModifier, Encrypted } from "@antelopejs/interface-database-decorators";
class UserCredentials extends Table.with(EncryptionModifier) {
@Encrypted({
secretKey: process.env.ENCRYPTION_KEY || "default-key",
algorithm: "aes-256-gcm",
ivSize: 16,
})
@Field("string")
declare creditCardNumber: string;
@Encrypted({ secretKey: process.env.ENCRYPTION_KEY || "default-key" })
@Field("any")
declare personalData: {
ssn: string;
birthDate: string;
};
}
How It Works
- Data is encrypted using the specified algorithm before database insertion.
- A random initialization vector (IV) is generated for each write.
- For authenticated encryption algorithms (e.g.,
aes-256-gcm), an auth tag is stored alongside the ciphertext. - On retrieval, the data is automatically decrypted.
- Date objects are preserved through JSON serialization with a custom encoder.
Options
| Option | Type | Default | Description |
|---|---|---|---|
secretKey | string | (required) | The encryption key. |
algorithm | string | "aes-256-gcm" | Encryption algorithm. |
ivSize | number | 16 | Initialization vector size in bytes. |
HashModifier
The HashModifier stores a one-way hash of field values. Unlike encryption, hashing is irreversible -- you cannot retrieve the original value. Use this for passwords and other sensitive data that only needs equality verification.
import { Table, Field, HashModifier, Hashed } from "@antelopejs/interface-database-decorators";
class User extends Table.with(HashModifier) {
@Field("string")
declare email: string;
@Hashed({ algorithm: "sha256" })
@Field("string")
declare password: string;
verifyPassword(plainPassword: string): boolean {
return this.testHash("password", plainPassword);
}
}
How It Works
- A random salt is generated and stored in the field's metadata on first write.
- The value is hashed with the salt using the specified algorithm.
- The
testHashmethod (added by the mixin) hashes a candidate value with the stored salt and compares the result.
Options
| Option | Type | Default | Description |
|---|---|---|---|
algorithm | string | "sha256" | Hashing algorithm. |
Mixin Method
The HashModifier mixin adds a testHash method to table instances:
testHash(field: string, value: unknown): boolean
Returns true if value, when hashed, matches the stored hash for the given field.
LocalizationModifier
The LocalizationModifier stores multiple language versions of a field value. Access a specific locale by calling localize() on the table instance.
import { Table, Field, LocalizationModifier, Localized } from "@antelopejs/interface-database-decorators";
class Product extends Table.with(LocalizationModifier) {
@Field("number")
declare price: number;
@Localized({ fallbackLocale: "en" })
@Field("string")
declare name: string;
@Localized({ fallbackLocale: "en" })
@Field("string")
declare description: string;
}
Usage
const product = new Product();
product._id = "prod-123";
product.price = 99.99;
// Set localized content
product.localize("en").name = "Premium Headphones";
product.localize("fr").name = "Casque Premium";
product.localize("es").name = "Auriculares Premium";
// Read localized content
const spanish = product.localize("es");
console.log(spanish.name); // "Auriculares Premium"
// Fallback to default locale when translation is missing
const german = product.localize("de");
console.log(german.name); // "Premium Headphones" (fallback)
Wildcard Key
The wildcard key "*" retrieves or sets all translations at once:
// Get all translations
const allNames = product.localize("*").name;
// { en: "Premium Headphones", fr: "Casque Premium", es: "Auriculares Premium" }
// Set all translations
product.localize("*").name = {
en: "Premium Headphones",
fr: "Casque Premium",
es: "Auriculares Premium",
de: "Premium-Kopfhoerer",
};
Options
| Option | Type | Description |
|---|---|---|
fallbackLocale | string | Locale to use when the requested locale is unavailable. |
Mixin Method
The LocalizationModifier mixin adds a localize method:
localize(locale: string, fields?: Array<keyof this>): this
Sets the active locale for accessing localized fields. If fields is provided, only those fields are unlocked for the specified locale.
AutoDateModifier
The AutoDateModifier automatically populates timestamp fields during insert and update operations. Use the CreationTime and UpdateTime decorators to mark fields.
import { Table, CreationTime, UpdateTime } from "@antelopejs/interface-database-decorators";
class Article extends Table {
declare title: string;
@CreationTime()
declare creationDate: Date;
@UpdateTime()
declare updateDate: Date;
}
How It Works
- On insert, both
CreationTimeandUpdateTimefields are set to the current date. - On update,
UpdateTimefields are refreshed whileCreationTimefields are removed from the payload so the original creation date is preserved. - This is an event-only modifier: it does not transform stored values and does not require adding a mixin through
Table.with().
Combine Multiple Modifiers
Pass multiple modifier classes to Table.with():
import {
Table,
Field,
EncryptionModifier,
HashModifier,
LocalizationModifier,
Encrypted,
Hashed,
Localized,
} from "@antelopejs/interface-database-decorators";
class UserProfile extends Table.with(EncryptionModifier, HashModifier, LocalizationModifier) {
@Encrypted({ secretKey: process.env.SECRET_KEY || "default-key" })
@Field("any")
declare privateInfo: {
address: string;
phoneNumber: string;
};
@Hashed()
@Field("string")
declare password: string;
@Localized({ fallbackLocale: "en" })
@Field("string")
declare bio: string;
}
Create Custom Modifiers
OneWayModifier
A OneWayModifier transforms data in one direction (when writing to the database). Use it for irreversible transformations like hashing.
import { OneWayModifier, MixinSymbol, attachModifier } from "@antelopejs/interface-database-decorators";
import { MakePropertyDecorator } from "@antelopejs/interface-core/decorators";
import { createHash } from "crypto";
class PrefixHashModifier extends OneWayModifier<string | undefined, [], {}, { algorithm?: string }> {
public readonly autolock = true;
public override lock(_locked: string | undefined, value: unknown) {
if (value === undefined) return undefined;
return createHash(this.options.algorithm || "sha256")
.update(JSON.stringify(value))
.digest("hex");
}
[MixinSymbol] = class {};
}
const PrefixHashed = MakePropertyDecorator(
(target, propertyKey, options?: { algorithm?: string }) => {
attachModifier(target.constructor, PrefixHashModifier, propertyKey, options || {});
},
);
The generic parameters for OneWayModifier<LockedType, Args, Meta, Options> are:
| Parameter | Description |
|---|---|
LockedType | The type stored in the database after transformation |
Args | Additional arguments for lock and test |
Meta | Per-field metadata stored alongside the value |
Options | Configuration passed via the decorator |
TwoWayModifier
A TwoWayModifier extends OneWayModifier with an unlock method for reversible transformations like encryption:
import { TwoWayModifier, MixinSymbol, attachModifier } from "@antelopejs/interface-database-decorators";
import { MakePropertyDecorator } from "@antelopejs/interface-core/decorators";
class Base64Modifier extends TwoWayModifier<string, [], {}, { prefix?: string }> {
public readonly autolock = true;
public readonly autounlock = true;
public override lock(_locked: string | undefined, value: unknown) {
if (value === undefined) return undefined;
const prefix = this.options.prefix || "b64-";
return `${prefix}${Buffer.from(JSON.stringify(value)).toString("base64")}`;
}
public override unlock(locked: string) {
const prefix = this.options.prefix || "b64-";
const encoded = locked.substring(prefix.length);
return JSON.parse(Buffer.from(encoded, "base64").toString("utf8"));
}
public override unlockrequest(data: any) {
return data;
}
[MixinSymbol] = class {};
}
const Base64Encoded = MakePropertyDecorator(
(target, propertyKey, options?: { prefix?: string }) => {
attachModifier(target.constructor, Base64Modifier, propertyKey, options || {});
},
);
ContainerModifier
For modifiers that store multiple values in a key-value structure (like LocalizationModifier), extend ContainerModifier. It provides built-in support for the wildcard key "*" for bulk operations:
import { ContainerModifier } from "@antelopejs/interface-database-decorators";
class MyContainerModifier extends ContainerModifier<{}, { fallback?: string }> {
// Override unlock() to add fallback behavior
// Override unlockrequest() for query-time data access
}
Modifier Events
Modifiers can hook into the data lifecycle by implementing event handler methods. These methods are called during data conversion and database operations:
| Event | Triggered When |
|---|---|
fromPlainData | After converting plain data to an instance |
toPlainData | After converting an instance to plain data |
fromDatabase | After reading from the database |
toDatabase | Before writing to the database |
insert | Before inserting a new record |
update | Before updating an existing record |
import { Modifier, MixinSymbol } from "@antelopejs/interface-database-decorators";
class TimestampModifier extends Modifier {
public insert(instance: any, field: string) {
if (field === "createdAt" && !instance[field]) {
instance[field] = new Date();
}
}
public update(instance: any, field: string) {
if (field === "updatedAt") {
instance[field] = new Date();
}
}
[MixinSymbol] = class {};
}
Event-only modifiers (without lock/unlock) are valid and useful for side effects like logging, validation, or auto-populating fields.
Table Definitions
The Database Decorators package provides a class-based approach to defining database tables. You extend the Table base class, declare each field with TypeScript's declare keyword and the @Field decorator, and use further decorators to configure indexes, relations, and initial data.
Data Models
Data models provide a high-level interface for interacting with database tables. They handle conversion between database records and TypeScript class instances, and expose methods for common CRUD operations.