meta-manager
core concepts
The package is built around five interlocking concepts. Understanding all five makes the API predictable across every surface.
base document shape
Every entity document inherits these fields automatically. You never need to declare them in additionalFields.
| field | type | default | note |
|---|
quick start
Connect to MongoDB, create an entity, mount its controller. That is the complete setup.
import mongoose from "mongoose"
import express from "express"
import Joi from "joi"
import { MetaEntity } from "@ubyjerome/meta-manager"
// 1. connect first - MetaEntity checks this at construction
await mongoose.connect("mongodb://localhost:27017/mydb")
const app = express()
app.use(express.json())
// 2. define the entity
const booksEntity = new MetaEntity("books", {
createSchema: {
title_name: Joi.string().required(),
isbn: Joi.string().optional(),
libraryId: Joi.string().required()
},
searchableFields: ["title_name", "description"],
softDelete: true,
defaultSort: "created_at",
defaultLimit: 20
})
// 3. mount the controller
app.use("/books", booksEntity.controller)
app.listen(3000)
That single mount registers 14+ endpoints. You can also reach every operation programmatically via
booksEntity.service.
typescript interface extension
Extend BaseEntityDocument and pass it as the generic to get fully typed service methods and
event callbacks.
interface BookDocument extends BaseEntityDocument {
isbn: string
pageCount: number
libraryId: string
}
const booksEntity = new MetaEntity<BookDocument>("books", { ... })
// service is IMetaService<BookDocument>
const book = await booksEntity.service.findById(id)
// book is BookDocument | null
entity options reference
| option | type | default | description |
|---|
entity
MetaEntity is the single constructor. It validates the MongoDB connection, compiles the Mongoose schema, creates the service and controller, and exposes the event emitter.
const entity = new MetaEntity(name: string, options: MetaEntityOptions)
// exposed properties
entity.entityName // string
entity.model // Mongoose Model<T>
entity.service // IMetaService<T>
entity.controller // Express Router
entity.nestedOps // NestedOpsService<T>
// methods
entity.trigger(events, callback)
entity.intercept(action, callback)
entity.nested(id, payload)
entity.nestedBatch(id, payloads)
additionalFields
Declare extra schema fields using Mongoose field definition syntax. The base fields are always present and cannot be overridden.
const profileEntity = new MetaEntity("profiles", {
additionalFields: {
userId: { type: String, required: true, unique: true },
bio: { type: String, default: null },
rating_average: { type: Number, min: 0, max: 5, default: 0 },
is_vetted: { type: Boolean, default: false },
services: { type: Array, default: [] },
personal_information: { type: Object, default: {} }
}
})
service
The service layer exposes every data operation as an async method. Accessing
entity.service gives you the same logic that powers the HTTP controller, usable directly in
your business code.
endpoints
events
event types
callback nullability
examples
interceptors
action types
notes
examples
nested operations
operators
usage examples
// set a nested object property
await profileEntity.nested(id, {
field: "personal_information.email",
operation: "set",
value: "new@email.com"
})
// update a specific services[] item by _mmid
await profileEntity.nested(id, {
field: "services",
operation: "patch_item",
value: { _mmid: "mmid-cleaning", category: "Premium Cleaning" }
})
// remove a service by _mmid
await profileEntity.nested(id, {
field: "services",
operation: "pull_id",
value: "mmid-laundry"
})
// batch - applied in minimum round-trips
await profileEntity.nestedBatch(id, [
{ field: "rating_average", operation: "increment", value: 0.5 },
{ field: "tags", operation: "push", value: "verified" },
{ field: "personal_information.phone", operation: "set", value: "08012345678" }
])
relations
validation
schema setup
options
| option | description |
|---|---|
| createSchema | Joi schema map for create. Required constraints are enforced. |
| updateSchema | Joi schema map for update. Defaults to createSchema with all keys optional. |
| createValidationOptions | Any Joi.ValidationOptions. Default: { abortEarly: false, allowUnknown: true }. |
| updateValidationOptions | Same as above, for update calls. |
| skipValidation | Pass { skipValidation: true } to service.create() or service.update() to bypass Joi entirely. |
TypeScript
document extension
key types exported
| type | description |
|---|---|
| MetaEntity<T> | The main entity class. |
| IMetaService<T> | Service interface. All methods typed against T. |
| BaseEntityDocument | Base document shape with all injected fields. |
| EventPath<T> | Union of all valid event strings inferred from T's shape. |
| CallbackForEvent<T, E> | Resolves correct callback nullability from the event string E. |
| UpdateEventCallback<T> | Both whatWas and whatIs are non-null Partial<T>. |
| CreateEventCallback<T> | whatWas and whatIs are null. Entity is T. |
| DeleteEventCallback<T> | whatWas is non-null Partial<T>. whatIs is null. |
| NestedOpPayload | { field, operation, value } for nested operations. |
| QueryOptions | Pagination, filter, sort, projection options. |
| PaginatedResult<T> | { data: T[], pagination: { total, page, ... } }. |
event path inference
// EventPath<BookDocument> produces a union including:
// "create" | "update" | "delete" | "restore"
// "update.isbn" | "update.pageCount" | "update.libraryId"
// "update.extra_data" | "update.extra_data[*]"
booksEntity.trigger(["update.isbn"], (whatWas, whatIs, book) => {
// whatWas: Partial<BookDocument> -- no null check needed
// whatIs: Partial<BookDocument> -- no null check needed
console.log(whatWas.isbn, "=>", whatIs.isbn)
})