@maykonpaulo/maestro-provider-mongodb
v1.1.1
Published
MongoDB provider for @maykonpaulo/maestro-core — schema-inferred introspection (sampling), real CRUD, and missing-collection creation, per ADR 0007 (the first document/schema-less family provider).
Readme
@maykonpaulo/maestro-provider-mongodb
📖 Guia completo — criar o admin de qualquer sistema: está no README do pacote
@maykonpaulo/maestro-serverno npm → https://www.npmjs.com/package/@maykonpaulo/maestro-server
A MongoDB provider for @maykonpaulo/maestro-core — the first document/schema-less family provider, per ADR 0007 — Universal Provider Strategy. It implements all three of the core's provider contracts against a real MongoDB database:
IntrospectionProvider— discovers collections and infers each one's shape by sampling documents (there is no fixed schema to read). Every entity is reported withschemaless: true.DatasourceProvider— reallist/findById/create/update/delete/count, translated to native MongoDB queries (filters, sort, search, pagination).SchemaWriteProvider—ensureEntitycreates the collection if it does not exist yet (idempotent).
Why a separate package instead of a dialect of @maykonpaulo/maestro-provider-sql
MongoDB's query model (documents, no fixed columns, no information_schema) is genuinely different from the relational family — it cannot share the SQL query-building layer the relational dialects reuse. ADR 0007 groups providers by paradigm, not by exact product: this package is the whole document-store family for now, the way @maykonpaulo/maestro-provider-sql is the whole relational family.
Installation
npm install @maykonpaulo/maestro-provider-mongodb @maykonpaulo/maestro-core@maykonpaulo/maestro-core and mongodb are peer dependencies — install the versions your project already uses.
Usage
import { runIntrospectionProvider } from '@maykonpaulo/maestro-core';
import { createMongoProvider } from '@maykonpaulo/maestro-provider-mongodb';
const provider = createMongoProvider({ uri: 'mongodb://localhost:27017', database: 'app' });
// Introspection — samples documents, validated/normalized via the core
const { result } = await runIntrospectionProvider(provider);
// Real CRUD against the same database
const created = await provider.create({ table: 'users', primaryKey: '_id', data: { name: 'Ada' } });
const page = await provider.list({ table: 'users', primaryKey: '_id', pagination: { strategy: 'page', page: 1, pageSize: 20 } });
// Create the collection if it isn't there yet
await provider.ensureEntity({ entity: { table: 'tags', fields: [] } });Configuration
export interface MongoProviderOptions {
uri?: string; // a mongodb://... connection string
client?: MongoClient; // an already-connected client — for tests/embedding; caller owns its lifecycle
database: string; // required — Mongo has no "current database" to infer
maxDocuments?: number; // caps documents read per collection during introspection — unset by default (scans the whole collection)
}Exactly one of uri or client must be provided.
_id handling
MongoDB always assigns a native ObjectId to _id unless the caller supplies one. This provider follows that default:
create()withprimaryKey: '_id'and no_idindatalets Mongo generate a nativeObjectId— the shape any pre-existing real collection almost certainly already uses. The returned record's_idis theObjectId's string form.findById/update/deletewithprimaryKey: '_id'accept a 24-hex-character id and match it against both a storedObjectIdand a stored plain string, so records created by this provider (which may hold either shape) and records that already existed in a real database (ObjectId) both resolve correctly.- A non-
_idprimary key (e.g.slug) is always treated as a plain value — Mongo has no special type for it.create()still generates arandomUUID()string when the caller doesn't supply one, matching every other provider in this monorepo.
What is introspected
Since there is no system catalog, introspection reads every document in every collection by default
(maxDocuments is unset unless you explicitly cap it for a very large collection) and infers:
- Fields — the union of keys observed across all scanned documents, each with the most-recently-seen non-null type.
- Nullability —
truewhenever a scanned document omitted the field or heldnull/undefinedfor it, or whenever fewer documents than were scanned actually carried the field. - Primary key —
_idis always markedisPrimaryKey: true. schemaless: trueon every entity (ADR 0007, DA-13) — signals to any consumer (CLI, declarative config generator, a future admin UI) that these fields are a likely shape, not a guarantee, unlike a relational provider's catalog-read columns. This is not about incomplete coverage of existing data — a full scan sees every document currently in the collection — it is an honest acknowledgment that MongoDB itself enforces no shape, so a document written a moment later can still introduce a field or type this result never saw. That is a property of the database, not a shortcut this provider takes.
What is not introspected (known limitations)
- Relations — MongoDB has no foreign-key constraint to read. Guessing one from a field-naming convention (e.g.
userId) would claim a confidence level ('definite'/'inferred') this provider cannot honestly back for a schemaless source, sorelationsis always[]. - Indices — not introspected in this version.
- Nested/embedded document shape — a field holding a sub-document is reported as a single
'json'-typed field; its inner structure is not recursively sampled.
Schema creation (ensureEntity)
Unlike a relational CREATE TABLE, a MongoDB collection carries no column definitions — entity.fields is not applied to the created collection, since there is nothing in Mongo's own model to apply it to. ensureEntity simply creates the (empty) collection if it doesn't already exist; the collection then accepts documents of whatever shape is written to it, per MongoDB's schemaless model.
Testing
Tests run against a real, ephemeral MongoDB server started per test run via
mongodb-memory-server (downloads a real mongod
binary once, then runs fully offline/in-process) — no Docker, no shared external database. See
tests/mongo-provider.test.ts.
Turnkey admin (point at your database, get a UI)
This provider plugs straight into the Maestro turnkey layer: install
@maykonpaulo/maestro-server and
@maykonpaulo/maestro-admin, point them at your database and get a
full governed admin (list/CRUD/RBAC/audit + a metadata-driven UI) with no
per-entity code.
import { createMongoProvider } from '@maykonpaulo/maestro-provider-mongodb';
import { createMaestroServer, createIntrospectedEngine } from '@maykonpaulo/maestro-server';
const provider = createMongoProvider({ uri: 'mongodb://localhost:27017', database: 'app' });
const engine = await createIntrospectedEngine({ provider, access: 'full' });
await createMaestroServer({ engine }).listen(3000);See the Turnkey admin guide.
