AbimongoSchema
AbimongoSchema is the schema abstraction used by @abimongo/core to describe document shapes, validations, indexes, relationships, virtual fields, and middleware hooks.
This page documents common usage patterns and the public API surface. For the authoritative implementation see packages/core/src/lib-core/schema/AbimongoSchema.ts.
At a glance
- Define fields and validators for documents
- Register indexes and apply them to collections
- Declare relationships (refs) to other collections
- Add virtual/computed fields
- Register middleware hooks (pre/post) for lifecycle events
All method signatures are presented as TypeScript snippets to preserve generics and avoid markdown-lint issues.
Constructor
Create a schema by passing a plain object describing fields and options:
constructor(definition: Record<string, any>, options?: SchemaOptions)
Notes
definitionfollows a lightweight schema DSL used across the codebase (type, required, ref, default, validate, index, etc.).optionscan include collection-level settings such as timestamps, soft-delete behavior, and GC (garbage collection) hints.
API: core methods
getSchema
Return the raw schema definition object.
getSchema(): Record<string, any>
validator
Register a field-level validator. The validator may be synchronous or return a Promise.
validator(field: string, fn: (value: any) => boolean | Promise<boolean>): void
validate
Validate a document against the registered validators. Throws on first failure.
validate(doc: OptionalUnlessRequiredId<T>): void
index
Declare an index to be applied later to a collection.
index(fields: Partial<Record<string, 1 | -1 | 'text'>>, options?: IndexOptions): void
applyIndexes
Apply previously-declared indexes to a MongoDB collection.
async applyIndexes(collection: Collection<any>): Promise<void>
addRelationship
Define a relationship (reference) to another collection. Useful for model-level population helpers.
addRelationship(name: string, localField: string, options?: { foreignCollection?: string; many?: boolean }): void
getRelationships
Return relationships declared on the schema.
getRelationships(): Array<{ name: string; localField: string; options?: any }>
virtual
Declare a virtual (computed) field.
virtual(name: string, getter: (doc: any) => any): void
applyVirtuals
Apply virtuals to a document instance (mutates the document).
applyVirtuals(doc: any): void
Middleware & hooks
AbimongoSchema supports registering hooks for lifecycle events (e.g., save, update, delete, aggregate). Hooks are composable and run in registration order.
addHook
Register a generic hook for an event.
addHook(event: string, fn: HookFunction): void
pre / post / getHooks
Convenience helpers for registering and inspecting hooks.
pre(action: string, fn: HookFunction): void
post(action: string, fn: HookFunction): void
getHooks(action: string): HookFunction[]
executeHooks
Execute registered hooks programmatically (used internally by models).
async executeHooks(event: string, payload: any): Promise<void>
triggerMiddleware
Alias used by model lifecycles to trigger middleware.
async triggerMiddleware(action: string, data: any): Promise<void>
Notes
- Pre-hooks may mutate the payload and can be async.
- Post-hooks receive the result and are intended for side-effects (publish events, invalidate caches, etc.).
Advanced features
- Index declarations support compound, text, and unique indexes and will be applied with
applyIndexes. - Relationships can be used by
AbimongoModelhelpers such aspopulateOneandpopulateManyto resolve related documents. - Virtuals are applied at read-time and do not persist to the DB unless explicitly mapped by your model logic.
- Schema options can include GC hints:
expiresIn,archiveBeforeDelete, andsoftDeletewhichAbimongoModelhonours duringrunGC()operations.
Examples
Define a simple user schema
const userSchema = new AbimongoSchema(
{
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
age: { type: Number, default: 18 },
profileId: { type: "ObjectId", ref: "profiles" },
createdAt: { type: Date, default: () => new Date() },
},
{ timestamps: true }
);
userSchema.validator(
"email",
(value) => typeof value === "string" && value.includes("@")
);
userSchema.virtual("isAdult", (doc) => (doc.age ?? 0) >= 18);
userSchema.index({ email: 1 }, { unique: true });
Apply indexes to a collection
await userSchema.applyIndexes(db.collection("users"));
Use hooks to add side-effects
userSchema.pre("save", async (doc) => {
doc.updatedAt = new Date();
});
userSchema.post("save", async (doc) => {
// notify or invalidate caches
await Cache.invalidate(`users:${doc._id}`);
});
Advanced Hook Examples
// Pre-save validation and transformation
userSchema.pre("save", async (doc) => {
// Auto-generate slug from name
if (doc.name && !doc.slug) {
doc.slug = doc.name.toLowerCase().replace(/\s+/g, "-");
}
// Hash password before saving
if (doc.password && doc.isModified && doc.isModified("password")) {
doc.password = await bcrypt.hash(doc.password, 10);
}
// Set timestamps
doc.updatedAt = new Date();
if (!doc.createdAt) {
doc.createdAt = new Date();
}
});
// Post-save actions
userSchema.post("save", async (doc) => {
// Clear related cache
await Cache.clear(`user:${doc._id}`);
// Send welcome email for new users
if (doc.isNew) {
await EmailService.sendWelcomeEmail(doc.email);
}
// Update search index
await SearchIndex.updateUser(doc);
});
// Pre-delete cleanup
userSchema.pre("delete", async (doc) => {
// Archive related data
await ArchiveService.archiveUserData(doc._id);
// Cancel subscriptions
await SubscriptionService.cancelUserSubscriptions(doc._id);
});
// Post-delete cleanup
userSchema.post("delete", async (doc) => {
// Remove from cache
await Cache.delete(`user:${doc._id}`);
// Log deletion
console.log(`User ${doc._id} has been deleted`);
});
Example: Full Workflow
import { AbimongoSchema } from "abimongo_core";
const userSchema = new AbimongoSchema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
age: { type: Number },
firstName: { type: String },
lastName: { type: String },
password: { type: String, required: true },
createdAt: { type: Date, default: Date.now },
updatedAt: { type: Date },
});
// Add custom validators
userSchema.validator("email", (value) => value.includes("@"));
userSchema.validator("age", (value) => value >= 0 && value <= 150);
// Add indexes
userSchema.index({ email: 1 }, { unique: true });
userSchema.index({ name: 1, age: 1 });
// Add virtual fields
userSchema.virtual("fullName", (doc) => `${doc.firstName} ${doc.lastName}`);
userSchema.virtual("isAdult", (doc) => doc.age >= 18);
// Add relationships
userSchema.addRelationship("orders", "userId");
userSchema.addRelationship("posts", "authorId");
// Add middleware hooks
userSchema.pre("save", async (doc) => {
console.log("Before saving:", doc);
doc.updatedAt = new Date();
});
userSchema.post("save", async (doc) => {
console.log("After saving:", doc);
// Trigger cache invalidation, notifications, etc.
});
userSchema.pre("delete", async (doc) => {
console.log("Before deleting:", doc._id);
// Cleanup related data
});
userSchema.post("delete", async (doc) => {
console.log("After deleting:", doc._id);
// Final cleanup
});
// Get all hooks for debugging
const saveHooks = userSchema.getHooks("save");
console.log(`Total save hooks: ${saveHooks.length}`);
// Manually trigger middleware (useful for testing)
await userSchema.triggerMiddleware("save", mockUserDocument);
Best practices
- Keep schemas focused and explicit: prefer small, well-typed fields over large free-form blobs.
- Use validators for field-level guarantees and keep cross-field validation in hooks if it depends on multiple fields.
- Declare indexes as part of the schema and apply them during application startup.
- Use virtuals for read-only computed fields only.
- Keep expensive hooks asynchronous and non-blocking where possible (perform heavy work in background jobs).
Where to go next
- See AbimongoModel docs for how schemas integrate into models and lifecycle events.
- Check the Multi-Tenancy docs if you need tenant-scoped schema behaviour.
Support
For questions or support, please open an issue on the project repository.