@maykonpaulo/maestro-server
v0.3.0
Published
Turnkey HTTP server for @maykonpaulo/maestro-core — mounts the framework-agnostic Maestro HTTP handlers on a real Express server and turns any live datasource provider into a full, governed admin over the whole database (ADR 0008). The core stays server-f
Readme
@maykonpaulo/maestro-server
Este guia leva um programador do zero a um painel administrativo governado (listar, filtrar, criar, editar, excluir, exportar, com RBAC e auditoria) sobre o banco de dados de qualquer sistema — sem escrever CRUD por entidade.
O que é o Maestro? Uma engine metadata-driven em TypeScript que você conecta a um banco existente para operá-lo com segurança, sem acesso direto ao banco. Você aponta para o banco, o Maestro descobre o schema (ou você declara), e ele entrega uma API REST + uma UI de admin genérica que se auto-monta. É agnóstico de framework, de banco e de domínio.
Índice
- Pré-requisitos
- Escolha seu banco (providers)
- Instalação
- Caminho A — Turnkey (recomendado)
- Caminho B — Declarativo/curado
- Autenticação (
actorResolver) - Autorização (RBAC)
- Auditoria
- Operações customizadas (ações de negócio)
- Campos: tipos, sensíveis, soft delete, relações
- A UI de admin (
maestro-admin) - A API HTTP (para frontends próprios)
- Referência rápida
- Produção: checklist e ressalvas
- Solução de problemas
- Pacotes e versões
1. Pré-requisitos
- Node.js 18+ e um gerenciador de pacotes (
npm,pnpmouyarn). - Acesso ao banco de dados do sistema que você quer administrar (string de conexão).
- Um mecanismo de autenticação seu (JWT, cookie de sessão, etc.) — o Maestro não faz auth; ele consome o "ator" que você já autenticou.
2. Escolha seu banco (providers)
O Maestro fala com o banco através de um provider. Existem 9 pacotes oficiais publicados — cada um implementa leitura de schema (introspecção), CRUD real e criação de coleção/tabela:
| Banco | Pacote |
|---|---|
| PostgreSQL · MySQL · SQLite · SQL Server | @maykonpaulo/maestro-provider-sql |
| MongoDB | @maykonpaulo/maestro-provider-mongodb |
| Redis | @maykonpaulo/maestro-provider-redis |
| Elasticsearch | @maykonpaulo/maestro-provider-elasticsearch |
| DynamoDB | @maykonpaulo/maestro-provider-dynamodb |
| Cassandra | @maykonpaulo/maestro-provider-cassandra |
| Couchbase | @maykonpaulo/maestro-provider-couchbase |
| Firestore | @maykonpaulo/maestro-provider-firestore |
| Neo4j | @maykonpaulo/maestro-provider-neo4j |
Banco fora dessa lista? Você pode implementar a interface
DatasourceProvider(6 métodos) — ver seção Solução de problemas. Mas para qualquer um acima, é só instalar.
3. Instalação
Instale a engine, o provider do seu banco, o servidor turnkey e a UI:
# engine + servidor + UI
npm i @maykonpaulo/maestro-core @maykonpaulo/maestro-server @maykonpaulo/maestro-admin
# provider do seu banco (exemplo: MongoDB)
npm i @maykonpaulo/maestro-provider-mongodbTroque o provider pelo do seu banco (ex.: @maykonpaulo/maestro-provider-sql).
4. Caminho A — Turnkey (recomendado)
Aponte para o banco e tenha um admin completo, sem declarar entidade nenhuma. O Maestro introspecta o banco, transforma cada coleção/tabela em uma entidade administrável e sobe um servidor REST. É o caminho mais rápido.
4.1 Conectar o provider
Escolha a fábrica do seu banco (confira as opções exatas de conexão no README do provider):
// MongoDB
import { createMongoProvider } from '@maykonpaulo/maestro-provider-mongodb';
const provider = createMongoProvider({ uri: process.env.MONGO_URL!, database: 'app' });
// PostgreSQL (mesma família: mysql/sqlite/mssql via `dialect`)
import { createSqlProvider } from '@maykonpaulo/maestro-provider-sql';
const provider = createSqlProvider({ dialect: 'postgres', connectionString: process.env.PG_URL! });
// sqlite: createSqlProvider({ dialect: 'sqlite', databasePath: './app.db' })
// mysql: createSqlProvider({ dialect: 'mysql', uri: process.env.MYSQL_URL! })
// Outros
import { createRedisProvider } from '@maykonpaulo/maestro-provider-redis';
const provider = createRedisProvider({ uri: 'redis://localhost:6379' });
// createElasticsearchProvider({ node, auth })
// createDynamoDbProvider({ region })
// createCassandraProvider({ contactPoints, localDataCenter, keyspace })
// createCouchbaseProvider({ connectionString, username, password, bucket })
// createFirestoreProvider({ projectId })
// createNeo4jProvider({ uri, username, password })4.2 Construir o engine por introspecção
import { createIntrospectedEngine } from '@maykonpaulo/maestro-server';
const engine = await createIntrospectedEngine({
provider,
access: 'full', // 'full' = list/detalhe/criar/editar/excluir/exportar
// 'readonly' = só navegar (sem risco de alterar)
});createIntrospectedEngine descobre toda coleção/tabela e habilita as capacidades de escrita
(o padrão do core seria só-leitura — este helper é o que liga o CRUD).
Opções:
| Opção | Descrição |
|---|---|
| provider | O provider conectado (obrigatório). |
| access | 'full' (padrão) ou 'readonly'. |
| datasourceId | id do datasource (padrão 'main'). |
| policies | Política RBAC (ver §7). Padrão: papel admin com ['*']. |
| overrides | Sobreposição curada (rótulos, campos, RBAC) mesclada sobre o schema descoberto. |
| operations | Operações customizadas (ver §9). |
| audit | Repositório de auditoria (ver §8). |
4.3 Subir o servidor
import { createMaestroServer } from '@maykonpaulo/maestro-server';
const server = createMaestroServer({ engine /*, actorResolver, cors, basePath */ });
await server.listen(3000);
// → REST admin para cada coleção em http://localhost:3000/entities/:colecao4.4 Autenticação
Sem actorResolver, o servidor usa um resolver só-dev que concede um ator admin a toda
requisição. Em produção, sempre passe o seu — ver §6.
4.5 Subir a UI
A UI genérica lê GET /metadata e se auto-monta (navegação, tabela, detalhe, formulários)
para todas as entidades. Rodando como app:
VITE_MAESTRO_API_URL=http://localhost:3000 \
npm --prefix node_modules/@maykonpaulo/maestro-admin run dev
# abra http://localhost:5173Ou embutida no seu app React — ver §11.
4.6 Servidor completo (copy-paste)
// server.ts
import { createMongoProvider } from '@maykonpaulo/maestro-provider-mongodb';
import { createMaestroServer, createIntrospectedEngine } from '@maykonpaulo/maestro-server';
import { InMemoryAuditRepository, type RbacPolicy } from '@maykonpaulo/maestro-core';
const provider = createMongoProvider({ uri: process.env.MONGO_URL!, database: 'app' });
const policies: RbacPolicy = {
roles: {
admin: { id: 'admin', name: 'Admin', permissions: ['*'] },
viewer: { id: 'viewer', name: 'Viewer', permissions: ['entity.*.list', 'entity.*.detail'] },
},
};
const engine = await createIntrospectedEngine({
provider,
access: 'full',
policies,
audit: new InMemoryAuditRepository(), // troque por seu repositório em produção (§8)
});
const server = createMaestroServer({
engine,
actorResolver: async (req) => {
// derive o ator da SUA autenticação (JWT/cookie)
const token = String(req.headers['authorization'] ?? '').replace('Bearer ', '');
const user = await verifyJwt(token); // sua função
return { actor: { id: user.sub, type: 'user', roles: user.roles } };
},
});
await server.listen(3000);
console.log('Admin API em http://localhost:3000');5. Caminho B — Declarativo/curado
Quando quiser rótulos bonitos, RBAC fino, tipos de campo explícitos e formulários controlados, declare as entidades. Você tem controle total; o Maestro monta o resto.
5.1 Declarar entidades
import { type DeclarativeConfig } from '@maykonpaulo/maestro-core';
const declarations: DeclarativeConfig = {
entities: [
{
entity: 'order', // = nome da tabela/coleção
label: 'Pedido',
pluralLabel: 'Pedidos',
fields: {
id: { type: 'string', primary: true },
code: { type: 'string', label: 'Código', required: true, searchable: true, sortable: true },
status: { type: 'enum', label: 'Status',
enumOptions: [
{ value: 'open', label: 'Aberto' },
{ value: 'paid', label: 'Pago' },
{ value: 'canceled', label: 'Cancelado' },
] },
total: { type: 'currency', label: 'Total', sortable: true },
paid: { type: 'boolean', label: 'Pago' },
createdAt: { type: 'datetime', label: 'Criado em', sortable: true },
notes: { type: 'text', label: 'Observações' },
},
// capacidades: liste o que este admin pode fazer nesta entidade
capabilities: { list: true, detail: true, create: true, update: true, delete: true, export: true },
},
],
};5.2 createMaestro + servir
import { createMaestro } from '@maykonpaulo/maestro-core';
import { createMaestroServer } from '@maykonpaulo/maestro-server';
const engine = createMaestro({
datasources: { main: provider }, // o provider do seu banco (§4.1)
declarations,
policies, // RBAC (§7)
audit: myAuditRepository, // (§8)
operations: [cancelOrder], // ações customizadas (§9)
});
await createMaestroServer({ engine, actorResolver }).listen(3000);5.3 A vs B (e híbrido)
- A (introspecção): o mais rápido; toda coleção vira entidade; schema inferido.
- B (declarativo): controle total; ideal para as entidades que os operadores mais usam.
- Híbrido: passe
overrides(config declarativa) paracreateIntrospectedEngine— o Maestro descobre tudo e aplica seus rótulos/regras por cima nas entidades que você curou.
6. Autenticação (actorResolver)
Autenticação é responsabilidade sua. O único ponto onde ela entra no Maestro é o
actorResolver, que converte cada requisição em um Actor:
const server = createMaestroServer({
engine,
actorResolver: async (req) => {
const token = String(req.headers['authorization'] ?? '').replace('Bearer ', '');
const user = await verifyJwt(token); // SUA autenticação
return {
actor: {
id: user.sub,
type: 'user', // 'user' | 'system' | 'job' | 'integration' | 'ai-agent'
name: user.name,
email: user.email,
roles: user.roles, // dão as permissões (§7)
},
correlationId: String(req.headers['x-correlation-id'] ?? ''), // opcional (rastreio na auditoria)
};
},
});- Omitir
actorResolver= rodar sem autenticação (o defaultdevActorResolverconcede admin a todos). Nunca faça isso em produção. - O
Actoré passado a toda operação; o RBAC decide o que ele pode fazer.
7. Autorização (RBAC)
Você define papéis e as permissões de cada um. O ator recebe permissões pelos seus roles.
import { type RbacPolicy } from '@maykonpaulo/maestro-core';
const policies: RbacPolicy = {
roles: {
admin: { id: 'admin', name: 'Admin', permissions: ['*'] },
operator: { id: 'operator', name: 'Operador', permissions: ['entity.order.*', 'operation.order.cancel'] },
viewer: { id: 'viewer', name: 'Leitura', permissions: ['entity.*.list', 'entity.*.detail', 'entity.*.export'] },
},
};Formato das permissões:
| Permissão | Concede |
|---|---|
| entity.<entidade>.<capacidade> | Uma ação em uma entidade. Capacidades: list, detail, create, update, clone, delete, softDelete, restore, export. |
| operation.<opId> | Executar uma operação customizada (§9). |
| entity.order.* | Tudo na entidade order. |
| entity.*.list | list em todas as entidades. |
| * | Tudo. |
Curinga: * (tudo) e prefixo.* (tudo abaixo do prefixo). Um 403 é retornado quando negado — a
UI trata isso como erro na tela, sem quebrar.
Importante: as capacidades (o que a entidade permite) e o RBAC (o que o ator pode) são checados juntos. No modo introspecção
access: 'full', todas as capacidades ficam ligadas; o RBAC é quem restringe por papel.
8. Auditoria
Toda operação emite um evento de auditoria quando você fornece um AuditRepository. Para começar,
use o InMemoryAuditRepository; em produção, implemente o contrato gravando no seu store:
import { type AuditRepository, type AuditEvent } from '@maykonpaulo/maestro-core';
class MyAuditRepository implements AuditRepository {
async record(event: AuditEvent): Promise<void> {
await db.collection('audit').insertOne(event); // grave onde quiser
}
async list(filter) { /* consulte por ator/ação/entidade/período/correlationId */ return []; }
}Cada evento inclui ação, ator, recurso, nível, before/after (em update/delete) e correlationId.
9. Operações customizadas (ações de negócio)
Além do CRUD, exponha ações (ex.: "cancelar pedido", "reenviar e-mail"). Elas aparecem como botões (por linha / em massa / globais) e executam sua lógica com RBAC + auditoria:
import { type OperationDef } from '@maykonpaulo/maestro-core';
const cancelOrder: OperationDef = {
id: 'order.cancel',
label: 'Cancelar pedido',
entity: 'order',
scope: 'record', // 'record' | 'bulk' | 'global'
requiredPermission: 'operation.order.cancel',
execute: async ({ record, input, actor }) => {
// sua lógica de negócio aqui
await paymentGateway.refund(record.id);
return { success: true, message: 'Pedido cancelado.' };
},
};
// registre no engine:
createMaestro({ /* ... */ operations: [cancelOrder] });
// ou createIntrospectedEngine({ /* ... */ operations: [cancelOrder] });Chamada HTTP: POST /operations/order.cancel com { entityId, record, input }.
10. Campos: tipos, sensíveis, soft delete, relações
Tipos de campo (type): string, text, number, integer, decimal, currency,
boolean, date, datetime, time, email, phone, url, document, uuid, enum,
json, relation, array. A UI escolhe o controle certo por tipo (checkbox, número, select,
data, textarea/JSON…).
Flags úteis por campo (no modo declarativo): required, readonly, primary, sensitive,
searchable, sortable, filterable, exportable, enumOptions (para enum), relationEntity
(para relation).
sensitive: true— mascara o valor (***) na exportação CSV e na UI. (Logging/before/aftercontinuam responsabilidade sua.)- Soft delete — configure um campo que marca "inativo" (ex.:
active/deletedAt). As operaçõessoftDelete/restorepassam a existir em vez de exclusão física. - Relações — o Maestro não infere FK em bancos NoSQL; declare relações à mão quando quiser abas de relacionamento no detalhe.
- Campos cifrados (E2EE / zero-knowledge) — se o seu backend cifra campos, eles chegam opacos; a UI os exibe como estão e nunca descriptografa. Comece o admin pelas coleções não cifradas.
11. A UI de admin (maestro-admin)
A UI (React + Vite + Tailwind) lê GET /metadata e monta sozinha: navegação por entidade, tabela
(com busca/ordenação/paginação/filtros vindos do metadata), detalhe, e formulários de criar/editar.
Ela respeita as capacidades (esconde New/Edit/Delete quando desabilitados) e o RBAC (403 vira
erro na tela). Uma tela, todas as entidades.
App standalone
VITE_MAESTRO_API_URL=http://localhost:3000 \
npm --prefix node_modules/@maykonpaulo/maestro-admin run dev # dev server
# ou build estático:
VITE_MAESTRO_API_URL=http://localhost:3000 \
npm --prefix node_modules/@maykonpaulo/maestro-admin run build:appVITE_MAESTRO_ROLE=viewer (ou ?role=viewer na URL) define o header X-Role — útil para testar RBAC.
Embutida no seu app React
import { MaestroAdmin } from '@maykonpaulo/maestro-admin';
import '@maykonpaulo/maestro-admin/src/styles.css';
export function AdminPage() {
return (
<MaestroAdmin
apiUrl="http://localhost:3000"
headers={() => ({ Authorization: `Bearer ${getToken()}` })} // pode ser função (token fresco a cada request)
title="Admin"
/>
);
}Também exportamos os blocos (EntityList, EntityDetail, EntityForm, Sidebar, useMetadata,
useEntityList, MaestroClient, …) para montar um layout próprio.
12. A API HTTP (para frontends próprios)
Se você quiser construir sua própria UI (ou integrar outro sistema), o servidor expõe:
GET /health
GET /metadata # descreve todas as entidades (campos, tipos, capacidades)
GET /metadata/:entidade
GET /entities/:entidade # list — query: page, pageSize, search, searchFields, sort, filter
POST /entities/:entidade # criar
GET /entities/:entidade/:id # detalhe
PATCH /entities/:entidade/:id # editar
DELETE /entities/:entidade/:id # excluir (hard delete)
POST /entities/:entidade/:id/clone
POST /entities/:entidade/:id/soft-delete
POST /entities/:entidade/:id/restore
GET /entities/:entidade/export # ?format=csv|json
POST /operations/:operationId # { entityId, record, input }Query string do list:
| Param | Exemplo | Significado |
|---|---|---|
| page, pageSize | ?page=1&pageSize=20 | Paginação. |
| search, searchFields | ?search=ada&searchFields=name,email | Busca textual. |
| sort | ?sort=name:asc (repetível) | Ordenação. |
| filter | ?filter=status:equals:open (repetível) | Filtro campo:operador:valor. |
Todo endpoint checa RBAC e emite auditoria. O GET /metadata é o contrato para gerar qualquer UI.
13. Referência rápida
Operadores de filtro: equals, notEquals, contains, startsWith, endsWith, in,
notIn, gt, gte, lt, lte, between, isNull, isNotNull, isTrue, isFalse.
Capacidades de entidade: list, detail, create, update, clone, delete, softDelete,
export, bulkActions. Padrão do core: list/detail ligadas, o resto desligado (por segurança).
No modo createIntrospectedEngine({ access: 'full' }), todas ligadas.
Métodos da engine (se você usar o core direto, sem o servidor): list, findById, create,
update, clone, softDelete, restore, hardDelete, export, executeOperation, getMetadata
— todos recebem o actor e checam RBAC + auditoria.
Actor: { id, type: 'user'|'system'|'job'|'integration'|'ai-agent', name?, email?, roles?, permissions?, metadata? }.
14. Produção: checklist e ressalvas
- [ ] Sempre forneça um
actorResolverreal (JWT/cookie). Sem ele = sem auth. - [ ] Defina papéis RBAC de verdade (não deixe todo mundo
admin/['*']). - [ ] Comece com
access: 'readonly'para validar a leitura do banco de produção sem risco; suba para'full'quando confiar. - [ ] Implemente um
AuditRepositoryque persista (não o InMemory). - [ ] Configure CORS conforme seu frontend (
createMaestroServer({ cors: { origin, credentials } })). - [ ] Comece o admin pelas coleções operacionais não cifradas (usuários, configs, etc.). Campos E2EE aparecem opacos.
- [ ] Rode o servidor atrás do seu gateway/HTTPS; o Maestro é só a camada de operação.
15. Solução de problemas
- "Nome da coleção não bate" (MongoDB/Mongoose). No modo introspecção, cada entidade é a coleção
real (ex.:
users). No modo declarativo, oentityda declaração é o nome da coleção — o Mongoose costuma pluralizar (User→users); declare o nome real, ou use oEntitySchemade baixo nível (source.table) para mapear um id amigável a uma coleção diferente. _id(MongoDB). O provider trata_idcomoObjectIdnativo ou string de forma transparente — funciona sobre coleções existentes sem migração.- Campos
currency/decimalaparecem como texto? Use a versão mais recente domaestro-admin. - Banco não coberto pelos 9 providers. Implemente
DatasourceProvider(6 métodos:list,findById,create,update,delete,count) e passe emdatasources: { main: seuProvider }. - Publicou no
@latestpor engano? Auth e canais de release não fazem parte deste guia — o admin em si só precisa dos pacotes instalados e do servidor no ar.
16. Pacotes e versões
Instale pela tag latest (estável):
npm i @maykonpaulo/maestro-core \
@maykonpaulo/maestro-server \
@maykonpaulo/maestro-admin \
@maykonpaulo/maestro-provider-<seu-banco>| Pacote | O que é |
|---|---|
| @maykonpaulo/maestro-core | Engine, contratos, metadata, RBAC, auditoria, operações, export. |
| @maykonpaulo/maestro-server | Servidor HTTP turnkey (createMaestroServer, createIntrospectedEngine). |
| @maykonpaulo/maestro-admin | UI genérica metadata-driven (app + componentes React). |
| @maykonpaulo/maestro-provider-* | Provider do seu banco (9 opções). |
| @maykonpaulo/maestro-cli | CLI opcional (introspect/generate/validate/diff/snapshot). |
Resumo em 6 linhas
const provider = createMongoProvider({ uri, database }); // 1. conecte o banco
const engine = await createIntrospectedEngine({ provider, access: 'full', policies, audit }); // 2. engine
const server = createMaestroServer({ engine, actorResolver }); // 3. auth + servidor
await server.listen(3000); // 4. API no ar
// 5. rode @maykonpaulo/maestro-admin apontando VITE_MAESTRO_API_URL para :3000
// 6. pronto: admin de todo o sistema, com RBAC e auditoriaReferência do pacote @maykonpaulo/maestro-server
The turnkey HTTP server for @maykonpaulo/maestro-core, per ADR 0008 — Camada Turnkey.
The core is deliberately server-free and framework-agnostic: it exposes createMaestroHttpHandlers (handlers you mount on your Express/Fastify/Lambda), never an actual server. This sibling package is the batteries-included layer that boots a real HTTP server, and — combined with a live datasource provider — turns any database into a complete, governed admin over every collection, without writing a line of per-entity code.
It adds only what a server needs and the core must not carry: JSON body parsing, CORS, route wiring and a listen()/close() lifecycle. All domain behaviour (RBAC, audit, governance, CRUD) still comes from the engine.
Installation
npm install @maykonpaulo/maestro-server @maykonpaulo/maestro-core@maykonpaulo/maestro-core is a peer dependency. express is a direct dependency of this package.
Point at a database, manipulate everything (introspection mode)
The fastest path: hand it a live provider (e.g. @maykonpaulo/maestro-provider-mongodb) and get a full admin over every discovered collection — no declarations required.
import { createMongoProvider } from '@maykonpaulo/maestro-provider-mongodb';
import { createMaestroServer, createIntrospectedEngine } from '@maykonpaulo/maestro-server';
const provider = createMongoProvider({ uri: process.env.MONGO_URL!, database: 'app' });
// Introspects the live DB and builds an engine over every collection, with write access enabled.
const engine = await createIntrospectedEngine({ provider, access: 'full' });
const server = createMaestroServer({ engine });
await server.listen(3000);
// → REST admin for every collection at http://localhost:3000/entities/:collectionaccess: 'readonly' enables only list/detail/export — a safe way to browse a production database without any risk of mutating it. access: 'full' (default) enables the complete CRUD surface.
Entities discovered by introspection are read-only under the core's
createMaestroFromIntrospection;createIntrospectedEngineis the piece that enables write capabilities according toaccess, so "manipulate everything" works out of the box.
Curated mode (declarations)
When you want nice labels, fine-grained RBAC and governance on chosen entities, build the engine yourself with createMaestro and just serve it:
import { createMaestro } from '@maykonpaulo/maestro-core';
import { createMaestroServer } from '@maykonpaulo/maestro-server';
const engine = createMaestro({ datasources: { main: provider }, declarations, policies, audit });
const server = createMaestroServer({ engine, actorResolver });
await server.listen(3000);Both modes can be combined: pass an overrides (curated) config to createIntrospectedEngine and the core HybridLoader merges it over the introspected schema.
Authentication is yours (the actorResolver hook)
Authentication stays the consumer's responsibility. createMaestroServer takes an actorResolver that derives the acting Actor from each request (a JWT, a cookie, a session):
const server = createMaestroServer({
engine,
actorResolver: async (req) => {
const user = await verifyJwt(req.headers['authorization']);
return { actor: { id: user.id, type: 'user', roles: user.roles } };
},
});When omitted, a development-only devActorResolver grants every request a single admin actor. Never use it in production.
API
createMaestroServer(options): MaestroServer
| Option | Description |
|---|---|
| engine | The MaestroEngine to serve (from createMaestro or createIntrospectedEngine). |
| actorResolver? | Resolves the Actor per request. Defaults to devActorResolver (dev only). |
| cors? | true/omitted → permissive defaults; an object → configured (origin, credentials, allowedHeaders); false → no CORS middleware. |
| basePath? | Path prefix for every route (e.g. /api/admin). |
| app? | Bring your own Express app (to add auth middleware/other routes). When given, JSON/CORS are not auto-applied. |
Returns { app, handlers, listen(port, host?) }. listen resolves with the Node http.Server once bound.
createIntrospectedEngine(options): Promise<MaestroEngine>
| Option | Description |
|---|---|
| provider | A provider implementing both IntrospectionProvider and DatasourceProvider. |
| access? | 'full' (default) enables all capabilities; 'readonly' enables list/detail/export only. |
| datasourceId? | Datasource id the entities bind to. Defaults to main. |
| overrides? | Optional curated overlay merged via the core HybridLoader. |
| policies? | RBAC policy. Defaults to an admin role with ['*']. |
| operations?, audit?, logger? | Forwarded to the engine. |
Routes
GET /health
GET /metadata
GET /metadata/:entity
GET /entities/:entity (list — filter/sort/paginate/search via query string)
POST /entities/:entity (create)
GET /entities/:entity/:id (detail)
PATCH /entities/:entity/:id (update)
DELETE /entities/:entity/:id (hard delete)
POST /entities/:entity/:id/clone
POST /entities/:entity/:id/soft-delete
POST /entities/:entity/:id/restore
GET /entities/:entity/export (?format=csv|json)
POST /operations/:operationIdWhat this package is not
It bundles no database driver — it depends on a DatasourceProvider you instantiate (the driver comes with the provider package). It does not handle authentication or identity — that is the actorResolver's job. Those boundaries are intentional and preserve the core's purity (ADR 0008).
