meta-manager v0.2.0

meta-manager

$ npm install @ubyjerome/meta-manager mongoose express

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.

typescript
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.

typescript
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.

Connection guard. MetaEntity throws immediately if mongoose.connection.readyState is 0 (disconnected). Always call mongoose.connect() before constructing entity instances.
typescript
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.

typescript
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

typescript
// 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

typescript

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

typescript

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

typescript
// 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)
})