@happyvertical/smrt-reports 0.42.5 → 0.42.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +33 -0
- package/README.md +120 -0
- package/dist/index.d.ts +483 -4
- package/dist/index.js +908 -5
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +1 -1
- package/dist/refresh.js +2 -2
- package/dist/scheduler.js +1 -1
- package/dist/smrt-knowledge.json +7 -5
- package/package.json +6 -5
package/AGENTS.md
CHANGED
|
@@ -13,6 +13,39 @@ Materialized aggregate report models for SMRT.
|
|
|
13
13
|
| refresh | Rebuild and incremental refresh engine with run tracking, watermarks, locks, and tenant scoping |
|
|
14
14
|
| state | Internal `_smrt_report_*` system models for runs, watermarks, locks, schedules, and refresh tasks |
|
|
15
15
|
| scheduler | Cron schedule runner, durable refresh job enqueueing, and `onChange` interceptor registration |
|
|
16
|
+
| adapter | Transport-neutral report descriptor, canonical materialized-row reads, and stable `id` row identity |
|
|
17
|
+
| lifecycle | Tenant-safe freshness, run, lock, failure, and manual refresh preview/apply surfaces |
|
|
18
|
+
|
|
19
|
+
## Adapter contract
|
|
20
|
+
|
|
21
|
+
- `buildReportAdapterDescriptor()` returns deterministic, serializable metadata
|
|
22
|
+
for a report surface: a stable resource id, typed persisted report columns,
|
|
23
|
+
the canonical `DataQuerySchema`, and UI-neutral DataTable hints. It must not
|
|
24
|
+
import `smrt-ui` or expose a report-domain class to the consumer.
|
|
25
|
+
- `queryReportMaterializedRows()` owns only the bounded read slice for rows that
|
|
26
|
+
are already materialized. It supports projection, offset/limit paging,
|
|
27
|
+
validated filters, deterministic multi-sort with an `id` tie-breaker, exact
|
|
28
|
+
totals, and dimension facets. At source-query compilation,
|
|
29
|
+
dimension and bucket filters compile to `WHERE`, aggregate-measure filters
|
|
30
|
+
compile to `HAVING`, and mixed `OR`/`NOT` filter scopes fail closed.
|
|
31
|
+
- `id` is the only row identity. It must be a non-empty persisted string and is
|
|
32
|
+
never replaced by a display index or page position.
|
|
33
|
+
- The descriptor is an exposure boundary. Sensitive/secret fields, fields with
|
|
34
|
+
`readPermission`, and transient, system, or non-column fields fail closed and
|
|
35
|
+
do not become public columns when no principal is available.
|
|
36
|
+
- `tenantScoped`/`tenantField` reflect actual registered tenant metadata. A
|
|
37
|
+
`tenantScope` option only contributes to the stable resource id; it is not
|
|
38
|
+
authorization. The default query path resolves the registered collection via
|
|
39
|
+
`ObjectRegistry`, so normal collection tenancy interceptors apply. An injected
|
|
40
|
+
collection is application-owned and must preserve the same boundary.
|
|
41
|
+
- `refresh` is a declaration, not execution. It describes configured mode,
|
|
42
|
+
triggers, positive-TTL stale-read behavior, and a permissioned/audited action
|
|
43
|
+
with preview/apply phases. The adapter remains read-only, while
|
|
44
|
+
`getReportLifecycle()` provides an explicit, tenant-safe lifecycle snapshot
|
|
45
|
+
and `previewReportRefresh()` / `applyReportRefresh()` delegate authorization,
|
|
46
|
+
audit, and queueing through an application action host. Only a registered
|
|
47
|
+
`SmrtReportCollection` may synchronously refresh stale reads, when its TTL is
|
|
48
|
+
positive and the report is not manual.
|
|
16
49
|
|
|
17
50
|
## Conventions
|
|
18
51
|
|
package/README.md
CHANGED
|
@@ -101,6 +101,126 @@ helpers:
|
|
|
101
101
|
@happyvertical/smrt-reports/aggregate
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
+
## Report adapter
|
|
105
|
+
|
|
106
|
+
The package also exports a transport-neutral adapter for report surfaces:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import {
|
|
110
|
+
buildReportAdapterDescriptor,
|
|
111
|
+
buildReportDrilldownQuery,
|
|
112
|
+
queryReportMaterializedRows,
|
|
113
|
+
reportMaterializedRowKey,
|
|
114
|
+
splitReportFilterScopes,
|
|
115
|
+
} from '@happyvertical/smrt-reports';
|
|
116
|
+
|
|
117
|
+
const descriptor = await buildReportAdapterDescriptor(MonthlyRevenue, {
|
|
118
|
+
tenantScope: 'current',
|
|
119
|
+
});
|
|
120
|
+
const reports = await MonthlyRevenueCollection.create({ db: 'app.db' });
|
|
121
|
+
const result = await queryReportMaterializedRows(
|
|
122
|
+
MonthlyRevenue,
|
|
123
|
+
{
|
|
124
|
+
version: 1,
|
|
125
|
+
requestId: 'monthly-revenue-first-page',
|
|
126
|
+
mode: 'rows',
|
|
127
|
+
projection: ['id', 'customer_id', 'revenue'],
|
|
128
|
+
filter: {
|
|
129
|
+
kind: 'all',
|
|
130
|
+
filters: [
|
|
131
|
+
{
|
|
132
|
+
kind: 'condition',
|
|
133
|
+
field: 'customer_id',
|
|
134
|
+
operator: 'eq',
|
|
135
|
+
value: 'customer-42',
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
kind: 'condition',
|
|
139
|
+
field: 'revenue',
|
|
140
|
+
operator: 'gte',
|
|
141
|
+
value: 1000,
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
},
|
|
145
|
+
page: { kind: 'offset', offset: 0, limit: 25 },
|
|
146
|
+
sort: [{ field: 'revenue', direction: 'desc' }],
|
|
147
|
+
},
|
|
148
|
+
{ collection: reports },
|
|
149
|
+
);
|
|
150
|
+
const rowKey = reportMaterializedRowKey(result.rows[0]);
|
|
151
|
+
const drilldown = await buildReportDrilldownQuery(MonthlyRevenue, result.rows[0]);
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
`ReportAdapterDescriptor` is deterministic JSON with a stable resource id,
|
|
155
|
+
`id` as the identity field, typed report columns, a canonical `DataQuerySchema`,
|
|
156
|
+
and structural DataTable hints. The hints are deliberately not a `smrt-ui`
|
|
157
|
+
dependency. Consumers may map the descriptor to their own presentation layer.
|
|
158
|
+
The adapter exposes only persisted report columns and the primary key: transient,
|
|
159
|
+
system, and non-column fields are not surfaced. Sensitive/secret fields and
|
|
160
|
+
fields with a `readPermission` are excluded without a principal, so the
|
|
161
|
+
descriptor fails closed.
|
|
162
|
+
|
|
163
|
+
The `dataTable.columns` entries carry neutral `headerPath`, `valueFormat`,
|
|
164
|
+
alignment, role, and responsive hints. Grouping fields, time buckets, and
|
|
165
|
+
aggregate measures receive deterministic multi-level header ancestry; a
|
|
166
|
+
consumer can override any column by stable id with
|
|
167
|
+
`buildReportAdapterDescriptor(..., { dataTable: { columns: { ... } } })`.
|
|
168
|
+
`valueFormat` is an instruction for the rendering boundary only: rows returned
|
|
169
|
+
by `queryReportMaterializedRows()` retain their raw JSON-safe values for
|
|
170
|
+
sorting, exports, and agents. Use `dataTable.structuralRows` for a computed
|
|
171
|
+
summary, subtotal, aggregate, or footer. Each is marked `selection: 'excluded'`
|
|
172
|
+
and `actions: 'excluded'`, and must be passed to the consumer table's structural
|
|
173
|
+
row surface rather than its selectable data rows.
|
|
174
|
+
|
|
175
|
+
`queryReportMaterializedRows()` is the bounded read slice for already-materialized
|
|
176
|
+
rows. It supports projection, offset/limit paging (default limit 50),
|
|
177
|
+
deterministic multi-sort (with `id` as the final tie-break), typed filters, and
|
|
178
|
+
database-backed dimension facets. A descriptor marks group/time fields as
|
|
179
|
+
`filterScope: 'where'` and aggregate measures as `filterScope: 'having'`; use
|
|
180
|
+
`splitReportFilterScopes()` when constructing a live source query. An AND may
|
|
181
|
+
combine the two scopes, but an OR or NOT cannot mix them, because moving either
|
|
182
|
+
side across a source `WHERE`/`HAVING` boundary would change its meaning.
|
|
183
|
+
|
|
184
|
+
The materialized collection executes the normalized, allowlisted predicate as
|
|
185
|
+
parameterized SQL and applies it identically to rows, totals, and facets. It
|
|
186
|
+
never accepts raw SQL, source field paths, tenant ids, or principal ids.
|
|
187
|
+
The descriptor's `queryExecution` contract declares three delivery choices:
|
|
188
|
+
`visible` (the default) returns rows for an already-authorized surface,
|
|
189
|
+
`silent` returns the same bounded result while making no visible-surface change,
|
|
190
|
+
and `background` delegates a normalized, authority-free task to the application's
|
|
191
|
+
`enqueueBackgroundQuery` host. A background result is only a queue handle, never
|
|
192
|
+
materialized rows. The host retains the authenticated principal, tenant, report
|
|
193
|
+
definition, field policy, database, and eventual job execution; none can be
|
|
194
|
+
supplied in a query request or background task.
|
|
195
|
+
`buildReportDrilldownQuery()` carries only the row's declared groups/buckets
|
|
196
|
+
and a fixed inheritance contract for the current principal, tenant, report
|
|
197
|
+
definition, and field policy; an authenticated source adapter must enforce that
|
|
198
|
+
contract before it reads source records. Time buckets remain declarative so the
|
|
199
|
+
source adapter keeps the report's database/timezone semantics. Every returned
|
|
200
|
+
row must have a non-empty string `id`; use that value, never a display or page
|
|
201
|
+
index, for row identity.
|
|
202
|
+
|
|
203
|
+
When no collection is injected, reads resolve the registered report collection
|
|
204
|
+
through `ObjectRegistry`, allowing normal s-m-r-t collection interceptors to enforce
|
|
205
|
+
tenant filtering. `tenantScoped` and `tenantField` in the descriptor are true
|
|
206
|
+
only when the report is actually registered with tenant metadata; a scope string
|
|
207
|
+
alone is not an authority boundary. An injected collection is application-owned
|
|
208
|
+
and must provide the equivalent tenant boundary. Raw aggregate refreshes still
|
|
209
|
+
need explicit tenant predicates as described above.
|
|
210
|
+
|
|
211
|
+
The descriptor's `refresh` section declares mode, triggers, stale-read behavior,
|
|
212
|
+
and a permissioned, audited `refresh` action with `preview` and `apply` phases.
|
|
213
|
+
The adapter itself stays read-only. Call `getReportLifecycle()` for a tenant-safe
|
|
214
|
+
snapshot of current, stale, refreshing, lock-skipped, or failed materialization
|
|
215
|
+
state; it redacts lock owners, raw errors, and tenant-fanout identifiers. Pass a
|
|
216
|
+
`lifecycle` option to `queryReportMaterializedRows()` only when a consumer needs
|
|
217
|
+
that context; the result then distinguishes a current, stale, or read-triggered
|
|
218
|
+
refresh. `previewReportRefresh()` and `applyReportRefresh()` require an
|
|
219
|
+
application action host to authorize and audit the caller before a durable
|
|
220
|
+
report-refresh job is queued. Only a registered `SmrtReportCollection` can
|
|
221
|
+
synchronously refresh a stale read, and only when its TTL policy is positive and
|
|
222
|
+
not manual.
|
|
223
|
+
|
|
104
224
|
## Development
|
|
105
225
|
|
|
106
226
|
```bash
|