@happyvertical/smrt-reports
Materialized aggregate read models with incremental refresh, schedules, and tenant fan-out, plus runtime reports compiled per principal from a stored spec, with browser surfaces.
What it gives you
Materialized aggregate read models with incremental refresh, schedules, and tenant fan-out, plus runtime reports compiled per principal from a stored spec, with browser surfaces.
- 01Decorated groupings, measures, and time buckets
- 02Incremental refresh with watermarks and affected-group recomputation
- 03Durable scheduled refresh through smrt-jobs
- 04Runtime reports: a declarative spec stored as data, never a stored result
- 05Saved-report routes and a report chart widget on the /svelte subpath
Runtime reports are data, not code
parseRuntimeReportSpec accepts a closed grammar with no SQL, expressions, joins, tables, or tenants. compileRuntimeReportSpec resolves it against a server-owned source allow-list, the registry field policy, the live principal permissions, and a fail-closed tenant predicate. A stored RuntimeReport is recompiled for the running principal on every run, and result rows are never persisted. The assistant tools that define and run them live in smrt-chat.
Preview a spec in the browser
The /spec subpath is the browser-safe half of runtime reports. It exports parseRuntimeReportSpec, the spec grammar and time helpers, and runtimeReportSpecDigest. runRuntimeReportInMemory evaluates a spec over rows the caller already holds, with the compiler's own rules. It is a preview and test runner, never an authority boundary. Stored reports still compile per principal on the server.
Two recipes with different surfaces
reports.materialized declares the internal refresh tables: runs, watermarks, locks, schedules, and refresh tasks. An app owns its report classes, so the recipe does not list them. Those tables have no REST or MCP surface, so the recipe has no nav and no browser surface. reports.runtime brings the RuntimeReport model with nav, the /reports and /reports/:id routes, and the runtime-report-chart widget.
Server surfaces for the browser components
createRuntimeReportConfirmHandler serves the confirmation a person gives before a proposed report is saved; runRuntimeReportForPrincipal and archiveRuntimeReportForPrincipal reuse the read and create grants. Source allow-lists come from the packages.reports.runtime.sources config block; no configuration means no sources. The components take server report types as type-only imports and draw charts with smrt-ui/charts.
The adapter is the exposure boundary
buildReportAdapterDescriptor and queryReportMaterializedRows bind tenant context, projection, paging, filters, and facets before rows cross an application boundary. The adapter redacts fields that the caller is not allowed to expose.
Refresh, views, exports, and schedules retain their state
getReportLifecycle describes availability; previewReportRefresh and applyReportRefresh run through an action host. Saved views revalidate policy and migrate from v0 to v1. Exports bind to a snapshot fingerprint and page offset worker contract. SmrtReportSchedule supports cron, durable enqueue, and onChange interception.
Package UI
Components in this package
Import these public components directly. When the package also exports playground entries, the next tab renders its real components with package-owned example data.
Package previews
Try the working views
These previews are owned by the package and use its public components. Mock mode is intended for safe browsing; live mode appears only where the package supports it.
Generated interface
REST
The exact route set comes from each model's @smrt({ api }) policy
and the application manifest—not from a second controller definition.
GET /api/reports
GET /api/reports/:id
POST /api/reports
PATCH /api/reports/:idWhat stays consistent
- Tenant scope and operation permissions run before model access.
- OpenAPI and generated clients use the same manifest fields.
- Packages can expose read-only or custom action sets.
Generated interface
MCP
AI tools are generated from the methods you explicitly include. Their inputs are validated against the same model metadata used by REST and CLI.
reports_list({ where, limit, orderBy })
reports_get({ id })
reports_create({ data })
reports_update({ id, data })Agent-safe by construction
- Tool availability can be narrower than the HTTP API.
- Principal execution intersects RBAC, agent ceilings, and persona tools.
- Descriptions and schemas are emitted from source metadata.
Browser-native agent interface
WebMCP
The generated web definitions carry the same tool names, descriptions, and JSON Schema
inputs as MCP. smrt-web registers them with document.modelContext.
import { webMcpToolDefinitions } from '@happyvertical/smrt-virt-web';
import { registerWebMcpTools } from '@happyvertical/smrt-web';
// Omitting an exposure policy registers read-only tools only.
const dispose = registerWebMcpTools(webMcpToolDefinitions);The page keeps its security boundary
- Tools execute through generated REST fetchers as the signed-in page user.
- Authentication, tenant gates, permissions, and field policy stay server-side.
- Feature detection makes registration a safe no-op outside supported browsers.
Generated interface
CLI
The app CLI turns manifest resources into predictable operator commands while keeping package-specific actions close to their models.
smrt reports list --limit 25 --order-by created_at:desc
smrt reports get <id>
smrt reports create --from-file ./record.jsonMade for automation
- Machine-readable output and file-based input.
- The same include/exclude policy as other surfaces.
- Migration, introspection, dispatch, and knowledge commands stay in smrt-cli.